RTOS中断处理Interrupt Handling
在RTOS中使用信号来触发线程间的行为是比较简单和高效的,而对于Cortex-M微控制器来讲,从中断源获取信号来触发线程同样是一种重要的方式。虽然在中断服务程序ISR中运行一段代码也可以,但在RTOS系统中,在中断运行的时间越短越好,因为太长的时间会延长定时器的节拍,并破坏RTOS内核。系统节拍运行的优先级在NVIC中是最低的,因此任何中断服务程序都会打断它。
在RTOS中最好把中断服务代码设计成一个线程,并分配给它一个比较高的优先级。中断服务线程里的第一行代码应该是等待一个信号标志,当中断到来时,中断处理程序Handler唯一的任务就是设置信号标志,然后就结束运行。中断服务线程为中断事件提供服务,并在服务完成后重新进入循环等待信号标志的再次到来。
在RTOS里,中断服务代码作为线程运行,当中断到来时,中断处理程序Handler给中断服务线程发信号。然后交给内核根据线程的优先级进行调度。
void Thread3(void)
{
while(1)
{
osSignalWait(isrSignal, waitForever);//等待ISR触发一个事件
...//处理中断
}
}
实际的中断程序只包含一点点代码:
void IRQ_Handler(void)
{
osSignalSet(thread3, isrSignal);//给线程3发信号
}
练习:中断信号
CMSIS-RTOS不会在中断服务里引入任何延迟,但是如果你锁住系统节拍中断时间太长的话,就会干扰RTOS的正常运行。这个练习展示了如何从中断处理程序Handler给一个线程发信号,然后在线程里给中断提供服务,这和常规的ISR不同。
打开Pack Installer,选择”Ex 9 Interrupt Signals”,然后install到你的指定路径。
在main函数里,我们初始化ADC,并且创建了一个ADC线程,并给它分配一个所有线程中最高的优先级:
osThreadDef(adc_Thread, osPriorityAboveNormal, 1, 0);
int main(void){
LED_Init();
init_ADC();
T_led_ID1 = osThreadCreate(osThread(led_Thread1), NULL);
T_led_ID2 = osThreadCreate(osThread(led_Thread2), NULL);
T_adc_ID = osThreadCreate(osThread(adc_Thread), NULL);
}
然而,在我们进入main函数后,会遇到一个问题:RTOS配置运行的所有线程都没有特权,因此我们就无法访问NVIC寄存器,针对这个问题有几种处理办法,最简单的就是给线程开放特权访问权限,只需要在RTX_Conf_CM.c文件里进行设置就可以了:
这里我们把线程执行模式切换到特权模式,这样线程就拥有访问Cortex-M处理器的所有访问权限,这里我们需要增加一个线程,同时可运行线程的个数也要增加一个。
编译工程并打开debugger仿真环境。
在led_Thread2, adc_Thread和AD1_2_IRQHandler分别设置断点。
全速运行代码
你会在启动ADC转换的地方遇到第一个断点,然后继续运行,进入ADC中断处理程序,该处理程序给adc线程完发信号就退出。发出的这个信号会让adc线程抢占其他正在运行的线程,紧接着adc线程运行ADC服务程序,然后就会重新挂起,等待下一个信号的到来。
练习:Keil RTX 和 SVC异常
在上一个例子中可以看到,当我们进入一个线程里,它就会运行到一个非特权模式。最简单的方法就是配置线程运行进入特权模式,但是这样就会运行线程拥有访问处理器的所有权限,就会引起潜在的运行时间错误。在这个例子中我们将看到如何使用系统调用异常来进入特权模式,进而运行系统级的代码。
打开Pack Installer,选择“Ex 10 Interrupt Signals”,然后install到你的指定路径。
在这个工程里我们已经添加了一个新的文件叫SVC_Table.s,
添加方法:右击Source Group 1,点击’Add New Item’,选择‘User Code Template’,然后选择CMSIS-RTOS User SVC。(实际工程中已经添加完这个文件——译者注)。
这个就是SVC中断查找表:
在这个文件里,我们需要为每个__SVC函数添加导入名称和表入口,在我们的例子里只需要__SVC_1
现在我们可以把ADC初始化函数转变成一个服务调用异常:
编译工程并启动仿真环境。
在init_ADC()函数前设置断点。
然后从左侧的寄存器窗口查看操作模式:
这里我们处于Thread模式,非特权,使用进程栈指针:
继续单步运行,穿过汇编代码,进入init_ADC函数:
这时我们可以看到已经进入了Handler模式,特权权限,使用主栈指针。这样我们就可以设置ADC,并拥有访问NVIC的权限了。