FreeRTOS interrupt management based on STM32

Article directory

1. The basic concept of exception and interrupt

2. Introduction of interruption

3. Explanation of terms related to interruption

4. The operation mechanism of interrupt management

5. The concept of interrupt delay

6. Application Scenarios of Interrupt Management

Seven, interrupt management explanation

8. Interrupt management experiment

9. Experimental phenomenon of interrupt management


1. The basic concept of exception and interrupt

      An exception is any event that causes the processor to break away from normal operation and turn to execute special codes. If it is not handled in time, it will cause a system error at least, or cause a catastrophic paralysis of the system at worst. Therefore, handling exceptions correctly and avoiding errors is a very important part of improving software robustness (stability), especially for real-time systems.

       An exception is any event that interrupts the normal execution of the processor and forces the processor into execution of a special privileged instruction. Exceptions can generally be divided into two categories: synchronous exceptions and asynchronous exceptions. Exceptions caused by internal events (such as those generated by the execution of processor instructions) are called synchronous exceptions, such as arithmetic operations that cause division by zero to cause an exception, and for example, in some processor architectures, for a certain data size must be Read and write operations are performed from even memory addresses. A read or write operation from an odd memory address will cause a memory access error event and cause an exception (called an alignment exception).

      An asynchronous exception mainly refers to an exception generated by an external exception source, and is an asynchronous exception caused by an event generated by an external hardware device. The difference between a synchronous exception and an asynchronous exception is the source of the event. The synchronous exception event is generated from within the processor due to the execution of certain instructions, while the source of the asynchronous exception event is an external hardware device. For example, an event generated by pressing a button on the device. The difference between a synchronous exception and an asynchronous exception is that after a synchronous exception is triggered, the system must process it immediately and cannot still execute the original program instruction steps; while an asynchronous exception can delay processing or even ignore it, such as a key interrupt exception, although the interrupt The exception is triggered, but the system can ignore it and continue to run (and ignore the corresponding key event as well).

      Interrupts are asynchronous exceptions. The so-called interruption means that when the central processing unit CPU is processing something, an external event occurs, the CPU is requested to process it quickly, the CPU temporarily interrupts the current work, and transfers to process the event that occurred. The place where it was originally interrupted continues the original work. This process is called interruption.

      Interrupts can interrupt the running of tasks, no matter what priority the task has, so interrupts are generally used to handle more urgent events, and only do simple processing, such as marking the event. When using the FreeRTOS system, it is generally recommended to use signals When the flag interrupt occurs, such as quantity, message or event flag group, these kernel objects are released to the processing task, and the processing task will do specific processing.

      Through the interrupt mechanism, when the peripheral hardware does not need CPU intervention, the CPU can perform other tasks, and when the peripheral hardware needs the CPU, the CPU can immediately stop the current task and respond to the interrupt request by generating an interrupt signal. In this way, the CPU can avoid spending a lot of time waiting and inquiring about the status of the peripherals, thus greatly improving the real-time performance and execution efficiency of the system.

      Readers here should know that there are many critical sections in the FreeRTOS source code. Although the critical section protects the execution of key codes from being interrupted, it will also affect the real-time of the system. Any interrupt response using the operating system will not Will be faster than bare metal. For example, at some point, a task is running, and part of the program of the task shields the interrupt, that is, enters the critical section. If an urgent interrupt event is triggered at this time, the interrupt will be suspended. To get a timely response, you must wait until the interrupt is turned on to get a response. If the shielding interrupt time exceeds the tolerance limit for emergency interrupts, the harm can be imagined. Therefore, the interrupt of the operating system will have an appropriate interrupt delay at certain times, so when the interrupt mask function is called to enter the critical section, it also needs to fast in and fast out. Of course, FreeRTOS can also allow some high-priority interrupts not to be masked, and can respond in time, but these interrupts are not managed by the system, and it is not allowed to call any API function interface related to interrupts in FreeRTOS.

Interrupt management support for FreeRTOS:

● On/off interrupts.

● Resume interruption.

● Interrupt enable.

● Interrupt masking.

●Selectable interrupt priority for system management.

