03 interrupt configuration and critical segment (ING) of FreeRTOS

1 interrupt configuration

1.1 Cortex-M interrupt

The MCU of Cortex-M kernel provides a nested vector interrupt controller (NVIC) for interrupt management. The NVIC of Cotex-M3 supports up to 240 IRQs (interrupt requests), 1 non maskable interrupt (NMI), 1 systick timer interrupt and multiple system exceptions.
The access address of NVIC is 0xE000_E000, the interrupt control / status registers of all nvics can only be accessed in word / half word / byte mode at the privilege level (except that the software trigger interrupt register can be accessed at the user level to generate software interrupts). In addition, several interrupt mask registers are also closely related to interrupt control. They are special function registers that can only be accessed through MRS/MSR and CPS.

1.1.1 interrupt management

Cortex-M processor has several programmable registers for managing interrupts and exceptions. Most of these registers are in NVIC and system control block (SCB). The specific register information can be viewed STM32F10xxx Cortex-M3 programming manual and Cortex ™- M3 technical reference manual.
CMSIS defines these registers as structures, taking STM32F103Z as an example, in the core_cm3.h defines two structures NVIC_Type and SCB_Type.

  \brief  Structure type to access the Nested Vectored Interrupt Controller (NVIC).
typedef struct
  __IOM uint32_t ISER[8U];               /*!< Offset: 0x000 (R/W)  Interrupt Set Enable Register */
        uint32_t RESERVED0[24U];
  __IOM uint32_t ICER[8U];               /*!< Offset: 0x080 (R/W)  Interrupt Clear Enable Register */
        uint32_t RSERVED1[24U];
  __IOM uint32_t ISPR[8U];               /*!< Offset: 0x100 (R/W)  Interrupt Set Pending Register */
        uint32_t RESERVED2[24U];
  __IOM uint32_t ICPR[8U];               /*!< Offset: 0x180 (R/W)  Interrupt Clear Pending Register */
        uint32_t RESERVED3[24U];
  __IOM uint32_t IABR[8U];               /*!< Offset: 0x200 (R/W)  Interrupt Active bit Register */
        uint32_t RESERVED4[56U];
  __IOM uint8_t  IP[240U];               /*!< Offset: 0x300 (R/W)  Interrupt Priority Register (8Bit wide) */
        uint32_t RESERVED5[644U];
  __OM  uint32_t STIR;                   /*!< Offset: 0xE00 ( /W)  Software Trigger Interrupt Register */
}  NVIC_Type;

  \brief  Structure type to access the System Control Block (SCB).
typedef struct
  __IM  uint32_t CPUID;                  /*!< Offset: 0x000 (R/ )  CPUID Base Register */
  __IOM uint32_t ICSR;                   /*!< Offset: 0x004 (R/W)  Interrupt Control and State Register */
  __IOM uint32_t VTOR;                   /*!< Offset: 0x008 (R/W)  Vector Table Offset Register */
  __IOM uint32_t AIRCR;                  /*!< Offset: 0x00C (R/W)  Application Interrupt and Reset Control Register */
  __IOM uint32_t SCR;                    /*!< Offset: 0x010 (R/W)  System Control Register */
  __IOM uint32_t CCR;                    /*!< Offset: 0x014 (R/W)  Configuration Control Register */
  __IOM uint8_t  SHP[12U];               /*!< Offset: 0x018 (R/W)  System Handlers Priority Registers (4-7, 8-11, 12-15) */
  __IOM uint32_t SHCSR;                  /*!< Offset: 0x024 (R/W)  System Handler Control and State Register */
  __IOM uint32_t CFSR;                   /*!< Offset: 0x028 (R/W)  Configurable Fault Status Register */
  __IOM uint32_t HFSR;                   /*!< Offset: 0x02C (R/W)  HardFault Status Register */
  __IOM uint32_t DFSR;                   /*!< Offset: 0x030 (R/W)  Debug Fault Status Register */
  __IOM uint32_t MMFAR;                  /*!< Offset: 0x034 (R/W)  MemManage Fault Address Register */
  __IOM uint32_t BFAR;                   /*!< Offset: 0x038 (R/W)  BusFault Address Register */
  __IOM uint32_t AFSR;                   /*!< Offset: 0x03C (R/W)  Auxiliary Fault Status Register */
  __IM  uint32_t PFR[2U];                /*!< Offset: 0x040 (R/ )  Processor Feature Register */
  __IM  uint32_t DFR;                    /*!< Offset: 0x048 (R/ )  Debug Feature Register */
  __IM  uint32_t ADR;                    /*!< Offset: 0x04C (R/ )  Auxiliary Feature Register */
  __IM  uint32_t MMFR[4U];               /*!< Offset: 0x050 (R/ )  Memory Model Feature Register */
  __IM  uint32_t ISAR[5U];               /*!< Offset: 0x060 (R/ )  Instruction Set Attributes Register */
        uint32_t RESERVED0[5U];
  __IOM uint32_t CPACR;                  /*!< Offset: 0x088 (R/W)  Coprocessor Access Control Register */
} SCB_Type;

