收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
…增加邮箱部分、任务通知发送消息 Demo 2021/11/28
…增加事件标志组和对应的测试 Demo 2021/11/29
…增加互斥量 优先级继承 Demo 2021/11/29
#### 目录
* [前言](#_8)
* [一、任务通知与信号量、事件标志组](#_27)
* + [1.1 基本概念](#11__29)
+ - [1.1.1 任务通知](#111__34)
- [1.1.2 二值信号量 Binary Semaphores](#112__Binary_Semaphores_44)
- [1.1.3 计数信号量 Counting Semaphores](#113__Counting_Semaphores_134)
- [1.1.4 互斥信号量 Mutexes](#114__Mutexes_161)
- [1.1.5 事件标志组 Events](#115__Events_254)
+ [1.2 Demo测试](#12_Demo_337)
+ - [1.2.1 二值信号量](#121__338)
- [1.2.2 计数信号量](#122__355)
- [1.2.3 互斥信号量(优先级继承)](#123__372)
- [1.2.4 事件标志组](#124__407)
* [二、消息队列与邮箱](#_471)
* + [2.1 邮箱](#21__477)
+ [2.2 邮箱Demo](#22_Demo_549)
+ - [2.2.1 基本测试](#221__574)
- [2.2.1 问题测试](#221__586)
* [三、任务通知与消息队列](#_650)
* + [3.1 任务通知发送消息Demo](#31_Demo_659)
## 前言
对于操作系统初学者,估计面对于这么多功能,一下子蒙圈了,虽然照着资料视频对于单独的功能都能够正常的操作,但是实际运用到项目中的时候,往往会犯选择困难症,感觉这种也可以,那种也可以,到底哪种好哪种不好?
其实使用中并没有绝对的界限,需要看自己使用的场景,对于普通小项目而言,所谓的合适不合适并没有那么明显。我们在裸机中使用全局变量当做标志位尚且能够应付很多小项目,加入操作系统为的是保证实时性,对于任务之间的通讯的这些功能模块不需要太“刻意”的最求最优的方式,但是对于这些不同类型的功能至少也得有个基本的了解
说明:FreeRTOS 专栏与我的 RT-Thread 专栏不同,我的 RT-Thread 专栏是从理论学习一步一步循序渐进,从 0 起步的 完整教学,而 FreeRTOS 更偏向于 我直接拿来使用,需要用到什么,然后引出知识点,在使用中发现问题,解然后再解决问题。
>
> [FreeRTOS记录(一、熟悉开发环境以及CubeMX下FreeRTOS配置)](https://bbs.csdn.net/topics/618679757)
> [FreeRTOS 记录(二、FreeRTOS 任务 API 认识和源码简析)](https://bbs.csdn.net/topics/618679757)
> [FreeRTOS 记录(三、RTOS 任务调度原理解析 \_Systick、PendSV、SVC)](https://bbs.csdn.net/topics/618679757)
> [FreeRTOS记录(四、FreeRTOS任务堆栈溢出问题和临界区)](https://bbs.csdn.net/topics/618679757)
> [FreeRTOS记录(五、FreeRTOS任务通知)](https://bbs.csdn.net/topics/618679757)
> [FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)](https://bbs.csdn.net/topics/618679757)
>
>
>
推荐一篇博文:[FreeRTOS消息队列、信号量、事件标志组、任务通知](https://bbs.csdn.net/topics/618679757),下图转至此博文:
![在这里插入图片描述](https://img-blog.csdnimg.cn/73ada313f2034a42a9aa5f97e56034da.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
## 一、任务通知与信号量、事件标志组
### 1.1 基本概念
信号量是用于任务与任务之间的同步,不能传递消息。类似于我们裸机程序中使用来做标记为的全局变量。
两个任务之间或者中断函数跟任务之间的同步功能,这个与事件标志组是类似的。其实就是共享资源为 1 的时候。
多个共享资源的管理。正在使用的资源有多少,信号量就减多少。
#### 1.1.1 任务通知
任务通知的基础知识请参考博文:[FreeRTOS记录(五、FreeRTOS任务通知)](https://bbs.csdn.net/topics/618679757)
![在这里插入图片描述](https://img-blog.csdnimg.cn/9f443dc9e728492e96824935d49ac50b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在任务通知函数表格中,有如下介绍:
![在这里插入图片描述](https://img-blog.csdnimg.cn/4187ff92f63b4559ba253c9baa7f886b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
说明我们可以使用任务通知来实现二值信号量和计数信号量,后面我们会使用Demo说明问题。
#### 1.1.2 二值信号量 Binary Semaphores
仅”空“(0)和”非空“(1)两种状态的信号量,信号量资源被获取了,信号量值就是 0,信号量资源被释放,信号量值就是 1。类似一个标志位,裸机中使用bool类型的全局变量`TRUE`和`FALSE` 。
在CubeMX中可以直接设置,如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/82350c016003442b940a2cefa5f37e74.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在freertos.c中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/e58ec2e635c9490581997054e5096dec.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
创建信号量的函数为`osSemaphoreCreate` ,在程序中如果参数2 count 的值为1,即为穿件2值信号量,如果不为1,则可能是计数信号量(下文有说明),源码如下:
/******************** Semaphore Management Functions **************************/
#if (defined (osFeature_Semaphore) && (osFeature_Semaphore != 0))
/**
* @brief Create and Initialize a Semaphore object used for managing resources
* @param semaphore_def semaphore definition referenced with \ref osSemaphore.
* @param count number of available resources.
* @retval semaphore ID for reference by other functions or NULL in case of error.
* @note MUST REMAIN UNCHANGED: \b osSemaphoreCreate shall be consistent in every CMSIS-RTOS.
*/
osSemaphoreId osSemaphoreCreate (const osSemaphoreDef_t *semaphore_def, int32_t count)
{
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
osSemaphoreId sema;
if (semaphore_def->controlblock != NULL){
if (count == 1) {
return xSemaphoreCreateBinaryStatic( semaphore_def->controlblock );
}
else {
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCountingStatic( count, count, semaphore_def->controlblock );
#else
return NULL;
#endif
}
}
else {
if (count == 1) {
vSemaphoreCreateBinary(sema);
return sema;
}
else {
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCounting(count, count);
#else
return NULL;
#endif
}
}
#elif ( configSUPPORT_STATIC_ALLOCATION == 1 ) // configSUPPORT_DYNAMIC_ALLOCATION == 0
if(count == 1) {
return xSemaphoreCreateBinaryStatic( semaphore_def->controlblock );
}
else
{
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCountingStatic( count, count, semaphore_def->controlblock );
#else
return NULL;
#endif
}
#else // configSUPPORT_STATIC_ALLOCATION == 0 && configSUPPORT_DYNAMIC_ALLOCATION == 1
osSemaphoreId sema;
if (count == 1) {
vSemaphoreCreateBinary(sema);
return sema;
}
else {
#if (configUSE_COUNTING_SEMAPHORES == 1 )
return xSemaphoreCreateCounting(count, count);
#else
return NULL;
#endif
}
#endif
}
#### 1.1.3 计数信号量 Counting Semaphores
用来事件计数和资源管理,
`myCountingSem01Handle = osSemaphoreCreate(osSemaphore(myCountingSem01), 3);`
上例中3是信号量的值,信号量值代表当前资源的可用数量,
`osSemaphoreRelease`(信号量+1)`osSemaphoreWait`(信号量-1)。
优先级翻转:在很多场合中,某些资源只有一个,当低优先级任务正在占用该资源的时候,即便高优先级任务也只能乖乖的等待低优先级任务使用完该资源后释放资源。这里高优先级任务无法运行而低优先级任务可以运行的现象称为“优先级翻转”。
ex:低优先级任务获取信号量后,被中优先级打断,中优先级任务执行时间较长,因为低优先级任务还未释放信号量,高优先级任务就无法获取信号量继续运行
在CubeMX中如果需要使用,需要先使能:
![在这里插入图片描述](https://img-blog.csdnimg.cn/1e46a077feb0475dbde0cf3bc62827f7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
然后才能创建:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ae4ac6592e2e4153b9fdfb5828e54c6e.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在freertos.c中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/f7e32532e6bc40269d63f784f37fcc99.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
创建函数和二值信号量一样为`osSemaphoreCreate` 。
#### 1.1.4 互斥信号量 Mutexes
拥有优先级继承的二值信号量,互斥量具有优先级继承机制,而信号量没有。
优先级继承:某个临界资源受到一个互斥量保护,如果这个资源正在被一个低优先级任务使用,那么此时的互斥量是闭锁状态,也代表了没有任务能申请到这个互斥量,如果此时一个高优先级任务想要对这个资源进行访问,去申请这个互斥量,那么高优先级任务会因为申请不到互斥量而进入阻塞态,那么系统会将现在持有该互斥量的任务的优先级临时提升到与高优先级任务的优先级相同,这个优先级提升的过程叫做优先级继承。
互斥量不能用于中断中:
* 互斥信号量有优先级继承的机制,所以只能用在任务中,不能用于中断服务函数。
* 中断服务函数中不能因为要等待互斥信号量而设置阻塞时间进入阻塞态。
在CubeMX中互斥量的设置如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/084607cd02dc42e4bb3cfbc7fba806ab.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在freertos.c中:
void MX_FREERTOS_Init(void) {
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Create the mutex(es) */
/* definition and creation of myMutex01 */
osMutexDef(myMutex01);
myMutex01Handle = osMutexCreate(osMutex(myMutex01));
互斥量的相关API函数:
/**************************** Mutex Management ********************************/
/**
创建互斥量
* @brief Create and Initialize a Mutex object
* @param mutex_def mutex definition referenced with \ref osMutex.
* @retval mutex ID for reference by other functions or NULL in case of error.
* @note MUST REMAIN UNCHANGED: \b osMutexCreate shall be consistent in every CMSIS-RTOS.
*/
osMutexId osMutexCreate (const osMutexDef_t *mutex_def)
{
#if ( configUSE_MUTEXES == 1)
#if( configSUPPORT_STATIC_ALLOCATION == 1 ) && ( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
if (mutex_def->controlblock != NULL) {
return xSemaphoreCreateMutexStatic( mutex_def->controlblock );
}
else {
return xSemaphoreCreateMutex();
}
#elif ( configSUPPORT_STATIC_ALLOCATION == 1 )
return xSemaphoreCreateMutexStatic( mutex_def->controlblock );
#else
return xSemaphoreCreateMutex();
#endif
#else
return NULL;
#endif
}
/*等待互斥量*/
osStatus osMutexWait (osMutexId mutex_id, uint32_t millisec)
/*释放互斥量*/
osStatus osMutexRelease (osMutexId mutex_id)
/*删除互斥量*/
osStatus osMutexDelete (osMutexId mutex_id)
关于递归互斥量, 递归互斥信号量解决死锁问题 :
MutexLock mutex;
void add()
{
mutex.lock();
// do something
mutex.unlock();
}
void count_all()
{
mutex.lock();
// do something
add();
mutex.unlock();
}
#### 1.1.5 事件标志组 Events
事件是一种实现任务间通信的机制,主要用于实现多任务间的同步,但事件通信只能是事件类型的通信,无数据传输。与信号量不同的是,它可以实现一对多,多对多的同步。 即一个任务可以等待多个事件的发生:可以是任意一个事件发生时唤醒任务进行事件处理;也可以是几个事件都发生后才唤醒任务进行事件处理。同样,也可以是多个任务同步多个事件。
在程序中使用`configUSE_16_BIT_TICKS`这个宏定义来确定用户可以使用的事件标志位是8个还是24个:
![在这里插入图片描述](https://img-blog.csdnimg.cn/eab793c16d0843bd8c359abe63f353d8.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_15,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/1f3e51d0b1294c8098d896fffd7ee515.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
事件标志组 就是 裸机中的标志位,裸机中需要对每一个需要判断的事件自行定义一个标志位,然后需要用户自行管理,而事件标志组,把这个功能集中起来方便管理,用户可以使用24个(configUSE\_16\_BIT\_TICKS == 1)事件管理位。
在CubeMX中,好像对事件标志组的支持不太完善:
![在这里插入图片描述](https://img-blog.csdnimg.cn/bd37b70f0374420bbb3f5e365ec4aa33.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
我两台电脑上的CubeMX版本有一个有 Events选项,另一个没有。这里不用在意,我们在生成的工程代码中,可以看到有对应的事件组标志的文件:
![在这里插入图片描述](https://img-blog.csdnimg.cn/0179cc66c11b4e15a58d3f63223ee482.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_10,color_FFFFFF,t_70,g_se,x_16)
在文件中,我们能够了解到和事件组有关的API函数:
注意中断中设置事件组`xEventGroupSetBitsFromISR`的注释,后面会有应用Demo
/*事件标志组句柄*/
typedef void * EventGroupHandle_t;
/*创建事件标志组,动态*/
#if( configSUPPORT_DYNAMIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreate( void ) PRIVILEGED_FUNCTION;
#endif
/*创建事件标志组,静态*/
#if( configSUPPORT_STATIC_ALLOCATION == 1 )
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t *pxEventGroupBuffer ) PRIVILEGED_FUNCTION;
#endif
/*等待事件标志位,可以在阻塞状态下等待一个或者多个事件位*/
EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup, //事件组名称
const EventBits_t uxBitsToWaitFor, //等待的位
const BaseType_t xClearOnExit, //读取之后是否清除,pdFALSE不清除,pdTRUE 清除
const BaseType_t xWaitForAllBits, //等待的位是与还是或,pdTRUE,则要等待全部的位都置1
TickType_t xTicksToWait ) //阻塞时间
/*清除标志位*/
EventBits_t xEventGroupClearBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToClear )
/*中断中清除标志位*/
BaseType_t xEventGroupClearBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet )
/*设置标志位*/
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet )
/*
中断中设置标志位
标记退出此函数以后是否进行任务切换,这个变量的值函数会自 动设置的,用户不用进行设置,
用户只需要提供一个变量来保存 这个值就行了。
当此值为 pdTRUE 的时候在退出中断服务函数之 前一定要进行一次任务切换。
返回值:
pdPASS : 事件位置 1 成功。
pdFALSE: 事件位置 1 失败。
*/
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
BaseType_t *pxHigherPriorityTaskWoken )
/*
多个事件的同步,在文件中官方给出了示例,需要了解请参考
任务A接收事件,将事件所需的一些处理委托给任务B、任务C、任务D三个任务,如果任务A在其他三个任务没有完成当前事件的处理时无法接收下一个事件,此时四个任务就需要彼此同步。每个任务执行到同步点后将在此等待其他任务完成处理并到达相应的同步点后才能继续执行,如此处的任务A只能在其他任务都达到同步点后才能接收另一个事件
必须为每个参与同步的任务分配唯一的事件位。
每个任务在到达同步点时设置自己的事件位。
设置自己的事件位后,事件组上的每个任务都会阻塞,以等待代表其他同步任务的事件位被设置。
*/
EventBits_t xEventGroupSync( EventGroupHandle_t xEventGroup,
const EventBits_t uxBitsToSet,
const EventBits_t uxBitsToWaitFor,
TickType_t xTicksToWait )
#define xEventGroupGetBits( xEventGroup ) xEventGroupClearBits( xEventGroup, 0 )
EventBits_t xEventGroupGetBitsFromISR( EventGroupHandle_t xEventGroup )
void vEventGroupDelete( EventGroupHandle_t xEventGroup )
### 1.2 Demo测试
#### 1.2.1 二值信号量
从二值信号量开始测试,逻辑: 在按键按下的时候释放信号量,在`THread`任务中等待信号量(阻塞方式),等到了就读取一次温湿度打印出来:
按键函数中,使用`osSemaphoreRelease`释放信号量:
![在这里插入图片描述](https://img-blog.csdnimg.cn/f85d3ad778174e7596c9494dc720d6ee.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在温湿度读取任务中,使用`osSemaphoreWait`一直等待信号量,收到信号量就执行一次温湿度读取,这里我完全是和任务通知使用的同样的方式:
![在这里插入图片描述](https://img-blog.csdnimg.cn/a79338c0b1184b6f83887da3498de3ce.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
测试结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/e0f4ad5404de43d3b5aebe2d34760c76.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_16,color_FFFFFF,t_70,g_se,x_16)
可以看到,在使用一个任务叫醒另外一个任务的时候,任务通知和二值信号量是一样的,我们在前面说过任务通知更加高效,占用RAM空间更小,所以这种情况,任务通知是有优势的。
#### 1.2.2 计数信号量
计数信号量的测试和以上一样,通过按键释放计数信号量,但是在测试过程中,其实发现计数信号量的使用不应该是这样,计数信号量典型的应用场合就是停车位模型,总共有多少个车位,就是多少个信号量,入口进入一辆车信号量-1,出口离开一辆车信号量+1,这里我依然把这么使用的程序和结果放出来,做一个参考。
测试时候发现(有待分析):信号量在创建的时候就已经存在了,如果某一个任务等待信号量使用阻塞方式`osWaitForever`,那么在程序运行开始的时候,就会直接接受到该信号量,如果是计数信号量,那么创建的时候有多少个数值,那么等待信号量的任务就会运行多少次
在KeyTask中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/9446edc228784fc8bf83de09ae748e71.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
读取温湿度任务:
![在这里插入图片描述](https://img-blog.csdnimg.cn/96e615ab999141448590fe1986e0ff11.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
测试结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/2fa1af609a3b49cbb09076ec1720d052.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_15,color_FFFFFF,t_70,g_se,x_16)
#### 1.2.3 互斥信号量(优先级继承)
互斥信号量我们做一个 优先级继承的测试。
新建两个任务作为测试任务:
![在这里插入图片描述](https://img-blog.csdnimg.cn/68d074f091724a16b90235ae7eb4d49f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)
在KeyTask中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/9d5746b0422e4ab29fd9a86cf310dbd7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在两个测试任务中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/7d857e573dc74d3b86123fa8d30bf7fa.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
测试结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/806308e26931435f97cf5a09b871cf24.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_15,color_FFFFFF,t_70,g_se,x_16)
接下来,我们加入互斥信号量,在按键中需要获得互斥量,然后释放,然后再使用优先级低的测试任务获得互斥量,然后释放。理论上来说,因为有高优先级的任务(按键任务)一直需要等待互斥量,所以在进行测试任务1 和测试任务2调度的时候,会先运行 获得互斥量的原本优先级低的测试任务,具体过程如下。
在KeyTask中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/9e1368f942174d74867877ad48a150fc.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在低优先级的测试任务中:
![在这里插入图片描述](https://img-blog.csdnimg.cn/acb279bccc744a5fb67531505480448f.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
测试结果如下:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ef543c1cea0b4aa584e47f6494dd17f9.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_15,color_FFFFFF,t_70,g_se,x_16)
从上图结果和前面结果可以看出,testtask01的优先级继承了 高优先级的按键任务的优先级。
#### 1.2.4 事件标志组
我们测试使用几个事件标志组来触发温湿度读取的任务:
(记录使用的两台电脑用的平台不一样,一个是STM32F103,一个是STM32L051,所以细心的朋友能够看出有些区别,但是整体上我还是尽量做到一致)
1、定时器定时10S以后发送一个标志位;
2、按钮按下发送一个标志位;
3、收到一个无线报文发送一个标志位;
先定义一下事件组的位:
![在这里插入图片描述](https://img-blog.csdnimg.cn/4619284dcac249d7a86896bcac59ae90.png)
创建事件标志组:
![在这里插入图片描述](https://img-blog.csdnimg.cn/ec70470c42594431a447309bba871377.png)
在KeyTask中 (任务中设置标志位):
![在这里插入图片描述](https://img-blog.csdnimg.cn/4c3c6cc3f0bf491aa4c071abbc36485c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在无线报文接收任务中(任务中设置标志位):
![在这里插入图片描述](https://img-blog.csdnimg.cn/50203266b2654517ba045cdc9074ba54.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在定时器中断函数中 (中断中设置标志位):
![在这里插入图片描述](https://img-blog.csdnimg.cn/929ac79de6c84ef2a07750a0efd7c8e3.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
但是注意,需要使用`xEventGroupSetBitsFromISR`函数需要如下的宏定义:
![在这里插入图片描述](https://img-blog.csdnimg.cn/90f67888cb644ab8a19bd724a9a8bf9c.png)
上面的3个宏定义可以自己在config里面添加,也可以在CubeMX里面设置,下面给出在CubeMX中对应的设置:
![在这里插入图片描述](https://img-blog.csdnimg.cn/9fb4ee8773a34275857aef68a784699f.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/2372ec3973d94d7391f1519149b24d1d.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/0ce9f2fcf1014f54ae7cfff20f4e96d6.png)
读取温湿度任务:
![在这里插入图片描述](https://img-blog.csdnimg.cn/730fe05b952947448290474bff2d5fb6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
测试结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/890fcb51c6324a79bf2a0af57f115d01.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_14,color_FFFFFF,t_70,g_se,x_16)
我们再修改一下接收事件任务中`xEventGroupWaitBits`的参数,使得需要等到所有的事件标志位置位才能执行:
![在这里插入图片描述](https://img-blog.csdnimg.cn/8a13b1acd7f347cea0a3738d018f58dc.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_20,color_FFFFFF,t_70,g_se,x_16)
在温湿度读取任务最后加上一个判断语句:
![在这里插入图片描述](https://img-blog.csdnimg.cn/55136654de944011aa20e871b60fdb16.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_17,color_FFFFFF,t_70,g_se,x_16)
测试结果:
![在这里插入图片描述](https://img-blog.csdnimg.cn/1e899585a6734f92ac11b66eef817ec6.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA55-c6L6w5omA6Ie0,size_16,color_FFFFFF,t_70,g_se,x_16)
## 二、消息队列与邮箱
任务与任务之间的消息传递,可以使用消息队列,邮箱。任务通知也是可以的(下一节)。
消息队列的使用在我上一篇博文中有过使用示例:[FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)](https://bbs.csdn.net/topics/618679757)
### 2.1 邮箱
邮箱可以指定发送对象,也可以全部task访问,队列则是全部task都可以访问。
邮箱只能发送一条消息,所以可以认为邮箱就是长度为1的消息队列。消息邮箱一次只能发一条会覆盖原来消息。
邮箱队列控制块结构体如下:
typedef struct os_mailQ_cb {
const osMailQDef_t *queue_def;
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新
需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)
在我上一篇博文中有过使用示例:FreeRTOS记录(六、FreeRTOS消息队列—Enocean模块串口通讯、RAM空间不足问题分析)
2.1 邮箱
邮箱可以指定发送对象,也可以全部task访问,队列则是全部task都可以访问。
邮箱只能发送一条消息,所以可以认为邮箱就是长度为1的消息队列。消息邮箱一次只能发一条会覆盖原来消息。
邮箱队列控制块结构体如下:
typedef struct os\_mailQ\_cb {
const osMailQDef\_t \*queue_def;
[外链图片转存中...(img-vQWfYvp5-1715786410516)]
[外链图片转存中...(img-W2o2L4VQ-1715786410516)]
**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!**
**由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新**
**需要这些体系化资料的朋友,可以加我V获取:vip1024c (备注嵌入式)**
**[如果你需要这些资料,可以戳这里获取](https://bbs.csdn.net/topics/618679757)**