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 num 和 TIMER 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 秒超时。
