返回

FreeRTOS 笔记

目录

RTOS

创建一个任务

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

void taskA(void* param) {
	while(1) {
		ESP_LOGI("main", "打印logs");
		vTaskDelay(pdMS_TO_TICKS(500));
	}
}

void app_main(void){
	xTaskCreatePinnedToCore(taskA, "taskName", 2048, NULL, 3, NULL, NULL);
}

队列

数字越大优先级越高

 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
#include <stdio.h>
#include "string.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_log.h"

QueueHandle_t Queue_handle = NULL;

typedef struct 
{
    int value;
} queue_data_t;


void TaskA(void* param) {
    queue_data_t data;
    while(1) {
        if(pdTRUE ==xQueueReceive(Queue_handle, &data, 100)) {
            ESP_LOGI("queue","res");
        } 
    }
}

void TaskB(void* param) {
    queue_data_t data;
    memset(&data, 0,sizeof(queue_data_t));
    while(1) {
        xQueueSend(Queue_handle, &data, 100);
        vTaskDelay(1000);
        data.value++;
    }
}

void app_main(void)
{
    // num , size
    Queue_handle = xQueueCreate(10,sizeof(queue_data_t));
    xTaskCreatePinnedToCore(TaskB, "B", 2048, NULL, 3, NULL, 1);
    xTaskCreatePinnedToCore(TaskA, "A", 2048, NULL, 3, NULL, 1);
}

信号量

二值

 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
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"

SemaphoreHandle_t bin_sem;

void TaskA(void* param) {
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
        ESP_LOGI("bin", "TaskA: giving semaphore");
        xSemaphoreGive(bin_sem); // 释放信号量,通知TaskB
    }
}

void TaskB(void* param) {
    while(1) {
        // 等待信号量(阻塞直到被Give)
        if(xSemaphoreTake(bin_sem, portMAX_DELAY) == pdTRUE) {
            ESP_LOGI("bin", "TaskB: took semaphore and is running!");
        }
    }
}

void app_main(void)
{
    bin_sem = xSemaphoreCreateBinary();
    xTaskCreatePinnedToCore(TaskB, "B", 2048, NULL, 4, NULL, 1);
    xTaskCreatePinnedToCore(TaskA, "A", 2048, NULL, 3, NULL, 1);
}

count

 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
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"

SemaphoreHandle_t buffer_sem;

// 模拟“使用资源”的任务
void TaskWorker(void* param) {
    int id = (int)param;
    while(1) {
        // 等待获取一个缓冲区
        if(xSemaphoreTake(buffer_sem, portMAX_DELAY) == pdTRUE) {
            ESP_LOGI("Worker", "Task %d got a buffer, processing...", id);
            vTaskDelay(pdMS_TO_TICKS(1000)); // 模拟处理1秒
            ESP_LOGI("Worker", "Task %d released buffer", id);
            xSemaphoreGive(buffer_sem); // 用完归还
        }
    }
}

void app_main(void)
{
    // 创建计数信号量:最多5个缓冲区,初始全部可用
    buffer_sem = xSemaphoreCreateCounting(5, 5);

    // 创建3个任务竞争5个缓冲区
    xTaskCreate(TaskWorker, "W1", 2048, (void*)1, 3, NULL);
    xTaskCreate(TaskWorker, "W2", 2048, (void*)2, 3, NULL);
    xTaskCreate(TaskWorker, "W3", 2048, (void*)3, 3, NULL);

    // 系统运行,3个任务共享5个缓冲区,不会冲突
}

互斥锁

优先级继承
当 H 等待 L 持有的 Mutex 时,FreeRTOS 临时将 L 的优先级提升到 H 的级别。

 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
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_log.h"

SemaphoreHandle_t mutex_sem;

void TaskA(void* param) {
    while(1) {
        xSemaphoreTake(mutex_sem, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(1000));
        xSemaphoreGive(mutex_sem);
    }
}

void TaskB(void* param) {
    while(1) {
        xSemaphoreTake(mutex_sem, portMAX_DELAY);
        vTaskDelay(pdMS_TO_TICKS(1000));
        xSemaphoreGive(mutex_sem);
    }
}

void app_main(void)
{
    mutex_sem = xSemaphoreCreateMutex();
    xTaskCreatePinnedToCore(TaskB, "B", 2048, NULL, 4, NULL, 1);
    xTaskCreatePinnedToCore(TaskA, "A", 2048, NULL, 3, NULL, 1);
}

事件组

任务可以共同读取的数组 事件组是 FreeRTOS 内核对象,天然线程安全