2. Introduction of interruption


        Interrupt-related hardware can be divided into three categories: peripherals, interrupt controllers, and the CPU itself.

        Peripherals: When a peripheral needs to request the CPU, it generates an interrupt signal, which is connected to the interrupt controller.

       Interrupt controller: The interrupt controller is one of the many peripherals of the CPU. On the one hand, it receives the input of interrupt signals from other peripherals, and on the other hand, it sends interrupt signals to the CPU. You can set the interrupt source priority, trigger mode, open and close source, etc. by programming the interrupt controller. The interrupt controller commonly used in Cortex-M series controllers is NVIC (Nested Vectored Interrupt Controller).

       CPU: The CPU will respond to the request of the interrupt source, interrupt the task currently being executed, and then execute the interrupt handler. The NVIC supports up to 240 interrupts, each with up to 256 priorities.

3. Explanation of terms related to interruption

       Interrupt number: Each interrupt request signal will have a specific flag, so that the computer can determine which device made the interrupt request. This flag is the interrupt number.

      Interrupt request: An "emergency event" needs to apply to the CPU, requiring the CPU to suspend the currently executing task and process the "emergency event". This application process is called an interrupt request.

      Interrupt priority: In order to enable the system to respond to and process all interrupts in a timely manner, the system divides interrupt sources into several levels according to the importance and urgency of the interrupt time, called interrupt priority.

      Interrupt handler: When the peripheral device generates an interrupt request, the CPU suspends the current task and responds to the interrupt request, that is, executes the interrupt handler.

       Interrupt trigger: The interrupt source sends out and sends a control signal to the CPU. Setting the interrupt trigger to "1" indicates that the interrupt source has generated an interrupt and requires the CPU to respond to the interrupt. The CPU suspends the current task and executes the corresponding interrupt handler. Interrupt trigger type: The external interrupt request is sent to the NVIC through a physical signal, which can be level-triggered or edge-triggered.

       Interrupt vector: The entry address of the interrupt service routine.

       Interrupt vector table: the storage area for storing interrupt vectors. Interrupt vectors correspond to interrupt numbers. Interrupt vectors are stored in the order of interrupt numbers in the interrupt vector table.

        Critical section: The critical section of the code is also called the critical section. Once this part of the code starts executing, no interruption is allowed. In order to ensure that the execution of the code in the critical section is not interrupted, the interrupt must be turned off before entering the critical section, and the interrupt must be turned on immediately after the code in the critical section is executed.  

4. The operation mechanism of interrupt management

When an interrupt occurs, the processor will execute in the following order:

1. Save the current processor state information

2. Load the exception or interrupt handler into the PC register

3. Transfer control to the handler function and start execution

4. When the processing function is executed, restore the processor state information

5. Return to previous program execution point from exception or interrupt

      Interrupts allow the CPU to process events when they occur, instead of having the CPU continuously inquire whether a corresponding event has occurred. Two special instructions: disable interrupt and enable interrupt can make the processor not respond or respond to interrupt. During the period of disabling interrupt, the processor will usually suspend the newly generated interrupt, and respond immediately when the interrupt is enabled, so there will be appropriate The delay response interrupt, so the user should fast in and fast out when entering the critical area.

There are two situations in which an interrupt occurs: in the context of a task, and in the context of interrupt service function processing.

 ● When the task is working, if an interrupt occurs at this time, no matter what the priority of the interrupt is, it will interrupt the execution of the current task and transfer to the corresponding interrupt service function for execution. The process is shown in Figure 1.

      Figure 1, (1) (3): When an interrupt occurs while the task is running, the interrupt will interrupt the running of the task, then the operating system will first save the context of the current task, and then process the interrupt service function.

       Figure 1, (2) (4): If and only when the interrupt service function is processed, the context of the task will be restored and the task will continue to run.

Figure 1 The interrupt occurs in the task context

● During the execution of the interrupt service routine, if an interrupt source with a higher priority level triggers an interrupt, since it is currently in the interrupt processing context, different processor architectures may have different processing methods, such as new interrupt waiting Suspend until the current interrupt processing leaves before responding; or a new high-priority interrupt interrupts the current interrupt processing process, and directly responds to this new higher-priority interrupt source. The latter case is called interrupt nesting. In a hard real-time environment, the former situation is not allowed to occur, and the time for responding to interrupts cannot be kept as short as possible. In software processing (soft real-time environment), FreeRTOS allows interrupt nesting, that is, during an interrupt service routine, the processor can respond to another interrupt with higher priority, as shown in Figure 2.

