开始
Cubemx初始化完后,任务只运行第一个,刚开始以为是没开启时间调度,后面查看代码,发现main函数中 osKernelStart() 封装了vTaskStartScheduler();后来发现是堆设置的不够,默认为3072B(字节),改为10*1024B(1KB=1024B)即为10KB(STM32103Fc8t6的FLASH大小为64KB,RAM为20KB(堆区占用的是RAM)),任务正常运行。(为何3072会不够用呢(只添加了两个任务),改成4000又够用了)。
因为使用串口,重定向了printf();调用C标准库,导致FLASH不够用,解决方法:在cmakelists.txt中加入代码,即可使用精简版的C标准库
add_link_options(-specs=nano.specs)
注: FreeRTOS Heap Usage报错应该是BUG,他只识别到了1024并没有识别到*10,所以报错提醒我堆已经没有剩余了
1 任务管理
![](https://i-blog.csdnimg.cn/direct/b047f4770c814258bb56947267d67734.png)
2 信号量
2.1 同步与互斥的理解:
两者都是两个任务之间不能同时运行,同步是一种特殊的互斥,同步是必须按某种特定的顺序运行。
2.2 阻塞时间的理解:
阻塞时间是当任务获取信号量的时候由于信号量无效从而导致任务进入阻塞态的最大时钟节拍数。(即任务进入阻塞的最大时间,若设置阻塞时间为portMAX_DELAY,则任务一直阻塞,直到信号量有资源才会被唤醒。)
2.3 互斥信号量的理解:
(用于互斥任务之间互斥的访问一个临界资源,同一时间只能一个任务可以使用)当多个任务需要互斥进行时(多个任务均有同一OLED显示的任务),创建一个互斥信号量,一个任务去获取这个信号量(xSemaphoreTake()),在完成相应任务之后,又释放这个信号量(xSemaphoreGive()),另一个任务再获取这个信号量,执行相应任务。当信号量被占用时,其他任务获取信号量,若获得失败,则进入阻塞态(阻塞的时间由阻塞时间参数决定)。
注:互斥信号量不能用于中断服务函数
2.4 二值信号量的理解:
(用于同步 使用信号量来同步的话任务只能与单个的事件或任务进行同步)在多任务系统中,经常会使用二值信号量来实现任务之间或者任务与中断之间的同步,比如,某个任务需要等待一个标记,那么任务可以在轮询中查询这个标记有没有被置位,则任务在等待的过程也会消耗CPU的资源。二值信号量就可以解决,一个任务只释放信号量(当其操作完成就释放信号量),一个任务只接收信号量(当其接收到信号量,就开始运行,其余时间处于堵塞态)。
2.5 二值与互斥信号量的区别:
互斥信号量拥有优先级继承。
二值信号量的使用可能会导致一个问题——优先级翻转。当低优先级的任务占据一共享资源的信号量,高优先级的任务由于无法获取到该信号量,而处于阻塞态,这样会破坏任务的预期顺序,可能 会导致严重的后果
![优先级翻转](https://i-blog.csdnimg.cn/direct/610c004c98d94b74bb8275efc5ea7e31.png)
优先级继承就能避免这个问题。当一个互斥信号量正在被一个低优先级的任务使用,而此时有个高优先级的任务也尝试获取这个互斥信号量的话就会被阻塞。不过这个高优先级的任务会将低优先级任务的优先级提升到与自己相同的优先级,这个过程就是优先级继承。
3 事件标志组
3.1 事件标志组的理解
某任务需要与多个事件进行同步,就需要使用事件标志组
事件组就是一组事件位,一个事件标志组有三十二位,最多可存储24个事件位(高八位有其它用),事件位0存储在这个变量的bit0,事件位1存储在bit1...当某事件发生,就将其对应的事件位置1(xEventGroupSetBits() /xEventGroupSetBitsFromISR())。
3.2 设置事件位
提供了四个函数(只介绍用于任务中的函数)
1.xEventGroupSetBits()
设置指定的事件位为 1
EventGroupHandle_t xEventGroup | 要操作的事件标志组的句柄 |
const EventBits_t uxBitsToSet | 指定要置 1 的事件位,比如要将bit3值1的话就设置为0X08。可以同时将多个bit置1,如设置为 0X09的话就是同时将bit3和bit0置1 |
返回值 | 任何值: 在将指定事件位置1后的事件组值 |
2.xEventGroupClearBits()
将事件标志组中的指定事件位清零
EventGroupHandle_t xEventGroup | 要操作的事件标志组的句柄 |
const EventBits_t uxBitsToClear | 指定要置 1 的事件位,比如要将bit3值1的话就设置为0X08。可以同时将多个bit置1,如设置为 0X09的话就是同时将bit3和bit0置1 |
返回值 | 任何值: 在将指定事件位置1后的事件组值 |
3.3等待指定事件位
使用xEventGroupWaitBits()等待指定事件位完成,调用函数以后如果任务要等待的事件位还没有准备好(置1或清零)的话任务就会进入阻塞态,直到阻塞时间到达或者所等待的事件位准备好。
xEventGroup | 指定要等待的事件标志组。 |
uxBitsToWaitFord | 指定要等待的事件位,比如要等待bit0和(或)bit2的时候此参数就是0X05, 如果要等待bit0和(或)bit1和(或)bit2 的时候此参数就是0X07,以此类推。 |
xClearOnExit | pdTRUE:那么在退出此函数之前由参数uxBitsToWaitFor 所设置的这些事件位就会清零。 pdFALSE :事件位不会改变。 |
xWaitForAllBits | pdTRUE:当 uxBitsToWaitFor 所设置的这些事件位都置 1,或者指定的阻塞时间到的时候函数 xEventGroupWaitBits()才会返回. pdFALSE: 只要uxBitsToWaitFor 所设置的这些事 件位其中的任意一个置1,或者指定的阻塞时间到的话函数 xEventGroupWaitBits()就会返回。 |
xTicksToWait | 设置阻塞时间,单位为节拍数 |
返回值 | 任何值:返回当所等待的事件位置1以后的事件标志组的值,或者阻塞时间到。根 据这个值我们就知道哪些事件位置1了。如果函数因为阻塞时间到而返回的话那么这个返回值就不代表任何的含义。 |
4 任务通知
4.1 对任务通知的理解
任务通知主要用于任务之间的事件通知和同步,一个任务向其他任务发送通知,以表明某些事件已发生。
FreeRTOS 的每个任务都有一个 32 位的通知值,任务控制块中的成员变量ulNotifiedValue 就是这个通知值(任务通知不用创建)
任务通知值的方法可以在一些场合中替代队列、二值信号量、计数型信号量和事件标志组。
使用任务通知来实现二值信号量功能的时候,解除任务阻塞的时间比直接使用二值信号量要快45% ,并且使用的RAM更少
4.2 发送任务通知
1. xTaskNotify()
TaskHandle_t xTaskToNotify, | 任务句柄,指定任务通知是发送给哪个任务的。 |
uint32_t ulValue, | 任务通知值。 |
eNotifyAction eAction | 任务通知更新的方法,eNotifyAction为枚举类型, typedef enum { eNoAction = 0, eSetBits, //更新指定的bit eIncrement, //通知值加一 eSetValueWithOverwrite, //覆写的方式更新通知值 eSetValueWithoutOverwrite //不覆写通知值 } eNotifyAction; |
返回值: | pdFAIL:参数eAction设置为 eSetValueWithoutOverwrite时,如果任务通知值没有更新成功就返回pdFAIL。 pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。 |
2. xTaskNotifyGive()
发送不带通知值,只是将任务通知值加一(适用于代替二值信号值)
TaskHandle_t xTaskToNotify | 任务句柄,指定任务通知是发送给哪个任务的 |
返回值 | pdPASS: 此函数只会返回pdPASS。 |
3. xTaskNotifyAndQuery()
和xTaskNotify()类似,比 xTaskNotify()多一个参数,用来保存更新前的通知值。
TaskHandle_t xTaskToNotify, | 任务句柄,指定任务通知是发送给哪个任务的。 |
uint32_t ulValue, | 任务通知值。 |
eNotifyAction eAction | 任务通知更新的方法,eNotifyAction为枚举类型, typedef enum { eNoAction = 0, eSetBits, //更新指定的bit eIncrement, //通知值加一 eSetValueWithOverwrite, //覆写的方式更新通知值 eSetValueWithoutOverwrite //不覆写通知值 } eNotifyAction; |
uint32_t * pulPreviousNotificationValue | 用来保存更新前的任务通知值 |
返回值: | pdFAIL:参数eAction设置为 eSetValueWithoutOverwrite时,如果任务通知值没有更新成功就返回pdFAIL。 pdPASS: eAction 设置为其他选项的时候统一返回pdPASS。 |
4.2 获取任务通知
1. ulTaskNotifyTake()
此函数为获取任务通知函数,当任务通知用作二值信号量或者计数型信号量的时候推荐使用此函数来获取信号量
BaseType_t xClearCountOnExit | 参数为 pdFALSE 的话在退出函数 ulTaskNotifyTake()的时候任务通知值 减一,类似计数型信号量。当此参数为pdTRUE的话在退出函数的时候 任务任务通知值清零,类似二值信号量 |
TickType_t xTicksToWait | 阻塞时间 |
返回值 | 任务通知值减少或者清零之前的值。 |
2. xTaskNotifyWait()
此函数也是用来获取任务通知的,不过此函数比ulTaskNotifyTake()更为强大,不管任务通知用作二值信号量、计数型信号量、队列和事件标志组中的哪一种,都可以使用此函数来获取任务通知
uint32_t ulBitsToClearOnEntry | 当没有接收到任务通知的时候将任务通知值与此参数的取反值进行按位与运算,当此参数为0xffffffff 或者ULONG_MAX的时候就会将任务通知值清零。 |
uint32_t ulBitsToClearOnExit | 如果接收到了任务通知,在做完相应的处理退出函数之前将任务通知值与此参数的取反值进行按位与运算,当此参数为 0xffffffff 或者 ULONG_MAX的时候就会将任务通知值清零 |
uint32_t * pulNotificationValue | 此参数用来保存任务通知值 |
TickType_t xTicksToWait | 阻塞时间 |
返回值 | pdTRUE: 获取到了任务通知。 pdFALSE: 任务通知获取失败 |