如果事件条件未满足,且你设置了阻塞等待(如 portMAX_DELAY),那么 xEventGroupWaitBits() 会阻塞当前任务,直到条件满足(或超时)—— 所以“不往下执行”是正确的!

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_log.h"

// 定义事件位(推荐用宏,清晰易读)
#define BIT_WIFI_READY    (1 << 0)   // bit 0
#define BIT_SENSOR_READY  (1 << 1)   // bit 1
#define BIT_USER_CONFIRM  (1 << 2)   // bit 2

EventGroupHandle_t event_group;

// 模拟网络任务:5秒后设置 Wi-Fi 就绪
void TaskWiFi(void* param) {
    vTaskDelay(pdMS_TO_TICKS(5000));
    ESP_LOGI("WiFi", "Connected to Wi-Fi!");
    xEventGroupSetBits(event_group, BIT_WIFI_READY);
    vTaskDelete(NULL);
}

// 模拟传感器任务:3秒后设置传感器就绪
void TaskSensor(void* param) {
    vTaskDelay(pdMS_TO_TICKS(3000));
    ESP_LOGI("Sensor", "Sensor initialized!");
    xEventGroupSetBits(event_group, BIT_SENSOR_READY);
    vTaskDelete(NULL);
}

// 模拟用户按键任务:8秒后模拟用户确认
void TaskUserKey(void* param) {
    vTaskDelay(pdMS_TO_TICKS(8000));
    ESP_LOGI("User", "User pressed CONFIRM!");
    xEventGroupSetBits(event_group, BIT_USER_CONFIRM);
    vTaskDelete(NULL);
}

// 主任务:等待所有事件到位
void TaskMain(void* param) {
    EventBits_t bits;

    ESP_LOGI("Main", "Waiting for all conditions...");

    // 等待所有位都被设置:使用 xWaitForAllBits = pdTRUE
    bits = xEventGroupWaitBits(
        event_group,                // 事件组句柄
        BIT_WIFI_READY | BIT_SENSOR_READY | BIT_USER_CONFIRM, // 等待这些位
        pdTRUE,                     // 退出时自动清除这些位(可选)
        pdTRUE,                     // 等待 ALL 位(不是任意一个)
        portMAX_DELAY               // 无限等待
    );

    ESP_LOGI("Main", "All conditions met! System ready.");
    // 此处可以启动主逻辑:如开始采集、上传数据等
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(2000));
        ESP_LOGI("Main", "System running...");
    }
}

void app_main(void)
{
    event_group = xEventGroupCreate();

    // 创建各个任务
    xTaskCreate(TaskWiFi, "WiFi", 2048, NULL, 3, NULL);
    xTaskCreate(TaskSensor, "Sensor", 2048, NULL, 3, NULL);
    xTaskCreate(TaskUserKey, "UserKey", 2048, NULL, 3, NULL);
    xTaskCreate(TaskMain, "Main", 2048, NULL, 4, NULL); // 主任务优先级高些
}

任务间通信

 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
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_log.h"

TaskHandle_t task_b_handle; // 用于向 TaskB 发送通知

void TaskA(void* param) {
    while(1) {
        vTaskDelay(pdMS_TO_TICKS(1000));
        // 直接通知 TaskB(相当于 Give 信号量)
        xTaskNotifyGive(task_b_handle); // 等价于对 TaskB 的通知值 +1
        ESP_LOGI("TaskA", "Sent notification to TaskB");
    }
}

void TaskB(void* param) {
    while(1) {
        // 等待通知(相当于 Take 信号量),阻塞直到收到
        ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // pdTRUE = 清除通知计数
        ESP_LOGI("TaskB", "Received notification!");
    }
}

void app_main(void)
{
    // 先创建 TaskB,拿到它的句柄
    xTaskCreate(TaskB, "TaskB", 2048, NULL, 3, &task_b_handle);
    xTaskCreate(TaskA, "TaskA", 2048, NULL, 2, NULL);
}

由高地址到低地址

  • 局部变量
  1. volatile 关键字 局部变量(在函数内部定义)→ 存在栈中
    全局变量或 static 变量 → 存在全局数据区
    其他没有使用该关键字的局部变量 -> 存放在寄存器中
  • 函数调用
  1. 为什么每个rtos任务需要拥有自己的栈空间 切换时,保存各自的局部变量、函数调用状态,实现安全的任务切换

由高地址到低地址

源码结构

变量前缀

1
2
3
4
5
6
7
8
9
c       char
S       int16_t, short
l       int32_t, long
x       BaseType_t, 其他非标准类型
u       unsigned
p       指针
uc      uint8_t, unsigned char
pc      char 指针
p...    对应的指针

