第十五章 System Timer实验
节读者将能够掌握如何利用ESP32-P4的系统定时器实现精确的时间控制为构建更高效的应用打下坚实基础。15.1 System Timer简介15.2 硬件设计15.3 程序设计15.4 下载验证15.1 System Timer简介ESP32-P4的系统定时器为52位计数器主要用于操作系统的Tick中断生成也可作为通用定时器用于生成周期性或一次性中断。它具有以下特点1双计数器与三比较器具备两个计数器UNIT0和UNIT1和三个比较器COMP0、COMP1、COMP2支持多种中断触发配置。2APB_CLK依赖系统定时器的寄存器配置等软件操作依赖APB_CLK时钟信号。3CNT_CLK生成CNT_CLK计数时钟由40 MHz的XTAL_CLK提供经过分频后频率平均为16 MHz。4报警模式支持目标模式一次性报警和周期模式周期性报警。5软件配置支持在轻度休眠唤醒后可加载RTC计时器的睡眠时间。接下来我们将查看ESP32-P4系统定时器的结构简图如下图所示。图15.1.1 系统定时器结构简图上图展示了ESP32-P4系统定时器的结构包括计数单元和比较器的连接关系主要组件有1计数单元Timer Counter0UNIT0和Timer Counter1UNIT1分别用于独立记录计时信息并可根据需求进行配置。2MUX多路复用器选择计数单元UNIT0或UNIT1的输出并将其发送至不同的比较器以灵活设置报警事件。3比较器Timer Comparator0、Timer Comparator1和Timer Comparator2用于接收计数单元的输出并将其与预设报警值进行比较生成独立的定时中断。接下来笔者将讲解ESP32-P4系统定时器的时钟源选择和控制方法。15.1.1 系统定时器的时钟源选择声明以下章节的寄存器均来自《ESP32-P4技术参考手册》。在图15.1.1中计数器和比较器可以选择XTAL_CLK外部石英晶振或RC_FAST_CLK快速RC时钟作为时钟源。时钟源选择通过配置寄存器HP_SYS_CLKRST_PERI_CLK_CTRL21_REG中的HP_SYS_CLKRST_REG_SYSTIMER_CLK_SRC_SEL字段实现如下图所示。图15.1.1配置系统定时器的时钟源当选择XTAL_CLK作为计数器的时钟源时通过将XTAL_CLK频率缩小2.5倍生成频率为CNT_CLK的计数器时钟信号其平均频率为16 MHz。在每个CNT_CLK周期内计数器会递增1计数间隔为1/16µs计数器每计数16次为1µs。在15.1.3小节中笔者将重点讲解计数器与比较器如何协同开启定时器的警报机制。15.1.2 系统定时器的控制和操作时钟源系统定时器的软件操作如寄存器配置使用APB_CLK作为时钟源。关于APB_CLK的详细信息请参考《ESP32-P4技术参考手册》中的第8章“Reset and Clock”。为使系统定时器接收APB_CLK信号需要在HP_SYS_CLKRST_SOC_CLK_CTRL2_REG寄存器中设置HP_SYS_CLKRST_REG_SYSTIMER_APB_CLK_EN位。此外要重置系统定时器可在HP_SYS_CLKRST_HP_RST_EN1_REG寄存器中设置HP_SYS_CLKRST_REG_RST_EN_STIMER位见下图所示。注意定时器复位后其寄存器将恢复到默认值。图15.1.2.1 控制和操作系统定时器15.1.3 系统定时器的警报生成机制系统定时器的定时警报功能至关重要它可以在特定时间点触发事件或中断处理。系统定时器通过计数器和比较器的配合实现警报生成与触发。图15.1.3.1展示了系统定时器中警报的生成流程本文将详细讲解该流程的工作机制。图15.1.3.1 系统定时器中警报的生成流程ESP32-P4系统定时器包含两个计数器Timer Counter和三个比较器Timer Comparator。警报生成的具体步骤如下1时钟源与计数器系统定时器通过分频器DIV将XTAL_CLK的主时钟信号频率缩小2.5倍生成频率为16 MHz的CNT_CLK计数时钟信号。在每个CNT_CLK周期内计数器Timer Counter递增计数值以形成基准时间。如前所述系统定时器配备两个52位的计数器计数时钟源为16 MHz意味着每次计数的时间间隔为1/16 µs。计数器与CPUxx0~1的工作状态可通过SYSTIMER_CONF_REG寄存器控制主要设置包括①启用计数器SYSTIMER_TIMER_UNITn_WORK_EN你可以通过设置这个选项来启动计数器。②CPU0暂停控制SYSTIMER_TIMER_UNITn_CORE0_STALL_EN如果你设置了这个选项当CPU0暂停工作时计数器会停止计数一旦CPU0恢复计数器会继续计数。③CPU1暂停控制SYSTIMER_TIMER_UNITn_CORE1_STALL_EN如果你设置了这个选项当CPU1暂停工作时计数器会停止计数一旦CPU1恢复计数器会继续计数。下图展示了在CPU暂停时对计数器UNITnn0~1的配置位。图15.1.3.2 计数器与CPU的联系通过上述寄存器位机制系统定时器能够精确跟踪时间并灵活适应CPUx的工作状态。接下来笔者将详细讲解计数器的工作原理及相关寄存器。当计数器UNITn工作时计数值递增停止时则保持不变。初始计数值的低32位和高20位从寄存器SYSTIMER_TIMER_UNITn_LOAD_LO和SYSTIMER_TIMER_UNITn_LOAD_HI中加载。写入1到SYSTIMER_TIMER_UNITn_LOAD会触发重新加载事件当前计数值立即更新若UNITn正在工作计数器将从新加载值继续计数。此外若向SYSTIMER_TIMER_UNITn_UPDATE写入1会触发更新事件则当前计数值的低32位和高20位将锁定到寄存器SYSTIMER_TIMER_UNITn_VALUE_LO和SYSTIMER_TIMER_UNITn_VALUE_HI中并将SYSTIMER_TIMER_UNITn_VALUE_VALID置为有效。值在下一次更新事件之前保持不变。2计数器与比较器的比较操作计数器生成的计时值实时传递到比较器Timer Comparator。比较器具备多种配置寄存器如SYSTIMER_TARGETx_WORK_EN、SYSTIMER_TARGETx_PERIOD_MODE、SYSTIMER_TARGETx_PERIOD、SYSTIMER_TARGETx_LO_REG、SYSTIMER_TARGETx_HI_REG等用于设置比较器的工作模式和目标值x0~2。SYSTIMER_CONF_REG寄存器中的SYSTIMER_TARGETx_WORK_EN位如图所示。图15.1.3.3 开启比较器COMP0、COMP1和COMP2上图的寄存器位是用来开启系统定时器的三个比较器。SYSTIMER_TARGETx_CONF_REG寄存器中的SYSTIMER_TARGETx_PERIOD_MODE和SYSTIMER_TARGETx_PERIODx为比较器0~2的索引位描述如下图所示。图15.1.3.4 配置比较器上图中SYSTIMER_TARGETx_PERIOD位用于设置定时警报周期δt。当模式为周期模式时定时器将在设定时间间隔t1 n*δt内持续触发警报。SYSTIMER_TARGETx_PERIOD_MODE位用于配置比较器为单次触发或周期性触发。同时SYSTIMER_TARGETx_TIMER_UNIT_SEL用于指定计数器以获取其计数值进行比较。SYSTIMER_TARGETx_LO_REG和SYSTIMER_TARGETx_HI_REG寄存器用于设定52位警报数值的低32位和高20位。在单次触发模式下通过对这两个寄存器的配置可以设定所需的警报值如下图所示。图15.1.3.5 配置警报值3触发中断信号当计数器的值达到比较器设置的目标值时比较器会生成一个Timer Interrupt警报信号并将该信号发送至CPU中断矩阵CPU Interrupt Matrix。中断矩阵负责处理中断信号并触发相应的中断服务程序ISR。通过对比较器的配置开发者可以灵活地设置警报时间和触发的中断利用系统定时器进行高效的事件调度。15.1.4 系统定时器的比较器及警报生成机制解析以下是根据前面小节内容总结的系统定时器原理希望读者能对其有更深入的认识和学习。根据前面的知识我们了解到ESP32-P4的系统定时器配备三个52位比较器COMPxx 0, 1, 2能够根据不同的警报值t或警报周期δt生成独立的中断。每个COMPx的工作模式可通过配置寄存器SYSTIMER_TARGETx_PERIOD_MODE来选择主要包括周期模式和单次模式1周期模式警报周期δt由寄存器SYSTIMER_TARGETx_PERIOD提供。假设当前计数值为t1当计数值达到t1 δt时会生成一个警报中断当计数值再次达到t1 2*δt时另一个警报中断将生成从而实现周期性警报。2单次模式警报值t的低32位和高20位由SYSTIMER_TIMER_TARGETx_LO和SYSTIMER_TIMER_TARGETx_HI提供。假设当前计数值为t2t2 ≤ t当计数值t2达到警报值t时将生成一次警报中断。与周期模式不同单次模式仅生成一次警报中断。此外SYSTIMER_TARGETx_TIMER_UNIT_SEL用于选择用于比较生成警报的计数值来源1表示使用来自UNIT1的计数值0表示使用来自UNIT0的计数值请参考图15.1.3.4。设置SYSTIMER_TARGETx_WORK_EN可启用COMPx进行计数值比较。在单次模式下COMPx将与警报值t进行比较在周期模式下则与起始值加上n倍警报周期t1 n * δt进行比较。警报生成的条件是当计数值等于警报值t时在单次模式或等于起始值加上n倍警报周期在周期模式时警报将被触发。如果寄存器中设置的警报值t小于当前计数值即目标已过当当前计数值大于警报值t并在范围0 ~ 251 – 1内时也会立即生成警报中断如下表所示。表15.1.4.1 系统定时器警报触发点注意无论是在目标模式还是周期模式实际警报值的低32位和高20位始终可以从SYSTIMER_TARGETx_LO_RO和SYSTIMER_TARGETx_HI_RO读取。15.2 硬件设计15.2.1 例程功能开启高精度定时器并设置时间为1s在定时周期内翻转LED0电平状态。15.2.2 硬件资源1LED灯LED 0 - IO512Systimer15.2.3 原理图本章实验使用的系统定时器为ESP32-P4的片上资源因此并没有相应的连接原理图。15.3 程序设计15.3.1 SysTimer的IDF驱动SysTimer外设驱动位于ESP-IDF的components\esp_timer目录。该目录中的include文件夹存放SysTimer相关的头文件声明了SysTimer函数和结构体等而src文件夹则存放实际的SysTimer操作函数。要使用SysTimer功能必须先导入以下头文件。#include esp_timer.h接下来作者将介绍一些常用的SysTimer函数这些函数的描述及其作用如下1创建系统定时器esp_timer_create该函数用于创建一个 ESP 定时器实例其函数原型如下esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args, esp_timer_handle_t* out_handle);函数形参表15.3.1.1 esp_timer_create函数形参描述返回值ESP_OK表示成功。ESP_ERR_INVALID_ARG表示如果某些create_args无效。ESP_ERR_INVALID_STAT表示esp_timer库尚未初始化。ESP_ERR_NO_MEM表示内存分配失败。create_args为指向SysTimer配置结构体的指针。接下来笔者将详细介绍esp_timer_create_args_t结构体中的各个成员变量如下代码所示typedef struct { esp_timer_cb_t callback; /* 定时器到期时执行的回调函数 */ void* arg; /* 传递给回调的参数 */ /* 从任务或 ISR 调度回调的方法 如果未指定将使用 esp_timer 任务 要使 ISR 工作还需设置 Kconfig 选项 CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD */ esp_timer_dispatch_t dispatch_method; const char* name; /* 定时器名称在esp_timer_dump()函数中使用 */ bool skip_unhandled_events; /* 设为跳过在轻睡眠状态下的周期性定时器未处理事件 */ } esp_timer_create_args_t;上述结构体用于配置SysTimer的定时参数以下对各个成员做简单介绍。1callback设置回调函数。根据esp_timer_cb_t类型编写回调。2arg设置回调函数传入形参。3dispatch_method设置调度模式。此字段可配置为ESP_TIMER_TASK定时器任务调度和ESP_TIMER_MA中断调度。4name定时器名称。5skip_unhandled_events设置执行未处理事件。Flase关闭true开启。2启动一个周期性定时器esp_timer_start_periodic该函数用于启动周期性定时器其函数原型如下esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period);函数形参表15.3.1.2 esp_timer_start_periodic函数形参描述返回值ESP_OK表示成功。ESP_ERR_INVALID_ARG表示句柄无效。ESP_ERR_INVALID_STATE表示定时器已经在运行。3启动一次性定时器esp_timer_start_once该函数用于启动单次性定时器其函数原型如下esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us);函数形参表15.3.1.3 esp_timer_start_once函数形参描述返回值ESP_OK表示成功。ESP_ERR_INVALID_ARG表示句柄无效。ESP_ERR_INVALID_STATE表示定时器已经在运行。4重新启动当前运行的定时器esp_timer_restart该函数用于重新启动当前运行的定时器其函数原型如下esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us);函数形参表15.3.1.4 esp_timer_restart函数形参描述返回值ESP_OK表示表示成功。ESP_ERR_INVALID_ARG表示句柄无效。ESP_ERR_INVALID_STATE表示定时器已经在运行。5停止运行的定时器esp_timer_stop该函数用于停止运行的定时器其函数原型如下esp_err_t esp_timer_stop(esp_timer_handle_t timer);函数形参表15.3.1.5 esp_timer_stop函数形参描述返回值ESP_OK表示成功。ESP_ERR_INVALID_STATE表示定时器未在运行。6删除一个 esp_timer 实例esp_timer_delete该函数删除一个 esp_timer 实例其函数原型如下esp_err_t esp_timer_delete(esp_timer_handle_t timer);函数形参表15.3.1.6 esp_timer_delete函数形参描述返回值ESP_OK表示成功。ESP_ERR_INVALID_STATE表示定时器仍在运行。15.3.2 程序流程图图15.3.2.1 SysTimer实验程序流程图15.3.3 程序解析在05_esptimer例程中作者在05_esptimer\components\BSP路径下新建ESPTIMER文件夹并且需要更改CMakeLists.txt内容以便在其他文件上调用。1SysTimer驱动代码这里我们只讲解核心代码详细的源码请大家参考光盘本实验对应源码。ESPTIMER驱动源码包括两个文件esptimer.c和esptimer.h。esptimer.h主要用于声明esptimer_init等函数以便在其他文件中调用具体内容不再赘述。下面我们再解析esptimer.c的程序看一下初始化函数esptimer_init代码如下/** * brief 初始化高分辨率定时器(ESP_TIMER) * param tps: 定时器周期,以微秒为单位(μs). * 若以一秒为定时器周期来执行一次定时器中断,那此处tps 1s 1000000μs * retval 无 */ void esptimer_init(uint64_t tps) { esp_timer_handle_t esp_tim_handle; /* 定义定时器句柄 */ /* 定义一个定时器结构体设置定时器配置参数 */ esp_timer_create_args_t timer_arg { .callback esptimer_callback, /* 计时时间到达时执行的回调函数 */ .arg NULL, /* 传递给回调函数的参数 */ .dispatch_method ESP_TIMER_TASK, /* 进入回调方式,从定时器任务进入 */ .name Timer, /* 定时器名称 */ }; /* 创建定时器 */ ESP_ERROR_CHECK(esp_timer_create(timer_arg, esp_tim_handle)); /* 启动周期性定时器,tps设置定时器周期(us单位) */ ESP_ERROR_CHECK(esp_timer_start_periodic(esp_tim_handle, tps)); }esptimer_init函数用于初始化并启动一个周期性定时器。它接受一个微秒为单位的周期参数 tps通过定义定时器句柄和配置参数设置了回调函数、参数传递方式以及定时器名称。该函数内部首先创建定时器然后启动它使其按照设定的周期触发回调执行特定的任务。整个过程通过错误检查确保创建和启动的成功适用于需要定时执行操作的场景如数据采集或状态监测。如下是系统定时器回调函数esptimer_callback的处理任务。/** * brief 定时器回调函数 * param arg: 不携带参数 * retval 无 */ void esptimer_callback(void *arg) { LED0_TOGGLE(); }esptimer_callback函数是定时器到期时执行的回调函数。它的参数arg可以用于传递任何额外的信息但在这个实现中未使用。函数内部调用LED0_TOGGLE()用于切换LED0的状实现定时闪烁的效果。2CMakeLists.txt文件本例程的功能实现主要依靠ESPTIMER驱动。要在main函数中成功调用ESPTIMER文件中的内容就得需要修改BSP文件夹下的CMakeLists.txt文件重点看红色内容修改如下set(src_dirs LED ESPTIMER) set(include_dirs LED ESPTIMER) set(requires driver esp_timer) idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs} REQUIRES ${requires}) component_compile_options(-ffast-math -O3 -Wno-errorformat-Wno-format)3main.c驱动代码在main.c里面编写如下代码。void app_main(void) { esp_err_t ret; ret nvs_flash_init(); /* 初始化NVS */ if(ret ESP_ERR_NVS_NO_FREE_PAGES || ret ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ESP_ERROR_CHECK(nvs_flash_init()); } led_init(); /* 初始化LED */ esptimer_init(1000000); /* 初始化ESP_TIMER */ while(1) { vTaskDelay(pdMS_TO_TICKS(10)); /* 延时10ms */ } }