Github

整个RTT学习过程中对工程的完善和开发都记录在Github上的一个项目中,项目还会随着学习的深入进行更新
项目URL:https://github.com/HITLIVING/-RT-Thread-.git
读者可以通过Pull下来整个项目进一步了解
对内部定时器驱动的实现需要格外关注
drv_timer.c
drv_timer.h
两个文件

外设配置

STM32F1RCT6芯片

Cube

不需要外部引脚,这里只使用普通计时器,cube中仅开启定时器的时钟即可
STM32F1RCT6的TIM6和TIM7为普通计数器image.png

Kconfig

设置定时器的驱动选项

  1. menuconfig BSP_USING_TIM
  2. bool "Enable timer"
  3. default n
  4. select RT_USING_HWTIMER
  5. if BSP_USING_TIM
  6. config BSP_USING_TIM6
  7. bool "Enable TIM6"
  8. default n
  9. config BSP_USING_TIM7
  10. bool "Enable TIM7"
  11. default n
  12. endif

Env

打开对TIM6和TIM7的驱动
image.png
image.png
image.png
image.png

使用 Scons 指令生成MDK5工程

MDK

image.png

即驱动配置成功
此时编译会报错TIM6和TIM7的初始化未定义
image.png

需要在 tim_config.h 文件中添加代码段
``

  1. #ifdef BSP_USING_TIM6
  2. #ifndef TIM6_CONFIG
  3. #define TIM6_CONFIG \
  4. { \
  5. .tim_handle.Instance = TIM6, \
  6. .tim_irqn = TIM6_IRQn, \
  7. .name = "timer6", \
  8. }
  9. #endif /* TIM6_CONFIG */
  10. #endif /* BSP_USING_TIM6 */
  11. #ifdef BSP_USING_TIM7
  12. #ifndef TIM7_CONFIG
  13. #define TIM7_CONFIG \
  14. { \
  15. .tim_handle.Instance = TIM7, \
  16. .tim_irqn = TIM7_IRQn, \
  17. .name = "timer7", \
  18. }
  19. #endif /* TIM7_CONFIG */
  20. #endif /* BSP_USING_TIM7 */

设置定时器设备名称和中断等

同时需要检查是否有对应的中断服务函数,如果没有需要手动添加drv_hwtimer.c

  1. #ifdef BSP_USING_TIM6
  2. void TIM6_IRQHandler(void)
  3. {
  4. /* enter interrupt */
  5. rt_interrupt_enter();
  6. HAL_TIM_IRQHandler(&stm32_hwtimer_obj[TIM6_INDEX].tim_handle);
  7. /* leave interrupt */
  8. rt_interrupt_leave();
  9. }
  10. #endif
  11. #ifdef BSP_USING_TIM7
  12. void TIM7_IRQHandler(void)
  13. {
  14. /* enter interrupt */
  15. rt_interrupt_enter();
  16. HAL_TIM_IRQHandler(&stm32_hwtimer_obj[TIM7_INDEX].tim_handle);
  17. /* leave interrupt */
  18. rt_interrupt_leave();
  19. }
  20. #endif

周期中断服务函数

  1. #ifdef BSP_USING_TIM6
  2. if (htim->Instance == TIM6)
  3. {
  4. rt_device_hwtimer_isr(&stm32_hwtimer_obj[TIM6_INDEX].time_device);
  5. }
  6. #endif
  7. #ifdef BSP_USING_TIM7
  8. if (htim->Instance == TIM7)
  9. {
  10. rt_device_hwtimer_isr(&stm32_hwtimer_obj[TIM7_INDEX].time_device);
  11. }
  12. #endif

再编译即可通过,完成对Timer的驱动

接口函数

查找定时器设备

应用程序根据硬件定时器设备名称获取设备句柄,进而可以操作硬件定时器设备,查找设备函数如下所示:

  1. rt_device_t rt_device_find(const char* name);
参数 描述
name 硬件定时器设备名称
返回 ——
定时器设备句柄 查找到对应设备将返回相应的设备句柄
RT_NULL 没有找到设备

打开定时器设备