内存管理算法

文件 优点 缺点
heap_1.c 分配简单,时间确定 只分配、不回收
heap_2.c 动态分配、最佳匹配 碎片、时间不定
heap_3.c 调用标准库函数 速度慢、时间不定
heap_4.c 相邻空闲内存可合并 可解决碎片问题、时间不定
heap_5.c 在 heap_4 基础上支持分隔的内存块 可解决碎片问题、时间不定

Heap_1

它只实现了pvPortMalloc,没有实现vPortFree

Heap_2

最佳匹配算法 新设计中不再推荐使用Heap_2。建议使用Heap_4来替代Heap_2,更加高效。
与Heap_4相比,Heap_2不会合并相邻的空闲内存,所以Heap_2会导致严重的"碎片化"问题。
如果申请、分配内存时大小总是相同的,这类场景下Heap_2没有碎片化的问题。
虽然不再推荐使用heap_2,但是它的效率还是远高于malloc、free。

Heap_3

使用标准C库里的malloc、free函数,所以堆大小由链接器的配置决定,配置项configTOTAL_HEAP_SIZE不再起作用。
C库里的malloc、free函数并非线程安全的,Heap_3中先暂停FreeRTOS的调度器,再去调用这些函数,使用这种方法实现了线程安全

Heap_4

使用 首次适应算法(first fit)来分配内存 。它还会把相邻的空闲内存合并为一个更大的空闲内存,这有助于较少内存的碎片问题。
执行的时间是不确定的,但是它的效率高于标准库的malloc、free

Heap_5

Heap_5分配内存、释放内存的算法跟Heap_4是一样的。
相比于Heap_4,Heap_5并不局限于管理一个大数组:它可以管理多块、分隔开的内存。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 原型
typedef struct HeapRegion
{
    uint8_t * pucStartAddress; // 起始地址
    size_t xSizeInBytes;       // 大小
} HeapRegion_t;

// 定义堆地址
HeapRegion_t xHeapRegions[] =
{
  { ( uint8_t * ) 0x80000000UL, 0x10000 }, // 起始地址0x80000000,大小0x10000
  { ( uint8_t * ) 0x90000000UL, 0xa0000 }, // 起始地址0x90000000,大小0xa0000
  { NULL, 0 } // 表示数组结束
 };

// 注册堆地址
 void vPortDefineHeapRegions( const HeapRegion_t * const pxHeapRegions );

Heap相关的函数

pvPortMalloc/vPortFree

如果分配内存不成功,则返回值为NULL。

xPortGetFreeHeapSize

当前还有多少空闲内存,这函数可以用来优化内存的使用情况。比如当所有内核对象都分配好后,执行此函数返回2000,那么configTOTAL_HEAP_SIZE就可减小2000。
注意:在heap_3中无法使用。

xPortGetMinimumEverFreeHeapSize

返回:程序运行过程中,空闲内存容量的最小值。
注意:只有heap_4、heap_5支持此函数。

malloc失败的钩子函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
void * pvPortMalloc( size_t xWantedSize )vPortDefineHeapRegions
{
    ......
    #if ( configUSE_MALLOC_FAILED_HOOK == 1 )
        {
            if( pvReturn == NULL )
            {
                extern void vApplicationMallocFailedHook( void );
                vApplicationMallocFailedHook();
            }
        }
    #endif
    
    return pvReturn;        
}

如果想使用这个钩子函数:
在FreeRTOSConfig.h中,把configUSE_MALLOC_FAILED_HOOK定义为1
提供vApplicationMallocFailedHook函数
pvPortMalloc失败时,才会调用此函数

任务管理

创建任务

1
2
3
4
5
6
BaseType_t xTaskCreate( TaskFunction_t pxTaskCode, // 函数指针, 任务函数
                        const char * const pcName, // 任务的名字
                        const configSTACK_DEPTH_TYPE usStackDepth, // 栈大小,单位为word,10表示40字节
                        void * const pvParameters, // 调用任务函数时传入的参数
                        UBaseType_t uxPriority,    // 优先级
                        TaskHandle_t * const pxCreatedTask ); // 任务句柄, 以后使用它来操作这个任务
