RT-Thread硬件定时器HWTIMER实战:在STM32F1上实现5秒周期任务(附完整代码)
1. 环境准备与基础概念
在开始硬件定时器的实战之前,我们需要先了解一些基本概念和准备工作。RT-Thread的硬件定时器(HWTIMER)设备提供了一套统一的接口来访问芯片内部的硬件定时器,使得开发者可以方便地实现精确的定时功能。
硬件准备:
- STM32F1系列开发板(如正点原子MiniSTM32、野火指南者等)
- USB转串口工具(用于调试输出)
- ST-Link或其他调试器(可选,用于程序下载和调试)
软件准备:
- RT-Thread Studio或Keil MDK开发环境
- RT-Thread源码(4.0.0或以上版本)
- STM32CubeMX(用于生成HAL库初始化代码)
硬件定时器与软件定时器的主要区别在于精度和资源占用。硬件定时器直接使用芯片内部的定时器外设,具有更高的精度(通常可达微秒级)且不占用CPU资源;而软件定时器基于系统tick实现,精度受限于系统tick周期(通常为1ms或10ms)。
2. 工程配置与硬件定时器初始化
2.1 RT-Thread Settings配置
首先,我们需要在RT-Thread Settings中启用硬件定时器支持:
- 打开项目中的RT-Thread Settings配置文件
- 在"硬件"选项卡下找到"硬件定时器设备驱动"
- 勾选启用该选项
- 保存配置,系统会自动生成相应的宏定义
2.2 board.h文件修改
接下来,我们需要在board.h文件中配置具体的定时器:
/* 硬件定时器配置 */ #define BSP_USING_TIM #define BSP_USING_TIM2 // 使用TIM2定时器注意:根据实际使用的定时器修改宏定义,例如使用TIM3则改为BSP_USING_TIM3。
2.3 CubeMX配置
使用STM32CubeMX进行定时器配置:
- 打开CubeMX,加载对应芯片的配置文件
- 在"Timers"选项卡中选择要使用的定时器(如TIM2)
- 配置定时器参数:
- Clock Source: Internal Clock
- Prescaler: 根据需求设置
- Counter Mode: Up
- Period: 自动重装载值
- 其他参数保持默认
- 生成代码,将
stm32f1xx_hal_msp.c中的HAL_TIM_Base_MspInit函数复制到board.c中
常见问题解决:
- 如果编译报错函数重复定义,删除
board.c中已有的HAL_TIM_Base_MspInit函数 - 确保
stm32f1xx_hal_conf.h中HAL_TIM_MODULE_ENABLED宏已取消注释
3. 硬件定时器应用开发
3.1 定时器设备查找与初始化
在main.c中,我们首先需要查找并初始化定时器设备:
#include <rtthread.h> #include <rtdevice.h> #define HWTIMER_DEV_NAME "timer2" // 定时器设备名称 static rt_device_t hw_dev = RT_NULL; /* 查找并打开定时器设备 */ int hwtimer_init(void) { rt_err_t ret = RT_EOK; /* 查找定时器设备 */ hw_dev = rt_device_find(HWTIMER_DEV_NAME); if (hw_dev == RT_NULL) { rt_kprintf("找不到 %s 设备!\n", HWTIMER_DEV_NAME); return -RT_ERROR; } /* 以读写方式打开设备 */ ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); if (ret != RT_EOK) { rt_kprintf("打开 %s 设备失败!\n", HWTIMER_DEV_NAME); return ret; } return RT_EOK; }3.2 定时器回调函数实现
定时器超时回调函数是定时器应用的核心部分,需要注意以下几点:
- 回调函数中不能使用可能导致阻塞的函数(如
rt_thread_mdelay) - 回调函数执行时间应尽可能短
- 如果需要执行耗时操作,建议使用消息队列或信号量通知其他线程处理
/* 定时器超时回调函数 */ static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) { static rt_uint32_t count = 0; rt_kprintf("定时器超时回调! 计数: %d\n", count++); rt_kprintf("当前系统tick: %d\n", rt_tick_get()); return RT_EOK; }3.3 定时器参数配置与启动
配置定时器参数并启动定时器:
int hwtimer_start(void) { rt_err_t ret = RT_EOK; rt_hwtimerval_t timeout_s; rt_hwtimer_mode_t mode; rt_uint32_t freq = 10000; // 计数频率10kHz /* 设置超时回调函数 */ rt_device_set_rx_indicate(hw_dev, timeout_cb); /* 设置计数频率 */ ret = rt_device_control(hw_dev, HWTIMER_CTRL_FREQ_SET, &freq); if (ret != RT_EOK) { rt_kprintf("设置计数频率失败! 错误码: %d\n", ret); return ret; } /* 设置模式为周期性定时器 */ mode = HWTIMER_MODE_PERIOD; ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); if (ret != RT_EOK) { rt_kprintf("设置定时器模式失败! 错误码: %d\n", ret); return ret; } /* 设置定时器超时值为5s并启动定时器 */ 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("设置超时值失败\n"); return -RT_ERROR; } return RT_EOK; }4. 完整示例与调试技巧
4.1 完整示例代码
将上述代码整合,并导出MSH命令:
#include <rtthread.h> #include <rtdevice.h> #define HWTIMER_DEV_NAME "timer2" static rt_device_t hw_dev = RT_NULL; /* 定时器超时回调函数 */ static rt_err_t timeout_cb(rt_device_t dev, rt_size_t size) { static rt_uint32_t count = 0; rt_kprintf("定时器超时回调! 计数: %d\n", count++); rt_kprintf("当前系统tick: %d\n", rt_tick_get()); return RT_EOK; } /* 硬件定时器示例 */ static int hwtimer_sample(int argc, char *argv[]) { rt_err_t ret = RT_EOK; rt_hwtimerval_t timeout_s; 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("找不到 %s 设备!\n", HWTIMER_DEV_NAME); return -RT_ERROR; } /* 打开设备 */ ret = rt_device_open(hw_dev, RT_DEVICE_OFLAG_RDWR); if (ret != RT_EOK) { rt_kprintf("打开 %s 设备失败!\n", HWTIMER_DEV_NAME); return ret; } /* 设置回调函数 */ rt_device_set_rx_indicate(hw_dev, timeout_cb); /* 设置计数频率 */ ret = rt_device_control(hw_dev, HWTIMER_CTRL_FREQ_SET, &freq); if (ret != RT_EOK) { rt_kprintf("设置计数频率失败! 错误码: %d\n", ret); goto __exit; } /* 设置模式为周期性定时器 */ mode = HWTIMER_MODE_PERIOD; ret = rt_device_control(hw_dev, HWTIMER_CTRL_MODE_SET, &mode); if (ret != RT_EOK) { rt_kprintf("设置定时器模式失败! 错误码: %d\n", ret); goto __exit; } /* 设置超时值并启动定时器 */ 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("设置超时值失败\n"); ret = -RT_ERROR; goto __exit; } rt_kprintf("硬件定时器已启动,5秒周期\n"); __exit: if (ret != RT_EOK) { rt_device_close(hw_dev); } return ret; } /* 导出到MSH命令列表 */ MSH_CMD_EXPORT(hwtimer_sample, 硬件定时器示例); int main(void) { rt_kprintf("硬件定时器示例工程\n"); return 0; }4.2 调试技巧与常见问题
调试技巧:
- 使用
list_device命令查看已注册的设备,确认定时器设备是否成功注册 - 在回调函数中添加
rt_kprintf输出,确认定时器是否正常工作 - 使用逻辑分析仪或示波器测量定���器相关引脚,验证定时精度
常见问题及解决方案:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 找不到定时器设备 | 1. 未在RT-Thread Settings中启用硬件定时器 2. board.h配置错误 3. 驱动未正确初始化 | 1. 检查RT-Thread Settings配置 2. 检查board.h中的宏定义 3. 检查驱动初始化代码 |
| 定时器不触发回调 | 1. 回调函数未正确设置 2. 定时器未启动 3. 计数频率设置不当 | 1. 检查rt_device_set_rx_indicate调用2. 检查 rt_device_write调用3. 调整计数频率 |
| 系统卡死或异常 | 1. 回调函数中使用了阻塞函数 2. 回调函数执行时间过长 | 1. 移除回调函数中的阻塞调用 2. 优化回调函数逻辑 |
性能优化建议:
- 根据实际需求选择合适的计数频率,过高的频率会增加系统开销
- 对于多个定时任务,可以考虑使用单个硬件定时器+软件逻辑的方式实现
- 在低功耗应用中,注意定时器的唤醒配置