When interrupt 2 occurs when the service function of interrupt 1 is being processed, because interrupt 2 has a higher priority than interrupt 1, interrupt nesting occurs, then the operating system will first save the context of the current interrupt service function, and turn to Processing interrupt 2, if and only when the execution of interrupt 2 is completed (2), the execution of interrupt 1 can be continued.

Figure 2 interrupt nesting occurs

5. The concept of interrupt delay

       Even if the response of the operating system is fast, there is still a problem of delayed response to interrupts, which we call Interrupt Latency.

      Interrupt latency is the time between the occurrence of a hardware interrupt and the start of execution of the first instruction of the interrupt handler. That is: the time when the system receives the interrupt signal until the operating system responds and completes the switch to the interrupt service routine. It can also be simply understood as: the time when (external) hardware (device) is interrupted and the system executes the first instruction of the interrupt service subroutine (ISR).

      The interrupt processing process is: after the external hardware is interrupted, the CPU reads the interrupt vector from the interrupt processor, and searches the interrupt vector table, finds the first address of the corresponding interrupt service subroutine (ISR), and then jumps to the corresponding ISR To deal with it accordingly. This part of the time, I call: identify interruption time.

      In a real-time operating system that allows interrupt nesting, interrupts are also priority-based, allowing high-priority interrupts to steal low-priority interrupts being processed. Therefore, if a higher-priority interrupt is currently being processed, even if there is a low-priority interrupt at this time For priority interrupts, the system will not respond immediately, but will not respond until the high priority interrupts are processed. Even if interrupt nesting is not supported, that is, interrupts have no priority and interrupts are not allowed to be interrupted, so if the current system is processing an interrupt and another interrupt arrives at this time, the system will not respond immediately Yes, but only after the current interrupt is processed, the subsequent interrupt will be processed. This part of the time, I call it: waiting for interrupt open time.

      In the operating system, many times we will actively enter the critical section, and the system does not allow the current state to be interrupted by interrupts, so the interrupt that occurs in the critical section will be suspended until the interrupt is turned on when exiting the critical section. This part of the time, I call it: off break time.

      Interrupt latency can be defined as the period of time from the moment the interrupt starts to the moment the interrupt service routine begins executing. Interrupt Latency = Recognition Interrupt Time + [Wait Interrupt On Time] + [Close Interrupt Time]. 

6. Application Scenarios of Interrupt Management

       Interrupts are widely used in embedded processors. A system without interrupts is not a good system, because only interrupts can start or stop a certain thing, and then switch to another thing. We can give an example in daily life to illustrate, if you are writing a letter to a friend, the phone rings, then you put down the pen in your hand to answer the phone, and continue to write the letter after the call. This example shows the process of interruption and its processing: the ringing of the phone makes you temporarily suspend your current work to deal with more urgent things-answer the phone, and come back when you have finished dealing with the urgent things Continue with the original thing. In this example, the ringtone of the phone can be called an "interruption request", and you pause to write a letter to answer the phone is called an "interruption response", then the process of answering the phone is "interruption processing". From this we can see that in the process of computer executing the program, due to a special situation (or called "special event"), the system temporarily suspends the current program and turns to execute the program that handles this special event. After the processing is completed, return to the interruption point of the original program and continue to execute downward.

      Why do you say that a system without interruption is not a good system? We can give another example to illustrate the role of interrupts. Suppose a friend comes to visit you, but because you don’t know when you will arrive, you can only wait at the door, so you can’t do anything; but if you install a doorbell at the door, you don’t have to wait at the door and can do other things at home. When a friend arrives and rings the doorbell to notify you, then you interrupt your work to open the door, which avoids unnecessary waiting. The same is true for the CPU. If time is wasted on querying, then the CPU can't do anything, so what's the use of it. Reasonable use of interrupts in embedded systems can make better use of CPU resources.

