μCOS-Ⅲ+GD32_SysTick与PendSV中断管理配置浅解
GD32移植μCOS-Ⅲ时,需要特别关注的两个与系统相关的且非常重要的中断,一个是提供OS系统时基的滴答定时器(SysTick_Handler中断),另一个是跟任务调度有关的(PendSV_Handler中断),成功移植后简单扒了一下GD32中断知识和μCOS-Ⅲ关于这两个中断的配置和管理方式。
文章目录
- μCOS-Ⅲ+GD32_SysTick与PendSV中断管理配置浅解
- 前言
- 一、GD32的中断
- 1.中断与中断优先级
- 2.异常向量表
- 二、μCOS-Ⅲ的中断与异常
- 1.滴答定时器SysTick中断
- 2.任务切换PendSV中断
- 总结
前言
μcos-III是一个可以基于ROM运行的、可裁剪的、抢占式、实时多任务内核,具有高度可移植性,特点:公开源代码、可移植性、可固化、可裁剪、多任务、占先式,特别适合于微处理器和控制器,适合很多商业操作系统性能相当的实时操作系统(RTOS)。在使用GD32F103单片机项目使用过μcos-III,这里作为一个关于SysTick与PendSV中断管理的笔记,欢迎大佬指正!!!!
一、GD32的中断
1.中断与中断优先级
中断:让CPU打断正常运行的程序,转而去处理紧急的事件,中断的一般步骤
1.中断请求:外设产生中断请求(GPIO外部中断、定时器中断)
2. 响应中断:CPU停止执行当前程序,转而去执行中断处理程序
3. 退出中断:执行完毕,返回被打断的程序处,继续往下执行
ARM Cortex-M使用了8位宽的寄存器来配置中断优先级(中断优先级配置寄存器)
GD32和STM32 只是用了中断优先级配置寄存器的[7:4]四位,0-15一共16级的中断优先级等级
数值越小优先级越高。高优先级中断可以打断低优先级中断,支持中断嵌套。
抢占优先级:抢占优先级高的可以打断抢占优先级低的中断(数值越小优先级越高)
子优先级:抢占优先级相同,子优先级数值越低优先执行,但不能互相打断(数值越小优先级越高)
为了方便管理还提出了中断优先级分组,每个分组分配了不同的抢占优先级数和子优先级数,可根据实际情况进行选择,分组配置是在寄存器SCB->AIRCR中配置。
优先级分组 | AIRCR[10:8] | IP Bit[7:4] | 分配情况 |
---|---|---|---|
NVIC_PriorityGroup_0 | 111 | 0:4 | 0位抢占优先级 4子优先级 |
NVIC_PriorityGroup_1 | 110 | 1:3 | 1位抢占优先级 3子优先级 |
NVIC_PriorityGroup_2 | 101 | 2:2 | 2位抢占优先级 2子优先级 |
NVIC_PriorityGroup_3 | 100 | 3:1 | 3位抢占优先级 1子优先级 |
NVIC_PriorityGroup_4 | 011 | 4:0 | 4位抢占优先级 0子优先级 |
特别注意:一个程序代码里只能有一个中断优先级分组在程序初始化时就配置完成,存在多个 优先级分组可能会导致程序出现奇奇怪怪的错误!
2.异常向量表
在谈μCOS-Ⅲ的中断管理之前再说一下GD32的异常向量表:
对于GD32,当某一个外设的中断发生时,CPU如何去调用相应外设的中断服务函数?这时异常向量表就非常重要了。GD32在起始文件中初始化的中断向量表可以看成是一个32位的指针数组,每个成员对应一种异常,这个数组的成员里存放的是一个的中断服务函数的入口地址(向量表首地址规定是栈顶指针)。当识别到到某个中断产生时,硬件会根据我们提供的中断号自动跳转到向量表中与这个中断号对应的这个中断服务函数的入口地址,执行相应的中断服务函数。
__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 handler */
DCD WWDGT_IRQHandler ; 16:Window Watchdog Timer
DCD LVD_IRQHandler ; 17:LVD through EXTI Line detect
DCD TAMPER_IRQHandler ; 18:Tamper Interrupt
DCD RTC_IRQHandler ; 19:RTC through EXTI Line
DCD FMC_IRQHandler ; 20:FMC
DCD RCU_IRQHandler ; 21:RCU
DCD EXTI0_IRQHandler ; 22:EXTI Line 0
DCD EXTI1_IRQHandler ; 23:EXTI Line 1
DCD EXTI2_IRQHandler ; 24:EXTI Line 2
DCD EXTI3_IRQHandler ; 25:EXTI Line 3
DCD EXTI4_IRQHandler ; 26:EXTI Line 4
DCD DMA0_Channel0_IRQHandler ; 27:DMA0 Channel 0
DCD DMA0_Channel1_IRQHandler ; 28:DMA0 Channel 1
DCD DMA0_Channel2_IRQHandler ; 29:DMA0 Channel 2
DCD DMA0_Channel3_IRQHandler ; 30:DMA0 Channel 3
DCD DMA0_Channel4_IRQHandler ; 31:DMA0 Channel 4
DCD DMA0_Channel5_IRQHandler ; 32:DMA0 Channel 5
DCD DMA0_Channel6_IRQHandler ; 33:DMA0 Channel 6
DCD ADC0_1_IRQHandler ; 34:ADC0 and ADC1
DCD USBD_HP_CAN0_TX_IRQHandler ; 35:USBD and CAN0 TX
DCD USBD_LP_CAN0_RX0_IRQHandler ; 36:USBD and CAN0 RX0
DCD CAN0_RX1_IRQHandler ; 37:CAN0 RX1
DCD CAN0_EWMC_IRQHandler ; 38:CAN0 EWMC
DCD EXTI5_9_IRQHandler ; 39:EXTI Line 5 to EXTI Line 9
DCD TIMER0_BRK_IRQHandler ; 40:TIMER0 Break
DCD TIMER0_UP_IRQHandler ; 41:TIMER0 Update
DCD TIMER0_TRG_CMT_IRQHandler ; 42:TIMER0 Trigger and Commutation
DCD TIMER0_Channel_IRQHandler ; 43:TIMER0 Channel Capture Compare
DCD TIMER1_IRQHandler ; 44:TIMER1
DCD TIMER2_IRQHandler ; 45:TIMER2
DCD TIMER3_IRQHandler ; 46:TIMER3
DCD I2C0_EV_IRQHandler ; 47:I2C0 Event
DCD I2C0_ER_IRQHandler ; 48:I2C0 Error
DCD I2C1_EV_IRQHandler ; 49:I2C1 Event
DCD I2C1_ER_IRQHandler ; 50:I2C1 Error
DCD SPI0_IRQHandler ; 51:SPI0
DCD SPI1_IRQHandler ; 52:SPI1
DCD USART0_IRQHandler ; 53:USART0
DCD USART1_IRQHandler ; 54:USART1
DCD USART2_IRQHandler ; 55:USART2
DCD EXTI10_15_IRQHandler ; 56:EXTI Line 10 to EXTI Line 15
DCD RTC_Alarm_IRQHandler ; 57:RTC Alarm through EXTI Line
DCD USBD_WKUP_IRQHandler ; 58:USBD WakeUp from suspend through EXTI Line
DCD TIMER7_BRK_IRQHandler ; 59:TIMER7 Break Interrupt
DCD TIMER7_UP_IRQHandler ; 60:TIMER7 Update Interrupt
DCD TIMER7_TRG_CMT_IRQHandler ; 61:TIMER7 Trigger and Commutation Interrupt
DCD TIMER7_Channel_IRQHandler ; 62:TIMER7 Channel Capture Compare
DCD ADC2_IRQHandler ; 63:ADC2
DCD EXMC_IRQHandler ; 64:EXMC
DCD SDIO_IRQHandler ; 65:SDIO
DCD TIMER4_IRQHandler ; 66:TIMER4
DCD SPI2_IRQHandler ; 67:SPI2
DCD UART3_IRQHandler ; 68:UART3
DCD UART4_IRQHandler ; 69:UART4
DCD TIMER5_IRQHandler ; 70:TIMER5
DCD TIMER6_IRQHandler ; 71:TIMER6
DCD DMA1_Channel0_IRQHandler ; 72:DMA1 Channel0
DCD DMA1_Channel1_IRQHandler ; 73:DMA1 Channel1
DCD DMA1_Channel2_IRQHandler ; 74:DMA1 Channel2
DCD DMA1_Channel3_4_IRQHandler ; 75:DMA1 Channel3 and Channel4
__Vectors_End
例如产生了复位异常,那么MCU会在异常向量表找到Reset_Handler函数的地址,然后跳转到该函数执行;与μCOS-Ⅲ密切相关的SysTick与PendSV中断也在异常向量表中实现了,下图为向量表结构(《CortexM3权威指南(中文)》,P45):
向量表的存储位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 处必须包含一张向量表,用于初始时的异常分配。初始化时会根据配置条件将异常向量表加载到程序起始运行地址处,在SystemInit(void)函数底部有这样一段代码描述的就是加载异常向量表的储存位置:
#ifdef VECT_TAB_SRAM
nvic_vector_table_set(NVIC_VECTTAB_RAM, VECT_TAB_OFFSET);
#else
nvic_vector_table_set(NVIC_VECTTAB_FLASH,VECT_TAB_OFFSET);
#endif
所以单片机手撸Bootloader的时候,记得更改Bootloader和APP的异常向量表的映射地址。
二、μCOS-Ⅲ的中断与异常
在μCOS-Ⅲ中有两个与系统非常密切的中断,一个是提供OS系统时基的滴答定时器(SysTick_Handler中断),另一个是跟任务调度有关的(PendSV_Handler中断),根据Cortex‐M3 中的异常类型表(《CortexM3权威指南(中文)》,P45),我们可以知道编号为 1-15 的对应系统异常,大于等于 16 的则全是外部中断。除了个别异常的优先级被定死外, 其它异常的优先级都是可编程的。SysTick与PendSV分别是系统中断中编号为15和14的异常。编号为1-3的异常优先级被定死不能更改,所以μCOS也不能对其进行编程操作。(注意: 所有能打断正常执行流的事件都称为异常,没有编号为 0 的异常)
结合第一章第一节中的描述,在μCOS-Ⅲ中,使用的是NVIC_PriorityGroup_4 优先级分组,相当于有0-15共16个抢占优先级,但是可供μCOS-Ⅲ管理的优先级只有4-15这12个,通过CPU_CFG_KA_IPL_BOUNDARY 设置,如果将宏定义为4,即中断优先级范围为4~15。
1.滴答定时器SysTick中断
要将SysTick作为μCOS的时基计数,首先需要明确SysTick的优先级和中断时间。整个OS运行的时间计数都由SysTick的中断产生,所以无论MCU在干什么只要SysTick中断触发就必须雷打不动地调用SysTick_Handler进行计数以保证系统时钟精度,所以SysTick的优先级必须为最高优级;其次中断周期决定了计数频率,相当于OS的系统频率,不能太快也不能太慢(太慢系统利用率不高,太快mcu频繁触发中断,时间都用来计数了),一般通过宏OS_CFG_TICK_RATE_HZ 设置为1000即可。
函数在开始任务时,需要调用OS_CPU_SysTickInit(cntr)来初始化SysTick;参数cntr决定SysTick的中断频率,一般这样设置先获取SysTick系统时钟,再除以滴答定时器中断频率得到cntr的值。
CPU_INT32U cntr = 0;
cntr = rcu_clock_freq_get(CK_SYS) / OS_CFG_TICK_RATE_HZ;
CPU_Init();
OS_CPU_SysTickInit(cntr);
OS_CPU_SysTickInit(cntr)函数内部:
这样就完成了SysTickInit的优先级和中断频率的配置,然后就是每次触发中断调用SysTick_Handle函数时调用OS的计数函数OS_CPU_SysTickHandler,转到OS系统;或将.s启动文件将SysTick_Handle函数名全部换成OS_CPU_SysTickHandler也可以。这样就完成了整个OS的时基配置。
2.任务切换PendSV中断
要将PendSV作为μCOS的任务切换的一个重要机制,OS在没有PendSV时,直接由当OS进行多个任务切换时候,如果此时产生 SysTick 异常时正在响应一个中断,则SysTick异常 因为优先级较高会抢占其 ISR。此时OS 不能执行上下文切换,否则将使中断请求被延迟,而且在真实系统中延迟时间往往又会有诸多变数,所以有任何实时性要求的系统都不会这么干。
后来演变早期的 OS 大多会检测当前是否有中断在活跃中,只有没有任何中断需要响应时,才执行上下文切换(切换期间无法响应中断)。然而,这种方法的弊端在于,它可以把任务切换动作拖延很久(因为如果抢占了 IRQ,则本次 SysTick 在执行后不得作上下文切换,只能等待下一次 SysTick 异常),尤其是当某中断源的频率和 SysTick 异常的频率比较接近时,会发生“共振”,这种情况会更加频繁发生。
后来出现PendSV,PendSV 异常会自动延迟上下文切换的请求,后来主要利用的是其“缓期执行”的特点——直到其它的 ISR 都完成了处理后才放行。为实现这个机制,需要把 PendSV 编程为最低优先。如果 OS 检测到某 IRQ 正在活动并且被 SysTick 抢占,它将悬起一个 PendSV 异常,以便缓期执行上下文切换。
PendSV在OS中是如何进行任务切换的,这里几句话可说不清楚,后面我专门讲任务切换,只需要知道通过SHPR3(地址:0xE000ED22)将PendSV设置为μC/OS系统最低优先级,保证系统任务切换不会阻塞系统其他中断的响应,函数OSStartHighRdy()中有如下代码:
总结
主要知识点就两个:
■μC/OS-III直接参与管理的中断优先级为4-15,
■与μC/OS-III系统直接相关的中断有两个,一个SysTick负责系统时基中断需要在优先级最高,一个PendSV负责任务切换中断需要优先级最低