Github
整个RTT学习过程中对工程的完善和开发都记录在Github上的一个项目中,项目还会随着学习的深入进行更新
项目URL:https://github.com/HITLIVING/-RT-Thread-.git
读者可以通过Pull下来整个项目进一步了解
对内部定时器驱动的实现需要格外关注drv_timer.c
drv_timer.h
两个文件
外设配置
STM32F1RCT6芯片
Cube
不需要外部引脚,这里只使用普通计时器,cube中仅开启定时器的时钟即可
STM32F1RCT6的TIM6和TIM7为普通计数器
Kconfig
设置定时器的驱动选项
menuconfig BSP_USING_TIM
bool "Enable timer"
default n
select RT_USING_HWTIMER
if BSP_USING_TIM
config BSP_USING_TIM6
bool "Enable TIM6"
default n
config BSP_USING_TIM7
bool "Enable TIM7"
default n
endif
Env
打开对TIM6和TIM7的驱动
使用 Scons
指令生成MDK5工程
MDK
即驱动配置成功
此时编译会报错TIM6和TIM7的初始化未定义
需要在 tim_config.h
文件中添加代码段
``
#ifdef BSP_USING_TIM6
#ifndef TIM6_CONFIG
#define TIM6_CONFIG \
{ \
.tim_handle.Instance = TIM6, \
.tim_irqn = TIM6_IRQn, \
.name = "timer6", \
}
#endif /* TIM6_CONFIG */
#endif /* BSP_USING_TIM6 */
#ifdef BSP_USING_TIM7
#ifndef TIM7_CONFIG
#define TIM7_CONFIG \
{ \
.tim_handle.Instance = TIM7, \
.tim_irqn = TIM7_IRQn, \
.name = "timer7", \
}
#endif /* TIM7_CONFIG */
#endif /* BSP_USING_TIM7 */
设置定时器设备名称和中断等
同时需要检查是否有对应的中断服务函数,如果没有需要手动添加drv_hwtimer.c
#ifdef BSP_USING_TIM6
void TIM6_IRQHandler(void)
{
/* enter interrupt */
rt_interrupt_enter();
HAL_TIM_IRQHandler(&stm32_hwtimer_obj[TIM6_INDEX].tim_handle);
/* leave interrupt */
rt_interrupt_leave();
}
#endif
#ifdef BSP_USING_TIM7
void TIM7_IRQHandler(void)
{
/* enter interrupt */
rt_interrupt_enter();
HAL_TIM_IRQHandler(&stm32_hwtimer_obj[TIM7_INDEX].tim_handle);
/* leave interrupt */
rt_interrupt_leave();
}
#endif
周期中断服务函数
#ifdef BSP_USING_TIM6
if (htim->Instance == TIM6)
{
rt_device_hwtimer_isr(&stm32_hwtimer_obj[TIM6_INDEX].time_device);
}
#endif
#ifdef BSP_USING_TIM7
if (htim->Instance == TIM7)
{
rt_device_hwtimer_isr(&stm32_hwtimer_obj[TIM7_INDEX].time_device);
}
#endif
再编译即可通过,完成对Timer的驱动
接口函数
查找定时器设备
应用程序根据硬件定时器设备名称获取设备句柄,进而可以操作硬件定时器设备,查找设备函数如下所示:
rt_device_t rt_device_find(const char* name);
参数 | 描述 |
---|---|
name | 硬件定时器设备名称 |
返回 | —— |
定时器设备句柄 | 查找到对应设备将返回相应的设备句柄 |
RT_NULL | 没有找到设备 |
打开定时器设备
通过设备句柄,应用程序可以打开设备。打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数 | 描述 |
---|---|
dev | 硬件定时器设备句柄 |
oflags | 设备打开模式,一般以读写方式打开,即取值:RT_DEVICE_OFLAG_RDWR |
返回 | —— |
RT_EOK | 设备打开成功 |
其他错误码 | 设备打开失败 |
设置超时回调函数
通过如下函数设置定时器超时回调函数,当定时器超时将会调用此回调函数:
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 | 超时回调函数,由调用者提供 |
返回 | —— |
RT_EOK | 成功 |
控制定时器设备
通过命令控制字,应用程序可以对硬件定时器设备进行配置,通过如下函数完成:
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
参数 | 描述 |
---|---|
dev | 设备句柄 |
cmd | 命令控制字 |
arg | 控制的参数 |
返回 | —— |
RT_EOK | 函数执行成功 |
-RT_ENOSYS | 执行失败,dev 为空 |
其他错误码 | 执行失败 |
硬件定时器设备支持的命令控制字如下所示:
控制字 | 描述 |
---|---|
HWTIMER_CTRL_FREQ_SET | 设置计数频率 |
HWTIMER_CTRL_STOP | 停止定时器 |
HWTIMER_CTRL_INFO_GET | 获取定时器特征信息 |
HWTIMER_CTRL_MODE_SET | 设置定时器模式 |
当使用第一个控制字HWTIMER_CTRL_FREQ_SET设置计数频率时:
定时器硬件及驱动支持设置计数频率的情况下设置频率才有效,一般使用驱动设置的默认频率即可。
当使用第三个控制字HWTIMER_CTRL_INFO_GET时:
获取定时器特征信息参数 arg 为指向结构体 struct rt_hwtimer_info 的指针,作为一个输出参数保存获取的信息。
当使用第四个关键字设置定时器模式时
参数 arg 可取如下值:
HWTIMER_MODE_ONESHOT 单次定时
HWTIMER_MODE_PERIOD 周期性定时
设置定时器超时值
通过如下函数可以设置定时器的超时值:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos, const void* buffer, rt_size_t size);
参数 | 描述 |
---|---|
dev | 设备句柄 |
pos | 写入数据偏移量,未使用,可取 0 值 |
buffer | 指向定时器超时时间结构体的指针 |
size | 超时时间结构体的大小 |
返回 | —— |
写入数据的实际大小 | |
0 | 失败 |
超时时间结构体原型如下所示:
typedef struct rt_hwtimerval
{
rt_int32_t sec; /* 秒 s */
rt_int32_t usec; /* 微秒 us */
} rt_hwtimerval_t;
获取定时器当前值
通过如下函数可以获取定时器当前值:
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 | 超时时间结构体的大小 |
返回 | —— |
超时时间结构体的大小 | 成功 |
0 | 失败 |
关闭定时器设备
通过如下函数可以关闭定时器设备:
rt_err_t rt_device_close(rt_device_t dev);
参数 | 描述 |
---|---|
dev | 定时器设备句柄 |
返回 | —— |
RT_EOK | 关闭设备成功 |
-RT_ERROR | 设备已经完全关闭,不能重复关闭设备 |
其他错误码 | 关闭设备失败 |
关闭设备接口和打开设备接口需配对使用,
打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。
上机实验
#include <rtthread.h>
#include <rtdevice.h>
#include "drv_timer.h"
/* 定时器超时回调函数 */
static rt_err_t timer6_handle(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;
}
rt_err_t timer0_init(void)
{
rt_err_t ret = RT_EOK;
rt_device_t timer6_dev = RT_NULL; /* 定时器设备句柄 */
/* 查找定时器设备 */
timer6_dev = rt_device_find("timer6");
if (timer6_dev == RT_NULL)
{
rt_kprintf("hwtimer sample run failed! can't find timer6 device!\n");
return ret;
}
/* 以读写方式打开设备 */
ret = rt_device_open(timer6_dev, RT_DEVICE_OFLAG_RDWR);
if (ret != RT_EOK)
{
rt_kprintf("open timer6 device failed!\n");
return ret;
}
/* 设置超时回调函数 */
rt_device_set_rx_indicate(timer6_dev, timer6_handle);
/* 不修改定时器计时频率 */
/* 设置模式为周期性定时器 */
rt_hwtimer_mode_t mode = HWTIMER_MODE_PERIOD;
ret = rt_device_control(timer6_dev, HWTIMER_CTRL_MODE_SET, &mode);
if (ret != RT_EOK)
{
rt_kprintf("timer6 set mode failed! ret is :%d\n", ret);
return ret;
}
/* 设置定时器超时值*/
rt_hwtimerval_t timeout_s;
timeout_s.sec = 5; /* 秒 */
timeout_s.usec = 0; /* 微秒 */
if (rt_device_write(timer6_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s))
{
rt_kprintf("timer6 set timeout value failed\n");
return RT_ERROR;
}
return ret;
}