Seven, interrupt management explanation

       The interrupt of the ARM Cortex-M series core is managed by hardware, while FreeRTOS is software, it does not take over the related interrupts managed by hardware (taking over simply means that all interrupts are managed by RTOS software, and the hardware interrupts , it is up to the software to decide whether to respond, you can suspend the interrupt, delay the response or not respond), only support simple switch interrupts, etc., so the use of interrupts in FreeRTOS is actually similar to that of bare metal, we need to configure interrupts ourselves, and enable interrupts , write the interrupt service function, use the kernel IPC communication mechanism in the interrupt service function, it is generally recommended to use the semaphore, message or event flag group to mark the occurrence of the event, publish the event to the processing task, and then the related processing task after exiting the interrupt Specifically handle interrupts.

      Users can customize the macro definition configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY of the highest interrupt priority that can be managed by the system. It is used to configure the basepri register in the kernel. When basepri is set to a certain value, NVIC will not respond to interrupts with a lower priority. Interrupts with a higher priority are not affected. That is to say, when this macro definition is configured as 5, these interrupts with interrupt priority values ​​at 0, 1, 2, 3, and 4 are not shielded by FreeRTOS, that is to say, even when the system enters a critical section, these interrupts It can also be triggered instead of waiting to be triggered when exiting the critical section. Of course, these interrupt service functions cannot call the API function interface provided by FreeRTOS, and these interrupts with interrupt priorities between 5 and 15 can be masked. It can also safely call the API function interface provided by FreeRTOS.

      ARM Cortex-M NVIC supports interrupt nesting function: when an interrupt is triggered and the system responds, the processor hardware will automatically push some of the currently running context registers into the interrupt stack. These registers include PSR, R0, R1, R2, R3 and R12 registers. When the system is servicing an interrupt, if a higher-priority interrupt is triggered, the processor will also interrupt the currently running interrupt service routine, and then put the old interrupt service routine context PSR, R0, R1 , R2, R3 and R12 registers are automatically saved to the interrupt stack. The behavior of saving these partial context registers to the interrupt stack is entirely a hardware behavior, which is the biggest difference from other ARM processors (in the past, it was necessary to rely on software to save the context).

      In addition, on the ARM Cortex-M series processors, all interrupts are processed by the interrupt vector table, that is, when an interrupt is triggered, the processor will directly determine which interrupt source it is, and then directly jump to the corresponding fixed position to process. In ARM7 and ARM9, it usually jumps to the IRQ entry first, and then the software judges which interrupt source is triggered, and after obtaining the corresponding interrupt service routine entry address, the subsequent interrupt processing is performed. The advantage of ARM7 and ARM9 is that all interrupts have a unified entry address, which is convenient for the unified management of the OS. The ARM Cortex-M series processors are just the opposite, each interrupt service routine must be arranged together at a unified address (this address must be set to the interrupt vector offset register of the NVIC). The interrupt vector table is generally defined by an array (or given in the start code). On STM32, the start code is given by default: see the code for details.

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                DCD     MemManage_Handler          ; MPU Fault Handler
                DCD     BusFault_Handler           ; Bus Fault Handler
                DCD     UsageFault_Handler         ; Usage Fault Handler
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     0                          ; Reserved
                DCD     SVC_Handler                ; SVCall Handler
                DCD     DebugMon_Handler           ; Debug Monitor Handler
                DCD     0                          ; Reserved
                DCD     PendSV_Handler             ; PendSV Handler
                DCD     SysTick_Handler            ; SysTick Handler

                ; External Interrupts
                DCD     WWDG_IRQHandler            ; Window Watchdog
                DCD     PVD_IRQHandler             ; PVD through EXTI Line detect
                DCD     TAMPER_IRQHandler          ; Tamper
                DCD     RTC_IRQHandler             ; RTC
                DCD     FLASH_IRQHandler           ; Flash
                DCD     RCC_IRQHandler             ; RCC
                DCD     EXTI0_IRQHandler           ; EXTI Line 0
                DCD     EXTI1_IRQHandler           ; EXTI Line 1
                DCD     EXTI2_IRQHandler           ; EXTI Line 2
                DCD     EXTI3_IRQHandler           ; EXTI Line 3
                DCD     EXTI4_IRQHandler           ; EXTI Line 4
                DCD     DMA1_Channel1_IRQHandler   ; DMA1 Channel 1
                DCD     DMA1_Channel2_IRQHandler   ; DMA1 Channel 2
                DCD     DMA1_Channel3_IRQHandler   ; DMA1 Channel 3
                DCD     DMA1_Channel4_IRQHandler   ; DMA1 Channel 4
                DCD     DMA1_Channel5_IRQHandler   ; DMA1 Channel 5
                DCD     DMA1_Channel6_IRQHandler   ; DMA1 Channel 6
                DCD     DMA1_Channel7_IRQHandler   ; DMA1 Channel 7
                DCD     ADC1_2_IRQHandler          ; ADC1 & ADC2
                DCD     USB_HP_CAN1_TX_IRQHandler  ; USB High Priority or CAN1 TX
                DCD     USB_LP_CAN1_RX0_IRQHandler ; USB Low  Priority or CAN1 RX0
                DCD     CAN1_RX1_IRQHandler        ; CAN1 RX1
                DCD     CAN1_SCE_IRQHandler        ; CAN1 SCE
                DCD     EXTI9_5_IRQHandler         ; EXTI Line 9..5
                DCD     TIM1_BRK_IRQHandler        ; TIM1 Break
                DCD     TIM1_UP_IRQHandler         ; TIM1 Update
                DCD     TIM1_TRG_COM_IRQHandler    ; TIM1 Trigger and Commutation
                DCD     TIM1_CC_IRQHandler         ; TIM1 Capture Compare
                DCD     TIM2_IRQHandler            ; TIM2
                DCD     TIM3_IRQHandler            ; TIM3
                DCD     TIM4_IRQHandler            ; TIM4
                DCD     I2C1_EV_IRQHandler         ; I2C1 Event
                DCD     I2C1_ER_IRQHandler         ; I2C1 Error
                DCD     I2C2_EV_IRQHandler         ; I2C2 Event
                DCD     I2C2_ER_IRQHandler         ; I2C2 Error
                DCD     SPI1_IRQHandler            ; SPI1
                DCD     SPI2_IRQHandler            ; SPI2
                DCD     USART1_IRQHandler          ; USART1
                DCD     USART2_IRQHandler          ; USART2
                DCD     USART3_IRQHandler          ; USART3
                DCD     EXTI15_10_IRQHandler       ; EXTI Line 15..10
                DCD     RTCAlarm_IRQHandler        ; RTC Alarm through EXTI Line
                DCD     USBWakeUp_IRQHandler       ; USB Wakeup from suspend
                DCD     TIM8_BRK_IRQHandler        ; TIM8 Break
                DCD     TIM8_UP_IRQHandler         ; TIM8 Update
                DCD     TIM8_TRG_COM_IRQHandler    ; TIM8 Trigger and Commutation
                DCD     TIM8_CC_IRQHandler         ; TIM8 Capture Compare
                DCD     ADC3_IRQHandler            ; ADC3
                DCD     FSMC_IRQHandler            ; FSMC
                DCD     SDIO_IRQHandler            ; SDIO
                DCD     TIM5_IRQHandler            ; TIM5
                DCD     SPI3_IRQHandler            ; SPI3
                DCD     UART4_IRQHandler           ; UART4
                DCD     UART5_IRQHandler           ; UART5
                DCD     TIM6_IRQHandler            ; TIM6
                DCD     TIM7_IRQHandler            ; TIM7
                DCD     DMA2_Channel1_IRQHandler   ; DMA2 Channel1
                DCD     DMA2_Channel2_IRQHandler   ; DMA2 Channel2
                DCD     DMA2_Channel3_IRQHandler   ; DMA2 Channel3
                DCD     DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End

