FreeRTOS

核心机制

队列

因为FreeRTOS系统中,用户可以使用多个任务来完成不同的功能,但是有时需要共享数据,例如任务A读取数据,任务B读取数据,此时就用到队列,任务A将数据读入队列,任务B将数据从队列中读出来。
这样有一个好处,就是如果当AB其中一个任务卡一小会,任务会暂时存储在队列里,等任务继续运行的时候能够正常工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
QueueHandle_t xSensorQueue = NULL; //创建全局队列句柄
// 任务A
void A_task(void *pvParameters){
int value = 10;
// 底层会进行值拷贝,最后一个参数0代表如果队列满了直接丢弃,不用等待,并返回错误
// 如果要进行比较大的值传递的画要用malloc来传递指针地址,任务B用完后free
if(xQueueSend(xSensorQueue, &value, 0) == pdPASS){
printf("[I2C Task] 数据入队成功!\n");
} else {
printf("[I2C Task] 警告:队列已满,数据丢失!\n");
}
}
// 任务B
void B_task(void *pvParameters){
int value;
// portMAX_DELAY 表示:如果队列里没有数据,这个任务就死死地挂起等待,绝不空转消耗 CPU!
if(xQueueReceive(xSensorQueue, &value, portMAX_DELAY) == pdTRUE){
printf("[SPI Task] 收到来自传感器数据: %d \n",value);
}
}

void app_main(){
xSensorQueue = xQueueCreate(5,sizeof(int)) //创建队列,里面最大可以存储5个int型数据
}

互斥量

互斥量可以想象成为一把锁,当任务A拿了锁,任务B就不可以使用,只有当任务A还了回来,任务B才可以使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//定义全局互斥量
SemaphoreHandle_t xUartMutex = NULL;

// 共用函数,用来打印内容,但是如果AB任务同时使用的话,可能会造成A还没打印完,就打印B的内容
// 因此这里用互斥量来解决,当A使用share_print的时候,因为上了锁,所以B拿不到锁,就会等待,
// 而当A执行完后解锁,B才能使用
void share_print(const char *msg){
// 上锁
xSemaphoreTake(xUartMutex, portMAX_DELAY);
// 故意拆成多次打印,制造冲突机会
for(int i=0; i<5; i++) {
printf("%s ", msg);
vTaskDelay(pdMS_TO_TICKS(50));
}
// 解锁
xSemaphoreGive(xUartMutex);
}

void app_main(){
xUartMutex = xSemaphoreCreateMutex(); //创建互斥量
}

二值信号量

二值信号量可以想象成是一个接力棒,效果是A任务执行到某个部分的时候,才让B可以执行
比如任务A用来初始化某个传感器,B用来读取传感器,所以一定要A先执行好B才能执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 创建全局二值信号量句柄
SemaphoreHandle_t xSyncSem = NULL;

// A任务
void A_task(void *pvParameters){
// 其他代码
// 发出信号量,通知B可以执行
xSemaphoreGive(xSyncSem);
// 其他代码
}

// B任务
void B_task(void *pvParameters){
// 阻塞等待信号量
xSemaphoreTake(xSyncSem, portMAX_DELAY);
// 其他代码
}
void app_main(){
xSyncSem = xSemaphoreCreateBinary(); // 创建全局二值信号量
}

事件组

功能方面:
事件组可以想想成等待 某些条件都达成 或 其中一个条件达成才会
比如当要上传读取的传感器的值,我们需要联网这个条件,和拿到传感器的值这个条件,这两个条件都具备的时候才能上传

在使用方面:
可以把事件组当做一个24 Bit的二进制开关,每一Bit代表一个独立的时间,Bit位为1代表满足,0代表不满足

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 创建事件组句柄
EventGroupHandle_t xGatwayEventGroup;
#define NET_CONNECTED_BIT (1<<0) // 0Bit代表网络连接
#define DATA_READY_BIT (1<<1) // 1Bit代表读到取传感器值

// 数据上传任务
void upload_task(*pvParameters){
EventBits_t uxBits = xEventGroupWaitBits(
xGatwayEventGroup, // 事件组句柄
NET_CONNECTED_BIT | DATA_READY_BIT, // 等待哪些事件发生
// 退出是否清理,即下一次运行upload_task还要检查这些事件是否满足,True清0,False不清0
// 这里如果使用True的话,下次再使用upload_task的时候还会等待网络 和 读取事件满足,但是网络初始化是只
// 触发一次事件置1的,如果这里退出清0了, 网络事件没办法再置1,所以这里用False
pdFALSE,
pdTRUE, // Ture是所有时间都满足,False是满足一个时间就行
portMAX_DELAY //超时时间
);

//上传代码

// 因为数据发送完了,所以要将 读取传感器数据为清0,等待下一次的读取,这样下次调用upload_task,只需要等待数据读取事件就行
xEventGroupClearBits(xGatewayEventGroup, DATA_READY_BIT);
}

// 进行网络初始化
void network_task(*pvParameters){
// 初始化代码
// 将网络事件开关置1
xEventGroupSetBits(xGatwayEventGroup, NET_CONNECTED_BIT);
}

// 周期读取传感器数据
void read_task(*pvParameters){
// 读取传感器数据代码
// 强读取传感器数据事件开关置1
xEventGroupSetBits(xGatwayEventGroup, DATA_READY_BIT);
}

void app_main(){
// 创建事件组
xGatwayEventGroup = xEventGroupCreate();
xTaskCreate(network_task, "network", 4096, NULL, 5, NULL);
xTaskCreate(read_task, "collect", 4096, NULL, 5, NULL);
xTaskCreate(upload_task, "upload", 4096, NULL, 5, NULL);
}

FreeRTOS
https://codechangeworld.pages.dev/2026/08/11/FreeRTOS/
作者
子非鱼
发布于
2026年8月11日
许可协议