参数 描述
pvTaskCode 函数指针,任务对应的 C 函数。任务应该永远不退出,或者在退出时调用 "vTaskDelete(NULL)"
pcName 任务的名称,仅用于调试目的,FreeRTOS 内部不使用。pcName 的长度为 configMAX_TASK_NAME_LEN
usStackDepth 每个任务都有自己的栈,usStackDepth 指定了栈的大小,单位为 word。例如,如果传入 100,表示栈的大小为 100 word,即 400 字节。最大值为 uint16_t 的最大值。确定栈的大小并不容易,通常是根据估计来设定。精确的办法是查看反汇编代码。
pvParameters 调用 pvTaskCode 函数指针时使用的参数:pvTaskCode(pvParameters)
uxPriority 任务的优先级范围为 0~(configMAX_PRIORITIES - 1)。数值越小,优先级越低。如果传入的值过大,xTaskCreate 会将其调整为 (configMAX_PRIORITIES - 1)
pxCreatedTask 用于保存 xTaskCreate 的输出结果,即任务的句柄(task handle)。如果以后需要对该任务进行操作,如修改优先级,则需要使用此句柄。如果不需要使用该句柄,可以传入 NULL。
返回值 成功时返回 pdPASS,失败时返回 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY(失败原因是内存不足)。请注意,文档中提到的失败返回值是 pdFAIL 是不正确的。pdFAIL 的值为 0,而 errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY 的值为 -1。

删除任务

1
vTaskDelete(xSoundTaskHandle);

任务优先级和Tick

数值越大优先级越高

通用方法

使用C函数实现,对所有的架构都是同样的代码。对configMAX_PRIORITIES的取值没有限制。但是configMAX_PRIORITIES的取值还是尽量小,因为取值越大越浪费内存,也浪费时间。
configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为0、或者未定义时,使用此方法。

架构相关的优化的方法

架构相关的汇编指令,可以从一个32位的数里快速地找出为1的最高位。使用这些指令,可以快速找出优先级最高的、可以运行的任务。使用这种方法时,configMAX_PRIORITIES的取值不能超过32。 configUSE_PORT_OPTIMISED_TASK_SELECTION被定义为1时,使用此方法。

Tick

时间片的长度由configTICK_RATE_HZ 决定,假设configTICK_RATE_HZ为100,那么时间片长度就是10ms

1
2
3
4
vTaskDelay(2);  // 等待2个Tick,假设configTICK_RATE_HZ=100, Tick周期时10ms, 等待20ms

// 还可以使用pdMS_TO_TICKS宏把ms转换为tick
vTaskDelay(pdMS_TO_TICKS(100));  // 等待100ms

注意,基于Tick实现的延时并不精确,比如vTaskDelay(2)的本意是延迟2个Tick周期,有可能经过1个Tick多一点就返回了。 使用vTaskDelay函数时,建议以ms为单位,使用pdMS_TO_TICKS把时间转换为Tick。

修改优先级

1
2
3
4
5
6
// 获得任务的优先级
UBaseType_t uxTaskPriorityGet( const TaskHandle_t xTask );

// 设置任务的优先级
void vTaskPrioritySet( TaskHandle_t xTask,
                       UBaseType_t uxNewPriority );

任务状态

1
2
3
4
5
6
7
8
// 进入暂停状态
// 参数xTaskToSuspend表示要暂停的任务,如果为NULL,表示暂停自己。

/**要退出暂停状态,只能由别人来操作:
别的任务调用:vTaskResume
中断程序调用:xTaskResumeFromISR
**/
void vTaskSuspend( TaskHandle_t xTaskToSuspend );

Delay函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// vTaskDelay:至少等待指定个数的Tick Interrupt才能变为就绪状态
// vTaskDelayUntil:等待到指定的绝对时刻,才能变为就绪态。
void vTaskDelay( const TickType_t xTicksToDelay ); /* xTicksToDelay: 等待多少给Tick */

/* pxPreviousWakeTime: 上一次被唤醒的时间
 * xTimeIncrement: 要阻塞到(pxPreviousWakeTime + xTimeIncrement)
 * 单位都是Tick Count
 */
BaseType_t xTaskDelayUntil( TickType_t * const pxPreviousWakeTime,
                            const TickType_t xTimeIncrement );

// 获取当前时间
TickType_t xLastWakeTime = xTaskGetTickCount();

空闲任务及其钩子函数

简单来说就是没有用户的任务需要执行的时候需要有一个空闲任务
钩子函数的作用有这些:

  • 执行一些低优先级的、后台的、需要连续执行的函数
  • 测量系统的空闲时间:空闲任务能被执行就意味着所有的高优先级任务都停止了,所以测量空闲任务占据的时间,就可以算出处理器占用率。
  • 让系统进入省电模式:空闲任务能被执行就意味着没有重要的事情要做,当然可以进入省电模式了。 空闲任务的钩子函数的限制:
  • 不能导致空闲任务进入阻塞状态、暂停状态
  • 如果你会使用vTaskDelete()来删除任务,那么钩子函数要非常高效地执行。如果空闲任务移植卡在钩子函数里的话,它就无法释放内存。