FreeRTOS also follows the same method as bare-metal interrupts on Cortex-M series processors. When users need to use custom interrupt service routines, they only need to define functions with the same name to override the weakened symbols. Therefore, the interrupt control of FreeRTOS on the Cortex-M series processor is actually no different from that of the bare metal.

8. Interrupt management experiment

       The interrupt management experiment is to create two tasks in FreeRTOS to obtain the semaphore and the message queue respectively, and define the trigger mode of the two keys KEY1 and KEY2 as interrupt triggering, and the triggered interrupt service function is the same as that of the bare metal. When the message is passed to the task through the message queue, the task will display the information through the serial port debugging assistant after receiving the message. Moreover, the interrupt management experiment also realized the DMA transmission + idle interrupt function of a serial port. When the serial port receives data of variable length, an idle interrupt is generated, and the semaphore is passed to the task during the interrupt. When the task receives the semaphore, it will The data of the serial port is read out and echoed in the serial port debugging assistant.

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
#include "bsp_exti.h"

/* 标准库头文件 */
#include <string.h>

/**************************** 任务句柄 ********************************/
/* 
 * 任务句柄是一个指针,用于指向一个任务,当任务创建好之后,它就具有了一个任务句柄
 * 以后我们要想操作这个任务都需要通过这个任务句柄,如果是自身的任务操作自己,那么
 * 这个句柄可以为NULL。
 */
