现在我们来讲解蜂鸣器和FreeRTOS的多任务协同
一、新建工程
按照上节讲述的方法,建立一个名为"Buzzer"的工程:
选择一个空白模板:

打开工程:

二、添加头文件
分别完善CMakeList.txt和main.c文件:
cpp
idf_component_register(SRCS "main.c"
PRIV_REQUIRES driver freertos
INCLUDE_DIRS ".")
cpp
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void app_main(void)
{
}
点击编译,验证没有问题:

三、编写逻辑代码
cpp
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void app_main(void)
{
gpio_config_t io_config = {
.pin_bit_mask = (1ULL << 18),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_config);
while (1)
{
gpio_set_level(18, 1);
vTaskDelay(pdMS_TO_TICKS(1000));
gpio_set_level(18, 0);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
我手头的蜂鸣器是低电平触发,也就是I/O口处为低电平时蜂鸣器会发出声音。我们在主函数内写一个死循环,每隔一秒改变一次蜂鸣器的状态,让它响一秒再沉默一秒,如此循环。
四、编译烧录
选择好串口号和烧录方式后,进行烧录,可以听到蜂鸣器响一秒沉默一秒:

五、多任务协同
现在,我想让流水灯和蜂鸣器同时作用。我们先打开上一节的工程,将代码简单粗暴的复制进去,看一下效果:
cpp
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void app_main(void)
{
gpio_config_t io_config = {
.pin_bit_mask = (1ULL << 18),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_config);
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL<<15)|(1ULL<<16)|(1ULL<<17),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
int a = 0;
while (1)
{
if (a > 2)
{
a = 0;
}
if (a == 0)
{
gpio_set_level(15, 1);
gpio_set_level(16, 0);
gpio_set_level(17, 0);
}
else if (a == 1)
{
gpio_set_level(15, 0);
gpio_set_level(16, 1);
gpio_set_level(17, 0);
}
else if (a == 2)
{
gpio_set_level(15, 0);
gpio_set_level(16, 0);
gpio_set_level(17, 1);
}
a++;
vTaskDelay(pdMS_TO_TICKS(1000));
gpio_set_level(18, 1);
vTaskDelay(pdMS_TO_TICKS(1000));
gpio_set_level(18, 0);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
点击编译烧录后,可以看到,因为我们把流水灯和蜂鸣器都放在了一个任务里,它们的延时函数会相互影响,所以最后呈现的效果,流水灯和蜂鸣器的时间间隔都会改变。
如何修改?
这里,就需要用到FreeRTOS里的多任务协同功能了,我们先把代码给出:
cpp
#include <stdio.h>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
void gpio_init_all(void)
{
gpio_config_t io_config = {
.pin_bit_mask = (1ULL << 18),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_config);
gpio_config_t io_conf = {
.pin_bit_mask = (1ULL<<15)|(1ULL<<16)|(1ULL<<17),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
gpio_config(&io_conf);
}
void led_task(void *pvParameters)
{
int a = 0;
while (1)
{
if (a > 2)
{
a = 0;
}
if (a == 0)
{
gpio_set_level(15, 1);
gpio_set_level(16, 0);
gpio_set_level(17, 0);
}
else if (a == 1)
{
gpio_set_level(15, 0);
gpio_set_level(16, 1);
gpio_set_level(17, 0);
}
else if (a == 2)
{
gpio_set_level(15, 0);
gpio_set_level(16, 0);
gpio_set_level(17, 1);
}
a++;
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void buzzer_task(void *pvParameters)
{
while (1)
{
gpio_set_level(18, 1);
vTaskDelay(pdMS_TO_TICKS(1000));
gpio_set_level(18, 0);
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
void app_main(void)
{
gpio_init_all();
xTaskCreate(
buzzer_task,
"buzzer_task",
2048,
NULL,
10,
NULL);
xTaskCreate(
led_task,
"led_task",
2048,
NULL,
10,
NULL);
}
在FreeRTOS中,app_main()函数知识任务创建入口,执行完创建任务的代码后就会退出,真正的业务逻辑在两个任务中。
因为我们的gpio初始化代码只需要运行一次,所以我们单独封装一个函数gpio_init_all(),只在上电的时候初始化一次。接下来是将流水灯和蜂鸣器分开拆成两个任务,一个命名为"led_task",另一个命名为"buzzer_task"。
在app_main()函数中,我们使用xTaskDelay来创建任务,其所需要的参数如下:
- 任务函数,我们在创建任务时,需要先定义一个函数把逻辑代码装进去,而且需要注意的是,任务的返回值必须是void *pvParameters,否则会报错。
- 任务名称,一般任务的名称和任务函数名称一样就可以。
- 任务栈大小,单位是字节,2048即可。
- 任务参数,这里我们填NULL。
- 任务优先级,因为这里流水灯和蜂鸣器并没有优先级一说,所以两个都设置为一样的就可以。
- 任务句柄,这里不需要,所以填NULL。
关于FreeRTOS内部任务是如何协调的,这里有一个流程图可以参考:

简单来说,在FreeRTOS中,每个人物都有"就绪态"和"阻塞态",当任务就绪时,CPU就会按照任务优先级进行调度。还记得上一节使用的vTaskDelay吗?当执行到这里时,当前的任务就会进入阻塞态,此时CPU会去执行非阻塞态的任务。而当vTaskDelay结束后,任务又回到就绪态,可以被CPU继续调度。
编译烧录后,可以看到流水灯和蜂鸣器都正常的每隔一秒切换一次状态。
