本帖最后由 正点原子运营 于 2025-12-19 09:46 编辑
第十六章 TIMG实验
1)实验平台:正点原子DNESP32P4开发板
2)章节摘自【正点原子】ESP32-P4开发指南— V1.0
3)购买链接:https://detail.tmall.com/item.htm?id=873309579825
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/esp32/ATK-DNESP32P4.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子DNESP32S3开发板技术交流群:132780729
在本章中,我们将深入探索ESP32-P4芯片中的定时器组(TIMG)功能。定时器不仅用于精确计时,还可用于生成中断和实现定时控制,为嵌入式系统的实时性能提供了重要支持。我们将详细介绍TIMG的结构、特性以及使用方法,通过一系列实验来展示如何配置和操作定时器。通过这些实验,读者将能够理解定时器的基本原理,并掌握在实际应用中如何高效利用这些功能。准备好进入定时器的世界吧!
16.1 TIMG简介
16.2 硬件设计
16.3 程序设计
16.4 下载验证
16.1 TIMG简介
通用定时器可用于精确计时、在特定时间后触发中断(可周期性或非周期性)或作为硬件时钟。ESP32-P4芯片包含两个定时器组,分别是定时器组0和定时器组1(以下简称TIMGn,其中n可以是0或1)。每个定时器组由两个通用定时器(以下简称Tx,其中x可以是0或1)和一个主系统看门狗定时器(MWDT)组成,如下图为TIMG定时器组的简图。
图16.1.1 TIMG定时器组简图
TIMG定时器组定时器的特性总结如下:
1)54位时间基准计数器:支持递增或递减配置,提供高精度的时间计量。
2)多种时钟源:可选择PLL_F80M_CLK、XTAL_CLK或RC_FAST_CLK作为时钟源。
3)16位时钟预分频器:预分频器范围从2到65536,允许用户根据具体要求调整计时精度。
4)实时计数器读取:可以实时读取时间基准计数器的当前值,方便监控和控制。
5)暂停与恢复功能:支持在需要时暂停和恢复计数器的计数,适应动态场景需求。
6)可编程报警生成:能够根据计时值生成可编程的报警,提高应用的灵活性。
7)定时器值重载:支持在报警时自动重载或通过软件控制进行即时重载。
8)时钟频率计算:根据晶体时钟测得的频率,可计算并生成TIMG0_CALI_CLK。
9)电平中断生成:能够生成电平中断,便于与其他系统组件进行有效协作。
10)支持多任务和事件:可同时支持多个ETM任务和事件,提升系统的并行处理能力。
在这里,我们已经大致了解了TIMG定时器组的相关特性。接下来,笔者将深入讲解这些定时器组的计时原理,以便读者能够更好地理解定时器的工作机制和应用场景。
16.1.1 定时器组的结构和原理
下图16.1.1.1展示了定时器组TIMGn中的定时器Tx。Tx包含一个16位整数分频器作为预分频器、一个基于定时器的计数器和一个用于报警生成的比较器。
图16.1.1.1 定时器组架构
上图展示了定时器组TIMGn的结构,包括定时器Tx的工作原理和各个组件。该架构被分为三个主要部分:时钟源配置、计时器的分频与配置、比较器的警报机制和定时中断。接下来,笔者分别详细说明每一部分的功能和原理。
1,时钟源配置
定时器组可以从三种时钟源中选择一个作为输入时钟:XTAL_CLK、RC_FAST_CLK或PLL_F80M_CLK。通过设置寄存器HP_SYS_CLKRST_PERI_CLK_CTRL20_REG或HP_SYS_CLKRST_PERI_CLK_CTRL21_REG中的HP_SYS_CLKRST_TIMERGRPn_Tx_SRC_SEL字段,可以选择所需的时钟源。这些寄存器位描述如下所示。
图16.1.1.2 配置哪个定时器组的哪个定时器时钟源
上图中,定时器的时钟源选择通过一个多路复用器来实现,配置范围为0~1。具体来说,定时器组0的0号定时器的时钟源可以通过HP_SYS_CLKRST_TIMERGRP0_T0_SRC_SEL寄存器进行配置。此寄存器的设置方式如下:
1)0:选择XTAL_CLK(晶体振荡器时钟)作为定时器的时钟源。
2)1:选择RC_FAST_CLK作为定时器的时钟源。
3)2:选择PLL_F80M_CLK作为定时器的时钟源。
注意:上图的字段名称是由笔者根据多个类似的寄存器总结得出的,所以用户请根据《ESP32-P4技术参考手册》的寄存器命名为准。
2,计时器的分频与配置
默认情况下,当定时器选择XTAL_CLK作为时钟源时,输入时钟经过图 16.1.1.1 中的16位预分频器进行分频操作,分频系数范围为2至63336。该分频器由TIMG_TxCONFIG_REG寄存器中的TIMG_Tx_DIVIDER字段决定,该寄存器描述如下所示。
图16.1.1.3 配置分频系数
上图中的TIMG_Tx_DIVCNT_RST字段用于重置预分频器。如果此位被置为1,则必须先禁用计数器,然后通过TIMG_Tx_DIVIDER字段配置预分频数值。时钟频率经过预分频器得到分频的时钟TB_CLK,该时钟作为计数器计数的基准。随后,通过TIMG_TxCONFIG_REG寄存器中的TIMG_Tx_INCREASE和TIMG_Tx_EN字段使能计数器的工作,这两个字段的描述如下所示。
图16.1.1.4 配置计数器的方向与使能计数器
上图中,我们通过配置TIMG_Tx_INCREASE字段来设置当前计数器的计数方向(0:向下计数;1:向上计数),而通过TIMG_Tx_EN字段来开启计数器。这样,我们就可以得到计数器在规定时间计数的数值了(图 16.1.1.1中的TIMG_VALUE)。
若我们想设置计数器的初始计数值或者重新装载值,可以通过向TIMG_Tx_LOAD_LO和TIMG_Tx_LOAD_HI寄存器写入起始值来实现。然后,通过向TIMG_TxLOAD_REG写入任意值将其重新加载到定时器中,这些寄存器描述如下图所示。
图16.1.1.5 配置重装载值(多个寄存器的合并图)
若我们要读取54位时间基准计数器的值,计时器值必须先被锁存到两个寄存器中,以便CPU读取(由于CPU是32位的,所以只能读取32位以内的数据)。通过向TIMG_TxUPDATE_REG写入任意值,当前54位计时器的值将开始被锁存到TIMG_TxLO_REG和TIMG_TxHI_REG寄存器中,前者包含低32位,后者包含高22位。当TIMG_TxUPDATE_REG被硬件清除时,表示锁存操作已完成,此时可以从TIMG_TxLO_REG和TIMG_TxHI_REG寄存器读取当前计时器值。TIMG_TxLO_REG和TIMG_TxHI_REG寄存器在CPU读取时将保持不变,直到TIMG_TxUPDATE_REG再次被写入。这些寄存器描述如下图所示。
图16.1.1.6 读取计数值(多个寄存器合并图)
若读者希望定时器以1µs计时,可以将预分频数值设置为80。其计算公式为:
根据上述的公式计算,80M / 80 = 1MHz的定时器时钟频率(图 16.1.1.1 中的TB_CLK),然后根据时间 T与频率F的关系,如下公式所示:
经过上述公式的计算,最终计数器计数一次的时钟为1µs。
3,比较器的警报机制
若计数器启动后,TIMG_VALUE的数值会输入到比较器(Comparator)中,比较器根据警报值进行比较。如果TIMG_VALUE数值与警报值相等或出现其他触发情况,则会触发定时组中断(TIMG_Tx_INT)。下表为警报触发情景。
表16.1.1.1 警报触发情景
图16.1.1.1中,我们通过TIMG_TxCONFIG_REG寄存器中的TIMG_Tx_ALARM_EN字段来使能比较器,而警报值可通过TIMG_TxALARMLO_REG寄存器和TIMG_TxALARMHI_REG寄存器进行配置。这些字段描述如下所示。
图16.1.1.7 配置警报数值(多个寄存器合并图)
注意:报警时,TIMG_Tx_ALARM_EN字段会自动清除。这意味着在下次显式重新设置TIMG_Tx_ALARM_EN之前,不会再触发任何报警。这种行为确保每个事件只会触发一次报警。
3,定时中断
当计数值与警报值相等时,将触发中断输出TIMG_Tx_INT至中断矩阵,最后通知CPU触发中断。
16.1.2 定时器重载
当定时器的当前值被覆盖为存储在TIMG_Tx_LOAD_LO和TIMG_Tx_LOAD_HI字段中的重载值时,定时器就会被重载。这两个字段分别对应定时器新值的低32位和高22位。然而,写入重载值到TIMG_Tx_LOAD_LO和TIMG_Tx_LOAD_HI不会导致定时器当前值的变化。相反,重载值在重载事件发生之前会被定时器忽略。
重载事件可以通过软件即时重载或报警自动重载触发。
1)软件即时重载:当CPU向TIMG_TxLOAD_REG写入任何值时,会触发即时重载,这会立即更新定时器的当前值。如果 TIMG_Tx_EN 被设置,定时器将继续从新值开始递增或递减。在这种情况下,如果TIMG_Tx_ALARM_EN被设置,定时器仍然会在表16.1.1.1中列出的场景中触发报警。如果TIMG_Tx_EN被清除,定时器将保持在新值处冻结,直到重新启用计数。
2)报警自动重载:当报警发生时,将导致定时器重载,从而允许定时器从重载值继续递增或递减。这在使用周期性报警时非常有用,可以重置定时器的值。要启用报警自动重载,应该设置TIMG_Tx_AUTORELOAD字段。如果没有启用,定时器的值将在报警后继续递增或递减,超过报警值。
16.2 硬件设计
16.2.1 程序功能
在1s周期内翻转LED0电平状态,并实时打印当前计数值。
16.2.2 硬件资源
1)LED灯
LED 0 - IO51
2)TIMG
16.2.3 原理图
本章实验使用的TIMG为ESP32-P4的片上资源,因此并没有相应的连接原理图。
16.3 程序设计
注意:本书提供了两个TIMG实验,分别为06_1_timg和06_2_timg。其中,06_1_timg实验使用旧版本的API,而06_2_timg实验使用新版API。之所以提供两个版本,是因为旧版和新版API在定时器组的选择方式上存在差异。06_1_timg实验允许用户自由选择定时器组和定时器,可以灵活指定使用哪个定时器组的哪个定时器。而在06_2_timg实验中,使用的新API要求创建定时器时必须按顺序从定时器组0的定时器0开始,依次创建到定时器组1的定时器1。这意味着在新版本中,用户不能随意选择定时器组和定时器,必须按照固定的顺序进行创建和使用。
本章节将以06_2_timg实验中使用的API函数为基准进行讲解,06_1_timg实验仅作为参考。
16.3.1 TIMG的IDF驱动
TIMG外设驱动位于ESP-IDF的components\esp_timer目录。该目录中的include文件夹存放TIMG相关的头文件,声明了TIMG函数和结构体等;而src文件夹则存放实际的TIMG操作函数。要使用TIMG功能,必须先导入以下头文件。
- <font size="3">#include "driver/gptimer.h"</font>
- <font size="3">#include "../src/gptimer_priv.h"</font>
复制代码 接下来,作者将介绍一些常用的TIMG函数,这些函数的描述及其作用如下:
1,创建一个新的通用定时器gptimer_new_timer
该函数用于创建一个新的通用定时器,其函数原型如下:
- <font size="3">esp_err_t gptimer_new_timer(const gptimer_config_t *config, </font>
- <font size="3">gptimer_handle_t *ret_timer);</font>
复制代码 函数形参:
表16.3.1.1 gptimer_new_timer函数形参描述
返回值:
ESP_OK表示成功创建GPTimer。
ESP_ERR_INVALID_ARG表示创建GPTimer失败,因参数无效。
ESP_ERR_NO_MEM表示创建GPTimer失败,因内存不足。
ESP_ERR_NOT_FOUND表示创建GPTimer失败,因所有硬件定时器已被使用,没有可用。
ESP_FAIL表示创建GPTimer失败,因其他错误。
config为指向TIMG配置结构体的指针。接下来,笔者将详细介绍gptimer_config_t结构体中的各个成员变量,如下代码所示:
- typedef struct {
- gptimer_clock_source_t clk_src; /* GPTimer时钟源 */
- gptimer_count_direction_t direction; /* 计数方向 */
- /* 计数器分辨率(工作频率),以 Hz 为单位,每次计数的 步长等于 (1/resolution_hz) 秒 */
- uint32_t resolution_hz;
- /* GPTimer中断优先级,如果设置为0,驱动程序将尝试分配相对较低优先级的中断(1,2,3) */
- int intr_priority;
- struct {
- uint32_t intr_shared: 1;/* 设为真,则定时器中断号可以与其他外设共享 */
- /* 如果设置,驱动程序将在进入/退出睡眠模式之前/之后备份/恢复 GPTimer 寄存器。
- 通过这种方式,系统可以关闭 GPTimer 的电源域。这可以节省电力,但会消耗更多的 RAM */
- uint32_t backup_before_sleep: 1;
- } flags; /* GPTimer 配置标志 */
- } gptimer_config_t;
复制代码 上述结构体用于配置TIMG的定时参数,以下对各个成员做简单介绍。
1)clk_src:
设置GPTimer时钟源。此字段可配置为GPTIMER_CLK_SRC_PLL_F80M、GPTIMER_CLK_SRC_RC_FAST、GPTIMER_CLK_SRC_XTAL和GPTIMER_CLK_SRC_DEFAULT。
2)direction:
设置定时计数方向。可选GPTIMER_COUNT_DOWN向下计数或GPTIMER_COUNT_UP向上计数。
3)resolution_hz:
设置计数器分辨率。
4)intr_priority:
设置定时器中断优先级。
5)flags.intr_shared
设置TIMG中断号共享。
6)flags.backup_before_sleep
设置进入睡眠。
2,通用定时器(GPTimer)设置回调函数gptimer_register_event_callbacks
该函数用于通用定时器(GPTimer)设置回调函数,其函数原型如下:
- esp_err_t gptimer_register_event_callbacks(gptimer_handle_t timer,
- const gptimer_event_callbacks_t *cbs,
- void *user_data);
复制代码 函数形参:
表16.3.1.2 gptimer_register_event_callbacks函数形参描述
返回值:
ESP_OK表示成功设置事件回调。
ESP_ERR_INVALID_ARG表示由于无效参数导致设置回调失败。
ESP_ERR_INVALID_STATE表示定时器不处于初始化状态,无法设置回调。
ESP_FAIL表示由于其他错误导致设置回调失败。
3,启用通用定时器gptimer_enable
该函数用于启用通用定时器,其函数原型如下:
- esp_err_t gptimer_enable(gptimer_handle_t timer);
复制代码 函数形参:
表16.3.1.3 gptimer_enable函数形参描述
返回值:
ESP_OK表示成功启用GPTimer。
ESP_ERR_INVALID_ARG表示由于无效参数导致启用失败。
ESP_ERR_INVALID_STATE表示定时器已经处于启用状态,无法再次启用。
ESP_FAIL表示由于其他错误导致启用失败。
4,通用定时器(GPTimer)设置报警事件的操作gptimer_set_alarm_action
该函数用于通用定时器(GPTimer)设置报警事件的操作,其函数原型如下:
- esp_err_t gptimer_set_alarm_action(gptimer_handle_t timer,
- const gptimer_alarm_config_t *config);
复制代码 函数形参:
表16.3.1.4 gptimer_set_alarm_action函数形参描述
返回值:
ESP_OK表示成功为GPTimer设置报警操作。
ESP_ERR_INVALID_ARG表示由于无效参数导致设置失败。
ESP_FAIL表示由于其他错误导致设置失败。
config为指向报警配置结构体的指针。接下来,笔者将详细介绍gptimer_alarm_config_t结构体中的各个成员变量,如下代码所示:
- typedef struct {
- uint64_t alarm_count; /* 报警目标计数值,当定时器计数达到此值时,将触发报警事件 */
- uint64_t reload_count;/* 报警重载计数值,仅在auto_reload_on_alarm设为真时有效 */
- struct {
- uint32_t auto_reload_on_alarm: 1; /* 报警事件发生时,硬件是否立即重载计数值 */
- } flags; /* 报警配置标志 */
- } gptimer_alarm_config_t;
复制代码 gptimer_alarm_config_t结构体用于传递通用定时器(GPTimer)的报警配置参数。以下是各个参数的说明。
表16.3.1.5 gptimer_alarm_config_t结构体的各个参数描述及可选项
5,启动通用定时器gptimer_start
该函数用于启动通用定时器,其函数原型如下:
- esp_err_t gptimer_start(gptimer_handle_t timer);
复制代码 函数形参:
表16.3.1.6 gptimer_start函数形参描述
返回值:
ESP_OK表示成功启动GPTimer。
ESP_ERR_INVALID_ARG表示启动GPTimer失败,原因是无效参数。
ESP_ERR_INVALID_STATE表示启动GPTimer失败,因为定时器未启用或已处于运行状态。
ESP_FAIL表示启动GPTimer失败,原因是其他错误。
16.3.2 程序流程图
图16.3.2.1 TIMG实验程序流程图
16.3.3 程序解析
在06_2_timg例程中,作者在06_2_timg\components\BSP路径下新建TIMG文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。
1,TIMG驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ESPTIMER驱动源码包括两个文件:timg.c和timg.h。
timg.h主要用于声明timg_init等函数,以便在其他文件中调用,具体内容不再赘述。
下面我们再解析timg.c的程序,看一下初始化函数timg_init,代码如下:
- <font size="3">/**</font>
- <font size="3"> * @brief 初始化定时器</font>
- <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url] alarm_count:触发警报事件的目标计数值</font>
- <font size="3"> * @param resolution: 定时器的分辨率</font>
- <font size="3"> * @retval 定时器的ID</font>
- <font size="3"> */</font>
- <font size="3">uint8_t timg_init(uint64_t alarm_count, uint32_t resolution)</font>
- <font size="3">{</font>
- <font size="3"> gptimer_handle_t gptimer = NULL; /* 定义一个通用定时器实例 */</font>
- <font size="3"> uint8_t group_index, timer_index, timer_id = 0;</font>
- <font size="3"> gptimer_config_t timer_config = /* 配置定时器参数 */</font>
- <font size="3"> {</font>
- <font size="3"> .clk_src = GPTIMER_CLK_SRC_DEFAULT, /* 选择定时器的时钟源 */</font>
- <font size="3"> .direction = GPTIMER_COUNT_UP, /* 设置定时器的计数方向 */</font>
- <font size="3">/* 设置内部计数器的分辨率,若为1000000即1MHz,1tick = 1us */</font>
- <font size="3"> .resolution_hz = resolution, </font>
- <font size="3"> };</font>
- <font size="3"> ESP_ERROR_CHECK(gptimer_new_timer(&timer_config, &gptimer));/* 实例化定时器 */</font>
- <font size="3"> </font>
- <font size="3"> /* 获取定时器ID信息 */</font>
- <font size="3"> group_index = gptimer->group->group_id;</font>
- <font size="3"> timer_index = gptimer->timer_id;</font>
- <font size="3"> timer_id |= (group_index << 1) | timer_index;</font>
- <font size="3">ESP_LOGI("timer", "group_index:%d timer_index:%d timer_id:%d",</font>
- <font size="3"> group_index, timer_index, timer_id); </font>
- <font size="3"> gptimer_event_callbacks_t callback_func = </font>
- <font size="3"> {</font>
- <font size="3"> .on_alarm = timeout_callback, /* 设置警报事件的回调函数 */</font>
- <font size="3">};</font>
- <font size="3">/* 将函数挂载到中断服务例程 (ISR) */</font>
- <font size="3">ESP_ERROR_CHECK(gptimer_register_event_callbacks(gptimer,</font>
- <font size="3">&callback_func, NULL)); </font>
- <font size="3"> gptimer_enable(gptimer); /* 使能定时器 */</font>
- <font size="3"> gptimer_alarm_config_t alarm_config = </font>
- <font size="3"> {</font>
- <font size="3"> .alarm_count = alarm_count, /* 报警目标计数值 */</font>
- <font size="3"> .reload_count = 0, /* 重载计数值为0 */</font>
- <font size="3"> .flags.auto_reload_on_alarm = true, /* 开启重加载 */</font>
- <font size="3"> }; </font>
- <font size="3"> gptimer_set_alarm_action(gptimer, &alarm_config); /* 设置触发报警条件 */</font>
- <font size="3"> gptimer_start(gptimer); /* 定时器开始工作 */</font>
- <font size="3"> return timer_id;</font>
- <font size="3">}</font>
复制代码 timg_init函数用于初始化一个通用定时器,配置其时钟源、计数方向和分辨率。它注册一个报警回调函数,使能定时器,并设置报警条件,包括目标计数值和自动重载功能。最后,函数启动定时器并返回定时器的ID,以便后续使用。如下是GPTimer回调函数timeout_callback的处理任务。
- <font size="3">/**</font>
- <font size="3"> * @brief 定时器回调函数</font>
- <font size="3"> * @param timer:通用定时器对象</font>
- <font size="3"> * @param edata:通用定时器警报事件数据</font>
- <font size="3"> * @param user_data:用户数据</font>
- <font size="3"> * @retval 布尔类型</font>
- <font size="3"> */</font>
- <font size="3">static bool IRAM_ATTR timeout_callback( gptimer_handle_t timer, </font>
- <font size="3">const gptimer_alarm_event_data_t *edata, </font>
- <font size="3">void *user_data)</font>
- <font size="3">{</font>
- <font size="3"> if (timer->group->group_id == 0) /* 定时器组0 */</font>
- <font size="3"> {</font>
- <font size="3"> if ((timer->timer_id)) /* 定时器1 */</font>
- <font size="3"> {</font>
- <font size="3"> /* 执行定时器1定时操作 */</font>
- <font size="3"> ESP_DRAM_LOGI("TimerGroup0", "timer1 time out");</font>
- <font size="3"> }</font>
- <font size="3"> else /* 定时器0 */</font>
- <font size="3"> {</font>
- <font size="3"> /* 执行定时器0定时操作 */</font>
- <font size="3"> ESP_DRAM_LOGI("TimerGroup0", "timer0 time out");</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> else /* 定时器组1 */</font>
- <font size="3"> {</font>
- <font size="3"> if ((timer->timer_id)) /* 定时器1 */</font>
- <font size="3"> {</font>
- <font size="3"> /* 执行定时器1定时操作 */</font>
- <font size="3"> ESP_DRAM_LOGI("TimerGroup1", "timer1 time out");</font>
- <font size="3"> }</font>
- <font size="3"> else /* 定时器0 */</font>
- <font size="3"> {</font>
- <font size="3"> /* 执行定时器0定时操作 */</font>
- <font size="3"> ESP_DRAM_LOGI("TimerGroup1", "timer0 time out");</font>
- <font size="3"> }</font>
- <font size="3"> }</font>
- <font size="3"> return pdTRUE;</font>
- <font size="3">}</font>
复制代码 timeout_callback是一个定时器回调函数,当定时器触发报警事件时被调用。该函数根据定时器所属的组和ID,判断是哪个定时器超时,并打印相应的日志信息。返回值为pdTRUE,表示回调执行成功。
2,CMakeLists.txt文件
本例程的功能实现主要依靠GPTimer驱动。要在main函数中,成功调用GPTimer文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件(重点看红色内容),修改如下:
- <font size="3">set(src_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> TIMG)</font>
- <font size="3">set(include_dirs</font>
- <font size="3"> LED</font>
- <font size="3"> TIMG)</font>
- <font size="3">set(requires</font>
- <font size="3"> driver</font>
- <font size="3"> esp_timer)</font>
- <font size="3">idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs}</font>
- <font size="3"> REQUIRES ${requires})</font>
- <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码 3,main.c驱动代码
在main.c里面编写如下代码。
- <font size="3">void app_main(void)</font>
- <font size="3">{</font>
- <font size="3"> esp_err_t ret;</font>
- <font size="3"> ret = nvs_flash_init(); /* 初始化NVS */</font>
- <font size="3"> if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
- <font size="3"> {</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_erase());</font>
- <font size="3"> ESP_ERROR_CHECK(nvs_flash_init());</font>
- <font size="3"> }</font>
- <font size="3"> led_init(); /* 初始化LED */</font>
- <font size="3"> /* 执行顺序会自动分配定时器 */</font>
- <font size="3"> timg_init(500000, 1000000); /* 定时器0初始化 */</font>
- <font size="3"> timg_init(1000000, 1000000); /* 定时器1初始化 */</font>
- <font size="3"> timg_init(2000000, 1000000); /* 定时器2初始化 */</font>
- <font size="3"> timg_init(4000000, 1000000); /* 定时器3初始化 */</font>
- <font size="3"> while(1)</font>
- <font size="3"> {</font>
- <font size="3"> LED0_TOGGLE();</font>
- <font size="3"> vTaskDelay(200);</font>
- <font size="3"> }</font>
- <font size="3">}</font>
复制代码 该函数首先按顺序初始化两个定时器组的四个定时器,每个定时器被设置为不同的定时时间。具体实现通过timeout_callback回调函数处理定时器超时事件,根据定时器的组和ID执行不同的操作。这样可以灵活管理多个定时器的行为,满足不同的定时需求。
16.4 下载验证
程序下载完成后,我们可打开监视器查看各个定时器的操作任务,如下图所示。
图16.4.1 四个通用定时器运行消息 |