static TaskHandle_t AppTaskCreate_Handle = NULL;/* 创建任务句柄 */
static TaskHandle_t LED_Task_Handle = NULL;/* LED任务句柄 */
static TaskHandle_t KEY_Task_Handle = NULL;/* KEY任务句柄 */

/********************************** 内核对象句柄 *********************************/
/*
 * 信号量,消息队列,事件标志组,软件定时器这些都属于内核的对象,要想使用这些内核
 * 对象,必须先创建,创建成功之后会返回一个相应的句柄。实际上就是一个指针,后续我
 * 们就可以通过这个句柄操作这些内核对象。
 *
 * 内核对象说白了就是一种全局的数据结构,通过这些数据结构我们可以实现任务间的通信,
 * 任务间的事件同步等各种功能。至于这些功能的实现我们是通过调用这些内核对象的函数
 * 来完成的
 * 
 */
QueueHandle_t Test_Queue =NULL;
SemaphoreHandle_t BinarySem_Handle =NULL;

/******************************* 全局变量声明 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些全局变量。
 */
 
extern char Usart_Rx_Buf[USART_RBUFF_SIZE];
 
 
/******************************* 宏定义 ************************************/
/*
 * 当我们在写应用程序的时候,可能需要用到一些宏定义。
 */
#define  QUEUE_LEN    4   /* 队列的长度,最大可包含多少个消息 */
#define  QUEUE_SIZE   4   /* 队列中每个消息大小(字节) */


/*
*************************************************************************
*                             函数声明
*************************************************************************
*/
static void AppTaskCreate(void);/* 用于创建任务 */

static void LED_Task(void* pvParameters);/* LED_Task任务实现 */
static void KEY_Task(void* pvParameters);/* KEY_Task任务实现 */

static void BSP_Init(void);/* 用于初始化板载相关资源 */

/*****************************************************************
  * @brief  主函数
  * @param  无
  * @retval 无
  * @note   第一步:开发板硬件初始化 
            第二步:创建APP应用任务
            第三步:启动FreeRTOS,开始多任务调度
  ****************************************************************/
int main(void)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  /* 开发板硬件初始化 */
  BSP_Init();
  
	printf("这是一个FreeRTOS中断管理实验!\n");
  printf("按下KEY1 | KEY2触发中断!\n");
  printf("串口发送数据触发中断,任务处理数据!\n");
  
   /* 创建AppTaskCreate任务 */
  xReturn = xTaskCreate((TaskFunction_t )AppTaskCreate,  /* 任务入口函数 */
                        (const char*    )"AppTaskCreate",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )1, /* 任务的优先级 */
                        (TaskHandle_t*  )&AppTaskCreate_Handle);/* 任务控制块指针 */ 
  /* 启动任务调度 */           
  if(pdPASS == xReturn)
    vTaskStartScheduler();   /* 启动任务,开启调度 */
  else
    return -1;  
//  
  while(1);/* 正常不会执行到这里 */    
}


/***********************************************************************
  * @ 函数名  : AppTaskCreate
  * @ 功能说明: 为了方便管理,所有的任务创建函数都放在这个函数里面
  * @ 参数    : 无  
  * @ 返回值  : 无
  **********************************************************************/
static void AppTaskCreate(void)
{
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  
  taskENTER_CRITICAL();           //进入临界区
  
  /* 创建Test_Queue */
  Test_Queue = xQueueCreate((UBaseType_t ) QUEUE_LEN,/* 消息队列的长度 */
                            (UBaseType_t ) QUEUE_SIZE);/* 消息的大小 */
  
	if(NULL != Test_Queue)
    printf("Test_Queue消息队列创建成功!\n");
	
  /* 创建 BinarySem */
  BinarySem_Handle = xSemaphoreCreateBinary();	 
  
	if(NULL != BinarySem_Handle)
    printf("BinarySem_Handle二值信号量创建成功!\n");
	
  /* 创建LED_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )LED_Task, /* 任务入口函数 */
                        (const char*    )"LED_Task",/* 任务名字 */
                        (uint16_t       )512,   /* 任务栈大小 */
                        (void*          )NULL,	/* 任务入口函数参数 */
                        (UBaseType_t    )2,	    /* 任务的优先级 */
                        (TaskHandle_t*  )&LED_Task_Handle);/* 任务控制块指针 */
  if(pdPASS == xReturn)
    printf("创建LED_Task任务成功!\n");
  /* 创建KEY_Task任务 */
  xReturn = xTaskCreate((TaskFunction_t )KEY_Task,  /* 任务入口函数 */
                        (const char*    )"KEY_Task",/* 任务名字 */
                        (uint16_t       )512,  /* 任务栈大小 */
                        (void*          )NULL,/* 任务入口函数参数 */
                        (UBaseType_t    )3, /* 任务的优先级 */
                        (TaskHandle_t*  )&KEY_Task_Handle);/* 任务控制块指针 */ 
  if(pdPASS == xReturn)
    printf("创建KEY_Task任务成功!\n");
  
  vTaskDelete(AppTaskCreate_Handle); //删除AppTaskCreate任务
  
  taskEXIT_CRITICAL();            //退出临界区
}



