RT-Thread 软件定时器 5 大 API 实战:单次/周期模式在 STM32 上的 3 个应用案例
RT-Thread 软件定时器 5 大 API 实战:单次/周期模式在 STM32 上的 3 个应用案例
1. 软件定时器基础与核心 API 解析
在嵌入式实时系统中,定时器是控制任务执行节奏的关键组件。RT-Thread 提供的软件定时器功能允许开发者突破硬件定时器数量的限制,创建灵活的定时任务。与硬件定时器不同,软件定时器的超时函数执行在 RT-Thread 的定时器线程上下文中,而非中断环境,这使得开发者可以在回调函数中使用更多的系统 API。
RT-Thread 软件定时器主要提供以下 5 个核心 API:
创建定时器 (rt_timer_create)
动态分配内存并初始化定时器控制块,适用于需要频繁创建/销毁的场景。关键参数包括:rt_timer_t rt_timer_create(const char* name, void (*timeout)(void*), void* parameter, rt_tick_t time, rt_uint8_t flag);name:定时器名称,用于调试timeout:超时回调函数指针time:定时周期(单位:tick)flag:组合标志(单次/周期、软件/硬件模式)
启动定时器 (rt_timer_start)
将定时器激活并插入到系统定时器链表中,开始计时:rt_err_t rt_timer_start(rt_timer_t timer);停止定时器 (rt_timer_stop)
将定时器从活动链表中移除,停止计时但不释放资源:rt_err_t rt_timer_stop(rt_timer_t timer);删除定时器 (rt_timer_delete)
彻底释放定时器占用的内存资源:rt_err_t rt_timer_delete(rt_timer_t timer);控制定时器 (rt_timer_control)
动态修改定时器参数,支持以下命令:rt_err_t rt_timer_control(rt_timer_t timer, rt_uint8_t cmd, void* arg);RT_TIMER_CTRL_SET_TIME:修改定时周期RT_TIMER_CTRL_SET_ONESHOT:切换为单次模式RT_TIMER_CTRL_SET_PERIODIC:切换为周期模式
关键区别:软件定时器(
RT_TIMER_FLAG_SOFT_TIMER)的超时函数执行在线程上下文,允许调用如rt_thread_delay等可能阻塞的 API;而硬件定时器模式(RT_TIMER_FLAG_HARD_TIMER)的超时函数执行在中断上下文,要求快速执行完毕。
2. 案例一:单次定时器控制 LED 闪烁
2.1 硬件连接与初始化
本案例基于 STM32F407 开发板,使用 PE3 引脚连接 LED。首先完成硬件初始化:
#define LED_PIN GET_PIN(E, 3) static void led_init(void) { rt_pin_mode(LED_PIN, PIN_MODE_OUTPUT); rt_pin_write(LED_PIN, PIN_HIGH); // 初始熄灭 }2.2 单次定时器实现
创建单次定时器,在超时后翻转 LED 状态并自动销毁:
static rt_timer_t led_timer = RT_NULL; static void led_timeout(void *param) { rt_uint32_t *count = (rt_uint32_t *)param; rt_pin_write(LED_PIN, !rt_pin_read(LED_PIN)); rt_kprintf("LED toggled %d times\n", ++(*count)); // 定时器自动销毁(单次模式) if (*count >= 5) { rt_kprintf("Timer completed its work\n"); } } int led_blink_sample(void) { static rt_uint32_t toggle_count = 0; led_init(); led_timer = rt_timer_create("led_timer", led_timeout, &toggle_count, 1000, // 1000 ticks = 1s (RT_TICK_PER_SECOND=1000) RT_TIMER_FLAG_ONE_SHOT | RT_TIMER_FLAG_SOFT_TIMER); if (led_timer != RT_NULL) { rt_timer_start(led_timer); rt_kprintf("Single-shot timer started\n"); } return RT_EOK; }关键点说明:
- 使用
RT_TIMER_FLAG_ONE_SHOT标志创建单次定时器 - 定时器超时后自动从系统移除,但控制块仍存在需手动删除
- 通过参数传递计数变量,实现状态保持
2.3 进阶用法:链式单次定时器
实现连续闪烁效果,每次超时后重新启动定时器:
static void chained_timeout(void *param) { rt_pin_write(LED_PIN, !rt_pin_read(LED_PIN)); // 重新配置并启动定时器 rt_timer_control(led_timer, RT_TIMER_CTRL_SET_TIME, (void*)500); rt_timer_start(led_timer); }3. 案例二:周期定时器实现数据采样
3.1 模拟数据采集环境
假设通过 ADC 采集传感器数据,此处用随机数模拟:
static rt_uint16_t mock_adc_read(void) { return rand() % 4096; // 12位ADC模拟值 }3.2 周期定时器配置
创建 100Hz 采样率的周期定时器:
#define SAMPLE_RATE_HZ 100 static rt_timer_t adc_timer = RT_NULL; static rt_uint32_t sample_count = 0; static void adc_sample_timeout(void *param) { rt_uint16_t adc_val = mock_adc_read(); rt_kprintf("[%04d] ADC: %04d\n", ++sample_count, adc_val); } int adc_sample_init(void) { rt_tick_t interval = RT_TICK_PER_SECOND / SAMPLE_RATE_HZ; adc_timer = rt_timer_create("adc_timer", adc_sample_timeout, RT_NULL, interval, RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER); if (adc_timer != RT_NULL) { rt_timer_start(adc_timer); rt_kprintf("ADC sampling started at %dHz\n", SAMPLE_RATE_HZ); } return RT_EOK; }性能优化技巧:
- 使用
RT_TIMER_FLAG_PERIODIC避免重复创建/启动开销 - 计算 tick 间隔时考虑整除问题,避免累积误差
- 在回调函数中避免耗时操作,防止影响定时精度
3.3 动态调整采样率
通过rt_timer_control实现运行时参数调整:
void set_adc_sample_rate(rt_uint32_t new_rate_hz) { rt_tick_t new_interval = RT_TICK_PER_SECOND / new_rate_hz; rt_timer_control(adc_timer, RT_TIMER_CTRL_SET_TIME, &new_interval); rt_kprintf("Sample rate changed to %dHz\n", new_rate_hz); }4. 案例三:动态修改定时周期实现 PWM 调光
4.1 PWM 原理与软件实现
通过动态调整 LED 亮灭时间的占空比,模拟 PWM 输出:
| 参数 | 说明 |
|---|---|
| 周期 | 固定为 20ms (50Hz) |
| 占空比 | 0-100% 可调 |
| 分辨率 | 1% (200μs/步进) |
4.2 代码实现
使用两个定时器分别控制亮灭时间:
static rt_timer_t pwm_on_timer = RT_NULL; static rt_timer_t pwm_off_timer = RT_NULL; static rt_uint8_t pwm_duty = 50; // 初始50%占空比 static void pwm_on_timeout(void *param) { rt_pin_write(LED_PIN, PIN_LOW); // LED亮 rt_timer_start(pwm_off_timer); // 启动灭定时器 } static void pwm_off_timeout(void *param) { rt_pin_write(LED_PIN, PIN_HIGH); // LED灭 rt_timer_start(pwm_on_timer); // 启动亮定时器 } void set_pwm_duty(rt_uint8_t duty) { if (duty > 100) duty = 100; pwm_duty = duty; rt_tick_t on_time = 20 * duty; // 单位:tick (假设1tick=1ms) rt_tick_t off_time = 2000 - on_time; rt_timer_control(pwm_on_timer, RT_TIMER_CTRL_SET_TIME, &on_time); rt_timer_control(pwm_off_timer, RT_TIMER_CTRL_SET_TIME, &off_time); } int pwm_init(void) { led_init(); pwm_on_timer = rt_timer_create("pwm_on", pwm_on_timeout, RT_NULL, 1000, // 初始1ms RT_TIMER_FLAG_ONE_SHOT | RT_TIMER_FLAG_SOFT_TIMER); pwm_off_timer = rt_timer_create("pwm_off", pwm_off_timeout, RT_NULL, 1000, // 初始1ms RT_TIMER_FLAG_ONE_SHOT | RT_TIMER_FLAG_SOFT_TIMER); if (pwm_on_timer && pwm_off_timer) { set_pwm_duty(pwm_duty); rt_timer_start(pwm_on_timer); rt_kprintf("Software PWM initialized at %d%% duty\n", pwm_duty); } return RT_EOK; }关键优化:
- 使用两个单次定时器形成循环触发
- 动态计算亮灭时间实现占空比调整
- 确保总周期时间恒定(20ms)
5. 软件定时器高级应用技巧
5.1 定时器精度优化策略
虽然软件定时器精度受限于系统 tick,但可通过以下方法提高:
提高系统 tick 频率
修改rtconfig.h中的RT_TICK_PER_SECOND,但会增加系统开销补偿技术
记录实际超时时间与理论时间的偏差,下次定时进行补偿:
static rt_tick_t last_time; static rt_int32_t accum_error = 0; static void precise_timeout(void *param) { rt_tick_t now = rt_tick_get(); rt_int32_t deviation = (now - last_time) - target_interval; accum_error += deviation; // 下次定时补偿误差 rt_tick_t adj_interval = target_interval - (accum_error / 4); rt_timer_control(timer, RT_TIMER_CTRL_SET_TIME, &adj_interval); last_time = now; // ... 业务逻辑 ... }5.2 多定时器协同工作
通过一个管理器定时器调度多个任务,减少系统资源占用:
#define MAX_TASKS 5 struct timer_task { rt_tick_t interval; rt_tick_t last_run; void (*func)(void); }; static struct timer_task task_list[MAX_TASKS]; static void scheduler_timeout(void *param) { rt_tick_t now = rt_tick_get(); for (int i = 0; i < MAX_TASKS; i++) { if (now - task_list[i].last_run >= task_list[i].interval) { task_list[i].func(); task_list[i].last_run = now; } } rt_timer_start((rt_timer_t)param); // 重新启动 }5.3 资源管理与注意事项
内存管理
- 动态定时器需配对使用
create/delete - 静态定时器使用
init/detach
- 动态定时器需配对使用
线程安全
- 在中断中操作定时器需使用
rt_interrupt_enter/leave
- 在中断中操作定时器需使用
性能监控
通过list_timer命令查看定时器状态:
msh />list_timer timer periodic timeout flag ------ --------- --------- ------- t1 1000 500 SOFT,PERIODIC t2 2000 1500 SOFT,ONE_SHOT- 常见问题排查
- 定时不准确:检查系统负载和 tick 配置
- 回调未执行:确认定时器是否已启动
- 内存泄漏:确保删除不再使用的定时器
