【星闪】Hi2821 | Timer定时器 + 循环定时例程

1. 简介

前面介绍了 Hi2821 的 SysTick 和 TCXO 定时器,这篇介绍它的另一种更常用的通用定时器。前面的两种定时器可以发现它们的功能会相对简单,无法满足复杂的应用需求,于是这是就需要通用定时器了。

通用定时器除了计时以外还可以触发中断,如超时中断;更重要的是它支持重装载值,在每次达到预设计数值时,定时器会触发超时中断,若该定时器配置为循环触发,那么将会重新装载计数值并重新启动计数。

Hi2821 里面一共有 4 个定时器(timer 0-3),每个定时器内部都分别有 2 个 32bit 的计数寄存器用于计数,以实现更高精度和更长的计时需求。定时器默认使用的是 32MHz 时钟进行计时。

2. 函数接口

  • uapi_timer_adapter(timer_index_t index, uint32_t int_id, uint16_t int_priority):适配定时器配置(index表示硬件定时器索引,int_id表示硬件定时器中断ID,int_priority表示硬件定时器中断优先级)。

  • uapi_timer_init(void):初始化Timer。

  • uapi_timer_deinit(void):去初始化Timer。

  • uapi_timer_create(timer_index_t index, timer_handle_t *timer):创建定时器(index表示硬件定时器索引,timer表示定时器处理返回值)。

  • uapi_timer_delete(timer_handle_t timer):删除指定定时器(timer表示被创建后的定时器)。

  • uapi_timer_start(timer_handle_t timer, uint32_t time_us, timer_callback_t callback, uintptr_t data):开启指定的定时器,开始计时(timer表示被创建后的定时器,time_us表示定时器超时时间,callback表示定时器回调函数,data表示传递给定时器回调函数的参数)。

  • uapi_timer_stop(timer_handle_t timer):停止当前定时器计时(timer表示被创建后的定时器)。

  • uapi_timer_get_max_us(void):用户可以获取到Timer最大可以设置的延时时间(us)。

  • uapi_timer_get_current_time_us(timer_index_t index, uint32_t *current_time_us):获取指定底层Timer定时器的当前时间(us)(index表示底层Timer定时器索引,current_time_us表示底层Timer定时器当前时间us值)。

  • uapi_timer_start_high_precision(timer_index_t index, timer_trigger_mode_t mode, uint32_t time_us, timer_irq_info_t* irq_info, high_precision_timer_callback_t callback):启动一个高精度的专用定时器(index表示硬件定时器索引,mode表示计时模式,time_us表示定时器超时时间,irq_info表示中断信息,callback表示定时器回调函数)。

  • uapi_timer_reset_high_precision(timer_index_t index, timer_trigger_mode_t mode, uint32_t time_us):重启一个高精度的专用定时器(index表示硬件定时器索引,mode表示计时模式,time_us表示定时器超时时间)。

  • uapi_timer_stop_high_precision(timer_index_t index):停止一个高精度的专用定时器(index表示硬件定时器索引)。

  • uapi_timer_suspend(uintptr_t val):挂起定时器模块,低功耗情况使用(val表示挂起时所需要的参数)。

  • uapi_timer_resume(uintptr_t val):恢复定时器模块,低功耗情况使用(val表示恢复时所需要的参数)。

3. 例程

例程包含普通定时器和高精度定时器的使用。

3.1 Kconfig

SDK 中定时器的编译是默认开启的,有几个配置项可以重点关注:

Max timers numTIMER max low layer nums :这两个都是定义定时器的最大数量,但前者是定义软件层面的最大数量,后者是定义硬件层面的最大数量。芯片只有 4 个硬件定时器,所以后者的范围为 0-4;但前者的范围理论上是无限的,不过 SDK 建议设置在 0-8 之间。

Timer Support timer high precision:使能高精度定时器支持。

Using 64 bits width of timer0-3:定时器使用 64 位宽。

解释一下软件和硬件层面的定时器数量:

芯片有多少个硬件定时器是固定无法改变的,但有时候应用中定时器不够用了,就可以使用定时器串联编程,使得在一个硬件定时器中实现多个定时任务。

