Configuración de interrupciones de FreeRTOS y secciones críticas


1. Interrupción Cortex-M

1. Introducción a las interrupciones
Las interrupciones son una característica muy común de los microcontroladores. Las interrupciones son generadas por el hardware. Cuando se genera una interrupción, la CPU interrumpirá el proceso actual y pasará a manejar el servicio de interrupción. El MCU central de Cortex-M proporciona una interrupción Controlador de interrupción vectorial anidado administrado (NVIC).

El NVIC de Cotex-M3 admite hasta 240 IRQ (solicitudes de interrupción), 1 interrupción no enmascarable (NMI), 1 interrupción del temporizador Systick (tick timer) y múltiples excepciones del sistema.

2. Introducción a la gestión de interrupciones
El procesador Cortex-M tiene múltiples registros programables para la gestión de interrupciones y excepciones. La mayoría de estos registros están en el
NVIC y el bloque de control del sistema (SCB). CMSIS define estos registros como estructuras. Tome STM32F103 como ejemplo, abra core_cm3.h, hay dos estructuras, NVIC_Type y SCB_Type, de la siguiente manera:

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;

Tanto NVIC como SCB se encuentran en el espacio de control del sistema (SCS). La dirección de SCS comienza en 0XE000E000. Las direcciones de SCB y NVIC también se definen en core_cm3.h, de la siguiente manera:

#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. La agrupación de prioridad define
a qué interrupción debe responder el procesador y está determinada por la prioridad de la interrupción cuando llegan múltiples interrupciones. Las interrupciones de nivel pueden adelantarse a las interrupciones de menor prioridad, esto es anidamiento de interrupciones. Algunas interrupciones del procesador Cortex-M tienen prioridades fijas, como reinicio, NMI y HardFault.Las prioridades de estas interrupciones son negativas y la prioridad es la más alta.

El procesador Cortex-M tiene tres prioridades fijas y 256 prioridades programables, con un máximo de 128 niveles de prioridad, pero el número real de prioridades lo determina el fabricante del chip. Sin embargo, la mayoría de los chips tendrán un diseño simplificado, por lo que la cantidad de prioridades realmente admitidas será menor, como 8 niveles, 16 niveles, 32 niveles, etc. Por ejemplo, STM32 solo tiene 16 niveles de prioridad. Al diseñar el chip, se cortarán varios bits efectivos de gama baja que expresan la prioridad para reducir la cantidad de prioridades, por lo que no importa cuántos bits se usen para expresar la prioridad, todos están alineados con MSB. tres bits para expresar la clase de prioridad.
inserte la descripción de la imagen aquí
En la figura anterior, Bit0 ~ Bit4 no están implementados, por lo que leerlos siempre devuelve cero y escribirlos ignorará el valor escrito. Por tanto, para el caso de 3 bits, se utilizan 8 niveles de prioridad: 0X00 (máxima prioridad), 0X20, 0X40, 0X60, 0X80, 0XA0, 0XC0 y 0XE0. 注意,这个是芯片厂商来决定的!不是我们能决定的,比如 STM32 就选择了 4 位作为优先级!
Algunos amigos pueden preguntar, el registro de configuración de prioridad tiene 8 bits de ancho, ¿por qué solo hay 128 niveles de prioridad? ¿No deberían ser 8 bits 256 niveles de prioridad? Para hacer que la función de prioridad sea más controlable, el procesador Cortex-M también divide las 256 prioridades en segmentos altos y bajos: prioridad de prioridad (prioridad de grupo) y subprioridad (subprioridad), NVIC tiene un registro es "Programa de aplicación Registro de control de interrupción y reinicio (AIRCR)". Hay un campo de bit en el registro AIRCR llamado "Grupo de prioridad", como se muestra en la siguiente tabla: PRIGROUP en la tabla anterior es el grupo de prioridad, que divide la prioridad en Hay
inserte la descripción de la imagen aquí
dos segmentos de bits: el segmento de bits donde se encuentra el MSB (a la izquierda) corresponde a la prioridad de preferencia, y el segmento de bits donde se encuentra el LSB (a la derecha) corresponde a la subprioridad, como se muestra en la siguiente tabla.
inserte la descripción de la imagen aquí
En cuanto a la agrupación de prioridad de STM32, dijimos anteriormente que STM32 usa 4 bits, por lo que hay como máximo 5 grupos de configuraciones de agrupación de prioridad.Estos 5 grupos se definen en msic.h, de la siguiente manera:

