OpenEdv-开源电子网

 找回密码
 立即注册
正点原子全套STM32/Linux/FPGA开发资料,上千讲STM32视频教程免费下载...
查看: 27|回复: 0

《ESP32-P4开发指南— V1.0》第十五章 System Timer实验

[复制链接]

1217

主题

1231

帖子

2

精华

超级版主

Rank: 8Rank: 8

积分
5240
金钱
5240
注册时间
2019-5-8
在线时间
1325 小时
发表于 10 小时前 | 显示全部楼层 |阅读模式
第十五章 System Timer实验

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


2.jpg

3.png

系统定时器(System Timer)是ESP32-P4芯片中的核心外设之一,专为实现精确计时和事件触发而设计。系统定时器提供高分辨率的计数功能,可生成精确的时间中断,广泛应用于操作系统的Tick计时、定时任务的触发、低功耗模式下的时间管理等场景。在本章节中,我们将详细探讨系统定时器的结构、时钟源配置、计数器与比较器的工作机制、以及如何利用这些功能进行应用开发。通过学习本章节,读者将能够掌握如何利用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),支持多种中断触发配置。
2)APB_CLK依赖:系统定时器的寄存器配置等软件操作依赖APB_CLK时钟信号。
3)CNT_CLK生成:CNT_CLK计数时钟由40 MHz的XTAL_CLK提供,经过分频后,频率平均为16 MHz。
4)报警模式:支持目标模式(一次性报警)和周期模式(周期性报警)。
5)软件配置支持:在轻度休眠唤醒后,可加载RTC计时器的睡眠时间。
接下来,我们将查看ESP32-P4系统定时器的结构简图,如下图所示。

第十五章 System Timer实验685.png
图15.1.1 系统定时器结构简图

上图展示了ESP32-P4系统定时器的结构,包括计数单元和比较器的连接关系,主要组件有:
1)计数单元:Timer Counter0(UNIT0)和Timer Counter1(UNIT1),分别用于独立记录计时信息,并可根据需求进行配置。
2)MUX(多路复用器):选择计数单元(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字段实现,如下图所示。

第十五章 System Timer实验1229.png
图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位(见下图所示)。注意:定时器复位后,其寄存器将恢复到默认值。

第十五章 System Timer实验1737.png
图15.1.2.1 控制和操作系统定时器

15.1.3 系统定时器的警报生成机制
系统定时器的定时警报功能至关重要,它可以在特定时间点触发事件或中断处理。系统定时器通过计数器和比较器的配合实现警报生成与触发。图15.1.3.1展示了系统定时器中警报的生成流程,本文将详细讲解该流程的工作机制。

第十五章 System Timer实验1886.png
图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。计数器与CPUx(x:0~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暂停时对计数器UNITn(n:0~1)的配置位。


第十五章 System Timer实验2503.png
图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等),用于设置比较器的工作模式和目标值(x:0~2)。
SYSTIMER_CONF_REG寄存器中的SYSTIMER_TARGETx_WORK_EN位如图所示。


第十五章 System Timer实验3231.png
图15.1.3.3 开启比较器COMP0、COMP1和COMP2

上图的寄存器位是用来开启系统定时器的三个比较器。
SYSTIMER_TARGETx_CONF_REG寄存器中的SYSTIMER_TARGETx_PERIOD_MODE和SYSTIMER_TARGETx_PERIOD(x为比较器0~2的索引)位描述如下图所示。


第十五章 System Timer实验3396.png
图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位。在单次触发模式下,通过对这两个寄存器的配置可以设定所需的警报值,如下图所示。

第十五章 System Timer实验3710.png
图15.1.3.5 配置警报值

3)触发中断信号
当计数器的值达到比较器设置的目标值时,比较器会生成一个Timer Interrupt警报信号,并将该信号发送至CPU中断矩阵(CPU Interrupt Matrix)。中断矩阵负责处理中断信号,并触发相应的中断服务程序(ISR)。
通过对比较器的配置,开发者可以灵活地设置警报时间和触发的中断,利用系统定时器进行高效的事件调度。

