ESP32_IDF学习(9)--定时器

定时器

简介

ESP32 芯片提供两组硬件定时器。每组包含 2 个通用硬件定时器。这些 64 位通用定时器均基于 16 位预分频器和 64 位可自动重新加载向上/向下计数器。

功能概述

下文介绍了配置和操作定时器的常规步骤:

  • 定时器初始化 - 启动定时器前应设置的参数,以及每个设置提供的具体功能。
  • 定时器控制 - 如何读取定时器的值,如何暂停/启动定时器以及如何改变定时器的操作方式。
  • 警报- 如何设置和使用警报。
  • 处理中断事务- 如何使用中断提供的回调函数。

定时器初始化

两个 ESP32 定时器组中,每组都有2个定时器,两组共有4个定时器供使用。ESP32 定时器组的类型为timer_group_t,每组中的个体定时器类型为timer_idx_t

首先调用 timer_init 函数,并将 timer_config_t 结构体传递给此函数,用于定义定时器的工作方式,实现定时器初始化。特别注意以下定时器参数可设置为:

  • 时钟源: 选择时钟源,它同时钟分频器一起决定了定时器的分辨率。默认的时钟源是 APB_CLK (一般是 80 MHz)。
  • 分频器: 设置定时器中计数器计数的速度,divider 的设置将用作输入时钟源的除数。
  • 模式: 设置计数器是递增还是递减。可通过从 timer_count_dir_t 中选取一个值,后使用 counter_dir 来选择模式。
  • 计数器使能: 如果计数器已使能,则在调用 timer_init() 后计数器将立即开始递增/递减。您可通过从 timer_start_t 中选取一个值,后使用 counter_en 改变此行为。
  • 报警使能: 可使用 alarm_en 设置。
  • 自动重载: 设置计数器是否应该在定时器警报上使用 auto_reload 自动重载首个计数值,还是继续递增或递减。
  • 16-bit预分频器:分频就是把系统工作频率分频后当做定时器的工作频率,例如系统时钟为12MHz,12分频后定时器的dao工作时钟为1MHz。按照ESP32的输入时钟频率为80MHZ,换句话说也就是1/80us=0.0125us就会计数加一,如何我们设置分频系数为80,则1us就会计数加一。分频系数范围是0-65536。
  • 64-bit时基计数器:这个更简单,就是累加计数器,按照输出时钟,每过一个’波‘就加一。它的计数范围是0-0xFFFF FFFF FFFF FFFF,非常大大大大大的数。

要获取定时器设置的当前值,请使用函数timer_get_config

定时器控制

定时器使能后便开始计数。要使能定时器,可首先设置 counter_entrue,然后调用函数 timer_init,或者直接调用函数 timer_start。您可通过调用函数 timer_set_counter_value 来指定定时器的首个计数值。要检查定时器的当前值,调用函数 timer_get_counter_valuetimer_get_counter_time_sec

可通过调用函数timer_pause 随时暂停定时器。要再次启动它,调用函数 timer_start

要重新配置定时器,可调用函数 timer_init,该函数详细介绍见定时器初始化

除此之外,还可通过使用专有函数更改个别设置来重新配置定时器:

设置专有函数描述
分频器timer_set_divider()更改计数频率。为避免发生不可预测情况,更改分频器时应暂停定时器。如果定时器正在运行,则使用timer_set_divider 将其暂停并更改设置,然后重启定时器。
模式timer_set_counter_mode()设置计数器应递增还是递减
自动重载timer_set_auto_reload()设置是否应在定时器警报上重载首个计数值

警报

要设置警报,先调用函数 timer_set_alarm_value,然后使用 timer_set_alarm 使能警报。当调用函数 timer_init 时,也可以在定时器初始化阶段使能警报。

警报已使能且定时器达到警报值后,根据配置,可能会出现以下两种行为:

  • 如果先前已配置,此时将触发中断。有关如何配置中断,请参见处理中断事务
  • auto_reload 已使能,定时器的计数器将重新加载,从先前配置好的值开始再次计数。应使用函数 timer_set_counter_value() 预先设置该值。
  • 如果已设置警报值且定时器已超过该值,则将立即触发警报。
  • 一旦触发后,警报将自动关闭,需要重新使能以再次触发。