#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 */

Se puede ver que STM32 tiene 5 grupos, ¡pero debemos prestar atención! ¡El valor correspondiente al grupo 0 definido en STM32 es 7! Si elegimos el grupo 4, es decir, NVIC_PriorityGroup_4, las 4 prioridades son todas prioritarias y no hay subprioridad, por lo que hay 16 prioridades que van de 0 a 15. Al trasplantar FreeRTOS, configuramos el grupo 4, como se muestra en la siguiente figura:

inserte la descripción de la imagen aquí
Si usa la rutina básica de ALIENTEK, la configuración predeterminada es el grupo 2, por lo que debe modificar la configuración de prioridad al migrar el controlador periférico en la rutina básica a FreeRTOS. La razón principal es que la configuración de interrupción de FreeRTOS no se ocupa de la subprioridad, por lo que solo se puede configurar como grupo 4, directamente con 16 prioridades, ¡y es fácil de usar!

4. Configuración de prioridad
Cada interrupción externa tiene un registro de prioridad correspondiente, cada registro ocupa 8 bits, por lo que el ancho máximo es de 8 bits, pero el mínimo es de 3 bits. Cuatro registros de prioridad adyacentes forman un registro de 32 bits. Como se mencionó anteriormente, de acuerdo con la configuración del grupo de prioridad, la prioridad se puede dividir en segmentos de bit alto y bajo, que se apropian respectivamente de la prioridad y la subprioridad. STM32 Hemos configurado el grupo de bits 4, por lo que solo hay prioridad de preferencia. Se puede acceder a los registros de prioridad por byte y, por supuesto, también se puede acceder a ellos por media palabra/palabra. El número de registros de prioridad significativos lo implementa el fabricante del chip, como se muestra en la siguiente tabla: Como se mencionó anteriormente, se
inserte la descripción de la imagen aquí
pueden Se ensambla un registro de 32 bits, por lo que la dirección de cuatro registros 0xE000_ED20~0xE000_ED23 se puede ensamblar en un registro de 32 bits cuya dirección es 0xE000_ED20. ¡Este punto es muy importante! Porque FreeRTOS opera directamente la dirección 0xE000_ED20 al establecer la prioridad de interrupción de PendSV y SysTick.

5. Registros especiales para enmascaramiento de interrupciones

  • (1) Registros PRIMASK y FAULTMASK

En muchas aplicaciones, es necesario enmascarar temporalmente todas las interrupciones y ejecutar algunas tareas que son estrictas con el tiempo. En este momento, se puede usar el registro PRIMASK. PRIMASK se usa para deshabilitar todas las excepciones e interrupciones excepto NMI y HardFalut. Puede ser utilizado en programación ensambladora.La instrucción CPS (Modify Processor State) modifica el valor del registro PRIMASK:

CPSIE I; //清除 PRIMASK(使能中断)
CPSID I; //设置 PRIMASK(禁止中断)

También se puede acceder al registro PRIMASK mediante las instrucciones MRS y MSR, de la siguiente manera:

MOVS R0, #1
MSR PRIMASK, R0 ;//将 1 写入 PRIMASK 禁止所有中断

así como también:

MOVS R0, #0
MSR PRIMASK, R0 ;//将 0 写入 PRIMASK 以使能中断

La protección del código del código de la sección crítica en UCOS se realiza a través de la interrupción del interruptor (UCOSIII también puede usar el método de prohibir la programación de tareas para realizar la protección del código de la sección crítica, que no se analiza aquí), y la interrupción del interruptor opera directamente el registro PRIMASK, por lo que al desactivar las interrupciones en UCOS, ¡todas las interrupciones excepto restablecer, NMI y HardFault se desactivan!

FAULTMASK es más despiadado que PRIMASK, incluso puede proteger HardFault, el método de uso es similar a PRIMASK, FAULTMASK se borrará automáticamente al salir.
Al programar en ensamblador, puede usar la instrucción CPS para modificar el estado actual de FAULTMASK:

CPSIE F ;清除 FAULTMASK
CPSID F ;设置 FAULTMASK

También se puede acceder al registro FAULTMASK usando las instrucciones MRS y MSR:

MOVS R0, #1
MSR FAULTMASK, R0 ;1 写入 FAULTMASK 禁止所有中断

así como también:

MOVS R0, #0
MSR FAULTMASK, R0 ;0 写入 FAULTMASK 使能中断
  • (2)
    Los registros PRIMASK y FAULTMASK del registro BASEPRI son demasiado toscos para deshabilitar directamente todas las demás interrupciones, excepto reinicio, NMI y HardFault, pero en algunos casos es necesario realizar un control más detallado en la máscara de interrupción, como solo Prioridad de enmascaramiento inferior a un cierto Umbral de interrupción. Entonces, ¿dónde se almacena este valor de prioridad como umbral? En el registro BASEPRI, pero escribir 0 en BASEPRI deja de enmascarar las interrupciones. Por ejemplo, si queremos blindar la interrupción cuya prioridad no es superior a 0X60, podemos utilizar la siguiente programación ensambladora:
MOV R0, #0X60
MSR BASEPRI, R0

Si necesita cancelar el enmascaramiento de interrupciones de BASEPRI, puede usar el siguiente código:

MOV R0, #0
MSR BASEPRI, R0

¡Aviso! ¡La interrupción del interruptor de FreeRTOS se realiza operando el registro BASEPRI! Puede desactivar las interrupciones por debajo de un cierto umbral, ¡y las interrupciones por encima de este umbral no se desactivarán!

2. Macro de configuración de interrupción de FreeRTOS

1. configPRIO_BITS
Esta macro se usa para establecer la prioridad de cuántos bits usa la MCU. STM32 usa 4 bits, ¡así que esta macro es 4!

2. configLIBRARY_LOWEST_INTERRUPT_PRIORITY
Esta macro se usa para establecer la prioridad más baja.Como se mencionó anteriormente, la prioridad STM32 usa 4 bits, y la configuración STM32 usa el grupo 4, es decir, los 4 bits son prioridad preventiva. Entonces, el número de prioridades es 16 y la prioridad más baja es 15. Así que esta macro es 15, ¡presta atención! Este valor es diferente para diferentes MCU, y la cantidad específica depende de la arquitectura de la MCU utilizada. ¡Este artículo solo explica para STM32!

3. configKERNEL_INTERRUPT_PRIORITY
Esta macro se utiliza para establecer la prioridad de interrupción del kernel.Esta macro se define de la siguiente manera:

#define configKERNEL_INTERRUPT_PRIORITY 
( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

La macro configKERNEL_INTERRUPT_PRIORITY es la macro
configLIBRARY_LOWEST_INTERRUPT_PRIORITY desplazada a la izquierda 8-configPRIO_BITS bits, es decir, desplazada a la izquierda 4 bits. ¿Por qué quieres desplazarte a la izquierda 4 bits? Como se mencionó anteriormente, STM32 usa 4 bits como prioridad, y estos 4 bits son los 4 bits superiores, por lo que desplazar 4 bits hacia la izquierda es la verdadera prioridad. Por supuesto, puede definir directamente la macro configLIBRARY_LOWEST_INTERRUPT_PRIORITY como 0XF0 sin cambios. Pero eso no parece intuitivo.

La macro configKERNEL_INTERRUPT_PRIORITY se usa para establecer la prioridad de interrupción de PendSV y el temporizador de tictac, que se define en port.c de la siguiente manera:

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

Se puede ver que tanto portNVIC_PENDSV_PRI como portNVIC_SYSTICK_PRI usan la macro
configKERNEL_INTERRUPT_PRIORITY, ¿por qué la macro portNVIC_PENDSV_PRI es la macro
configKERNEL_INTERRUPT_PRIORITY desplazada 16 bits a la izquierda? La macro portNVIC_SYSTICK_PRI también se desplaza a la izquierda 24 bits. La configuración de prioridad de interrupción de PendSV y SysTcik opera en la dirección 0xE000_ED20
, de modo que se escriben datos de 32 bits a la vez, y los registros de prioridad de SysTick y PendSV corresponden a los 8 bits más altos y los segundos 8 bits más altos de los 32. -datos de bits, ¿no? Uno se desplaza a la izquierda 16 bits y el otro se desplaza a la izquierda 24 bits.

¿Dónde están establecidas las prioridades de PendSV y SysTick? Establecida en la función xPortStartScheduler(), esta función está en el archivo port.c, la función es la siguiente:

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;
}

