实时操作系统(Real-time operating system, RTOS),又称即时操作系统,它会按照排序运行、管理系统资源,并为开发应用程序提供一致的基础。
实时操作系统与一般的操作系统相比,最大的特色就是“实时性”,如果有一个任务需要执行,实时操作系统会马上(在较短时间内)执行该任务,不会有较长的延时。这种特性保证了各个任务的及时执行。

概念:TICK

time slicing 的最小单位,可在menuconfig中更改,一些函数如

1
2
vTaskDelay(tick);
vTaskDelay(1000/portTICK_PERIOD_MS);

是以tick为单位延时,对应的宏为portTICK_PERIOD_MS,可写第二行代码转为毫秒。

概念:MUTEX

多线程访问全局变量,解决重复写入和冲突等问题。
相当于创建一把钥匙,这个钥匙可以用来访问全局变量,每个任务可以选择性获取和给出这把钥匙,避免多个任务对全局变量的修改冲突。

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
// 创建钥匙
SemaphoreHandle xMutexInventory;

//main()
// 创建mutex
xMutexInventory = xSemaphoreCreateMutex();
if (xMutexInventory == NULL) {
printf("创建Semaphore失败,检查是否有足够内存")
} else {
xTaskCreate(...);
...
}
//task()
int timeOut = 1000;
void task() {
while(1) {
// 在timeout时间内如果能够获取就继续
if (xSemaphoreTake(xMutexInventory, timeOut) == pdPASS){
// 被MUTEX保护的内容叫做Critical Section
...



// 返还钥匙
xSemaphoreGive(xMutexInventory);
} else {
// 无法获取钥匙做什么
}
}
}

使用多核心

xPortGetCoreID() 获取当前任务工作在哪个核心。
xTaskCreatePinnedToCore() 设置任务工作在哪个核心

1
2
3
4
5
6
7
8
xTaskCreatePinnedToCore( TaskFunction_t pxTaskCode, 
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
const BaseType_t xCoreID
)

以绝对的频率运行任务

uint32_t tick = xTaskGetTickCount() 获取tick数。
一种实现方式

1
2
3
4
5
6
7
8
9
void task(void *pd) {
for (;;) {
uint32_t a = xTaskGetTickCount();
// do something

uint32_t b = xTaskGetTickCount();
vTaskDelay(3000 - (b-a));
}
}

这种方法存在弊端:其中a b的各种运算也需要消耗时间,频率不准确。
另一种方案
1
2
3
4
5
6
7
8
9
10
11
12
13
void task(void *pd) {
static float price = 99.57;

TickType_t xLastWakeTime = xTaskGetTickCount();
const TickType_t xFrequency = 3000; // 间隔3s

for(;;) {
printf("%d", price);


vTaskDelayUntil(&xLastWakeTime, xFrequency); // 第一个参数给指针类型让此函数自动更新XLastWakeTime
}
}

软件定时器

  1. TimerHandle_t lockHandle, checkHandle; 声明两个Handle。
  2. 创建Timer
    1
    2
    3
    4
    5
    6
    7
    8
    9
    void lockCarCallback(TimerHandle_t xTimer) {
    // do something
    }
    lockHandle = xTimerCreate("Lock Car", // 名字
    2000, // 时间 2000tick
    pdFALSE, // 一次性事件 timer时间内按多次按钮只执行一次
    // pdTRUE 多次事件 周期执行
    (void*)0, // ID 空指针 多个timer需要改变
    lockCarCallback); // 回调函数
  3. 开始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
    void carKey(void *pd) {
    gpio_set_direction(GPIO_NUM_23, GPIO_MODE_INPUT);
    for(;;){
    if (gpio_get_level(GPIO_NUM_23) == 0) {
    // timeout 3000 ticks
    // xTimerStart 只是开始时间而已,不是创造时间对象
    // 所以多次按按钮不会有多个时间对象生成
    // 多次按按钮相当于每次重置timer xTimerReset()
    if(xTimerStart(lockHandle, 3000) == pdPASS) {
    // 将要锁车
    } else {
    // 不能锁车
    }
    vTaskDelay(100); // 按钮防抖

    }
    }
    }

    // 当pdTRUE 无限大的delay 当此timer负责极其重要的传感器任务时
    xTimerStart(checkHandle, portMAX_DELAY);

    xTaskCreate(carKey,
    "Check if key was pushed",
    1024 * 1,
    NULL,
    1,
    NULL);

    任务内存管理

    查看占用

    uint32_t size = esp_get_free_heap_size() 获取空闲heap大小
    1
    2
    3
    printf("Free heap size: %ld \n", esp_get_free_heap_size());
    // 每个任务除stack size 外 还占有大概300多字节的空间 用于恢复和控制任务。
    // 调用printf可能会消耗1060字节左右的空间
    int waterMark = uxTaskGetStackHighWaterMark(taskHandle)
    1
    2
    int waterMark = uxTaskGetStackHighWaterMark(t1);
    printf("Water mark: %d \n", waterMark);
    利用此功能来确定任务栈内存的最低水平
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void demo_task(void *pd) {
    uint8_t statck_hwm=0, temp;
    for (;;) {
    temp = uxTaskGetStackHighWaterMark(nullptr); // 空指针代表当前任务
    if (!stack_hwm || temp<stack_hwm) {
    stack_hwm = temp;
    printf("Stack size for demo_task %u\n", stack_hwm);
    }
    }
    }

    任务管理

    任务的暂停恢复与删除

    vTaskSuspend(), vTaskResume(), vTaskDelete()
    1
    2
    3
    4
    5
    6
    7
    TaskHandle_t bHandle = NULL;
    vTaskCreate(Task, "TASK", 1024*4, NULL, 1, &bHandle);
    vTaskSuspend(bHandle);
    vTaskResume(bHandle);
    // 一定要判断bHandle是否为空,以及删除后将bHandle置空。
    vTaskDelete(bHandle);
    bHandle = NULL;