硬件定时器一般有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);
在C语言和C++语言中,将一个函数声明为’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))
#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);
硬件定时器设备支持的命令控制字如下:
获取定时器特征信息参数arg为指向结构体struct rt_hwtimer_info的指针,作为一个输出参数保存获取的信息。
设置定时器模式时,参数arg可取:
#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);
超时时间结构体原型:
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);
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);
关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。
注:可能出现定时误差。假设计数器最大值0xFFFF,计数频率1MHz,定时时间1s又1us。
由于定时一次最多只能计时到65535us,对于1000001us的定时要求,可以50000us定时20次完成,此时会出现计算误差1us。
硬件定时器设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:
//例程导出了hwtimer_sample命令到控制终端
//程序功能:硬件定时器超时回调函数周期性的打印当前tick值,2次tick值之差换算为时间等同于定时时间
#include
#include
#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);