使用钩子函数的前提
  • 把这个宏定义为1:configUSE_IDLE_HOOK
  • 实现vApplicationIdleHook函数

调度算法

FreeRTOSConfig.h的两个配置项来配置调度算法:configUSE_PREEMPTION、configUSE_TIME_SLICING。

configUSE_PREEMPTION 高优先级的任务能否优先执行

  • 可以:被称作"可抢占调度"(Pre-emptive),高优先级的就绪任务马上执行,下面再细化。
  • 不可以:不能抢就只能协商了,被称作"合作调度模式"(Co-operative Scheduling)
  1. 当前任务执行时,更高优先级的任务就绪了也不能马上运行,只能等待当前任务主动让出CPU资源。
  2. 其他同优先级的任务也只能等待:更高优先级的任务都不能抢占,平级的更应该老实点

configUSE_TIME_SLICING 可抢占的前提下,同优先级的任务是否轮流执行

  • 轮流执行:被称为"时间片轮转"(Time Slicing),同优先级的任务轮流执行,你执行一个时间片、我再执行一个时间片
  • 不轮流执行:英文为"without Time Slicing",当前任务会一直执行,直到主动放弃、或者被高优先级任务抢占

configIDLE_SHOULD_YIELD 空闲任务是否让步于用户任务

  • 空闲任务低人一等,每执行一次循环,就看看是否主动让位给用户任务
  • 空闲任务跟用户任务一样,大家轮流执行,没有谁更特殊
配置项 A B C D E
configUSE_PREEMPTION 1 1 1 1 0
configUSE_TIME_SLICING 1 1 0 0 x
configIDLE_SHOULD_YIELD 1 0 1 0 x
说明 常用 很少用 很少用 很少用 几乎不用

同步互斥与通信

队列

CRUD

 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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 创建
QueueHandle_t xQueueCreateStatic(*
                    UBaseType_t uxQueueLength,*
                    UBaseType_t uxItemSize,*
                    uint8_t *pucQueueStorageBuffer,*
                    StaticQueue_t *pxQueueBuffer*
                 );

// 列恢复为初始状态
BaseType_t xQueueReset( QueueHandle_t pxQueue);

// 删除
void vQueueDelete( QueueHandle_t xQueue );


/* 等同于xQueueSendToBack
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSend(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往队列尾部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToBack(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );


/* 
 * 往队列尾部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToBackFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );

/* 
 * 往队列头部写入数据,如果没有空间,阻塞时间为xTicksToWait
 */
BaseType_t xQueueSendToFront(
                                QueueHandle_t    xQueue,
                                const void       *pvItemToQueue,
                                TickType_t       xTicksToWait
                            );

/* 
 * 往队列头部写入数据,此函数可以在中断函数中使用,不可阻塞
 */
BaseType_t xQueueSendToFrontFromISR(
                                      QueueHandle_t xQueue,
                                      const void *pvItemToQueue,
                                      BaseType_t *pxHigherPriorityTaskWoken
                                   );


// 读队列
BaseType_t xQueueReceive( QueueHandle_t xQueue,
                          void * const pvBuffer,
                          TickType_t xTicksToWait );

BaseType_t xQueueReceiveFromISR(
                                    QueueHandle_t    xQueue,
                                    void             *pvBuffer,
                                    BaseType_t       *pxTaskWoken
                                );

// 查询
/*
 * 返回队列中可用数据的个数
 */
UBaseType_t uxQueueMessagesWaiting( const QueueHandle_t xQueue );

/*
 * 返回队列中可用空间的个数
 */
UBaseType_t uxQueueSpacesAvailable( const QueueHandle_t xQueue );

队列集

如何及时读取到多个队列的数据?要使用队列集

CRUD

1
2
3
4
5
6
7
8
// 队列集长度,最多能存放多少个数据(队列句柄)  
QueueSetHandle_t xQueueCreateSet( const UBaseType_t uxEventQueueLength )

// 把队列加入队列集
BaseType_t xQueueAddToSet( QueueSetMemberHandle_t xQueueOrSemaphore,QueueSetHandle_t xQueueSet );

// 读取队列集
QueueSetMemberHandle_t xQueueSelectFromSet( QueueSetHandle_t xQueueSet,TickType_t const xTicksToWait );

信号量

 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