Both NVIC and SCB are located in the system control space (SCS). The address of SCS starts from 0XE000E000, and the address definitions of SCB and NVIC are also in the core_cm3.h.

/* Memory mapping of Core Hardware */
#define SCS_BASE            (0xE000E000UL)                            /*!< System Control Space Base Address */
#define NVIC_BASE           (SCS_BASE +  0x0100UL)                    /*!< NVIC Base Address */
#define SCB_BASE            (SCS_BASE +  0x0D00UL)                    /*!< System Control Block Base Address */

#define SCB                 ((SCB_Type       *)     SCB_BASE      )   /*!< SCB configuration struct */
#define NVIC                ((NVIC_Type      *)     NVIC_BASE     )   /*!< NVIC configuration struct */

1.1.2 priority grouping

The priority of interrupts determines the sequence of interrupt response. High priority interrupts (with small priority number) must get the response first, and high priority interrupts can preempt low priority interrupts, which is interrupt nesting. Some interrupts of Cortex-M processor have fixed priority, such as reset, NMI and HardFault. The priority of these interrupts is negative and the priority is the highest.
Cortex-M processor has 3 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. For example, STM32F1 has only 16 levels of priority. The priority configuration register is 8 bits wide. When designing the chip, several low-end significant bits expressing priority will be cut to reduce the priority level. Therefore, no matter how many bits are used to express priority, they are MSB aligned. STM32F1 selects the upper 4 valid bits.
There is a register "application interrupt and reset control register (AIRCR)" in NVIC. There is a bit segment named priority group in AIRCR register, which divides the priority into two bit segments: the bit segment where MSB is located corresponds to preemptive priority, and the bit segment where LSB is located corresponds to sub priority.

STM32F1 selects 4 bits valid in the priority configuration register, so there are up to 5 groups of priority grouping settings. The priority grouping is shown in the following figure and stm32f1xx_ hal_ The code in cortex. H is shown in.

/** @defgroup CORTEX_Preemption_Priority_Group CORTEX Preemption Priority Group
  * @{
#define NVIC_PRIORITYGROUP_0         0x00000007U /*!< 0 bits for pre-emption priority
                                                      4 bits for subpriority */
#define NVIC_PRIORITYGROUP_1         0x00000006U /*!< 1 bits for pre-emption priority
                                                      3 bits for subpriority */
#define NVIC_PRIORITYGROUP_2         0x00000005U /*!< 2 bits for pre-emption priority
                                                      2 bits for subpriority */
#define NVIC_PRIORITYGROUP_3         0x00000004U /*!< 3 bits for pre-emption priority
                                                      1 bits for subpriority */
