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.