La parte roja en el código anterior es para establecer la prioridad de PendSV y SysTick, escriben
los datos de prioridad directamente en la dirección portNVIC_SYSPRI2_REG, portNVIC_SYSPRI2_REG es una macro, definida en el archivo port.c, de la siguiente manera:

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

¡Puede ver que la macro portNVIC_SYSPRI2_REG es la dirección 0XE000ED20! Al mismo tiempo, también se puede ver que las prioridades de interrupción de PendSV y SysTick son las más bajas en FreeRTOS.

4. configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY Esta macro se usa para establecer la prioridad máxima que el sistema FreeRTOS puede administrar, es decir , el umbral de prioridad
que explicamos sobre el registro BASEPRI en la Sección 1.5. Puede configurarlo libremente y lo configuré en 5 aquí.
Es decir, FreeRTOS no administra la prioridad superior a 5 (número de prioridad inferior a 5).

5. configMAX_SYSCALL_INTERRUPT_PRIORITY
Esta macro es configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY desplazada 4 bits a la izquierda, la razón es la misma que la macro configKERNEL_INTERRUPT_PRIORITY. Después de establecer esta macro, las interrupciones inferiores a esta prioridad pueden llamar de forma segura a las funciones de la API de FreeRTOS, las interrupciones superiores a esta prioridad no pueden ser deshabilitadas por FreeRTOS y las funciones de servicio de interrupción no pueden llamar a las funciones de la API de FreeRTOS.

Tomando STM32 como ejemplo, hay 16 prioridades, 0 es la prioridad más alta y la prioridad más baja es 15. La configuración es la siguiente: ●
configMAX_SYSCALL_INTERRUPT_PRIORITY== 5
● configKERNEL_INTERRUPT_PRIORITY==15
El resultado se muestra en la siguiente figura:
inserte la descripción de la imagen aquí

Dado que las prioridades superiores a configMAX_SYSCALL_INTERRUPT_PRIORITY no están bloqueadas por el kernel de FreeRTOS, las tareas que tienen requisitos estrictos en tiempo real pueden usar estas prioridades, como la detección de obstáculos en un cuadricóptero.

3. Interrupción del interruptor de FreeRTOS

Las funciones de interrupción del conmutador de FreeRTOS son portENABLE_INTERRUPTS() y portDISABLE_INTERRUPTS() Estas dos funciones son en realidad definiciones de macro, que se definen en portmacro.h, de la siguiente manera:

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

Se puede ver que la interrupción del interruptor se implementa realmente a través de las funciones vPortSetBASEPRI(0) y vPortRaiseBASEPRI() Estas dos funciones son las siguientes:

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
	}
}

La función vPortSetBASEPRI() es para escribir un valor en el registro BASEPRI, y este valor se pasa como parámetro ulBASEPRI, portENABLE_INTERRUPTS() es para habilitar una interrupción, pasa un 0 a vPortSetBASEPRI(), de acuerdo con nuestra explicación anterior de el registro BASEPRI, el resultado es habilitar una interrupción.
La función vPortRaiseBASEPRI() es escribir la macro configMAX_SYSCALL_INTERRUPT_PRIORITY en el registro BASEPRI, luego se enmascarará la interrupción cuya prioridad sea menor que configMAX_SYSCALL_INTERRUPT_PRIORITY.

Cuarto, el código de la sección crítica