46
47
48
/* 创建一个二进制信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinary( void );

/* 创建一个二进制信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateBinaryStatic( StaticSemaphore_t *pxSemaphoreBuffer );

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数内部会分配信号量结构体 
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCounting(UBaseType_t uxMaxCount, UBaseType_t uxInitialCount);

/* 创建一个计数型信号量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * uxMaxCount: 最大计数值
 * uxInitialCount: 初始计数值
 * pxSemaphoreBuffer: StaticSemaphore_t结构体指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateCountingStatic( UBaseType_t uxMaxCount, UBaseType_t uxInitialCount, StaticSemaphore_t *pxSemaphoreBuffer );


/*
 * xSemaphore: 信号量句柄,你要删除哪个信号量
 */
void vSemaphoreDelete( SemaphoreHandle_t xSemaphore );

BaseType_t xSemaphoreGive( SemaphoreHandle_t xSemaphore );

BaseType_t xSemaphoreGiveFromISR(SemaphoreHandle_t xSemaphore,BaseType_t *pxHigherPriorityTaskWoken);

BaseType_t xSemaphoreTake(
                   SemaphoreHandle_t xSemaphore,
                   TickType_t xTicksToWait
               );

BaseType_t xSemaphoreTakeFromISR(
                        SemaphoreHandle_t xSemaphore,
                        BaseType_t *pxHigherPriorityTaskWoken
                    );

互斥量(Mutex)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 需要定义
#define configUSE_MUTEXES 1

/* 创建一个互斥量,返回它的句柄。
 * 此函数内部会分配互斥量结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutex( void );

/* 创建一个互斥量,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticSemaphore_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
SemaphoreHandle_t xSemaphoreCreateMutexStatic( StaticSemaphore_t *pxMutexBuffer );

递归锁

  • 任务A获得递归锁M后,它还可以多次去获得这个锁
  • “take"了N次,要"give"N次,这个锁才会被释放
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/* 创建一个递归锁,返回它的句柄。*

 * 此函数内部会分配互斥量结构体* 

 * 返回值: 返回句柄,非NULL表示成功*

 */

SemaphoreHandle_t xSemaphoreCreateRecursiveMutex( void );

// 释放

BaseType_t xSemaphoreGiveRecursive( SemaphoreHandle_t xSemaphore );

// 获得

BaseType_t xSemaphoreTakeRecursive(

         SemaphoreHandle_t xSemaphore,

         TickType_t xTicksToWait

        );

事件组(event group)

事件组用一个整数来表示,其中的高8位留给内核使用,只能用其他的位来表示事件。那么这个整数是多少位的?

  • 如果configUSE_16_BIT_TICKS是1,那么这个整数就是16位的,低8位用来表示事件
  • 如果configUSE_16_BIT_TICKS是0,那么这个整数就是32位的,低24位用来表示事件

configUSE_16_BIT_TICKS是用来表示Tick Count的,怎么会影响事件组?这只是基于效率来考虑

  • 如果configUSE_16_BIT_TICKS是1,就表示该处理器使用16位更高效,所以事件组也使用16位
  • 如果configUSE_16_BIT_TICKS是0,就表示该处理器使用32位更高效,所以事件组也使用32位
 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
/* 创建一个事件组,返回它的句柄。
 * 此函数内部会分配事件组结构体 
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreate( void );

/* 创建一个事件组,返回它的句柄。
 * 此函数无需动态分配内存,所以需要先有一个StaticEventGroup_t结构体,并传入它的指针
 * 返回值: 返回句柄,非NULL表示成功
 */
EventGroupHandle_t xEventGroupCreateStatic( StaticEventGroup_t * pxEventGroupBuffer );

/*
 * xEventGroup: 事件组句柄,你要删除哪个事件组
 */
void vEventGroupDelete( EventGroupHandle_t xEventGroup )


/* 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位? 
 *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 *              可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * 返回值: 返回原来的事件值(没什么意义, 因为很可能已经被其他任务修改了)
 */
EventBits_t xEventGroupSetBits( EventGroupHandle_t xEventGroup,
                                    const EventBits_t uxBitsToSet );

/* 设置事件组中的位
 * xEventGroup: 哪个事件组
 * uxBitsToSet: 设置哪些位? 
 *              如果uxBitsToSet的bitX, bitY为1, 那么事件组中的bitX, bitY被设置为1
 *              可以用来设置多个位,比如 0x15 就表示设置bit4, bit2, bit0
 * pxHigherPriorityTaskWoken: 有没有导致更高优先级的任务进入就绪态? pdTRUE-有, pdFALSE-没有
 * 返回值: pdPASS-成功, pdFALSE-失败
 */
