Main Takeaway
Following 哈工深上传到B站的电控组培训来入门robomaster电控组,我购买了普中科技玄武套餐开发板作为硬件。
本篇介绍我学习FreeRTOS的见闻,自己看FreeRTOS中文参考手册进行琢磨。
FreeRTOS(2)——中断管理
中断管理
嵌入式实时系统需要对整个系统环境产生的事件作出反应。需要处理来自各种源头产生的事件,因此中断是必要的!
中断服务例程(ISR)
延迟中断处理
二值信号量:可看作一个深度为1的队列(所以仅二值)
延迟处理任务(优先级自己设置)对一个信号量进行带阻塞性质的”take”调用,意思是进入阻塞态以等待事件发生。当事件发生后, ISR 对同一个信号量进行”give”操作,使得延迟处理任务解除阻塞,从而事件在延迟处理任务中得到相应的处理。
- vSemaphoreCreateBinary() :创建二值信号量
- xSemaphoreTake() :延迟处理任务调用xSemaphoreTake()时,等效于带阻塞时间地读取队列
Tips:take了信号就没了
除互斥信号量外(Recursive Semaphore)——偶也还没搞懂互斥是什么意思之后文章会填坑,所有信号量都可以调用该API来获取
- xSemaphoreGiveFromISR() :ISR 简单地通过调用 xSemaphoreGiveFromISR()放置一个令牌(信号量)到队列中,使得队列成为满状态。这也使得延迟处理任务切出阻塞态,并移除令牌,使得队列再次成为空。当任务完成处理后,再次读取队列,发现队列为空,又
Tips:即实现了中断和延时处理任务的同步
除互斥信号量外(Recursive Semaphore),所有信号量都可以调用该API来给出
ISR中:1
2
3
4
5
6
7
8xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
if( xHigherPriorityTaskWoken == pdTRUE )
{
/* 给出信号量以使得等待此信号量的任务解除阻塞。如果解出阻塞的任务的优先级高于当前任务的优先级 – 强制进行一次任务切换,以确保中断直接返回到解出阻塞的任务(优选级更高)。
说明:在实际使用中, ISR中强制上下文切换的宏依赖于具体移植。此处调用的是基于Open Watcom DOS移植的宏。其它平台下的移植可能有不同的语法要求。对于实际使用的平台,请参如数对应移植自带的示例程序,以决定正确的语法和符号。
*/
portSWITCH_CONTEXT();
}
main函数中要先创建二值信号量且安装中断服务例程1
2
3
4
5/* 信号量在使用前都必须先创建。本例中创建了一个二值信号量 */
vSemaphoreCreateBinary( xBinarySemaphore );
/* 安装中断服务例程 */
_dos_setvect( 0x82, vExampleInterruptHandler );
Tips:中断服务例程(ISR) 越短越好,中断频率相对较慢用二值信号量很好
延时中断处理流程
中断产生。
中断服务例程启动,给出信号量以使延迟处理任务解除阻塞。
当中断服务例程退出时,延迟处理任务得到执行。延迟处理任务做的第一件事便是获取信号量。
延迟处理任务完成中断事件处理后,试图再次获取信号量——如果此时信号量无效,任务将切入阻塞待等待事件发生
计数信号量
概念
二值信号量相当于一个深度为1的队列,计数信号量即可自己设定深度,对事件计数
如果在延迟处理任务完成上一个中断事件的处理之前,新的中断事件又发生了,等效于将新的事件锁存在二值信号量中,使得延迟处理任务在处理完上一个事件之后,立即就可以处理新的事件。也就是说,延迟处理任务在两次事件处理之间,不会有进入阻塞态的机会,因为信号量中锁存有一个事件,所以当 xSempaphoreTake()调用时,信号量立即有效。
Tips:leaving one more "latched" semaphore available
用法
事件计数
在这种用法中,每次事件发生时,中断服务例程都会“给出(Give)”信号量——信号量在每次被给出时其计数值加 1。延迟处理任务每处理一个任务都会”获取(Take)”一次信号量——信号量在每次被获取时其计数值减 1。信号量的计数值其实就是已发生事件的数目与已处理事件的数目之间的差值。
资源管理
在这种用法中,信号量的计数值用于表示可用资源的数目。一个任务要获取资源的控制权,其必须先获得信号量——使信号量的计数值减 1。当计数值减至 0,则表示没有可用资源。当任务利用资源完成工作后,将给出(归还)信号量——使信号量的计数值加 1。用于资源管理的信号量,在创建时其计数值被初始化为可用资源总数。
API
- xSemaphoreCreateCounting() :创建一个计数信号量
使用队列
- xQueueSendToFrontFromISR()
- xQueueSendToBackFromISR()
- xQueueReceiveFromISR()
分别是 xQueueSendToFront(),xQueueSendToBack()与 xQueueReceive()的中断安全版本,专门用于中断服务例程中。
信号量用于事件通信。而队列不仅可以用于事件通信,还可以用来传递数据。
xQueueSendFromISR()完全等同于 xQueueSendToBackFromISR()
将接收到的字符先缓存到内存中。当接收到一个传输完成消息,或是检测到传输中断后,使用信号量让某个任务解除阻塞,这个任务将对字符缓存进行处理。
在中断服务中直接解析接收到的字符,然后通过队列将解析后经解码得到的命令发送到处理任务。这种技术仅适用于数据流能够快速解析的场合,这样整个数据解析工作才可以放在中断服务中完成。
中断嵌套
中断嵌套需要在 FreeRTOSConfig.h 中定义表 17 详细列出的一个或两个常量。
| 常量 | 描述 |
|---|---|
| configKERNEL_INTERRUPT_PRIORITY | 设置系统心跳时钟的中断优先级。如 果 在 移 植 中 没 有 使 用 常 量configMAX_SYSCALL_INTERRUPT_PRIORITY,那么需要调用中断安全版本 FreeRTOS API的中断都必须运行在此优先级上。 |
| configMAX_SYSCALL_INTERRUPT_PRIORITY | 设置中断安全版本 FreeRTOS API 可以运行的最高中断优先级。 |
建立一个全面的中断嵌套模型需要设置 configMAX_SYSCALL_INTERRUPT_PRIRITY为比configKERNEL_INTERRUPT_PRIORITY更高的优先级
- 处于中断优先级 1 到 3(含)的中断会被内核或处于临界区的应用程序阻塞执行, 但是它们可以调用中断安全版本的 FreeRTOS API 函数
- 处于中断优先级 4 及以上的中断不受临界区影响,所以其不会被内核的任何行为阻塞,可以立即得到执行——这是由微控制器本身对中断优先级的限定所决定的。通常 需 要 严 格 时 间 精 度 的 功 能 ( 如 电 机 控 制 ) 会 使 用 高于configMAX_SYSCALL_INTERRUPT_PRIRITY 的优先级,以保证调度器不会对其中断响应时间造成抖动。
- 不需要调用任何FreeRTOS API 函数的中断,可以自由地使用任意优先级。
Tips:此处不是很懂,希望以后在实践中能有所感悟。
CubeMX中freeRTOS的使用
配置
首先打开 Middleware 标签,选中其中的 FREERTOS 选项,进入 FREERTOS 的配置页面
在 Mode 页面下,选择 Interface 的版本,这里选择 CMSIS_V1——完成对freeRTOS的开启
Tips:CMSIS 是由 Keil 提供的一套特殊的函数接口,他对 freeRTOS 的功能函数进行了封装,使其变得更加易用, 在使用freeRTOS 时,不需要再去直接调用 freeRTOS 的函数,只需要调用 CMSIS 为我们提供的函数即可,目前 CMSIS 有 V1 和 V2 两个版本
在 Configuration 页面下进行 freeRTOS 的配置,一些比较重要的配置:
| 名称 | 功能 |
|---|---|
| USE_PREEMPTION | 是否支持抢占机制,支持则设为 Enabled |
| TICK_RATE_HZ | 系统时钟速率,时钟按照该速率为诶 freeRTOS 中各个任务执行计时,设置为 1000Hz,则每个任务的最小调度时间为1ms |
|---|---|
| MAX_PRIORITIES | 最大优先级数量,默认为 7 |
| MINIMAL_STACK_SIZE | 最小任务栈大小,每当创建一个任务时,都需要为该任务分配一定大小的栈空间,任务需要使用的变量等都存储在该栈空间中。默认的最小值为 128 个字。 |
| MAX_TASK_NAME_LEN | 最大任务名称长度,在创建任务时,需要给每个任务起名作为标识,这个名称可以用一个字符串表示,本参数规定了字符串长度的上限值,默认为 16 |
创建任务
首先在 freeRTOS 的配置页面中的Configuration 下,选中 Tasks and Queues 标签页, 存在一个已经创建的默认任务为“defaultTask”, 点击进入配置选项可以进行修改
Add进一个新的任务:
| 名称 | 功能 |
|---|---|
| Task Name | 任务名称 |
| Priority | 任务创建时的优先级 |
| Stack Size(Words) | 任务栈的大小,默认单位为字 |
| Entry Function | 任务函数的入口 |
| Code Generation Option | 任务函数代码生成方式,设置为 Default 则会产生一个普通的任务函数,As weak: 产生一个用__weak 修饰符修饰的任务函数; As external:产生一个外部引用的任务函数,用户需要自己实现该函数; Default:产生一个默认格式的任务函数,用户需要在该函数实现功能 |
也可以自己在程序中创建函数1
2osThreadDef(LED_RED, red_led_task, osPriorityNormal, 0, 128);
LED_REDHandle = osThreadCreate(osThread(LED_RED), NULL);
- osThreadDef,实际上这不是一个函数,而是一个由 CMSIS 提供的宏定义,用于对要创建的任务进行设置
- 再调用osThreadCreate来创建任务
1 | void blue_led_task(void const * argument) |
Tips:自己创建的函数要放在while(1)中
References
- 哈工深电控内训
- FreeRTOS中文参考手册