15.1.4 系统定时器的比较器及警报生成机制解析
以下是根据前面小节内容总结的系统定时器原理,希望读者能对其有更深入的认识和学习。
根据前面的知识,我们了解到ESP32-P4的系统定时器配备三个52位比较器(COMPx,x = 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提供。假设当前计数值为t2(t2 ≤ 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)内时,也会立即生成警报中断,如下表所示。

1.png
表15.1.4.1 系统定时器警报触发点

注意:无论是在目标模式还是周期模式,实际警报值的低32位和高20位始终可以从SYSTIMER_TARGETx_LO_RO和SYSTIMER_TARGETx_HI_RO读取。

15.2 硬件设计

15.2.1 例程功能
开启高精度定时器,并设置时间为1s,在定时周期内翻转LED0电平状态。

15.2.2 硬件资源
1)LED灯
        LED        0        - IO51
2)Systimer

15.2.3 原理图
本章实验使用的系统定时器为ESP32-P4的片上资源,因此并没有相应的连接原理图。

15.3 程序设计

15.3.1 SysTimer的IDF驱动
SysTimer外设驱动位于ESP-IDF的components\esp_timer目录。该目录中的include文件夹存放SysTimer相关的头文件,声明了SysTimer函数和结构体等;而src文件夹则存放实际的SysTimer操作函数。要使用SysTimer功能,必须先导入以下头文件。
  1. #include "esp_timer.h"
复制代码
接下来,作者将介绍一些常用的SysTimer函数,这些函数的描述及其作用如下:

1,创建系统定时器esp_timer_create
该函数用于创建一个 ESP 定时器实例,其函数原型如下:
  1. <font size="3">esp_err_t esp_timer_create(const esp_timer_create_args_t* create_args,</font>
  2. <font size="3">                           esp_timer_handle_t* out_handle);</font>
复制代码
函数形参:

2.png
表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结构体中的各个成员变量,如下代码所示:
  1. typedef struct {
  2.     esp_timer_cb_t callback;                /* 定时器到期时执行的回调函数 */
  3. void* arg;                               /* 传递给回调的参数 */
  4. /* 从任务或 ISR 调度回调的方法;
  5. 如果未指定,将使用 esp_timer 任务,
  6. 要使 ISR 工作,还需设置 Kconfig 选项
  7. `CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD` */
  8. esp_timer_dispatch_t dispatch_method;   
  9.     const char* name;                         /* 定时器名称,在esp_timer_dump()函数中使用 */
  10.     bool skip_unhandled_events;                /* 设为跳过在轻睡眠状态下的周期性定时器未处理事件 */
  11. } esp_timer_create_args_t;
复制代码
上述结构体用于配置SysTimer的定时参数,以下对各个成员做简单介绍。
1)callback:
设置回调函数。根据esp_timer_cb_t类型编写回调。
2)arg:
设置回调函数传入形参。
3)dispatch_method:
设置调度模式。此字段可配置为ESP_TIMER_TASK定时器任务调度和ESP_TIMER_MA中断调度。
4)name:
定时器名称。
5)skip_unhandled_events
设置执行未处理事件。Flase:关闭;true:开启。

2,启动一个周期性定时器esp_timer_start_periodic
该函数用于启动周期性定时器,其函数原型如下:
  1. esp_err_t esp_timer_start_periodic(esp_timer_handle_t timer, uint64_t period);
复制代码
函数形参:

3.png
表15.3.1.2 esp_timer_start_periodic函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_INVALID_ARG表示句柄无效。
ESP_ERR_INVALID_STATE表示定时器已经在运行。

3,启动一次性定时器esp_timer_start_once
该函数用于启动单次性定时器,其函数原型如下:
  1. esp_err_t esp_timer_start_once(esp_timer_handle_t timer, uint64_t timeout_us);
复制代码
函数形参:

4.png
表15.3.1.3 esp_timer_start_once函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_INVALID_ARG表示句柄无效。
ESP_ERR_INVALID_STATE表示定时器已经在运行。

4,重新启动当前运行的定时器esp_timer_restart
该函数用于重新启动当前运行的定时器,其函数原型如下:
  1. esp_err_t esp_timer_restart(esp_timer_handle_t timer, uint64_t timeout_us);
复制代码
函数形参:

5.png
表15.3.1.4 esp_timer_restart函数形参描述

返回值:
ESP_OK表示表示成功。
ESP_ERR_INVALID_ARG表示句柄无效。
ESP_ERR_INVALID_STATE表示定时器已经在运行。

5,停止运行的定时器esp_timer_stop
该函数用于停止运行的定时器,其函数原型如下:
  1. esp_err_t esp_timer_stop(esp_timer_handle_t timer);
复制代码
函数形参:

6.png
表15.3.1.5 esp_timer_stop函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_INVALID_STATE表示定时器未在运行。

6,删除一个 esp_timer 实例esp_timer_delete
该函数删除一个 esp_timer 实例,其函数原型如下:
  1. esp_err_t esp_timer_delete(esp_timer_handle_t timer);
复制代码
函数形参:

7.png
表15.3.1.6 esp_timer_delete函数形参描述

返回值:
ESP_OK表示成功。
ESP_ERR_INVALID_STATE表示定时器仍在运行。

15.3.2 程序流程图

第十五章 System Timer实验8032.png
图15.3.2.1 SysTimer实验程序流程图

15.3.3 程序解析
在05_esptimer例程中,作者在05_esptimer\components\BSP路径下新建ESPTIMER文件夹,并且需要更改CMakeLists.txt内容,以便在其他文件上调用。

1,SysTimer驱动代码
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。ESPTIMER驱动源码包括两个文件:esptimer.c和esptimer.h。
esptimer.h主要用于声明esptimer_init等函数,以便在其他文件中调用,具体内容不再赘述。
下面我们再解析esptimer.c的程序,看一下初始化函数esptimer_init,代码如下:
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief              初始化高分辨率定时器(ESP_TIMER)</font>
  3. <font size="3"> * [url=home.php?mod=space&uid=271674]@param[/url]            tps: 定时器周期,以微秒为单位(μs).</font>
  4. <font size="3"> *                    若以一秒为定时器周期来执行一次定时器中断,那此处tps = 1s = 1000000μs</font>
  5. <font size="3"> * @retval            无</font>
  6. <font size="3"> */</font>
  7. <font size="3">void esptimer_init(uint64_t tps)</font>
  8. <font size="3">{</font>
  9. <font size="3">    esp_timer_handle_t esp_tim_handle;                   /* 定义定时器句柄  */</font>

  10. <font size="3">    /* 定义一个定时器结构体设置定时器配置参数 */</font>
  11. <font size="3">    esp_timer_create_args_t timer_arg = {</font>
  12. <font size="3">        .callback = &esptimer_callback,                /* 计时时间到达时执行的回调函数 */</font>
  13. <font size="3">        .arg = NULL,                                          /* 传递给回调函数的参数 */</font>
  14. <font size="3">        .dispatch_method = ESP_TIMER_TASK,                /* 进入回调方式,从定时器任务进入 */</font>
  15. <font size="3">        .name = "Timer",                           /* 定时器名称 */</font>
  16. <font size="3">};</font>
  17. <font size="3">/* 创建定时器 */</font>
  18. <font size="3">ESP_ERROR_CHECK(esp_timer_create(&timer_arg, &esp_tim_handle));     </font>
  19. <font size="3">/* 启动周期性定时器,tps设置定时器周期(us单位) */</font>
  20. <font size="3">    ESP_ERROR_CHECK(esp_timer_start_periodic(esp_tim_handle, tps));     </font>
  21. <font size="3">}</font>