BaseType_t xEventGroupSetBitsFromISR( EventGroupHandle_t xEventGroup,
                                      const EventBits_t uxBitsToSet,
                                      BaseType_t * pxHigherPriorityTaskWoken );

EventBits_t xEventGroupWaitBits( EventGroupHandle_t xEventGroup,
                                 const EventBits_t uxBitsToWaitFor,
                                 const BaseType_t xClearOnExit,
                                 const BaseType_t xWaitForAllBits,
                                 TickType_t xTicksToWait );

同步点

使用 xEventGroupSync() 函数可以同步多个任务

1
2
3
4
EventBits_t xEventGroupSync(    EventGroupHandle_t xEventGroup,
                                const EventBits_t uxBitsToSet,
                                const EventBits_t uxBitsToWaitFor,
                                TickType_t xTicksToWait );

任务通知(Task Notifications)

任务通知的优势:

  • 效率更高:使用任务通知来发送事件、数据给某个任务时,效率更高。比队列、信号量、事件组都有大的优势。
  • 更节省内存:使用其他方法时都要先创建对应的结构体,使用任务通知时无需额外创建结构体。

任务通知的限制:

  • 不能发送数据给ISR:
  • ISR并没有任务结构体,所以无法使用任务通知的功能给ISR发送数据。但是ISR可以使用任务通知的功能,发数据给任务。
  • 数据只能给该任务独享
  • 使用队列、信号量、事件组时,数据保存在这些结构体中,其他任务、ISR都可以访问这些数据。使用任务通知时,数据存放入目标任务中,只有它可以访问这些数据。
  • 在日常工作中,这个限制影响不大。因为很多场合是从多个数据源把数据发给某个任务,而不是把一个数据源的数据发给多个任务。
  • 无法缓冲数据
  • 使用队列时,假设队列深度为N,那么它可以保持N个数据。
  • 使用任务通知时,任务结构体中只有一个任务通知值,只能保持一个数据。
  • 无法广播给多个任务
  • 使用事件组可以同时给多个任务发送事件。
  • 使用任务通知,只能发个一个任务。
  • 如果发送受阻,发送方无法进入阻塞状态等待
  • 假设队列已经满了,使用 xQueueSendToBack() 给队列发送数据时,任务可以进入阻塞状态等待发送完成。