但这种拓展定时器的方法会损失定时精度,所以需要根据使用场景进行权衡。

3.2 代码

cpp 复制代码
#include "soc_osal.h"
#include "app_init.h"
#include "common_def.h"
#include "timer.h"
#include "chip_core_irq.h"

#include <string.h>
#include <stdint.h>


static timer_handle_t timer1_handle;


static void timer1_callback(uintptr_t data)
{
    unused(data);

    osal_printk("timer 1 timeout\r\n");
    uapi_timer_start(timer1_handle, 1000000, timer1_callback, 0);
}

static void timer0_callback(timer_index_t index)
{
    unused(index);

    osal_printk("timer 0 timeout\r\n");
}

void app_main(void *unused)
{
    (void)(unused);

    uapi_timer_deinit();
    uapi_timer_init();

    /* 初始化定时器0 */
    timer_irq_info_t timer0_irq_info = {
        .irq = TIMER_0_IRQN,
        .priority = 2
    };
    uapi_timer_start_high_precision(TIMER_INDEX_0, TIMER_MODE_PERIODIC, 2000000, &timer0_irq_info, &timer0_callback);

    /* 初始化定时器1 */
    uapi_timer_adapter(TIMER_INDEX_1, TIMER_1_IRQN, 3);
    uapi_timer_create(TIMER_INDEX_1, &timer1_handle);
    uapi_timer_start(timer1_handle, 1000000, timer1_callback, 0);

    while (1) {
        osal_msleep(1000);
    }
}

例程中使用定时器0演示高精度定时器接口,使用定时器1演示普通定时器接口。

高精度定时器函数接口:

仅需调用 uapi_timer_start_high_precision 一个函数即可,参数一为定时器序号,有以下:

cpp 复制代码
typedef enum timer_index {
    TIMER_INDEX_0,                      /*!< Timer0 index. */
    TIMER_INDEX_1,                      /*!< Timer1 index. */
    TIMER_INDEX_2,                      /*!< Timer2 index. */
    TIMER_INDEX_3,                      /*!< Timer3 index. */
    TIMER_MAX_NUM
} timer_index_t;

参数二为定时器的工作模式,定义如下:

cpp 复制代码
typedef enum timer_trigger_mode {
    /** @if Eng  Timer mode: one shot mode.
     *  @else    定时器控制模式:单触发模式。
     *  @endif */
    TIMER_MODE_ONE_SHOT,
    /** @if Eng  Timer mode: periodic mode.
     *  @else    定时器控制模式:周期触发模式。
     *  @endif */
    TIMER_MODE_PERIODIC,
} timer_trigger_mode_t;

参数三为超时时间,单位为微秒;参数四为中断配置结构体,定义如下:

cpp 复制代码
typedef struct timer_irq_info {
    /** @if Eng  irq num.
     *  @else    中断号。
     *  @endif */
    uint32_t irq;
    /** @if Eng  irq priority.
     *  @else    中断优先级。
     *  @endif */
    uint16_t priority;
} timer_irq_info_t;

中断号在 chip_core_irq.h 头文件中可以找到;优先级范围为 0-8,数字越低优先级越高。

参数五为超时回调函数,函数内会传入定时器的序号,所以理论上高精度定时器都可以共用一个回调函数。

普通定时器接口:

先调用 uapi_timer_adapter 函数去配置中断相关参数;接着调 uapi_timer_create 去创建一个定时器实例,返回一个句柄;最后调 uapi_timer_start 函数去开始计时,参数一为定时器句柄,参数二为超时时间,参数三为超时回调函数,参数四为回调函数用户指针。

普通定时器是单次触发的,所以如果要周期性的触发的话,需要在回调函数中重新调用 uapi_timer_start 函数去启动计时。

如果使用普通定时器接口,可以通过多次调用 uapi_timer_create 函数创建多个定时任务,能创建的数量由前面提到的软件层面定时器最大数量决定。

3.3 测试

定时器 0 设置的是 2 秒超时,定时器 1 设置的是 1 秒超时。