/**********************************************************************
  * @ 函数名  : LED_Task
  * @ 功能说明: LED_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void LED_Task(void* parameter)
{	
  BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  uint32_t r_queue;	/* 定义一个接收消息的变量 */
  while (1)
  {
    /* 队列读取(接收),等待时间为一直等待 */
    xReturn = xQueueReceive( Test_Queue,    /* 消息队列的句柄 */
                             &r_queue,      /* 发送的消息内容 */
                             portMAX_DELAY); /* 等待时间 一直等 */
								
		if(pdPASS == xReturn)
		{
			printf("触发中断的是 KEY%d !\n",r_queue);
		}
		else
		{
			printf("数据接收出错\n");
		}
		
    LED1_TOGGLE;
  }
}

/**********************************************************************
  * @ 函数名  : LED_Task
  * @ 功能说明: LED_Task任务主体
  * @ 参数    :   
  * @ 返回值  : 无
  ********************************************************************/
static void KEY_Task(void* parameter)
{	
	BaseType_t xReturn = pdPASS;/* 定义一个创建信息返回值,默认为pdPASS */
  while (1)
  {
    //获取二值信号量 xSemaphore,没获取到则一直等待
		xReturn = xSemaphoreTake(BinarySem_Handle,/* 二值信号量句柄 */
                              portMAX_DELAY); /* 等待时间 */
    if(pdPASS == xReturn)
    {
      printf("收到数据:%s\n",Usart_Rx_Buf);
      memset(Usart_Rx_Buf,0,USART_RBUFF_SIZE);/* 清零 */
      LED2_TOGGLE;
    }
  }
}

/***********************************************************************
  * @ 函数名  : BSP_Init
  * @ 功能说明: 板级外设初始化,所有板子上的初始化均可放在这个函数里面
  * @ 参数    :   
  * @ 返回值  : 无
  *********************************************************************/
static void BSP_Init(void)
{
	/*
	 * STM32中断优先级分组为4,即4bit都用来表示抢占优先级,范围为:0~15
	 * 优先级分组只需要分组一次即可,以后如果有其他的任务需要用到中断,
	 * 都统一用这个优先级分组,千万不要再分组,切忌。
	 */
	NVIC_PriorityGroupConfig( NVIC_PriorityGroup_4 );
	
	/* LED 初始化 */
	LED_GPIO_Config();

	/* DMA初始化	*/
	USARTx_DMA_Config();
	
	/* 串口初始化	*/
	USART_Config();
  
  /* 按键初始化	*/
  Key_GPIO_Config();
	
	/* 按键初始化	*/
	EXTI_Key_Config();
	
}

/********************************END OF FILE****************************/

interrupt service routine

/* Includes ------------------------------------------------------------------*/
#include "stm32f10x_it.h"

/* FreeRTOS头文件 */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"
/* 开发板硬件bsp头文件 */
#include "bsp_led.h"
#include "bsp_usart.h"
#include "bsp_key.h"
#include "bsp_exti.h"

/**
  * @brief  This function handles SysTick Handler.
  * @param  None
  * @retval None
  */
extern void xPortSysTickHandler(void);
//systick中断服务函数
void SysTick_Handler(void)
{	
#if (INCLUDE_xTaskGetSchedulerState  == 1 )
  if (xTaskGetSchedulerState() != taskSCHEDULER_NOT_STARTED)
  {
#endif  /* INCLUDE_xTaskGetSchedulerState */  
    
    xPortSysTickHandler();
    
#if (INCLUDE_xTaskGetSchedulerState  == 1 )
  }
#endif  /* INCLUDE_xTaskGetSchedulerState */
}



