本文参考自[野火EmbedFire]《RT-Thread内核实现与应用开发实战——基于STM32》,仅作为个人学习笔记。更详细的内容和步骤请查看原文(可到野火资料下载中心下载)
异常与中断的基本概念
异常
异常是指任何打断处理器正常执行,并且迫使处理器进入一个由有特权的特殊指令执行的事件。 异常通常可以分为两类:同步异常和异步异常。
由内部事件(相处理器指令运行产生的事件)引起的异常称为异步异常,例如造成被零除的算术运算引发的一个异常。
异步异常主要指由于外部异常源产生的异常,是一个由外部硬件装置产生的事件引起的异步异常。
同步异常事件是由于执行某些指令而从处理器内部产生的,而异步异常事件的来源是外部硬件装置。例如按下设备某个按钮产生的事件。它们的区别还在于,同步异常触发后,系统必须立即进行处理而不能够依然执行原有的程序指令步骤;而异步异常则可以延缓处理甚至忽略。
中断
中断属于异步异常。所谓中断是指中央处理器正在处理某件事的时候,外部发生了某一事件,请求 CPU 快速处理,CPU 暂停当前的工作,转而处理所发生的事件,处理完成后,再回到原来被中断的地方,继续原来的工作。
中断能打断线程的运行,无论该线程具有什么样的优先级,因此中断一般用于处理比较紧急的事件,而且只做简单处理。
RT-Thread 源码中有许多处临界段的地方,临界段虽然保护了关键代码的执行不被打断,但也影响系统的实时,任何使用了操作系统的中断响应都不会比裸机快。
RT-Thread 的中断管理支持:
中断的介绍
与中断相关的硬件可以划分为三类:外设、中断控制器、CPU 本身。 外设:当外设需要请求 CPU 时,产生一个中断信号,该信号连接至中断控制器。 中断控制器:中断控制器是 CPU 众多外设中的一个,它一方面接收其它外设中断信号的输入,另一方面,它会发出中断信号给 CPU。可以通过对中断控制器编程实现对中断源的优先级、触发方式、打开和关闭源等设置操作。在 Cortex-M 系列控制器中常用的中断控制器是 NVIC(内嵌向量中断控制器 Nested Vectored Interrupt Controller)。 CPU:CPU 会响应中断源的请求,中断当前正在执行的线程,转而执行中断处理程序。NVIC 最多支持 240 个中断,每个中断最多 256 个优先级。
——原文
中断管理的运作机制
中断产生时,处理机将按如下的顺序执行:
- 保存当前处理机状态信息
- 载入异常或中断处理函数到 PC 寄存器
- 把控制权转交给处理函数并开始执行
- 当处理函数执行完成时,恢复处理器状态信息
- 从异常或中断中返回到前一个程序的执行点
中断发生的环境有两种情况:在线程的上下文中,在中断服务函数处理上下文中。
中断发生在线程的上下文中
线程在运行时,如果出现了中断,无论中断优先级多大,都会打断当前线程的执行,当中断服务函数处理完成后才会恢复线程的上下文环境,继续运行线程。
图片来源:《RT-Thread内核实现与应用开发实战》
中断发生在中断服务函数上下文中
在执行中断服务例程的过程中,如果有更高优先级的中断源触发中断,由于当前处于中断处理上下文环境中,根据不同的处理器架构可能有不同的处理方式,比如新的中断挂起等待,直到当前中断处理离开后再响应;或新的高优先级中断打断当前中断处理过程,直接去响应新的中断源。后面这种情况被称为中断嵌套。
图片来源:《RT-Thread内核实现与应用开发实战》
中断管理讲解
ARM Cortex-M 内核的中断不受 RT-Thread 管理,即中断的配置、使能以及中断服务函数等代码都需要我们手动编写。在中断服务函数中如果要使用 RT-Thread 的 IPC 通信机制,一般建议使用信号量、消息或事件标志组等标志事件,即只在中断里改变标志值,具体处理操作放到线程中执行。
ARM Cortex-M 内核支持中断嵌套功能,在处理低优先级中断时如果有一个高优先级的中断触发,处理器会打断当前运行的中断服务程序。Cortex-M 和 其他 ARM 内核的一个很大的区别是:Cortex-M 的中断嵌套里,被打断的中断服务程序的上下文会被保存在中断栈中(硬件寄存器),即由硬件管理,而其他 ARM 处理器一般都需要依赖软件来保存上下文。
另外,在 Cortex-M 处理器上,所有中断都采用中断向量表的方式来管理,当一个中断触发时,处理器会直接判断是哪个中断源,然后直接跳转到相应的固定位置执行。而在 ARM7、ARM9 中,一般是先跳转进入 IRQ 入口,然后再由软件进行判断是哪个中断源触发,获得了相应的终端服务函数入口地址后,再进行处理。
下面是 STM32F10x 的中断向量表:
__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
RT-Thread 在 Cortex-M 处理器上的中断处理和裸机相同,当用户需要自定义中断服务程序时,只需要定义相同名称的函数来覆盖弱化符号即可。
中断管理实验
野火例程没有现成的带按键和串口的工程,需要我们自己来整理,
首先,将 bsp_usart.c 中 串口初始化函数里的添加中断的配置:
从野火的 “18-EXTI—外部中断事件控制器” 实验工程中拷贝 bsp_exti 文件夹到我们的工程中,我根据我的开发板原理图,进行了适当的修改,修改后的源代码和头文件如下:
bsp_exti.c
#include "bsp_exti.h"
static void EXTI_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
NVIC_InitStruct.NVIC_IRQChannel = KEY0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
NVIC_InitStruct.NVIC_IRQChannel = WK_UP_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
}
void EXIT_Key_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
EXTI_InitTypeDef EXTI_InitStruct;
EXTI_NVIC_Config();
RCC_APB2PeriphClockCmd(KEY0_INT_GPIO_CLK | WK_UP_INT_GPIO_CLK, ENABLE);
GPIO_InitStruct.GPIO_Pin = KEY0_INT_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(KEY0_INT_GPIO_PORT, &GPIO_InitStruct);
GPIO_InitStruct.GPIO_Pin = WK_UP_INT_GPIO_PIN;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD;
GPIO_Init(WK_UP_INT_GPIO_PORT, &GPIO_InitStruct);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
GPIO_EXTILineConfig(KEY0_INT_GPIO_PORT_SRC, KEY0_INT_GPIO_PIN_SRC);
EXTI_InitStruct.EXTI_Line = KEY0_INT_EXTI_LINE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
GPIO_EXTILineConfig(WK_UP_INT_GPIO_PORT_SRC, WK_UP_INT_GPIO_PIN_SRC);
EXTI_InitStruct.EXTI_Line = WK_UP_INT_EXTI_LINE;
EXTI_InitStruct.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStruct.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStruct.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStruct);
}
bsp_exti.h
#ifndef __BSP_EXTI_H
#define __BSP_EXTI_H
#include "stm32f10x.h"
#define KEY0_INT_GPIO_PIN GPIO_Pin_4
#define KEY0_INT_GPIO_PORT GPIOE
#define KEY0_INT_GPIO_CLK RCC_APB2Periph_GPIOE
#define KEY0_INT_GPIO_PORT_SRC GPIO_PortSourceGPIOE
#define KEY0_INT_GPIO_PIN_SRC GPIO_PinSource4
#define KEY0_INT_EXTI_LINE EXTI_Line4
#define KEY0_IRQHandler EXTI4_IRQHandler
#define KEY0_IRQn EXTI4_IRQn
#define WK_UP_INT_GPIO_PIN GPIO_Pin_0
#define WK_UP_INT_GPIO_PORT GPIOA
#define WK_UP_INT_GPIO_CLK RCC_APB2Periph_GPIOA
#define WK_UP_INT_GPIO_PORT_SRC GPIO_PortSourceGPIOA
#define WK_UP_INT_GPIO_PIN_SRC GPIO_PinSource0
#define WK_UP_INT_EXTI_LINE EXTI_Line0
#define WK_UP_IRQHandler EXTI0_IRQHandler
#define WK_UP_IRQn EXTI0_IRQn
void EXIT_Key_Config(void);
#endif
修改 board.c 文件,将 EXIT_Key_Config(); 加入到 rt_hw_board_init() 函数中:
下面是实验代码:
#include "board.h"
#include "rtthread.h"
#include <string.h>
static rt_thread_t usart_thread = RT_NULL;
static rt_thread_t key_thread = RT_NULL;
rt_mq_t test_mq = RT_NULL;
rt_sem_t test_sem = RT_NULL;
#define USART_RX_BUFF_SIZE 1024
char Usart_Rx_Buf[USART_RX_BUFF_SIZE];
rt_uint32_t Usart_Cnt;
rt_uint32_t send_data1 = 0;
rt_uint32_t send_data2 = 1;
static void usart_thread_entry(void *parameter)
{
rt_err_t uwRet = RT_EOK;
while(1)
{
uwRet = rt_sem_take(test_sem, 0);
if(RT_EOK == uwRet)
{
rt_kprintf("收到数据:%s\n", Usart_Rx_Buf);
memset(Usart_Rx_Buf, 0, USART_RX_BUFF_SIZE);
Usart_Cnt = 0;
}
}
}
static void key_thread_entry(void *parameter)
{
rt_err_t uwRet = RT_EOK;
uint32_t r_queue;
while(1)
{
uwRet = rt_mq_recv(test_mq, &r_queue, sizeof(r_queue), RT_WAITING_FOREVER);
if(RT_EOK == uwRet)
rt_kprintf("触发中断的是 %s.\n", r_queue ? "WK_UP" : "KEY0");
else
rt_kprintf("数据接收错误,错误码:0x%1x\n", uwRet);
}
}
int main(void)
{
test_mq = rt_mq_create("test_mq", 4, 2, RT_IPC_FLAG_FIFO);
if(test_mq != RT_NULL)
rt_kprintf("消息队列创建成功!\n");
test_sem = rt_sem_create("test_sem", 0, RT_IPC_FLAG_FIFO);
if(test_sem != RT_NULL)
rt_kprintf("信号量创建成功!\n");
usart_thread =
rt_thread_create("usart",
usart_thread_entry,
RT_NULL,
512,
3,
20);
if(usart_thread != RT_NULL)
rt_thread_startup(usart_thread);
else
return -1;
key_thread =
rt_thread_create("key",
key_thread_entry,
RT_NULL,
512,
2,
20);
if(key_thread != RT_NULL)
rt_thread_startup(key_thread);
else
return -1;
}
void KEY0_IRQHandler(void)
{
if(EXTI_GetITStatus(KEY0_INT_EXTI_LINE) != RESET)
{
rt_mq_send(test_mq, &send_data1, sizeof(send_data1));
EXTI_ClearITPendingBit(KEY0_INT_EXTI_LINE);
}
}
void WK_UP_IRQHandler(void)
{
if(EXTI_GetITStatus(WK_UP_INT_EXTI_LINE) != RESET)
{
rt_mq_send(test_mq, &send_data2, sizeof(send_data2));
EXTI_ClearITPendingBit(WK_UP_INT_EXTI_LINE);
}
}
void DEBUG_USART_IRQHandler(void)
{
if(USART_GetITStatus(DEBUG_USARTx,USART_IT_RXNE)!=RESET)
{
Usart_Rx_Buf[Usart_Cnt++] = USART_ReceiveData(DEBUG_USARTx);
}
if(USART_GetITStatus(DEBUG_USARTx, USART_IT_IDLE) != RESET)
{
USART1->SR;
USART1->DR;
rt_sem_release(test_sem);
}
}
实验现象
本实验分别测试了外部中断和串口中断:
|