【ESP32 C++教程】Unit6-1 定时器

本节主要讲解Timer类,FreeRTOS定时器的使用。

定时器是指从指定的时刻开始,经过一个指定时间触发超时事件,按类型分为硬件定时器和软件定时器。

硬件定时器是芯片本身提供的定时功能。一般是由外部晶振提供给芯片输入时钟,芯片向软件模块提供一组配置寄存器,接受控制输入,到达设定时间值后芯片中断控制器产生时钟中断。硬件定时器的精度一般很高,可以达到纳秒级别,并且是中断触发方式。

软件定时器是由底层系统提供的一类系统接口,它构建在硬件定时器基础之上,使系统能够提供不受硬件定时器资源限制的定时器服务,它实现的功能与硬件定时器也是类似的。


ESP32 Arduino开发框架内的定时器类

【ESP32 C++教程】Unit6-1 定时器
其中SwTimer为软件定时器,封装了FreeRTOS中的timer相关函数;
HwTimer为硬件定时器,封装了Arduino-ESP32中内置的Ticker库;


下面用两个例子来说明定时器的使用

从 https://gitee.com/billyzh/esp32-cpp-lesson 下载本教程的源码到本地硬盘文件夹,如d:\esp32-cpp-lesson
在VSCode中,选择【文件】->【打开文件夹...】选择上一步保存的文件夹打开

示例一:用定时器控制LED闪烁

打开项目后,选择config.h文件,修改第10行为
#define APP_LESSON61_A 1

打开unit6-lesson61a/app_config.h文件,设置启用软件定时器,
#define CONFIG_USE_SW_TIMER 1
打开unit6-lesson61a/board_config.h文件,设置LED使用的引脚,
#define BUILTIN_LED_PIN GPIO_NUM_4
配置启用GpioLed
#define CONFIG_USE_LED_GPIO 1

创建LED实例,代码如下(unit6-lesson61a/my_board.cpp):

MyBoard::MyBoard() : Board() {
    Log::Info(TAG, "===== Create Board ...... =====");

    Log::Info(TAG, "initial led.");
    led_ = new GpioLed(BUILTIN_LED_PIN, false);

    Log::Info( TAG, "===== Board config completed. =====");
}

程序很简单,就是创建一个GpioLed实例。

控制LED闪烁

控制代码如下(unit6-lesson61a/my_application.cpp):

void MyApplication::OnInit() {
    // 创建定时器
    timer1_ = TimerFactory::CreateTimer("timer1");
    timer1_->Start(1000, [this]() {
        Blink();
    });
}

void MyApplication::Blink() {
    Led *led = Board::GetInstance().GetLed();
    if (state_==0) {
        led->TurnOn();
        state_ = 1;
    } else {
        led->TurnOff();
        state_ = 0;
    }
}

程序解读
1. 在OnInit方法中使用TimerFactory类创建定时器,然后启动定时器,时间间隔为1000ms,回调函数为Lambda表达式,最终调用Blink成员函数;
2. 在Blink方法中先获取Led实例,然后根据state_变量的值打开或关闭LED,以实现闪烁效果;

与前面小节在Loop方法中使用delay来实现LED闪烁相比,使用定时器类似于多任务并行执行,更加灵活,也可避免在Loop方法中处理太多功能。

编译项目并上传开发板检验

示例二:多个定时器

打开项目后,选择config.h文件,修改第10行为
#define APP_LESSON61_B 1

打开unit6-lesson61b/app_config.h文件,设置启用软件定时器,
#define CONFIG_USE_SW_TIMER 1
打开unit6-lesson61b/board_config.h文件,设置LED使用的引脚,
#define BUILTIN_LED_PIN GPIO_NUM_4
配置启用GpioLed
#define CONFIG_USE_LED_GPIO 1

创建LED实例,代码如下(unit6-lesson61b/my_board.cpp):

MyBoard::MyBoard() : Board() {
    Log::Info(TAG, "===== Create Board ...... =====");

    Log::Info(TAG, "initial led.");
    led_ = new GpioLed(BUILTIN_LED_PIN, false);

    Log::Info( TAG, "===== Board config completed. =====");
}

程序很简单,就是创建一个GpioLed实例。

定时器任务

控制代码如下(unit6-lesson61b/my_application.cpp):

void MyApplication::OnInit() {
    // 创建定时器
    timer1_ = TimerFactory::CreateTimer("timer1");
    timer1_->Start(1000, [this]() {
        Timer1Task();
    });

    timer2_ = TimerFactory::CreateTimer("timer2");
    timer2_->Start(500, [this]() {
        Timer2Task();
    });
}

void MyApplication::Timer1Task() {
    Log::Info(TAG, "Time1Task execute.");

    Led *led = Board::GetInstance().GetLed();
    if (state_==0) {
        led->TurnOn();
        state_ = 1;
    } else {
        led->TurnOff();
        state_ = 0;
    }
}

void MyApplication::Timer2Task() {
    Log::Info(TAG, "Time2Task execute...");
    
    // 执行时间大于定时器时间(500s)
    delay(600);
}