El código de la sección crítica también se denomina sección crítica, que se refiere a la sección del código que debe ejecutarse por completo y no se puede interrumpir. Por ejemplo, la inicialización de algunos periféricos requiere un tiempo estricto y no se puede interrumpir durante el proceso de inicialización. FreeRTOS necesita desactivar la interrupción al ingresar el código de sección crítica y activar la interrupción después de procesar el código de sección crítica. El propio sistema FreeRTOS tiene muchos códigos de sección crítica, y estos códigos están protegidos por códigos de sección crítica. Cuando escribimos nuestros propios programas de usuario, también necesitamos agregar protección de código de sección crítica en algunos lugares.

FreeRTOS tiene 4 funciones relacionadas con la protección de código de sección crítica: taskENTER_CRITICAL(),
taskEXIT_CRITICAL(), taskENTER_CRITICAL_FROM_ISR() y
taskEXIT_CRITICAL_FROM_ISR(), estas cuatro funciones son en realidad definiciones de macro, que se definen en el archivo task.h. La diferencia entre estas cuatro funciones es que las dos primeras son protección de código de sección crítica a nivel de tarea y las dos últimas son protección de código de sección crítica a nivel de interrupción.

1. Protección de código de sección crítica a nivel de tarea
taskENTER_CRITICAL() y taskEXIT_CRITICAL() son protección de código crítico a nivel de tarea, una es para ingresar a la sección crítica, la otra es para salir de la sección crítica, estas dos funciones se usan en pares, la La definición de esta función es la siguiente:

#define taskENTER_CRITICAL() portENTER_CRITICAL()
#define taskEXIT_CRITICAL() portEXIT_CRITICAL()

Y portENTER_CRITICAL() y portEXIT_CRITICAL() también son definiciones de macro, que se definen en el archivo portmacro.h, de la siguiente manera:

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

Las funciones vPortEnterCritical() y vPortExitCritical() están en el archivo port.c, y las funciones son las siguientes:

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();
	}
}

Se puede ver que después de ingresar la función vPortEnterCritical(), la interrupción se desactivará primero y luego se agregará una a la variable uxCriticalNesting.uxCriticalNesting es una variable global utilizada para registrar el número de anidamientos de secciones críticas. La función vPortExitCritical() se llama al salir del segmento crítico. La función disminuirá uxCriticalNesting en uno cada vez. Solo cuando uxCriticalNesting sea 0 se llamará a la función portENABLE_INTERRUPTS() para habilitar las interrupciones. Esto asegura que cuando hay varios códigos de sección crítica, la protección de otras secciones críticas no se verá interrumpida debido a la salida de un determinado código de sección crítica, ¡y la interrupción se habilitará solo después de que todos los códigos de sección crítica hayan salido!

La protección de código crítico a nivel de tarea se utiliza de la siguiente manera:

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), ingrese al área crítica.
(2) Salga de la sección crítica.
El código entre (1) y (2) es el código de la sección crítica, ¡preste atención al código de la sección crítica que debe simplificarse! ¡Debido a que ingresar a la sección crítica deshabilitará las interrupciones, esto hará que las interrupciones con una prioridad inferior a configMAX_SYSCALL_INTERRUPT_PRIORITY no se respondan a tiempo!


2. Funciones de protección de código de sección crítica a nivel de interrupción
taskENTER_CRITICAL_FROM_ISR() y taskEXIT_CRITICAL_FROM_ISR() La protección de código de sección crítica a nivel de interrupción se utiliza en las rutinas de servicio de interrupción, y la prioridad de esta interrupción debe ser inferior a configMAX_SYSCALL_INTERRUPT_PRIORITY. La razón ya se ha dicho antes. Estas dos funciones se definen en el archivo task.h de la siguiente manera:

#define taskENTER_CRITICAL_FROM_ISR() portSET_INTERRUPT_MASK_FROM_ISR()
#define taskEXIT_CRITICAL_FROM_ISR( x ) portCLEAR_INTERRUPT_MASK_FROM_ISR( x )

Luego busque portSET_INTERRUPT_MASK_FROM_ISR() y
portCLEAR_INTERRUPT_MASK_FROM_ISR(), que se definen en el archivo portmacro.h de la siguiente manera:

#define portSET_INTERRUPT_MASK_FROM_ISR() ulPortRaiseBASEPRI()
#define portCLEAR_INTERRUPT_MASK_FROM_ISR(x) vPortSetBASEPRI(x)

