Article directory
1. Cortex-M interrupt
1. Introduction to Interrupts
Interrupts are a very common feature of microcontrollers. Interrupts are generated by hardware. When an interrupt is generated, the CPU will interrupt the current process and turn to handle the interrupt service. The Cortex-M core MCU provides an interrupt Managed Nested Vectored Interrupt Controller (NVIC).
The NVIC of Cotex-M3 supports up to 240 IRQs (interrupt requests), 1 non-maskable interrupt (NMI), 1 Systick (tick timer) timer interrupt and multiple system exceptions.
2. Introduction to interrupt management
The Cortex-M processor has multiple programmable registers for managing interrupts and exceptions. Most of these registers are in the
NVIC and the system control block (SCB). CMSIS defines these registers as structures. Take STM32F103 as an example, open core_cm3.h, there are two structures, NVIC_Type and SCB_Type, as follows:
typedef struct
{
__IO uint32_t ISER[8]; /*!< Offset: 0x000 Interrupt Set Enable Register */
uint32_t RESERVED0[24];
__IO uint32_t ICER[8]; /*!< Offset: 0x080 Interrupt Clear Enable Register */
uint32_t RSERVED1[24];
__IO uint32_t ISPR[8]; /*!< Offset: 0x100 Interrupt Set Pending Register */
uint32_t RESERVED2[24];
__IO uint32_t ICPR[8]; /*!< Offset: 0x180 Interrupt Clear Pending Register */
uint32_t RESERVED3[24];
__IO uint32_t IABR[8]; /*!< Offset: 0x200 Interrupt Active bit Register */
uint32_t RESERVED4[56];
__IO uint8_t IP[240]; /*!< Offset: 0x300 Interrupt Priority Register (8Bit wide) */
uint32_t RESERVED5[644];
__O uint32_t STIR; /*!< Offset: 0xE00 Software Trigger Interrupt Register */
} NVIC_Type;
typedef struct
{
__I uint32_t CPUID; /*!< Offset: 0x00 CPU ID Base Register */
__IO uint32_t ICSR /*!< Offset: 0x04 Interrupt Control State Register */
__IO uint32_t VTOR; /*!< Offset: 0x08 Vector Table Offset Register */
__IO uint32_t AIRCR; /*!< Offset: 0x0C Application Interrupt / Reset Control Register */
__IO uint32_t SCR; /*!< Offset: 0x10 System Control Register */
__IO uint32_t CCR; /*!< Offset: 0x14 Configuration Control Register */
__IO uint8_t SHP[12]; /*!< Offset: 0x18 System Handlers Priority Registers (4-7, 8-11, 12-15)*/
__IO uint32_t SHCSR; /*!< Offset: 0x24 System Handler Control and State Register */
__IO uint32_t CFSR; /*!< Offset: 0x28 Configurable Fault Status Register */
__IO uint32_t HFSR; /*!< Offset: 0x2C Hard Fault Status Register */
__IO uint32_t DFSR; /*!< Offset: 0x30 Debug Fault Status Register */
__IO uint32_t MMFAR; /*!< Offset: 0x34 Mem Manage Address Register */
__IO uint32_t BFAR; /*!< Offset: 0x38 Bus Fault Address Register */
__IO uint32_t AFSR; /*!< Offset: 0x3C Auxiliary Fault Status Register */
__I uint32_t PFR[2]; /*!< Offset: 0x40 Processor Feature Register */
__I uint32_t DFR; /*!< Offset: 0x48 Debug Feature Register */
__I uint32_t ADR; /*!< Offset: 0x4C Auxiliary Feature Register */
__I uint32_t MMFR[4]; /*!< Offset: 0x50 Memory Model Feature Register */
__I uint32_t ISAR[5]; /*!< Offset: 0x60 ISA Feature Register */
} SCB_Type;
Both NVIC and SCB are located in the system control space (SCS). The address of SCS starts from 0XE000E000. The addresses of SCB and NVIC are also defined in core_cm3.h, as follows:
#define SCS_BASE (0xE000E000) /*!< System Control Space Base Address */
#define NVIC_BASE (SCS_BASE + 0x0100) /*!< NVIC Base Address */
#define SCB_BASE (SCS_BASE + 0x0D00) /*!< System Control Block Base Address */
#define SCB ((SCB_Type * ) SCB_BASE ) /*!< SCB configuration struct */
#define NVIC ((NVIC_Type* ) NVIC_BASE ) /*!< NVIC configuration struct *//
3. Priority grouping defines
which interrupt the processor should respond to is determined by the priority of the interrupt when multiple interrupts come. Level interrupts can preempt lower priority interrupts, this is interrupt nesting. Some interrupts of the Cortex-M processor have fixed priorities, such as reset, NMI, and HardFault. The priorities of these interrupts are negative, and the priority is the highest.
The Cortex-M processor has three fixed priorities and 256 programmable priorities, with a maximum of 128 preemption levels, but the actual number of priorities is determined by the chip manufacturer. However, most chips will be simplified in design, so that the number of priorities actually supported will be less, such as 8 levels, 16 levels, 32 levels, etc. For example, STM32 only has 16 levels of priority. When designing the chip, several low-end effective bits that express the priority will be cut off to reduce the number of priorities, so no matter how many bits are used to express the priority, they are all MSB-aligned. The following figure uses three bits to express the priority. class.
In the above figure, Bit0~Bit4 are not implemented, so reading them always returns zero, and writing them will ignore the written value. Therefore, for the case of 3 bits, there are 8 priority levels used: 0X00 (highest priority), 0X20, 0X40, 0X60, 0X80, 0XA0, 0XC0, and 0XE0. 注意,这个是芯片厂商来决定的!不是我们能决定的,比如 STM32 就选择了 4 位作为优先级!
Some friends may ask, the priority configuration register is 8 bits wide, why there are only 128 preemption levels? Shouldn't 8 bits be 256 preemption levels? In order to make the preemption function more controllable, the Cortex-M processor also divides the 256 priorities into high and low segments: preemption priority (group priority) and sub-priority (sub-priority), NVIC has One register is "Application Program Interrupt and Reset Control Register (AIRCR)". There is a bit field in the AIRCR register called "Priority Group", as shown in the following table: PRIGROUP in the above table is the priority group, which divides the priority
into There are two bit segments: the bit segment where the MSB is located (on the left) corresponds to the preemption priority, and the bit segment where the LSB is located (on the right) corresponds to the sub-priority, as shown in the table below.
Looking at the priority grouping of STM32, we said earlier that STM32 uses 4 bits, so there are at most 5 groups of priority grouping settings. These 5 groups are defined in msic.h, as follows:
#define NVIC_PriorityGroup_0 ((uint32_t)0x700) /*!< 0 bits for pre-emption priority
4 bits for subpriority */
#define NVIC_PriorityGroup_1 ((uint32_t)0x600) /*!< 1 bits for pre-emption priority
3 bits for subpriority */
#define NVIC_PriorityGroup_2 ((uint32_t)0x500) /*!< 2 bits for pre-emption priority
2 bits for subpriority */
#define NVIC_PriorityGroup_3 ((uint32_t)0x400) /*!< 3 bits for pre-emption priority
1 bits for subpriority */
#define NVIC_PriorityGroup_4 ((uint32_t)0x300) /*!< 4 bits for pre-emption priority
0 bits for subpriority */
It can be seen that STM32 has 5 groups, but we must pay attention! The value corresponding to group 0 defined in STM32 is 7! If we choose group 4, that is, NVIC_PriorityGroup_4, the 4 priorities are all preemptive priorities, and there is no sub-priority, so there are 16 priorities ranging from 0 to 15. When transplanting FreeRTOS, we configured group 4, as shown in the following figure:
If you use ALIENTEK's basic routine, the default configuration is group 2, so you need to modify the priority configuration when porting the peripheral driver in the basic routine to FreeRTOS. The main reason is that the interrupt configuration of FreeRTOS does not deal with sub-priority, so it can only be configured as group 4, directly with 16 priorities, and it is easy to use!
4. Priority setting
Each external interrupt has a corresponding priority register, each register occupies 8 bits, so the maximum width is 8 bits, but the minimum is 3 bits. Four adjacent priority registers form a 32-bit register. As mentioned above, according to the setting of the priority group, the priority can be divided into high and low bit segments, which respectively preempt the priority and sub-priority. STM32 We have set bit group 4, so there is only preemption priority. Priority registers can be accessed by byte, and of course they can also be accessed by halfword/word. The number of meaningful priority registers is implemented by the chip manufacturer, as shown in the following table: As mentioned above, 4 adjacent registers can
be A 32-bit register is assembled, so the four registers address 0xE000_ED20~0xE000_ED23 can be assembled into a 32-bit register whose address is 0xE000_ED20. this point is very important! Because FreeRTOS directly operates the address 0xE000_ED20 when setting the interrupt priority of PendSV and SysTick.
5. Special registers for interrupt masking
- (1) PRIMASK and FAULTMASK registers
In many applications, it is necessary to temporarily mask all interrupts and execute some tasks that are strict with timing. At this time, the PRIMASK register can be used. PRIMASK is used to disable all exceptions and interrupts except NMI and HardFalut. It can be used in assembly programming. The CPS (Modify Processor State) instruction modifies the value of the PRIMASK register:
CPSIE I; //清除 PRIMASK(使能中断)
CPSID I; //设置 PRIMASK(禁止中断)
The PRIMASK register can also be accessed by the MRS and MSR instructions, as follows:
MOVS R0, #1
MSR PRIMASK, R0 ;//将 1 写入 PRIMASK 禁止所有中断
as well as:
MOVS R0, #0
MSR PRIMASK, R0 ;//将 0 写入 PRIMASK 以使能中断
The code protection of the critical section code in UCOS is realized through the switch interrupt (UCOSIII can also use the method of prohibiting task scheduling to realize the protection of the critical section code, which is not discussed here), and the switch interrupt directly operates the PRIMASK register, so When turning off interrupts in UCOS, all interrupts except reset, NMI and HardFault are turned off!
FAULTMASK is more ruthless than PRIMASK, it can even shield HardFault, the method of use is similar to PRIMASK, FAULTMASK will be automatically cleared when exiting.
When programming in assembly, you can use the CPS instruction to modify the current state of FAULTMASK:
CPSIE F ;清除 FAULTMASK
CPSID F ;设置 FAULTMASK
The FAULTMASK register can also be accessed using the MRS and MSR instructions:
MOVS R0, #1
MSR FAULTMASK, R0 ;将 1 写入 FAULTMASK 禁止所有中断
as well as:
MOVS R0, #0
MSR FAULTMASK, R0 ;将 0 写入 FAULTMASK 使能中断
- (2)
The PRIMASK and FAULTMASK registers of the BASEPRI register are too rough to directly disable all other interrupts except reset, NMI and HardFault, but in some cases it is necessary to perform more fine-grained control on the interrupt mask, such as only masking priority lower than a certain Threshold interruption. So where is this priority value stored as a threshold? In the BASEPRI register, but writing 0 to BASEPRI stops masking interrupts. For example, if we want to shield the interrupt whose priority is not higher than 0X60, we can use the following assembly programming:
MOV R0, #0X60
MSR BASEPRI, R0
If you need to cancel BASEPRI's masking of interrupts, you can use the following code:
MOV R0, #0
MSR BASEPRI, R0
Notice! The switch interrupt of FreeRTOS is realized by operating the BASEPRI register! It can turn off interrupts below a certain threshold, and interrupts above this threshold will not be turned off!
2. FreeRTOS interrupt configuration macro
1. configPRIO_BITS
This macro is used to set the priority of how many bits the MCU uses. STM32 uses 4 bits, so this macro is 4!
2. configLIBRARY_LOWEST_INTERRUPT_PRIORITY
This macro is used to set the lowest priority. As mentioned earlier, the STM32 priority uses 4 bits, and the STM32 configuration uses group 4, that is, the 4 bits are preemptive priority. So the number of priorities is 16, and the lowest priority is 15. So this macro is 15, pay attention! This value is different for different MCUs, and the specific amount depends on the architecture of the MCU used. This article only explains for STM32!
3. configKERNEL_INTERRUPT_PRIORITY
This macro is used to set the kernel interrupt priority. This macro is defined as follows:
#define configKERNEL_INTERRUPT_PRIORITY
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
The macro configKERNEL_INTERRUPT_PRIORITY is the macro
configLIBRARY_LOWEST_INTERRUPT_PRIORITY shifted left by 8-configPRIO_BITS bits, that is, shifted left by 4 bits. Why do you want to shift left by 4 bits? As mentioned earlier, STM32 uses 4 bits as the priority, and these 4 bits are the upper 4 bits, so shifting 4 bits to the left is the real priority. Of course, you can directly define the macro configLIBRARY_LOWEST_INTERRUPT_PRIORITY as 0XF0 without shifting! But that doesn't seem intuitive.
The macro configKERNEL_INTERRUPT_PRIORITY is used to set the interrupt priority of PendSV and tick timer, which is defined in port.c as follows:
#define portNVIC_PENDSV_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) <<
16UL )
#define portNVIC_SYSTICK_PRI ( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) <<
24UL )
It can be seen that both portNVIC_PENDSV_PRI and portNVIC_SYSTICK_PRI use the macro
configKERNEL_INTERRUPT_PRIORITY, why the macro portNVIC_PENDSV_PRI is the macro
configKERNEL_INTERRUPT_PRIORITY shifted 16 bits to the left? The macro portNVIC_SYSTICK_PRI is also shifted left by 24 bits. The interrupt priority settings of PendSV and SysTcik operate on the 0xE000_ED20 address
, so that a 32-bit data is written at a time, and the priority registers of SysTick and PendSV correspond to the highest 8 bits and the second highest 8 bits of the 32-bit data, isn’t it? One is shifted left by 16 bits, and the other is shifted left by 24 bits.
Where are the priorities of PendSV and SysTick set? Set in the function xPortStartScheduler(), this function is in the file port.c, the function is as follows:
BaseType_t xPortStartScheduler( void )
{
configASSERT( configMAX_SYSCALL_INTERRUPT_PRIORITY );
configASSERT( portCPUID != portCORTEX_M7_r0p1_ID );
configASSERT( portCPUID != portCORTEX_M7_r0p0_ID );
#if( configASSERT_DEFINED == 1 )
{
volatile uint32_t ulOriginalPriority;
volatile uint8_t * const pucFirstUserPriorityRegister = ( uint8_t * )
( portNVIC_IP_REGISTERS_OFFSET_16 +
portFIRST_USER_INTERRUPT_NUMBER );
volatile uint8_t ucMaxPriorityValue;
ulOriginalPriority = *pucFirstUserPriorityRegister;
*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;
ucMaxPriorityValue = *pucFirstUserPriorityRegister;
configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY &
ucMaxPriorityValue ) );
ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY &
ucMaxPriorityValue;
ulMaxPRIGROUPValue = portMAX_PRIGROUP_BITS;
while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
{
ulMaxPRIGROUPValue--;
ucMaxPriorityValue <<= ( uint8_t ) 0x01;
}
ulMaxPRIGROUPValue <<= portPRIGROUP_SHIFT;
ulMaxPRIGROUPValue &= portPRIORITY_GROUP_MASK;
*pucFirstUserPriorityRegister = ulOriginalPriority;
}
#endif /* conifgASSERT_DEFINED */
portNVIC_SYSPRI2_REG |= portNVIC_PENDSV_PRI; //设置 PendSV 中断优先级
portNVIC_SYSPRI2_REG |= portNVIC_SYSTICK_PRI; //设置 SysTick 中断优先级
vPortSetupTimerInterrupt();
uxCriticalNesting = 0;
prvStartFirstTask();
return 0;
}
The red part in the above code is to set the priority of PendSV and SysTick, they
write the priority data directly to the address portNVIC_SYSPRI2_REG, portNVIC_SYSPRI2_REG is a macro, defined in the file port.c, as follows:
#define portNVIC_SYSPRI2_REG ( * ( ( volatile uint32_t * ) 0xe000ed20 ) )
You can see that the macro portNVIC_SYSPRI2_REG is the address 0XE000ED20! At the same time, it can also be seen that the interrupt priorities of PendSV and SysTick are the lowest in FreeRTOS!
4. configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY This macro is used to set the maximum priority that the FreeRTOS system can manage, that is , the threshold priority
we explained about the BASEPRI register in Section 1.5. You can set this freely, and I set it to 5 here.
That is, priority higher than 5 (priority number less than 5) is not managed by FreeRTOS!
5. configMAX_SYSCALL_INTERRUPT_PRIORITY
This macro is configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY shifted 4 bits to the left, the reason is the same as the macro configKERNEL_INTERRUPT_PRIORITY. After this macro is set, interrupts lower than this priority can safely call FreeRTOS API functions, interrupts higher than this priority cannot be disabled by FreeRTOS, and interrupt service functions cannot call FreeRTOS API functions!
Taking STM32 as an example, there are 16 priorities, 0 is the highest priority, and 15 is the lowest priority. The configuration is as follows:
● configMAX_SYSCALL_INTERRUPT_PRIORITY== 5
● configKERNEL_INTERRUPT_PRIORITY==15
The result is shown in the figure below:
Since priorities higher than configMAX_SYSCALL_INTERRUPT_PRIORITY are not blocked by the FreeRTOS kernel, tasks that have strict real-time requirements can use these priorities, such as obstacle detection in a quadcopter.
3. FreeRTOS switch interrupt
The FreeRTOS switch interrupt functions are portENABLE_INTERRUPTS () and portDISABLE_INTERRUPTS(). These two functions are actually macro definitions, which are defined in portmacro.h, as follows:
#define portDISABLE_INTERRUPTS() vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS() vPortSetBASEPRI(0)
It can be seen that the switch interrupt is actually implemented through the functions vPortSetBASEPRI(0) and vPortRaiseBASEPRI(). These two functions are as follows:
static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
{
__asm
{
msr basepri, ulBASEPRI
}
}
/*-----------------------------------------------------------*/
static portFORCE_INLINE void vPortRaiseBASEPRI( void )
{
uint32_t ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
msr basepri, ulNewBASEPRI
dsb
isb
}
}
The function vPortSetBASEPRI() is to write a value to the register BASEPRI, and this value is passed in as a parameter ulBASEPRI, portENABLE_INTERRUPTS() is to enable an interrupt, it passes a 0 to vPortSetBASEPRI(), according to our previous explanation of the BASEPRI register, the result is to enable an interrupt .
The function vPortRaiseBASEPRI() is to write the macro configMAX_SYSCALL_INTERRUPT_PRIORITY to the register BASEPRI, then the interrupt whose priority is lower than configMAX_SYSCALL_INTERRUPT_PRIORITY will be masked!
Fourth, the critical section code
The critical section code is also called the critical section, which refers to the code section that must run completely and cannot be interrupted. For example, the initialization of some peripherals requires strict timing and cannot be interrupted during the initialization process. FreeRTOS needs to turn off the interrupt when entering the critical section code, and turn on the interrupt after processing the critical section code. The FreeRTOS system itself has a lot of critical section codes, and these codes are protected by critical section codes. When we write our own user programs, we also need to add critical section code protection in some places.
FreeRTOS has 4 functions related to critical section code protection: taskENTER_CRITICAL(),
taskEXIT_CRITICAL(), taskENTER_CRITICAL_FROM_ISR() and
taskEXIT_CRITICAL_FROM_ISR(), these four functions are actually macro definitions, which are defined in the task.h file. The difference between these four functions is that the first two are task-level critical section code protection, and the latter two are interrupt-level critical section code protection.
1. Task-level critical section code protection
taskENTER_CRITICAL() and taskEXIT_CRITICAL() are task-level critical code protection, one is to enter the critical section, the other is to exit the critical section, these two functions are used in pairs, the definition of this function is as follows :
#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()
And portENTER_CRITICAL() and portEXIT_CRITICAL() are also macro definitions, which are defined in the file portmacro.h, as follows:
#define portENTER_CRITICAL() vPortEnterCritical()
#define portEXIT_CRITICAL() vPortExitCritical()
The functions vPortEnterCritical() and vPortExitCritical() are in the file port.c, and the functions are as follows:
void vPortEnterCritical( void )
{
portDISABLE_INTERRUPTS();
uxCriticalNesting++;
if( uxCriticalNesting == 1 )
{
configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );
}
}
void vPortExitCritical( void )
{
configASSERT( uxCriticalNesting );
uxCriticalNesting--;
if( uxCriticalNesting == 0 )
{
portENABLE_INTERRUPTS();
}
}
It can be seen that after entering the function vPortEnterCritical(), the interrupt will be turned off first, and then one will be added to the variable uxCriticalNesting. uxCriticalNesting is a global variable used to record the number of critical section nesting. The function vPortExitCritical() is called when exiting the critical segment. The function will decrease uxCriticalNesting by one each time. Only when uxCriticalNesting is 0 will the function portENABLE_INTERRUPTS() be called to enable interrupts. This ensures that when there are multiple critical section codes, the protection of other critical sections will not be disrupted due to the exit of a certain critical section code, and the interrupt will be enabled only after all the critical section codes have exited!
Task-level critical code protection is used as follows:
void taskcritical_test(void)
{
while(1)
{
taskENTER_CRITICAL(); (1)
total_num+=0.01f;
printf("total_num 的值为: %.4f\r\n",total_num);
taskEXIT_CRITICAL(); (2)
vTaskDelay(1000);
}
}
(1), enter the critical area.
(2) Exit the critical section.
The code between (1) and (2) is the critical section code, pay attention to the critical section code must be streamlined! Because entering the critical section will disable interrupts, this will cause interrupts with a priority lower than configMAX_SYSCALL_INTERRUPT_PRIORITY not to be responded in time!
2. Interrupt-level critical section code protection
Functions taskENTER_CRITICAL_FROM_ISR() and taskEXIT_CRITICAL_FROM_ISR() interrupt-level critical section code protection are used in interrupt service routines, and the priority of this interrupt must be lower than
configMAX_SYSCALL_INTERRUPT_PRIORITY! The reason has already been said before. These two functions are defined in the file task.h as follows:
#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )
Then look for portSET_INTERRUPT_MASK_FROM_ISR() and
portCLEAR_INTERRUPT_MASK_FROM_ISR(), which are defined in the file portmacro.h as follows:
#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)
vPortSetBASEPRI() has been explained earlier, it is to write a value to the BASEPRI register.
The function ulPortRaiseBASEPRI() is defined in the file portmacro.h, as follows:
static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )
{
uint32_t ulReturn, ulNewBASEPRI = configMAX_SYSCALL_INTERRUPT_PRIORITY;
__asm
{
mrs ulReturn, basepri (1)
msr basepri, ulNewBASEPRI (2)
dsb
isb
}
return ulReturn; (3)
}
(1) First read the value of BASEPRI and save it in ulReturn.
(2). Write configMAX_SYSCALL_INTERRUPT_PRIORITY into register BASEPRI.
(3), return ulReturn, this value should be used when exiting the code protection of the critical section!
The use method of interrupt level critical code protection is as follows:
//Timer 3 interrupt service function
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
status_value=taskENTER_CRITICAL_FROM_ISR(); (1)
total_num+=1;
printf("float_num 的值为: %d\r\n",total_num);
taskEXIT_CRITICAL_FROM_ISR(status_value); (2)
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
(1), enter the critical area.
(2) Exit the critical section.
5. FreeRTOS interrupt test experiment
1. Experimental program design
(1) Experimental purpose
We mentioned above that in FreeRTOS, interrupts whose priority is lower than configMAX_SYSCALL_INTERRUPT_PRIORITY will be masked, and those higher than configMAX_SYSCALL_INTERRUPT_PRIORITY will not be masked, so in this section we will write a simple routine test. Use two timers, one with a priority of 4 and one with a priority of 5. The two timers output a string of strings through the serial port every 1s. Then turn off the interrupt for a period of time in a certain task, and check the output of the two timers.
(2) Experiment design
This experiment designs two tasks start_task() and interrupt_task(), the task functions of these two tasks are as follows:
start_task(): create another task. interrupt_task() : Interrupt the test task, the task will call the FreeRTOS interrupt function portDISABLE_INTERRUPTS() to disable the interrupt for a period of time.
(3) Experimental procedure and analysis
● Task setting
#define START_TASK_PRIO 1 //任务优先级
#define START_STK_SIZE 256 //任务堆栈大小
TaskHandle_t StartTask_Handler; //任务句柄
void start_task(void *pvParameters); //任务函数
#define INTERRUPT_TASK_PRIO 2 //任务优先级
#define INTERRUPT_STK_SIZE 256 //任务堆栈大小
TaskHandle_t INTERRUPTTask_Handler; //任务句柄
void interrupt_task(void *p_arg); //任务函数
● main() function
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//设置系统中断优先级分组 4
delay_init(); //延时函数初始化
uart_init(115200); //初始化串口
LED_Init(); //初始化 LED
TIM3_Int_Init(10000-1,7200-1); //初始化定时器 3,定时器周期 1S
TIM5_Int_Init(10000-1,7200-1); //初始化定时器 5,定时器周期 1S
//创建开始任务
xTaskCreate((TaskFunction_t )start_task, //任务函数
(const char* )"start_task", //任务名称
(uint16_t )START_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )START_TASK_PRIO, //任务优先级
(TaskHandle_t* )&StartTask_Handler); //任务句柄
vTaskStartScheduler(); //开启任务调度
}
● task function
//开始任务任务函数
void start_task(void *pvParameters)
{
taskENTER_CRITICAL(); //进入临界区
//创建中断测试任务
xTaskCreate((TaskFunction_t )interrupt_task, //任务函数 (1)
(const char* )"interrupt_task", //任务名称
(uint16_t )INTERRUPT_STK_SIZE, //任务堆栈大小
(void* )NULL, //传递给任务函数的参数
(UBaseType_t )INTERRUPT_TASK_PRIO, //任务优先级
(TaskHandle_t* )&INTERRUPTTask_Handler); //任务句柄
vTaskDelete(StartTask_Handler); //删除开始任务
taskEXIT_CRITICAL(); //退出临界区
}
//中断测试任务函数
void interrupt_task(void *pvParameters)
{
static u32 total_num=0;
while(1)
{
printf("秒数%d\r\n",total_num);
total_num+=1;
if(total_num==5)
{
printf("关闭中断.............\r\n");
portDISABLE_INTERRUPTS(); //关闭中断
delay_xms(5000); //延时5s
printf("打开中断.............\r\n"); //打开中断
portENABLE_INTERRUPTS();
}
LED0=~LED0;
vTaskDelay(1000);
}
}
● Timer interrupt
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); //时钟使能
//定时器TIM3初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE ); //使能指定的TIM3中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4; //先占优先级4级 (1)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能TIMx
}
//通用定时器5中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器5!
void TIM5_Int_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); //时钟使能
//定时器TIM5初始化
TIM_TimeBaseStructure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
TIM_TimeBaseStructure.TIM_Prescaler =psc; //设置用来作为TIMx时钟频率除数的预分频值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM5,TIM_IT_Update,ENABLE ); //使能指定的TIM5中断,允许更新中断
//中断优先级NVIC设置
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //TIM5中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 5; //先占优先级5级 (2)
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //初始化NVIC寄存器
TIM_Cmd(TIM5, ENABLE); //使能TIM5
}
//定时器3中断服务函数
void TIM3_IRQHandler(void)
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET) //溢出中断
{
printf("TIM3输出.......\r\n"); (3)
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update); //清除中断标志位
}
//定时器5中断服务函数
void TIM5_IRQHandler(void)
{
if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET) //溢出中断
{
printf("TIM5输出.......\r\n"); (4)
}
TIM_ClearITPendingBit(TIM5,TIM_IT_Update); //清除中断标志位
}
(1) Set the preemption priority of timer 3 to 4, which is higher than configMAX_SYSCALL_INTERRUPT_PRIORITY, so timer 3 will not be affected when the function portDISABLE_INTERRUPTS() is called to disable interrupts.
(2) Set the preemption priority of timer 5 to 5, which is equal to configMAX_SYSCALL_INTERRUPT_PRIORITY, so when the function portDISABLE_INTERRUPTS() is called to disable the interrupt, the timer 5 interrupt will definitely be disabled.
(3) and (4), timer 3 and timer 5 serial port output information.
2. Experimental program running results
Compile and download the code to the development board, open the serial port debugging assistant to view the data output, the result is shown in the figure below:
From the figure above, it can be seen that the interrupt was not turned off at the beginning, so both TIM3 and TIM5 are running normally. The part indicated by the red box. When the task interrupt_task() runs 5 times, the interrupt is turned off. At this time, because the interrupt priority of TIM5 is 5, which is equal to configMAX_SYSCALL_INTERRUPT_PRIORITY, TIM5 is turned off. However, the interrupt priority of TIM3 is higher than configMAX_SYSCALL_INTERRUPT_PRIORITY and will not be closed, so TIM3 runs normally, as shown in the green box. After the interrupt is disabled for 5 seconds, the function portENABLE_INTERRUPTS() will be called to re-enable the interrupt. After re-enabling the interrupt, TIM5 will resume operation, as shown in the blue box.