FreeRTOS就是为了复杂流程而生的。
契机
参加了机赛,自动分拣项目需要一个复杂的流程。代码实在不是很优雅。想到了现在熟视无睹的FreeRTOS,或许能发挥点作用呢,于是潜心学习去了。
资源
我是通过一下几个渠道学习的。之前看过一个csdn的教程,上来就解释一堆宏和函数用法,着实看不懂。
我觉得最舒服的学习方法是:
有了需求之后,结合自己的需求去看官方的文档和推荐的资料
看的不用很细,详略得当地看,应用时再来细细研究
同时可以从别人的项目那里看实际应用例子,充实自己的认知
官方网站
掌握FreeRTOS文档
csdn上的一些项目
关于发行和堆
对于FreeRTOS的移植,因为CubeMX里面已经移植了FreeRTOS了,所以不是很有必要去深究。
但是可以关注的是config.h
,来开关一些功能。
值得注意的是CubeMX所采用的是CMSIS OS
的版本,对FreeRTOS进行了封装,这就是为什么osDelay
和vTaskDelay
是同一个东西的原因。
至于heap的内存管理,我只知道heap4是最好用的版本。
任务
在死循环里放你想执行的任务,配合各种事件来做出相应,如队列,信号量,事件组等
任务状态
阻塞:等待事件(时间事件也算,如Delay
)到达,激活后切成就绪
挂起:用resume
来恢复
就绪:根据优先级来判断是否运行
运行:就是运行
时间测量和Delay
设置FreeRTOS的时候,一般会开一个用定时器作为滴答时间测量的,相当于生产时间戳,Delay
就是通过这个实现的
每一个滴答间隔之间都有一个滴答中断,来决定下一个滴答运行哪个任务
延时函数一种是vTaskDelay,另一种是vTaskDelayUntil,区别大概是后者更精确吧,这个有待了解
调度方式
时间分片:相同优先级的在相邻滴答周期交替进行
抢占式:FreeRTOS的灵魂
优先级
可以通过函数改变自身或通过句柄他人的优先级
相关函数
任务函数本身
创建函数
删除函数
延时函数
优先级相关函数
队列
作为在堆的一个内核对象,它复制数据到堆里,负责任务间传输数据
问题来了,为啥不用extern呢
发送与接收
结合优先级,写入和读取各有阻塞,可以实现一些有意思的操作。
如读取任务优先级高于写入任务,则可以保证队列里只有一个数据,因为一写入,接受任务就会抢占,读取并删除数据,这时进入阻塞状态,等待写入任务发送数据。
交换优先级可以让队列永远是满的。
如果有两个发送任务优先级相等,而接受任务高于他俩,则会交替发送,一发完就接受。
结合上述知识,队列可以帮助实现流程中的任务间数据转移,或任务“通知”。
多源和指针
可以通过发送结构体(包含id和数据)来让接受任务区分是哪个发送者发出的。
可以通过发送指针,来发送不同类型和长度的数据
队列集
这个可以看成是多源和指针的高级加强版,可以有信号量和队列两种元素在队列集里面。
这有一个很实用的例子:
1 | /* 从中接收字符指针的队列句柄。 */ |
邮箱
这只是仅有单个数据的队列。
但是使用不同的函数让数据不会被读完删除,而是只读,写入覆盖这样。
相关函数
队列创建函数
发送到头或尾的函数
接收函数
返回队列项目数的函数
队列集创建,加入,择取
覆写,只读不删函数
定时器
定时器由一个回调函数和定时器实例(包括定时器命令队列,守护进程任务)组成。
具体机制是:发出定时器命令(如开启,重置等)到命令队列里面,守护进程任务会检查这个队列,并执行相应的回调函数。
守护进程任务的优先级最好设置的高一点,虽然较低也不会影响它执行回调函数,估计是用到了滴答间隔的滴答中断来检查定时器列表。
特性
可以运行和休眠
有单次和周期性定时器
定时器有ID来辨识身份
可以手动改变周期或者重置
想法
底盘任务里判断是否到达指定位置:即定期检查里程误差,就可以用定时器回调和重置的功能
相关函数
创建函数
开始,停止函数
重置或改变周期函数
获取滴答计数函数
设置ID查询ID函数
回调函数
资源管理
原子操作:一种不想被打断的连续操作,就像开大时外面电话响了,你得完事才出去接吧。
专业一点,就是线程安全,访问堆栈上的变量是安全的,但是多个函数都可以访问一个全局/静态存储区里的变量是不安全的(static变量)。这个我了解不是很清楚。
临界区
基本临界区是由调用宏taskENTER_critical()
和taskEXIT_CRITICAL()
包围的代码区域,里面存放原子操作。vTaskSuspendAll()
和xTaskResumeAll()
挂起的是调度器,从而禁止任务切换。
临界区也有中断版本的,都中断嵌套。
互斥锁
也称为二进制信号量,MUTEXES
,这个就像绝命毒师里面的话语权抱枕,拿到它的人才可以说话。
在xSemaphoreTake()
获得互斥锁后,才会执行后面的代码。互斥锁必须给回去。
这会发生一个问题,叫优先级反转:高优先级的任务在等待持有互斥锁的低优先级任务。
死锁和递归互斥锁,后者解决前者,这个我之后再写。
看门人任务
这个也可以达到保护资源的目的。只有看门人任务可以直接访问资源,其他人需要通过看门人间接访问资源。可以使用队列来通知看门人任务事件到达。
滴答钩子函数
这一节多介绍了滴答钩子函数,调度器在滴答间隔马上执行这个钩子函数。所以必须要快,用ISR版的API函数。
相关函数
临界区的两个宏
挂起所有和恢复所有函数
创建,拿取,返回互斥锁函数
滴答钩子函数
中断和信号量
在中断中使用ISR这种安全的API接口
中断与任务切换
任务切换在这个文档里也有一种说法是上下文切换。
关于pxHigherPriorityTaskWoken
这个参数,它决定了中断里面发生的一些事件(如发送队列)使关联的高优先级任务进入就绪状态后————会不会立马运行。设置这个参数还有些技巧,一开始得是pdFALSE,再变成pdTRUE才可以被读到。
这个参数一般在类似xSemaphoreGiveFromISR()
的函数中,这个函数在下文会提到。portYIELD_FROM_ISR
的宏和pxHigherPriorityTaskWoken
参数是配合使用的,调用这个宏即可进行任务切换。
信号量和中断
延迟中断处理的意思是,因为中断要尽可能短,所以设置一个任务跟在中断屁股后面给它做任务,这就需要结合上一节所说的参数和宏,和信号量Semaphore
核心思想是,先创造一个二进制信号量;然后在中断那里给出,同时唤醒高优先级任务;这个延时处理任务拿到信号量,进行处理,达到“与中断同步”的效果。
关于中断的还有好多
估计一时半会用不到,先不写了。
相关函数
一堆
事件组
特性:可以多个事件组合发生,可以在阻塞状态下等待一个事件或多个
事件组设置和等待
读取
用掩码设置和读取。
1 | /*"事件组中事件位的定义。 */ |
等待
等待时是阻塞的。
等待有xWaitForAllBits
和xClearOnExit
的模式,且可以设置超时时间。
同步任务
可以通过事件组来同步多个任务到一个同步点。
没细看,以后写。