#define NVIC_PRIORITYGROUP_4         0x00000003U /*!< 4 bits for pre-emption priority
                                                      0 bits for subpriority */

Generally, the interrupt configuration of FreeRTOS does not deal with sub priority, so it is configured as group 4 (NVIC_PRIORITYGROUP_4), which directly has 16 priorities and is easy to use!

1.1.3 setting priority

For details, please refer to Cortex-M3 authoritative guide (Chinese). pdf translated by song Yan. chpt08 NVIC and interrupt control are described in detail.
Each external interrupt has a corresponding priority register. Each register occupies 8 bits, but it is allowed to use at least the highest 3 bits. Four adjacent priority registers are combined into a 32-bit register. As mentioned earlier, according to the priority group setting, the priority can be divided into high and low bit segments, namely preemptive priority and sub priority. Priority registers can be accessed by byte or half word / word. The number of meaningful priority registers is determined by the number of interrupts implemented by the chip manufacturer.

Four adjacent priority registers are combined into a 32-bit register, so the address is 0xE000_ED20~0xE000_ED23 these four registers can be spliced into one address 0xe000_ 32-bit register of ed20. [important]: FreeRTOS directly operates the address 0xe000 when setting the interrupt priority of PendSV and SysTick_ ED20.

1.1.4 special registers for interrupt masking (PRIMASK, FAULTMASK and BASEPRI)

For details, please refer to Cortex-M3 authoritative guide (Chinese). pdf translated by song Yan. chpt08 NVIC and interrupt control are described in detail.
PRIMASK, FAULTMASK and BASEPRI are special function registers, which are closely related to interrupt control.

    PRIMASK is used to prohibit all exceptions and interrupts except NMI and HardFalut. It effectively changes the current priority to 0 (the highest priority in the programmable priority). This register can be accessed through MRS and MSR:

    MOVS R0, #1
    MSR PRIMASK, R0 ;//Write 1 to PRIMASK to disable all interrupts
    MOVS R0, #0
    MSR PRIMASK, R0 ;//Write 0 to PRIMASK enable interrupt

    In addition, the above functions can be quickly completed through CPS instructions:

    CPSIE I; //Clear primask (enable interrupt)
    CPSID I; //Set primask (no interrupt)

    FAULTMASK is more unique. It changes the current priority to ‐ 1, which can mask even HardFault. The use method is similar to PRIMASK, but it should be noted that FAULTMASK will automatically clear when abnormal exit. This register can be accessed through MRS and MSR:

    MOVS R0, #1
    MSR FAULTMASK, R0 ;//Write 1 to FAULTMASK to disable all interrupts
    MOVS R0, #0
    MSR FAULTMASK, R0 ;//Write 0 to FAULTMASK enable interrupt

    In addition, the above functions can be quickly completed through CPS instructions:

  • BASEPRI register
    In a more sophisticated design, interrupt masking needs to be more finely controlled (only interrupts with priority lower than a certain threshold). The priority value of this threshold is stored in the BASEPRI register, but writing 0 to BASEPRI will stop masking interrupts. For example, if you need to mask all interrupts with priority no higher than 0x60, you can program as follows:

    MOV R0, #0X60

    If you need to cancel the shielding of interrupts by BASEPRI, you can use the following code:

    MOV R0, #0

    [note]: the switch interrupt of FreeRTOS is realized by operating BASEPRI register! It can close interrupts below a certain threshold, and interrupts above this threshold will not be closed!

1.2 FreeRTOS interrupt configuration macro

with 01 - Introduction to FreeRTOS and transplantation on Cortex-M3 (the latest version of FreeRTOS on the official website) 2.2 stm32submx software directly creates the FreeRTOSConfig.h file of the project with FreeRTOS, and only retains the code after interrupt configuration related macros, as follows.

/* Cortex-M specific definitions. */
 /* __BVIC_PRIO_BITS will be specified when CMSIS is being used. */
 #define configPRIO_BITS         __NVIC_PRIO_BITS
 #define configPRIO_BITS         4