要检查某特定的警报值,调用函数 timer_get_alarm_value()

处理中断事务

通过调用 timer_isr_callback_add() 函数并向该函数传递组 ID、定时器 ID、回调处理程序以及用户数据,可以给某个定时器注册一个中断回调函数。回调处理程序会在 ISR 上下文中调用,因此用户不能在回调函数中放置任何会阻塞 CPU 的 API。

相较于从头编写中断处理程序,使用中断回调函数的好处是,用户无需检测和处理中断的状态位,这些操作会由驱动中默认的中断处理程序替我们完成。

使用例子

开头数据定义

#define TIMER_DIVIDER (16)                           // 硬件定时器时钟分频器    1/80Mhz=0.0125us   *16=0.2us
#define TIMER_SCALE (TIMER_BASE_CLK / TIMER_DIVIDER) //

typedef struct
{
    int timer_group;
    int timer_idx;
    int alarm_interval;
    bool auto_reload;
} timer_info_t; //另存定时器信息

static xQueueHandle timer_queue; //创建一个消息队列,用于调出中断中的参数

回调函数

static bool IRAM_ATTR timer_isr_callback(void *args)
{
    BaseType_t high_task_awoken = pdFALSE;
    timer_info_t *info = (timer_info_t *)args;

  //  uint64_t timer_counter_value = timer_group_get_counter_value_in_isr(info->timer_group, info->timer_idx);

    //将定时器中断信息传递给队列中,可用此方法判断是那个定时器中断。
    
    // xQueueSendFromISR(timer_queue, (void*)&timer_counter_value, &high_task_awoken);
    xQueueSendFromISR(timer_queue, (void *)&info, &high_task_awoken);

    return high_task_awoken;
}

定时器初始化

static void timer_isr_init(int group, int timer, bool auto_reload, int timer_interval_sec)
{
    /*选择并初始化计时器的基本参数*/
    timer_config_t config = {
        .divider = TIMER_DIVIDER,
        .counter_dir = TIMER_COUNT_UP,
        .counter_en = TIMER_PAUSE,
        .alarm_en = TIMER_ALARM_EN,
        .auto_reload = auto_reload,
    }; //默认时钟源为APB 80mhz
    timer_init(group, timer, &config);

    /*计时器计数器最初将从以下值开始。
    此外,如果设置了自动重新加载,该值将在报警时自动重新加载*/
    timer_set_counter_value(group, timer, 0);

    /*配置报警值和报警中断*/
    timer_set_alarm_value(group, timer, timer_interval_sec * TIMER_SCALE);
    timer_enable_intr(group, timer);
    //这一步将定时器参数传入回调函数中
    timer_info_t *timer_info = calloc(1, sizeof(timer_info_t));
    timer_info->timer_group = group;
    timer_info->timer_idx = timer;
    timer_info->auto_reload = auto_reload;
    timer_info->alarm_interval = timer_interval_sec;

    timer_isr_callback_add(group, timer, timer_isr_callback, timer_info, 0);

    timer_start(group, timer);
}

主函数和任务函数

void Timer_task(void *pvParameter)
{
    timer_info_t *pt_info;
    while (1)
    {
        if(xQueueReceive(timer_queue,(void *)&pt_info,portMAX_DELAY))
        {
            printf("pt_info->timer_group = %d \n",pt_info->timer_group);
            printf("pt_info->timer_idx = %d \n",pt_info->timer_idx);
            printf("pt_info->alarm_interval = %d \n",pt_info->alarm_interval);
        }
      vTaskDelay(  100 / portTICK_RATE_MS );  
    }
    

}
void app_main(void)
{
    timer_queue = xQueueCreate(10, sizeof(timer_info_t)); //创建一个新的消息队列。为新的队列分配所需的存储内存,并返回一个队列处理。
    timer_isr_init(TIMER_GROUP_1,TIMER_0,true,5);
    timer_isr_init(TIMER_GROUP_0,TIMER_1,true,2);
    printf("timer init .............\n");
    xTaskCreate(Timer_task, "timer_task", 2048, NULL, 5, NULL);
}

  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值