复制代码
esptimer_init函数用于初始化并启动一个周期性定时器。它接受一个微秒为单位的周期参数 tps,通过定义定时器句柄和配置参数,设置了回调函数、参数传递方式以及定时器名称。该函数内部首先创建定时器,然后启动它,使其按照设定的周期触发回调,执行特定的任务。整个过程通过错误检查确保创建和启动的成功,适用于需要定时执行操作的场景,如数据采集或状态监测。如下是系统定时器回调函数esptimer_callback的处理任务。
  1. <font size="3">/**</font>
  2. <font size="3"> * @brief             定时器回调函数</font>
  3. <font size="3"> * @param             arg: 不携带参数</font>
  4. <font size="3"> * @retval           无</font>
  5. <font size="3"> */</font>
  6. <font size="3">void esptimer_callback(void *arg)</font>
  7. <font size="3">{</font>
  8. <font size="3">    LED0_TOGGLE();</font>
  9. <font size="3">}</font>
复制代码
esptimer_callback函数是定时器到期时执行的回调函数。它的参数arg可以用于传递任何额外的信息,但在这个实现中未使用。函数内部调用LED0_TOGGLE(),用于切换LED0的状,实现定时闪烁的效果。

2,CMakeLists.txt文件
本例程的功能实现主要依靠ESPTIMER驱动。要在main函数中,成功调用ESPTIMER文件中的内容,就得需要修改BSP文件夹下的CMakeLists.txt文件(重点看红色内容),修改如下:
  1. <font size="3">set(src_dirs</font>
  2. <font size="3">            LED</font>
  3. <font size="3">            ESPTIMER)</font>

  4. <font size="3">set(include_dirs</font>
  5. <font size="3">            LED</font>
  6. <font size="3">            ESPTIMER)</font>

  7. <font size="3">set(requires</font>
  8. <font size="3">            driver</font>
  9. <font size="3">            esp_timer)</font>

  10. <font size="3">idf_component_register(SRC_DIRS ${src_dirs} INCLUDE_DIRS ${include_dirs}</font>
  11. <font size="3">                       REQUIRES ${requires})</font>

  12. <font size="3">component_compile_options(-ffast-math -O3 -Wno-error=format=-Wno-format)</font>
复制代码

3,main.c驱动代码
在main.c里面编写如下代码。
  1. <font size="3">void app_main(void)</font>
  2. <font size="3">{</font>
  3. <font size="3">    esp_err_t ret;</font>
  4. <font size="3">    </font>
  5. <font size="3">    ret = nvs_flash_init();                             /* 初始化NVS */</font>

  6. <font size="3">    if(ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)</font>
  7. <font size="3">    {</font>
  8. <font size="3">        ESP_ERROR_CHECK(nvs_flash_erase());</font>
  9. <font size="3">        ESP_ERROR_CHECK(nvs_flash_init());</font>
  10. <font size="3">    }</font>

  11. <font size="3">    led_init();                                         /* 初始化LED */</font>
  12. <font size="3">    esptimer_init(1000000);                             /* 初始化ESP_TIMER */</font>

  13. <font size="3">    while(1)</font>
  14. <font size="3">    {</font>
  15. <font size="3">        vTaskDelay(pdMS_TO_TICKS(10));          /* 延时10ms */</font>
  16. <font size="3">    }</font>
  17. <font size="3">}</font>
复制代码
app_main函数首先调用led_init和esptimer_init,分别用于初始化LED和配置系统定时器,设定定时需求为1秒。接着,通过一个无限循环与10毫秒的延时保持主任务的运行,这样应用程序在后台持续待命,以便能够及时响应定时器产生的中断事件。这种结构确保了程序的高效运行和对定时任务的及时处理。

15.4 下载验证
程序下载完成后,LED0在1s周期内电平状态翻转。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


关闭

原子哥极力推荐上一条 /1 下一条

正点原子公众号

QQ|手机版|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2025-12-18 20:30

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

快速回复 返回顶部 返回列表