/* The lowest interrupt priority that can be used in a call to a "set priority"
function. */

/* The highest interrupt priority that can be used by any interrupt service
routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
PRIORITY THAN THIS! (higher priorities are lower numeric values. */

/* Interrupt priorities used by the kernel port layer itself.  These are generic
to all Cortex-M ports, and do not rely on any particular library functions. */
/* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */

1.2.1 configPRIO_BITS

This macro is used to set the priority of several bits used by MCU. STM32 uses 4 bits, so this macro is 4


This macro is used to set the lowest priority. STM32 uses 4 bits for priority, and the use group 4 configured by STM32, that is, 4 bits, is preemptive priority. Therefore, the priority level is 16, and the lowest priority is 15. So this macro is 15. Note that this value is different for different MCU. The specific value depends on the architecture of the MCU used.


02 - programming specification and system configuration of FreeRTOS Described in.
This macro is used to set the priority of kernel interrupts (because FreeRTOS kernel interrupts do not allow preemption of interrupts used by users, this macro is generally defined as the lowest priority of hardware), which is defined as follows:


Macro configKERNEL_INTERRUPT_PRIORITY is the macro configLIBRARY_LOWEST_INTERRUPT_PRIORITY shift left 8-configPRIO_BITS bits, because STM32 uses 4 bits as the priority, and these 4 bits are the high 4 bits, it is necessary to shift 4 bits to the left that is the real priority.
Macro configKERNEL_INTERRUPT_PRIORITY is used to set the interrupt priority of PendSV and tick timer. port.c has the following definitions:

#define portNVIC_PENDSV_PRI					( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 16UL )
#define portNVIC_SYSTICK_PRI				( ( ( uint32_t ) configKERNEL_INTERRUPT_PRIORITY ) << 24UL )

As you can see, portNVIC_PENDSV_PRI and NVIC_ SysTick_ Pri uses the macro configKERNEL_INTERRUPT_PRIORITY, where the priority registers of SysTick and PendSV correspond to 0xe000 respectively_ The highest 8 bits and the second highest 8 bits of the 32-bit data stored in the ed20 address, so one is shifted to the left by 16 bits and the other is shifted to the left by 24 bits. It can be seen that in FreeRTOS, PendSV and SysTick have the lowest interrupt priority!!!
PendSV and SysTick priorities are set in the xPortStartScheduler() function in the file port.c. the functions are as follows:

BaseType_t xPortStartScheduler( void )
	#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;

		/* Determine the maximum priority from which ISR safe FreeRTOS API
		functions can be called.  ISR safe functions are those that end in
		"FromISR".  FreeRTOS maintains separate thread and ISR API functions to
		ensure interrupt entry is as fast and simple as possible.

		Save the interrupt priority value that is about to be clobbered. */
		ulOriginalPriority = *pucFirstUserPriorityRegister;

		/* Determine the number of priority bits available.  First write to all
		possible bits. */
		*pucFirstUserPriorityRegister = portMAX_8_BIT_VALUE;

		/* Read the value back to see how many bits stuck. */
		ucMaxPriorityValue = *pucFirstUserPriorityRegister;

		/* The kernel interrupt priority should be set to the lowest
		priority. */
		configASSERT( ucMaxPriorityValue == ( configKERNEL_INTERRUPT_PRIORITY & ucMaxPriorityValue ) );

		/* Use the same mask on the maximum system call priority. */
		ucMaxSysCallPriority = configMAX_SYSCALL_INTERRUPT_PRIORITY & ucMaxPriorityValue;

		/* Calculate the maximum acceptable priority group value for the number
		of bits read back. */
		while( ( ucMaxPriorityValue & portTOP_BIT_OF_BYTE ) == portTOP_BIT_OF_BYTE )
			ucMaxPriorityValue <<= ( uint8_t ) 0x01;

		#ifdef __NVIC_PRIO_BITS
			/* Check the CMSIS configuration that defines the number of
			priority bits matches the number of priority bits actually queried
			from the hardware. */
			configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == __NVIC_PRIO_BITS );

		#ifdef configPRIO_BITS
			/* Check the FreeRTOS configuration that defines the number of
			priority bits matches the number of priority bits actually queried
			from the hardware. */
			configASSERT( ( portMAX_PRIGROUP_BITS - ulMaxPRIGROUPValue ) == configPRIO_BITS );

		/* Shift the priority group value back to its position within the AIRCR
		register. */

		/* Restore the clobbered interrupt priority register to its original
		value. */
		*pucFirstUserPriorityRegister = ulOriginalPriority;
	#endif /* conifgASSERT_DEFINED */

	/* Make PendSV and SysTick the lowest priority interrupts. */

	/* Start the timer that generates the tick ISR.  Interrupts are disabled
	here already. */

	/* Initialise the critical nesting count ready for the first task. */
	uxCriticalNesting = 0;

	/* Start the first task. */

	/* Should not get here! */
	return 0;