通过设备句柄,应用程序可以打开设备。打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备:

  1. rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
参数 描述
dev 硬件定时器设备句柄
oflags 设备打开模式,一般以读写方式打开,即取值:RT_DEVICE_OFLAG_RDWR
返回 ——
RT_EOK 设备打开成功
其他错误码 设备打开失败

设置超时回调函数

通过如下函数设置定时器超时回调函数,当定时器超时将会调用此回调函数:

  1. rt_err_t rt_device_set_rx_indicate(rt_device_t dev,
  2. rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size))
参数 描述
dev 设备句柄
rx_ind 超时回调函数,由调用者提供
返回 ——
RT_EOK 成功

控制定时器设备

通过命令控制字,应用程序可以对硬件定时器设备进行配置,通过如下函数完成:

  1. 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 可取如下值:

  1. HWTIMER_MODE_ONESHOT 单次定时
  2. HWTIMER_MODE_PERIOD 周期性定时

设置定时器超时值

通过如下函数可以设置定时器的超时值:

  1. 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 失败

超时时间结构体原型如下所示:

  1. typedef struct rt_hwtimerval
  2. {
  3. rt_int32_t sec; /* 秒 s */
  4. rt_int32_t usec; /* 微秒 us */
  5. } rt_hwtimerval_t;

获取定时器当前值

通过如下函数可以获取定时器当前值:

  1. 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 失败

关闭定时器设备

通过如下函数可以关闭定时器设备:

  1. rt_err_t rt_device_close(rt_device_t dev);
参数 描述
dev 定时器设备句柄
返回 ——
RT_EOK 关闭设备成功
-RT_ERROR 设备已经完全关闭,不能重复关闭设备
其他错误码 关闭设备失败

关闭设备接口和打开设备接口需配对使用,
打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。

上机实验

  1. #include <rtthread.h>
  2. #include <rtdevice.h>
  3. #include "drv_timer.h"
  4. /* 定时器超时回调函数 */
  5. static rt_err_t timer6_handle(rt_device_t dev, rt_size_t size)
  6. {
  7. rt_kprintf("this is hwtimer timeout callback fucntion!\n");
  8. rt_kprintf("tick is :%d !\n", rt_tick_get());
  9. return 0;
  10. }
  11. rt_err_t timer0_init(void)
  12. {
  13. rt_err_t ret = RT_EOK;
  14. rt_device_t timer6_dev = RT_NULL; /* 定时器设备句柄 */
  15. /* 查找定时器设备 */
  16. timer6_dev = rt_device_find("timer6");
  17. if (timer6_dev == RT_NULL)
  18. {
  19. rt_kprintf("hwtimer sample run failed! can't find timer6 device!\n");
  20. return ret;
  21. }
  22. /* 以读写方式打开设备 */
  23. ret = rt_device_open(timer6_dev, RT_DEVICE_OFLAG_RDWR);
  24. if (ret != RT_EOK)
  25. {
  26. rt_kprintf("open timer6 device failed!\n");
  27. return ret;
  28. }
  29. /* 设置超时回调函数 */
  30. rt_device_set_rx_indicate(timer6_dev, timer6_handle);
  31. /* 不修改定时器计时频率 */
  32. /* 设置模式为周期性定时器 */
  33. rt_hwtimer_mode_t mode = HWTIMER_MODE_PERIOD;
  34. ret = rt_device_control(timer6_dev, HWTIMER_CTRL_MODE_SET, &mode);
  35. if (ret != RT_EOK)
  36. {
  37. rt_kprintf("timer6 set mode failed! ret is :%d\n", ret);
  38. return ret;
  39. }
  40. /* 设置定时器超时值*/
  41. rt_hwtimerval_t timeout_s;
  42. timeout_s.sec = 5; /* 秒 */
  43. timeout_s.usec = 0; /* 微秒 */
  44. if (rt_device_write(timer6_dev, 0, &timeout_s, sizeof(timeout_s)) != sizeof(timeout_s))
  45. {
  46. rt_kprintf("timer6 set timeout value failed\n");
  47. return RT_ERROR;
  48. }
  49. return ret;
  50. }