程序解读
1. 在OnInit方法中使用TimerFactory类创建两个定时器,然后启动定时器;
2. 在Timer1Task方法中实现LED闪烁效果;
3. 在Timer2Task方法中延时600ms;

编译项目并上传开发板检验

FreeRTOS定时器说明

使用定时器的初衷是可以实现一些复杂的定时任务,但是实际上使用xTimerStart得到的实例只能处理一些简单的任务,I/O,数据的运算。注意有一些操作会导致回调函数不执行,如刷新IIC接口的屏幕,在本框架中,可以使用Schedule方法将操作调度到事件循环中执行。

定时任务的规则其实是按照绝对时间来启用任务的,但是如果回调函数运行时长超时,也会一直运行到正常结束。这样子程序是稳定了,但是会不会按照意愿进行就不能保证了。

举个例子:定时任务500ms执行一次,但是任务执行1ms。这是非常复合预期的结果。事实上就是每500ms运行一次。如果任务执行时间为600ms。则在600ms时任务就会变为就绪状态。600ms任务执行完成,则会直接再次执行任务。

【ESP32 C++教程】Unit6-1 定时器

在多任务应用中,单纯的使用定时器,很有可能造成一些不可估量的后果。如果存在两个任务都定时为50ms,但是运行时间都是30ms。则会出现两个任务每30ms交替运行一次。一次循环就是60ms。和预期的50ms出现了不符。如果再极端一点,任务执行时间为100ms,则一次循环就是200ms,若任务优先级不一样,则时间一直会被高优先级的任务独占,低优先级的无法运行。

ESP32-Arduino开发框架中的定时器

本开发框架中需要定时执行的任务都是使用的Timer类,如Sensor类(传感器)就是使用Timer来定时获取传感器数据并传递到Application中进行后续处理。

例1中的LED闪烁,可直接调用Led类的Blink方法来实现,Blink方法内部使用Timer类来实现闪烁效果。


作业

1.修改示例程序使用硬件定时器(HwTimer)
2.在使用HwTimer时,观察任务执行时间超过定时间隔时的触发情况

- 本文由用户 老张 发布,文中观点仅代表作者本人,不代表本站立场。
- 如需转载,请联系作者;如有侵权,请联系本站处理。

02-17   阅读(28)   评论(0)
 标签: 编程 ESP32 FreeRTOS ESP32-ArduinoFx

涨知识
GPIO

GPIO 是指单片机(微控制器)主板上的一组引脚,这些引脚可以发送或接收电信号,但它们不是为任何特定目的而设计的,这就是为什么它们被称为“通用”IO。

评论:
相关文章
MimiClaw 配置飞书机器人和添加硬件控制技能

本文本介绍配置飞书机器人为MimiClaw的一个输入/输出端,和添加一个控制WS2812与LED的控制技能。


ESP32-S3 部署 MimicLaw 完整教程:从零到成功调用 DeepSeek

一块 30 块钱的开发板 + 一个大模型 API,就能做出可以听懂人话的智能硬件。 本文记录完整安装过程和踩坑经验,确保你跟着做就能跑通。


MimiClaw 架构全解析,把 “智能龙虾” 跑在 ESP32 上

本文将从手绘架构图入手,逐层拆解 MimiClaw 的分层设计、核心模块、数据流转与底层实现,带你解剖这只“智能虾”的技术骨架,看懂在 C 语言加持下,AI 智能体如何以可穿戴设备的形态,在你身边稳稳运行、离线服务、主动响应。


如何用 platform.local.txt 深度定制 ESP32 编译流程?

本文介绍如何在不脱离 ArduinoIDE 可视化开发的前提下,通过一个名为 platform.local.txt 的小文件,实现对 ESP32 编译流程的精准控制。


优化Arduino-ESP32程序体积

本文将系统分析程序体积增长的五大根源,并提供经过验证的优化方案,帮助减小固件大小。


开发ESP32大模型AI语音助手-从软件到硬件

本文所DIY的语音助手设备端使用的是MicroPython、服务端是Python,对于很多开发者来说MicroPython入门没难度。


【ESP32 C++教程】Unit10-2:音频录制

本小节使用音频开发框架实现一个音频录制到文件的示例。


ESP32 I2S 接口深度解析:从时序、格式到 ESP-IDF 驱动实战

I2S协议通过BCLK、LRCLK和DATA三线精准传输音频数据,但时序边沿、帧格式、时钟源等细节常引发噪声或断连。本文详解ESP32的I2S实现,从协议原理到ESP-IDF v5.x代码配置,助你避开常见陷阱,确保音频稳定传输。


【ESP32 C++教程】Unit10-1:音频播放

本小节介绍音频的基础知识、音频开发框架和AudioCodec的简介,用一个音频播放示例来说明音频管道的使用。


MimiClaw – 开源超轻量级AI助手,无需高级运行环境

MimiClaw是基于ESP32-S3芯片的超轻量级AI助手,通过Telegram或WebSocket提供Claude/GPT智能服务。