ESP32-S3开发教程三:蜂鸣器与FreeRTOS多任务协同

现在我们来讲解蜂鸣器和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来创建任务,其所需要的参数如下:

  1. 任务函数,我们在创建任务时,需要先定义一个函数把逻辑代码装进去,而且需要注意的是,任务的返回值必须是void *pvParameters,否则会报错。
  2. 任务名称,一般任务的名称和任务函数名称一样就可以。
  3. 任务栈大小,单位是字节,2048即可。
  4. 任务参数,这里我们填NULL。
  5. 任务优先级,因为这里流水灯和蜂鸣器并没有优先级一说,所以两个都设置为一样的就可以。
  6. 任务句柄,这里不需要,所以填NULL。

关于FreeRTOS内部任务是如何协调的,这里有一个流程图可以参考:

简单来说,在FreeRTOS中,每个人物都有"就绪态"和"阻塞态",当任务就绪时,CPU就会按照任务优先级进行调度。还记得上一节使用的vTaskDelay吗?当执行到这里时,当前的任务就会进入阻塞态,此时CPU会去执行非阻塞态的任务。而当vTaskDelay结束后,任务又回到就绪态,可以被CPU继续调度。

编译烧录后,可以看到流水灯和蜂鸣器都正常的每隔一秒切换一次状态。

相关推荐
机器视觉知识推荐、就业指导3 小时前
为什么同一个引脚不能同时做按键和串口
stm32·单片机·嵌入式硬件
崇山峻岭之间3 小时前
单片机基本定时器实验
单片机·嵌入式硬件
DS小龙哥3 小时前
基于ESP32设计的智能养蜂监测系统
stm32·单片机·嵌入式硬件·物联网·华为云
夜月yeyue4 小时前
STM32 DMA 双缓冲采样
linux·stm32·单片机·嵌入式硬件·系统架构
ScilogyHunter4 小时前
Buildroot完全指南:从入门到实战
linux·嵌入式·buildroot
西城微科方案开发4 小时前
SIC8P370D2L-PLP16 8位OTP单片机 低功耗多功能MCU详解
单片机·嵌入式硬件
踏着七彩祥云的小丑7 小时前
嵌入式测试第 32 天:升级测试:固件OTA升级、断点续传、回滚测试
单片机·嵌入式硬件·学习
点灯小铭7 小时前
基于单片机与DAC0832的双路波形信号发生系统设计
数据库·单片机·mongodb·毕业设计·课程设计·期末大作业
sramdram7 小时前
基于MCU微控制器的电子血压计应用解决方案
单片机·嵌入式硬件·mcu·mcu微控制器
Szime7 小时前
AD9218 国产替代方向:双通道 10 位 105MSPS ADC 选型支持
单片机·嵌入式硬件·fpga开发·汽车