The priority of PendSV and SysTick set in the above code is directly to the address portNVIC_SYSPRI2_REG writes priority data. This macro is defined in the file port.c as follows:

#define portNVIC_SYSPRI2_REG				( * ( ( volatile uint32_t * ) 0xe000ed20 ) )

You can see the macro portNVIC_SYSPRI2_REG is the address 0XE000ED20.


This macro is used to set the maximum priority that can be managed by the FreeRTOS system, that is, the threshold priority stored in the BASEPRI register mentioned above. This can be set freely. I set it to 5 here. That is, priorities higher than 5 (priority levels less than 5) are not managed by FreeRTOS.


02 - programming specification and system configuration of FreeRTOS Described in.
This macro is configlibray_ MAX_ SYSCALL_ INTERRUPT_ Priority is shifted to the left by 4 bits. The reason and macro configkernel_ INTERRUPT_ Same as priority. After this macro is set, interrupts lower than this priority can safely call FreeRTOS API functions. Interrupts higher than this priority cannot be prohibited, and interrupt service functions cannot call FreeRTOS API functions.
Taking STM32F1 as an example, there are 16 priorities, 0 is the highest priority and 15 is the lowest priority. The configuration and results are as follows:

Because it is higher than configMAX_SYSCALL_INTERRUPT_PRIORITY priorities are not masked by the FreeRTOS kernel, so those tasks with strict real-time requirements can use these priorities.

1.3 FreeRTOS switch interrupt

The FreeRTOS switch interrupt function is portENABLE_INTERRUPTS() and portDISABLE_INTERRUPTS(), which are actually macro definitions, are defined in portmacro.h as follows:

#define portDISABLE_INTERRUPTS()				vPortRaiseBASEPRI()
#define portENABLE_INTERRUPTS()					vPortSetBASEPRI( 0 )

static portFORCE_INLINE void vPortSetBASEPRI( uint32_t ulBASEPRI )
		/* Barrier instructions are not used as this function is only used to
		lower the BASEPRI value. */
		msr basepri, ulBASEPRI

static portFORCE_INLINE void vPortRaiseBASEPRI( void )

		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		msr basepri, ulNewBASEPRI

The function vPortSetBASEPRI() writes a value to the register BASEPRI, which is passed in as the parameter ulBASEPRI, portENABLE_INTERRUPTS() is an interrupt. It passes a 0 to vportsetbasepri (). According to the BASEPRI register we explained earlier, the result is an interrupt.
The function vportraisebaepri() writes the macro configmax to the register baseepri_ SYSCALL_ INTERRUPT_ Priority, then the priority is lower than configMAX_SYSCALL_INTERRUPT_PRIORITY interrupts are masked.

1.4 critical segment code protection

