Skip to content

简单的(伪)多任务调度

本文章主要介绍一种不使用 FreeRTOS 来实现简单的多任务调度的方式。

问题

同时执行多个任务是嵌入式开发中非常经典的问题。常见的场景有:

  1. 智能时钟内,设置时间闪烁的同时不影响按键的使用
  2. 播放音乐时不影响按键的使用
  3. 内置计时器不够用
  4. 通过 MQTT 将数据在一定时间间隔后上传至云端,同时不影响其他功能

如果只是简单地使用 HAL_Delay 或其他的延时来实现是不够的。

c
void loop() {
    // 逻辑 1
    HAL_Delay(1000);
}

while (1) {
    if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET) {
        // 逻辑 2
    }

    loop();
}

这样能够实现每 1000ms 执行一次 逻辑 1 的效果。但这样做的问题也很明显,按下按键后 逻辑 2 不能够稳定执行了。

解决方案 1

记录一下 while 循环的次数,循环至一定次数后,执行 逻辑 1,同时清空次数。

c
uint16_t counter = 0;

void loop() {
    if (counter == 1000) {
        // 逻辑 1

        // 清空次数
        counter = 0;
    }
}

while (1) {
    if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET) {
        // 逻辑 2
    }

    loop();
    counter++;
}

解决方案 2

不妨来看看 HAL 库内 HAL_Delay 函数的原理:

c
__weak void HAL_Delay(uint32_t Delay)
{
    // 获取当前时刻
    uint32_t tickstart = HAL_GetTick();
    uint32_t wait = Delay;

    // 处理最小等待时间,该部分可以忽略
    /* Add a freq to guarantee minimum wait */
    if (wait < HAL_MAX_DELAY)
    {
        wait += (uint32_t)(uwTickFreq);
    }

    // 若没到达指定等待时刻,执行空循环
    while ((HAL_GetTick() - tickstart) < wait)
    {
    }
}

可见,因为该函数内部执行了空循环,所以程序的其他部分才会受到影响。

那能不能让程序的主循环来代替这个空循环呢?这样在等待的过程中,按键也能够被检测了。

于是可以把 HAL_Delay 中的实现照搬过来:

c
// 获取当前时刻
uint32_t tickstart = HAL_GetTick();
uint32_t wait = 1000;

void loop() {
    // 若该 if 的条件不满足,则会回到主循环
    if ((HAL_GetTick() - tickstart) >= wait) {
        // 逻辑 1

        // 重置 tickstart
        tickstart = HAL_GetTick();
    }
}

while (1) {
    if (HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin) == GPIO_PIN_RESET) {
        // 逻辑 2
    }

    loop();
}