vPortSetBASEPRI() se ha explicado anteriormente, es para escribir un valor en el registro BASEPRI.
La función ulPortRaiseBASEPRI() se define en el archivo portmacro.h, de la siguiente manera:

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) Primero lea el valor de BASEPRI y guárdelo en ulReturn.
(2) Escriba configMAX_SYSCALL_INTERRUPT_PRIORITY en el registro BASEPRI.
(3), devuelva ulReturn, ¡este valor debe usarse al salir de la protección de código de la sección crítica!

El método de uso de la protección de código crítico de nivel de interrupción es el siguiente:
// Función de servicio de interrupción del temporizador 3

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), ingrese al área crítica.
(2) Salga de la sección crítica.

5. Experimento de prueba de interrupción de FreeRTOS

1. Diseño de programa experimental
(1) Propósito experimental
Mencionamos anteriormente que en FreeRTOS, las interrupciones cuya prioridad sea inferior a configMAX_SYSCALL_INTERRUPT_PRIORITY se enmascararán, y las que sean superiores a configMAX_SYSCALL_INTERRUPT_PRIORITY no se enmascararán, por lo que en esta sección escribiremos una prueba de rutina simple. Utilice dos temporizadores, uno con una prioridad de 4 y otro con una prioridad de 5. Los dos temporizadores emiten una cadena de cadenas a través del puerto serie cada 1 s. Luego apague la interrupción por un período de tiempo en una determinada tarea y verifique la salida de los dos temporizadores.

(2) Diseño del experimento
Este experimento diseña dos tareas start_task() e interrupt_task(), las funciones de tarea de estas dos tareas son las siguientes:
start_task(): crea otra tarea. interrupt_task() : interrumpa la tarea de prueba, la tarea llamará a la función de interrupción de FreeRTOS portDISABLE_INTERRUPTS() para deshabilitar la interrupción durante un período de tiempo.

(3) Procedimiento y análisis experimental
● Definición de tareas

#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); //任务函数

● función principal()

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(); //开启任务调度
}

● función de tarea

//开始任务任务函数
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);
    }
} 

● Interrupción del temporizador

//通用定时器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) Establezca la prioridad de preferencia del temporizador 3 a 4, que es más alta que configMAX_SYSCALL_INTERRUPT_PRIORITY, de modo que el temporizador 3 no se vea afectado cuando se llame a la función portDISABLE_INTERRUPTS() para deshabilitar las interrupciones.

(2) Establezca la prioridad de preferencia del temporizador 5 a 5, que es igual a configMAX_SYSCALL_INTERRUPT_PRIORITY, de modo que cuando se llame a la función portDISABLE_INTERRUPTS() para deshabilitar la interrupción, la interrupción del temporizador 5 definitivamente se deshabilitará.

(3) y (4), información de salida del puerto serie del temporizador 3 y del temporizador 5.

2. Programa experimental ejecutando resultados
Compile y descargue el código a la placa de desarrollo, abra el asistente de depuración del puerto serie para ver la salida de datos, el resultado se muestra en la figura a continuación: En la figura anterior, se puede ver
inserte la descripción de la imagen aquí
que la interrupción fue no se apagó al principio, por lo que tanto TIM3 como TIM5 funcionan con normalidad La parte indicada por el cuadro rojo. Cuando la tarea interrupt_task() se ejecuta 5 veces, la interrupción se apaga.En este momento, debido a que la prioridad de interrupción de TIM5 es 5, que es igual a configMAX_SYSCALL_INTERRUPT_PRIORITY, TIM5 se apaga. Sin embargo, la prioridad de interrupción de TIM3 es mayor que configMAX_SYSCALL_INTERRUPT_PRIORITY y no se cerrará, por lo que TIM3 se ejecuta normalmente, como se muestra en el cuadro verde. Después de deshabilitar la interrupción durante 5 segundos, se llamará a la función portENABLE_INTERRUPTS() para volver a habilitar la interrupción. Después de volver a habilitar la interrupción, TIM5 reanudará la operación, como se muestra en el cuadro azul.

Supongo que te gusta

Origin blog.csdn.net/Dustinthewine/article/details/130047466
Recomendado
Clasificación