Critical segment code, also known as critical area, refers to those code segments that must run completely and cannot be interrupted. For example, the initialization of some peripherals requires strict timing and cannot be interrupted during initialization. FreeRTOS needs to close the interrupt when entering the critical segment code, and then open the interrupt after processing the critical segment code.
FreeRTOS has four functions related to critical segment 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 critical segment code protection at task level and the last two are critical segment code protection at interrupt level.

1.4.1 task level critical segment code protection

taskENTER_CRITICAL() and taskEXIT_CRITICAL() is the critical code protection at the task level. One is to enter the critical section and the other is to exit the critical section. These two functions are used in pairs.


portENTER_CRITICAL() and portEXIT_CRITICAL() is also a macro definition, which is defined in the file portmacro.h:

#define portENTER_CRITICAL()					vPortEnterCritical()
#define portEXIT_CRITICAL()						vPortExitCritical()

The functions vPortEnterCritical() and vPortExitCritical() are in the file port.c. the functions are as follows:

void vPortEnterCritical( void )

	/* This is not the interrupt safe version of the enter critical function so
	assert() if it is being called from an interrupt context.  Only API
	functions that end in "FromISR" can be used in an interrupt.  Only assert if
	the critical nesting count is 1 to protect against recursive calls if the
	assert function also uses a critical section. */
	if( uxCriticalNesting == 1 )
		configASSERT( ( portNVIC_INT_CTRL_REG & portVECTACTIVE_MASK ) == 0 );

void vPortExitCritical( void )
	configASSERT( uxCriticalNesting );
	if( uxCriticalNesting == 0 )

It can be seen that after entering the function vPortEnterCritical(), the interrupt will be closed first, and then the variable uxCriticalNesting will be added with 1. uxCriticalNesting is a global variable used to record the nesting times of critical segments. The function vPortExitCritical() is called to exit the critical section. Each time the function reduces uxCriticalNesting by 1, the function portenable will be called only when uxCriticalNesting is 0_ Interrupts() enables interrupts. This ensures that when there are multiple critical segment codes, the protection of other critical segments will not be disrupted due to the exit of one critical segment code, and the interruption will be enabled only after all critical segment codes exit.
[note]: note that the code in the critical area must be simplified, because entering the critical area will close the interrupt, which will cause the priority to be lower than configmax_ SYSCALL_ INTERRUPT_ The interruption of priority cannot be responded in time.

1.4.2 interrupt level critical segment code protection

Function taskENTER_CRITICAL_FROM_ISR() and taskEXIT_CRITICAL_FROM_ISR() interrupt level critical segment code protection is used in interrupt service program, and the priority of this interrupt must be lower than configMAX_SYSCALL_INTERRUPT_PRIORITY. These two functions are defined in the file task.h as follows:


portSET_INTERRUPT_MASK_FROM_ISR() and portCLEAR_INTERRUPT_MASK_FROM_ISR() is defined in the file portmacro.h:


vPortSetBASEPRI() has been explained earlier, which is to write a value to the BASEPRI register. The function ulPortRaiseBASEPRI() is defined in the file portmacro.h. The functions and comments are as follows:

static portFORCE_INLINE uint32_t ulPortRaiseBASEPRI( void )

		/* Set BASEPRI to the max syscall priority to effect a critical
		section. */
		mrs ulReturn, basepri          //First read out the value of BASEPRI and save it in ulReturn
		msr basepri, ulNewBASEPRI      //Set configMAX_SYSCALL_INTERRUPT_PRIORITY is written to the register BASEPRI.

	return ulReturn;                  //Return ulReturn. This value should be used when exiting the critical area code protection

1.5 interrupt test procedure

. . .

Relevant reference

1. FreeRTOS source code explanation and application development

That's all.Check it.

Tags: Single-Chip Microcomputer stm32 RTOS

Posted on Sun, 05 Dec 2021 23:02:44 -0500 by moola