通知状态和通知值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
typedef struct tskTaskControlBlock
{
    ......
    /* configTASK_NOTIFICATION_ARRAY_ENTRIES = 1 */
    volatile uint32_t ulNotifiedValue[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    volatile uint8_t ucNotifyState[ configTASK_NOTIFICATION_ARRAY_ENTRIES ];
    ......
} tskTCB;

// 任务没有在等待通知
##define taskNOT_WAITING_NOTIFICATION              ( ( uint8_t ) 0 )  /* 也是初始状态 */

// 任务在等待通知
##define taskWAITING_NOTIFICATION                  ( ( uint8_t ) 1 )
// 任务接收到了通知,也被称为pending
##define taskNOTIFICATION_RECEIVED                 ( ( uint8_t ) 2 )

任务通知的使用

基本

1
2
3
4
5
BaseType_t xTaskNotifyGive( TaskHandle_t xTaskToNotify );

void vTaskNotifyGiveFromISR( TaskHandle_t xTaskHandle, BaseType_t *pxHigherPriorityTaskWoken );

uint32_t ulTaskNotifyTake( BaseType_t xClearCountOnExit, TickType_t xTicksToWait );

高级

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
BaseType_t xTaskNotify( TaskHandle_t xTaskToNotify, uint32_t ulValue, eNotifyAction eAction );

BaseType_t xTaskNotifyFromISR( TaskHandle_t xTaskToNotify,
                               uint32_t ulValue, 
                               eNotifyAction eAction, 
                               BaseType_t *pxHigherPriorityTaskWoken );

BaseType_t xTaskNotifyWait( uint32_t ulBitsToClearOnEntry, 
                            uint32_t ulBitsToClearOnExit, 
                            uint32_t *pulNotificationValue, 
                            TickType_t xTicksToWait );

软件定时器(software timer)

  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
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
/* 使用动态分配内存的方法创建定时器
 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到
 * xTimerPeriodInTicks: 周期, 以Tick为单位
 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
 * pxCallbackFunction: 回调函数
 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL
 */
TimerHandle_t xTimerCreate( const char * const pcTimerName, 
                            const TickType_t xTimerPeriodInTicks,
                            const UBaseType_t uxAutoReload,
                            void * const pvTimerID,
                            TimerCallbackFunction_t pxCallbackFunction );

/* 使用静态分配内存的方法创建定时器
 * pcTimerName:定时器名字, 用处不大, 尽在调试时用到
 * xTimerPeriodInTicks: 周期, 以Tick为单位
 * uxAutoReload: 类型, pdTRUE表示自动加载, pdFALSE表示一次性
 * pvTimerID: 回调函数可以使用此参数, 比如分辨是哪个定时器
 * pxCallbackFunction: 回调函数
 * pxTimerBuffer: 传入一个StaticTimer_t结构体, 将在上面构造定时器
 * 返回值: 成功则返回TimerHandle_t, 否则返回NULL
 */
TimerHandle_t xTimerCreateStatic(const char * const pcTimerName,
                                 TickType_t xTimerPeriodInTicks,
                                 UBaseType_t uxAutoReload,
                                 void * pvTimerID,
                                 TimerCallbackFunction_t pxCallbackFunction,
                                 StaticTimer_t *pxTimerBuffer );


/* 删除定时器
 * xTimer: 要删除哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"删除命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
*/
BaseType_t xTimerDelete( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 启动定时器
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"启动命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStart( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 启动定时器(ISR版本)
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"启动命令"无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStartFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );

/* 停止定时器
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"停止命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStop( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 停止定时器(ISR版本)
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"停止命令"无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerStopFromISR(    TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );


/* 复位定时器
 * xTimer: 哪个定时器
 * xTicksToWait: 超时时间
 * 返回值: pdFAIL表示"复位命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerReset( TimerHandle_t xTimer, TickType_t xTicksToWait );

/* 复位定时器(ISR版本)
 * xTimer: 哪个定时器
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"停止命令"无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerResetFromISR(   TimerHandle_t xTimer,
                                 BaseType_t *pxHigherPriorityTaskWoken );

/* 修改定时器的周期
 * xTimer: 哪个定时器
 * xNewPeriod: 新周期
 * xTicksToWait: 超时时间, 命令写入队列的超时时间 
 * 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerChangePeriod(   TimerHandle_t xTimer,
                                 TickType_t xNewPeriod,
                                 TickType_t xTicksToWait );

/* 修改定时器的周期
 * xTimer: 哪个定时器
 * xNewPeriod: 新周期
 * pxHigherPriorityTaskWoken: 向队列发出命令使得守护任务被唤醒,
 *                            如果守护任务的优先级比当前任务的高,
 *                            则"*pxHigherPriorityTaskWoken = pdTRUE",
 *                            表示需要进行任务调度
 * 返回值: pdFAIL表示"修改周期命令"在xTicksToWait个Tick内无法写入队列
 *        pdPASS表示成功
 */
BaseType_t xTimerChangePeriodFromISR( TimerHandle_t xTimer,
                                      TickType_t xNewPeriod,
                                      BaseType_t *pxHigherPriorityTaskWoken );

/* 获得定时器的ID
 * xTimer: 哪个定时器
 * 返回值: 定时器的ID
 */
void *pvTimerGetTimerID( TimerHandle_t xTimer );

/* 设置定时器的ID
 * xTimer: 哪个定时器
 * pvNewID: 新ID
 * 返回值: 无
 */
void vTimerSetTimerID( TimerHandle_t xTimer, void *pvNewID );

资源管理(Resource Management)

 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
/* 在任务中,当前时刻中断是使能的
 * 执行这句代码后,屏蔽中断
 */
taskENTER_CRITICAL();

/* 访问临界资源 */

/* 重新使能中断 */
taskEXIT_CRITICAL();

void vAnInterruptServiceRoutine( void )
{
    /* 用来记录当前中断是否使能 */
    UBaseType_t uxSavedInterruptStatus;
    
    /* 在ISR中,当前时刻中断可能是使能的,也可能是禁止的
     * 所以要记录当前状态, 后面要恢复为原先的状态
     * 执行这句代码后,屏蔽中断
     */
    uxSavedInterruptStatus = taskENTER_CRITICAL_FROM_ISR();
    
    /* 访问临界资源 */

    /* 恢复中断状态 */
    taskEXIT_CRITICAL_FROM_ISR( uxSavedInterruptStatus );
    /* 现在,当前ISR可以被更高优先级的中断打断了 */
}

/* 暂停调度器 */
void vTaskSuspendAll( void );

/* 恢复调度器
 * 返回值: pdTRUE表示在暂定期间有更高优先级的任务就绪了
 *        可以不理会这个返回值
 */
BaseType_t xTaskResumeAll( void );
Licensed under CC BY-NC-SA 4.0