/* 声明引用外部队列 & 二值信号量 */
extern QueueHandle_t Test_Queue;
extern SemaphoreHandle_t BinarySem_Handle;

static uint32_t send_data1 = 1;
static uint32_t send_data2 = 2;

/*********************************************************************************
  * @ 函数名  : KEY1_IRQHandler
  * @ 功能说明: 中断服务函数
  * @ 参数    : 无  
  * @ 返回值  : 无
  ********************************************************************************/
void KEY1_IRQHandler(void)
{
	BaseType_t pxHigherPriorityTaskWoken;
  //确保是否产生了EXTI Line中断
  uint32_t ulReturn;
  /* 进入临界段,临界段可以嵌套 */
  ulReturn = taskENTER_CRITICAL_FROM_ISR();
  
	if(EXTI_GetITStatus(KEY1_INT_EXTI_LINE) != RESET) 
	{
    /* 将数据写入(发送)到队列中,等待时间为 0  */
		xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
											&send_data1,/* 发送的消息内容 */
											&pxHigherPriorityTaskWoken);
		
		//如果需要的话进行一次任务切换
		portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
		
		//清除中断标志位
		EXTI_ClearITPendingBit(KEY1_INT_EXTI_LINE);     
	}  
  
  /* 退出临界段 */
  taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

/*********************************************************************************
  * @ 函数名  : KEY1_IRQHandler
  * @ 功能说明: 中断服务函数
  * @ 参数    : 无  
  * @ 返回值  : 无
  ********************************************************************************/
void KEY2_IRQHandler(void)
{
	BaseType_t pxHigherPriorityTaskWoken;
  uint32_t ulReturn;
  /* 进入临界段,临界段可以嵌套 */
  ulReturn = taskENTER_CRITICAL_FROM_ISR();
  
  //确保是否产生了EXTI Line中断
	if(EXTI_GetITStatus(KEY2_INT_EXTI_LINE) != RESET) 
	{
    /* 将数据写入(发送)到队列中,等待时间为 0  */
		xQueueSendFromISR(Test_Queue, /* 消息队列的句柄 */
											&send_data2,/* 发送的消息内容 */
											&pxHigherPriorityTaskWoken);
		
		//如果需要的话进行一次任务切换
		portYIELD_FROM_ISR(pxHigherPriorityTaskWoken);
		
		//清除中断标志位
		EXTI_ClearITPendingBit(KEY2_INT_EXTI_LINE);     
	}  
  
  /* 退出临界段 */
  taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}

/*********************************************************************************
  * @ 函数名  : DEBUG_USART_IRQHandler
  * @ 功能说明: 串口中断服务函数
  * @ 参数    : 无  
  * @ 返回值  : 无
  ********************************************************************************/
void DEBUG_USART_IRQHandler(void)
{
  uint32_t ulReturn;
  /* 进入临界段,临界段可以嵌套 */
  ulReturn = taskENTER_CRITICAL_FROM_ISR();

	if(USART_GetITStatus(DEBUG_USARTx,USART_IT_IDLE)!=RESET)
	{		
		Uart_DMA_Rx_Data();       /* 释放一个信号量,表示数据已接收 */
		USART_ReceiveData(DEBUG_USARTx); /* 清除标志位 */
	}	 
  
  /* 退出临界段 */
  taskEXIT_CRITICAL_FROM_ISR( ulReturn );
}


9. Experimental phenomenon of interrupt management

      After the program is compiled, use a USB cable to connect the computer to the USB interface of the development board (corresponding to the silkscreen is USB to serial port), download the supporting program to the STM32 development board with an emulator, open the serial port debugging assistant on the computer, and then reset the development board. See the print information of the serial port in the debugging assistant, press the KEY1 button of the development board to trigger an interrupt to send message 1, and press the KEY2 button to send message 2; we press KEY1 and KEY2 to try, in the serial port debugging assistant, you can see the running As a result, then send a message of variable length through the serial port debugging assistant, triggering an interrupt will send a semaphore to notify the task in the interrupt service function, and print out the serial port information when the task receives the semaphore.

 

Guess you like

Origin blog.csdn.net/qq_61672347/article/details/125688979