定时器简介
硬件定时器一般有2种工作模式,定时器模式和计数器模式。不管是工作在哪一种模式,实质都是通过内部计数器模块对脉冲信号进行计数,下面是定时器的一些重要概念。
- 计数器模式:对外部输入引脚的外部脉冲信号计数。
- 定时器模式:对内部脉冲信号计数。定时器常用作定时时钟,以实现定时检测,定时响应,定时控制。
计数器:计数器可以递增计数或者递减计数,16位计数器的最大计数值为65535.
计数频率:定时器模式时,计数器单位时间内的计数次数,由于系统时钟频率是定值,所以根据计数器的计数值计算出定时时间,定时时间=计数值/计数频率。
例如计数频率为1MHz,计数器计数一次的时间为1/1000000,也就是没经过1微妙计数器加一,此时16位计数器的最大定时能力为65535微妙,即65.535毫秒。
本定时器设备框架内部会自动处理硬件定时器超时的问题,例如16位定时器在1MHz的频率下最大只能维持65.535ms。
但是本定时器框架下,用户可以将定时器的溢出时间设置为例如500ms,框架内部会自动处理硬件溢出问题。当时间达到500ms后,框架会调用用户预先设置好的回调函数。
访问硬件定时器设备
查找定时器设备
应用程序根据硬件定时器设备名称获取设备句柄,进而可以操作硬件定时器设备。
rt_device_t rt_device_find(const char* name);
一般情况下,注册到系统的硬件定时器设备名称为timer0,timer1等。
#define HWTIMER_DEV_NAME "timer0";
rt_device_t hw_dev;
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
打开定时器设备
通过设备句柄,应用程序可以打开设备。
打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
- dev:硬件定时器设备句柄。
- oflags:设备打开模式,一般以读写方式打开,即RT_DEVICE_OFLAG_RDWR
设置超时回调函数
在C语言和C++语言中,将一个函数声明为’static’具有以下含义:
- 作用域限制:函数声明为’static’会将其作用域限制在当前文件中,这意味着该函数只能在包含它的源文件中调用,而不能在其他文件中调用。这可以用于隐藏函数的实现细节,避免与其他文件中的同名函数发生冲突。
- 链接性:'static’函数具有内部链接性,这意味着它不会被放在全局符号表中,无法被其他文件访问。这有助于减小程序的全局命名空间污染。
rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev, rt_size_t size))
- dev:设备句柄。
- rx_ind:超时回调函数,由调用者提供。
#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */
rt_device_t hw_dev; /* 定时器设备句柄 */
/* 定时器超时回调函数 */
static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size)
{
rt_kprintf("this is hwtimer timeout callback fucntion!\n");
rt_kprintf("tick is :%d !\n", rt_tick_get());
return 0;
}
static int hwtimer_sample(int argc, char *argv[])
{
/* 查找定时器设备 */
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
/* 以读写方式打开设备 */
rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
...
/* 设置超时回调函数 */
rt_device_set_rx_indicate(hw_dev, timeout_cb);
return 0;
}
控制定时器设备
通过命令控制字,应用程序可以对硬件定时器设备进行配置。
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void *arg);
- dev:设备句柄
- cmd:命令控制字
- arg:控制的参数
- 返回:RT_EOK:函数执行成功。-RT_ENSYS:执行失败,dev为空。
硬件定时器设备支持的命令控制字如下:
- HWTIMER_CTRL_FRQ_SET:设置计数频率(若未设置该项,默认为1MHz或支持的最小计数频率)
- HWTIMER_CTRL_STOP:停止计时器
- HWTIMER_CTRL_INFO_GET:获取定时器特征信息
- HWTIMER_CTRL_MODE_SET:设置定时器模式(若未设置,默认是HWTIMER_MODE_ONESHOT)
获取定时器特征信息参数arg为指向结构体struct rt_hwtimer_info的指针,作为一个输出参数保存获取的信息。
设置定时器模式时,参数arg可取:
- HWTIMER_MODE_ONESHOT:单次定时
- HWTIMER_MODE_PERIOD:周期性定时
#define HWTIMER_DEV_NAME "timer0" /* 定时器名称 */
rt_device_t hw_dev; /* 定时器设备句柄 */
rt_hwtimer_mode_t mode; /* 定时器模式 */
rt_uint32_t freq = 10000; /* 计数频率 */
static int hwtimer_sample(int argc, char *argv[])
{
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
rt_device_control(hw_dev, HWTIMER_CTRL_FREQ_SET, &freq);
mode = HWTIMER_MODE_PERIOD;
rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode);
return 0;
}
设置定时器超时值
通过如下函数可以设置定时器的超时值,在调用该函数后,定时器更新参数并开启。
rt_size_t rt_device_write(rt_device_t dev, rt_odd_t pos, const void* buffer, rt_size_t size);
- dev:设备句柄。
- pos:写入数据偏移量,未使用,可取0值。
- buffer:指向定时器超时时间结构体的指针。
- size:超时时间结构体的大小。
- 返回:写入数据的实际大小,0:失败。
超时时间结构体原型:
typedef struct rt_hwtimerval
{
rt_int32_t sec;
rt_int32_t usec;
}
设置定时器超时值的使用示例如下:
#define HWTIMER_DEV_NAME "timer0"
rt_device_t hw_dev; //定时器设备句柄
rt_hwtimerval_t timeout_s; //定时器超时值
static int hwtimer_sample(int argc, char *argv[])
{
//查找定时器设备
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
//设置定时器超时值为5s并启动定时器
timeout_s.sec = 5;
timeout_s.usec = 0;
rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s));
return 0;
}
获取定时器当前值
通过如下函数可以获取自定时器开始(rt_device_write)之后的运行时:
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);
- dev:定时器设备句柄
- pos:写入数据偏移量,未使用,可取0值
- buffer:输出参数,指向定时器超时时间结构体的指针
- size:超时时间结构体的大小
- return:成功-超时时间结构体的大小,0:失败
rt_hwtimerval_t t;
rt_device_read(hw_dev, 0, &t, sizeof(t));
rt_kprintf("Read: Sec = %d, Usec = %d\n", t.sec, t.usec);
关闭定时器设备
通过如下函数关闭定时器设备:
rt_err_t rt_device_close(rt_device_t dev);
- dev:定时器设备句柄
- 返回:RT_EOK-关闭设备成功,-RT_ERROR-设备已经完全关闭,不能重复关闭设备,其它错误码-关闭设备失败。
关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。
注:可能出现定时误差。假设计数器最大值0xFFFF,计数频率1MHz,定时时间1s又1us。
由于定时一次最多只能计时到65535us,对于1000001us的定时要求,可以50000us定时20次完成,此时会出现计算误差1us。
硬件定时器设备完整使用示例
硬件定时器设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:
- 首先根据定时器设备名称“timer0”查找设备获取设备句柄。
- 以读写方式打开设备“timer0”。
- 设置定时器超时回调函数。
- 设置定时器模式为周期性定时器,并设置超时时间为5s,此时定时器启动。
- 延时3500ms后读取定时器时间,读取到的值会以秒和微妙的形式显示。
//例程导出了hwtimer_sample命令到控制终端
//程序功能:硬件定时器超时回调函数周期性的打印当前tick值,2次tick值之差换算为时间等同于定时时间
#include <rtthread.h>
#include <rtdevice.h>
#define HWTIMER_DEV_NAME "timer0" //定时器名称
//定时器超时回调函数
static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size)
{
rt_kprintf("this is hwtimer timeout callback function\n");
rt_kprintf("tick is : %d \n",rt_tick_get());
return 0;
}
static int hwtimer_sample(int argc, char *argv[])
{
rt_err_t ret = RT_EOK;
rt_hwtimerval_t timeout_s; //定时器超时值
rt_device_t hw_dev = RT_NULL;
rt_hwtimer_mode_t mode; //定时器模式
rt_uint32_t freq = 10000; //计数频率
hw_dev = rt_device_find(HWTIMER_DEV_NAME);
if(hw_dev == RT_NULL)
{
rt_kprintf("hwtimer sample run failed! can't find %s device!\n", HWTIMER_DEV_NAME);
return RT_ERROR;
}
ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR);
if(ret != RT_EOK)
{
rt_kprintf("open %s device failed\n",HWTIMER_DEV_NAME);
return ret;
}
rt_device_set_rx_indicate(hw_dev, timeout_cb);
rt_device_control(hw_dev, HWTIMER_CTRL_FREQ_SET, &freq);
mode = HWTIMER_MODE_PERIOD;
ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode);
if (ret != RT_EOK)
{
rt_kprintf("set mode failed! ret is :%d\n", ret);
return ret;
}
timeout_s.sec = 5;
timeout_s.usec = 0;
if(rt_device_write(hw_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s))
{
rt_kprintf("set timeout value failed\n");
return RT_ERROR;
}
rt_thread_mdelay(3500);
rt_device_read(hw_dev, 0, &timeout_s, sizeof(timeout_s));
rt_kprintf("Read: Sec = %d, Usec = %d\n", timeout_s.sec, timeout_s.usec);
return ret;
}
MSH_CMD_EXPORT(hwtimer_sample, hwtimer sample);