在嵌入式系统开发中,实现精准的时间控制、周期性任务调度以及延时操作,都离不开定时器这一核心组件。无论是直接操作硬件的底层定时器,还是运行在操作系统之上的软件定时器,它们各自在裸机、RTOS及Linux等不同环境中扮演着关键角色。
定时器分类概览
分类图示
嵌入式定时器
├── 硬件定时器
│ ├── 系统定时器 (SysTick,系统滴答定时器)
│ ├── 通用定时器 (TIM,支持PWM/捕获/比较)
│ ├── RTC (实时时钟)
│ ├── LPTIM (低功耗定时器)
│ └── WDT (看门狗定时器)
└── 软件定时器
├── RTOS软件定时器 (如FreeRTOS/RT-Thread)
├── 裸机软件定时器 (基于硬件定时器封装)
└── Linux系统定时器
├── timerfd (定时器文件描述符)
├── alarm/setitimer (Unix信号定时器)
└── 高精度定时器 (HRT)
特性对比表
| 定时器类型 |
精度 |
功耗 |
核心功能 |
典型应用场景 |
| SysTick |
高 (ms级) |
低 |
系统节拍 |
RTOS任务调度、系统延时 |
| 通用TIM |
很高 (ns级) |
中 |
PWM输出、输入捕获、输出比较 |
电机控制、信号测量与生成 |
| RTC |
低 (秒级) |
极低 |
日历、闹钟 |
时间戳记录、定时唤醒 |
| LPTIM |
中 |
极低 |
低功耗定时 |
电池供电设备周期性采样 |
| WDT |
低 |
低 |
系统监控 |
程序跑飞或死锁恢复 |
| RTOS软件定时器 |
中 |
中 |
任务调度 |
应用层周期性任务 |
| timerfd |
高 |
中 |
事件驱动定时 |
Linux应用层高精度定时 |
硬件定时器详解与应用
SysTick(系统滴答定时器)
核心特性:
- ARM Cortex-M 内核内置的24位递减计数器。
- 独立于外设,不占用额外的定时器资源。
- 主要服务于操作系统,提供稳定的时间基准。
工作原理流程图:
[配置重装载值] --> [启动定时器] --> [计数器递减] --> [计数到0触发中断]
↑ |
|_______________________________________________|
[自动重装载,形成周期]
STM32 HAL库配置示例 (1ms中断):
#include "stm32f4xx.h"
// SysTick配置(1ms中断)
void SysTick_Config_1ms(void) {
// 系统时钟频率(例如168MHz)
uint32_t sysclk = SystemCoreClock;
// 计算重装载值:1ms = sysclk / 1000
uint32_t reload = sysclk / 1000;
// 配置SysTick寄存器
SysTick->LOAD = reload - 1; // 设置重装载值
SysTick->VAL = 0; // 清除当前计数值
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | // 使用处理器时钟
SysTick_CTRL_TICKINT_Msk | // 使能中断
SysTick_CTRL_ENABLE_Msk; // 启动定时器
}
// SysTick中断服务函数
void SysTick_Handler(void) {
// 如果使用了RTOS,调用其系统节拍处理函数
#ifdef USE_RTOS
osSystickHandler();
#endif
// 自定义的毫秒节拍计数
static uint32_t tick_count = 0;
tick_count++;
}
// 基于SysTick计数的简单延时函数
void delay_ms(uint32_t ms) {
uint32_t start_tick = HAL_GetTick();
while ((HAL_GetTick() - start_tick) < ms) {
// 空循环等待
}
}
主要应用场景:
- RTOS(如FreeRTOS、RT-Thread)的任务调度节拍源。
- 实现
HAL_Delay()或自定义的delay_ms()等系统延时函数。
- 为
HAL_GetTick()提供时间基准。
通用定时器(TIM)
核心特性:
- 功能丰富:支持基础定时、PWM信号生成、输入捕获、输出比较、编码器接口等多种模式。
- 精度极高:通过预分频和自动重载寄存器可实现纳秒级精度控制。
- 多通道:一个定时器常支持多个独立通道,便于同时控制多路信号。
功能模式示意图:
通用定时器 (TIM)
├── 定时模式 --> 周期性中断、单次触发
├── PWM输出模式 --> 电机控制、LED调光、蜂鸣器驱动
├── 输入捕获模式 --> 脉冲宽度测量、频率测量、边沿检测
├── 输出比较模式 --> 精确延时、波形生成
└── 编码器模式 --> 正交编码器读数
PWM输出配置示例 (1kHz, 50%占空比):
#include "stm32f4xx_hal.h"
TIM_HandleTypeDef htim2;
// TIM2配置为PWM输出
void TIM2_PWM_Init(void) {
TIM_OC_InitTypeDef sConfigOC = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 168 - 1; // 预分频:168MHz / 168 = 1MHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 1000 - 1; // 自动重载值:1MHz / 1000 = 1kHz
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim2);
// 配置PWM通道1参数
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 比较值,占空比 = 500/1000 = 50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
// 启动PWM输出
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}
// 动态调整PWM占空比
void TIM2_SetDutyCycle(uint16_t duty) {
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}
输入捕获配置示例 (测量脉冲高电平宽度):
TIM_HandleTypeDef htim3;
volatile uint32_t capture_value1 = 0, capture_value2 = 0, pulse_width = 0;
// TIM3配置为输入捕获模式
void TIM3_InputCapture_Init(void) {
TIM_IC_InitTypeDef sConfigIC = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 168 - 1; // 计数频率1MHz (1us分辨率)
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 0xFFFFFFFF; // 32位计数器满量程
HAL_TIM_IC_Init(&htim3);
// 通道1捕获上升沿
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
sConfigIC.ICFilter = 0;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_1);
// 通道2捕获下降沿
sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_FALLING;
HAL_TIM_IC_ConfigChannel(&htim3, &sConfigIC, TIM_CHANNEL_2);
// 启动捕获并使能中断
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_1);
HAL_TIM_IC_Start_IT(&htim3, TIM_CHANNEL_2);
}
// 输入捕获中断回调函数
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1) {
// 上升沿捕获值
capture_value1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
} else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) {
// 下降沿捕获值
capture_value2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
// 计算脉冲宽度 (单位:微秒)
if (capture_value2 > capture_value1) {
pulse_width = capture_value2 - capture_value1;
} else {
// 处理计数器溢出
pulse_width = (0xFFFFFFFF - capture_value1) + capture_value2;
}
printf("Pulse width: %lu us\r\n", pulse_width);
}
}
RTC(实时时钟)
核心特性:
- 日历功能:提供年、月、日、时、分、秒的计时。
- 超低功耗:通常由纽扣电池等备用电源供电,主系统掉电后仍可运行。
- 闹钟与唤醒:可设定闹钟时间,用于定时触发任务或唤醒系统。
应用场景:
- 为数据记录添加精确的时间戳。
- 实现每天定点执行的任务(如数据上报)。
- 在低功耗系统中作为定时唤醒源。
STM32 RTC基础配置示例:
#include "stm32f4xx_hal.h"
RTC_HandleTypeDef hrtc;
// RTC初始化
void RTC_Init(void) {
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24;
hrtc.Init.AsynchPrediv = 127;
hrtc.Init.SynchPrediv = 255;
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE;
HAL_RTC_Init(&hrtc);
}
// 设置时间
void RTC_SetTime(uint8_t hour, uint8_t min, uint8_t sec) {
RTC_TimeTypeDef sTime = {0};
sTime.Hours = hour;
sTime.Minutes = min;
sTime.Seconds = sec;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
}
// 设置日期
void RTC_SetDate(uint8_t year, uint8_t month, uint8_t day) {
RTC_DateTypeDef sDate = {0};
sDate.Year = year;
sDate.Month = month;
sDate.Date = day;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}
// 配置闹钟(每天10:30触发)
void RTC_SetAlarm(void) {
RTC_AlarmTypeDef sAlarm = {0};
sAlarm.AlarmTime.Hours = 10;
sAlarm.AlarmTime.Minutes = 30;
sAlarm.AlarmTime.Seconds = 0;
sAlarm.AlarmMask = RTC_ALARMMASK_DATEWEEKDAY; // 忽略日期,每天触发
sAlarm.Alarm = RTC_ALARM_A;
HAL_RTC_SetAlarm_IT(&hrtc, &sAlarm, RTC_FORMAT_BIN);
}
// 闹钟中断回调函数
void HAL_RTC_AlarmAEventCallback(RTC_HandleTypeDef *hrtc) {
printf("Daily alarm triggered!\r\n");
// 执行定时任务...
}
LPTIM(低功耗定时器)
核心特性:
- 低功耗运行:可在STOP、SLEEP等低功耗模式下保持工作。
- 低速时钟驱动:通常由LSI(内部低速)或LSE(外部低速)时钟源驱动,功耗极低。
- 适用于电池供电场景:在需要长期待机并间歇性工作的设备中至关重要。
应用场景:
- 低功耗模式下的周期性唤醒(如每秒唤醒一次采集传感器数据)。
- 对功耗极其敏感的便携式设备、IoT节点。
STM32 LPTIM配置示例:
#include "stm32l4xx_hal.h"
LPTIM_HandleTypeDef hlptim1;
// LPTIM1初始化
void LPTIM1_Init(void) {
hlptim1.Instance = LPTIM1;
hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC; // 时钟源
hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV128; // 预分频
hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE; // 软件触发
HAL_LPTIM_Init(&hlptim1);
}
// 启动一个低功耗定时(例如1秒后超时)
void LPTIM1_Start_Timeout(uint32_t period_ms) {
// 假设LSI=32kHz,预分频128后计数频率为250Hz
uint32_t count = (period_ms * 250) / 1000; // 计算所需的计数值
HAL_LPTIM_TimeOut_Start_IT(&hlptim1, 0xFFFF, count);
}
// 超时中断回调
void HAL_LPTIM_TimeOutCallback(LPTIM_HandleTypeDef *hlptim) {
printf("LPTIM timeout, system wake up.\r\n");
// 执行唤醒后的任务,例如采集数据,然后可再次进入低功耗模式
}
看门狗定时器(WDT)
核心特性:
- 系统监控:用于检测软件故障(如程序跑飞、死循环)。
- 需定期“喂狗”:程序需在设定的超时时间内重置看门狗计数器,否则将触发复位。
- 保障系统可靠性:是提高嵌入式系统鲁棒性的重要机制。
工作原理简图:
启动WDT并设置超时时间
|
v
[主程序运行]
| 定期“喂狗”
|-------------------> [重置WDT计数器]
| |
v (若程序异常) v (正常流程)
[未及时喂狗] [计数器被重置,重新计时]
| |
v |
[WDT计数器超时] |
| |
v |
[触发系统复位]-------------------/
STM32独立看门狗(IWDG)配置示例:
#include "stm32f4xx_hal.h"
IWDG_HandleTypeDef hiwdg;
// 独立看门狗初始化(超时时间约1秒)
void IWDG_Init(void) {
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_64; // 预分频系数
hiwdg.Init.Reload = 625; // 重装载值: 625 * 64 / 32kHz ≈ 1秒
if (HAL_IWDG_Init(&hiwdg) != HAL_OK) {
Error_Handler();
}
}
// “喂狗”函数
void IWDG_Feed(void) {
HAL_IWDG_Refresh(&hiwdg);
}
// 在主循环中使用
int main(void) {
// ... 其他初始化
IWDG_Init();
while (1) {
// 1. 执行关键任务
do_critical_task();
// 2. 在超时前喂狗,证明程序运行正常
IWDG_Feed();
// 3. 执行其他非阻塞任务或延时
HAL_Delay(100);
}
}
软件定时器原理与实现
RTOS环境下的软件定时器
核心特性:
- 基于系统Tick:由RTOS内核的时钟节拍驱动。
- 灵活易用:提供创建、启动、停止、修改周期等API,管理方便。
- 回调机制:定时到期后,在RTOS的定时器任务(或中断)上下文中执行用户回调函数。
在复杂的RTOS(如FreeRTOS、RT-Thread)中,软件定时器是管理应用层定时任务的首选工具。
FreeRTOS软件定时器示例:
#include "FreeRTOS.h"
#include "timers.h"
TimerHandle_t xMyTimer;
// 定时器回调函数
void vTimerCallback(TimerHandle_t xTimer) {
uint32_t *pTimerID = (uint32_t *)pvTimerGetTimerID(xTimer);
printf("Timer %lu callback executed.\r\n", *pTimerID);
// 执行具体的定时任务...
}
void create_software_timer(void) {
uint32_t timerID = 1;
// 创建一个周期性定时器,周期1000ms
xMyTimer = xTimerCreate(
"MyTimer", // 定时器名称(调试用)
pdMS_TO_TICKS(1000), // 定时周期,转换为Tick数
pdTRUE, // 自动重载 (周期性)
(void *)&timerID, // 传递给回调的标识符
vTimerCallback // 超时回调函数
);
if (xMyTimer != NULL) {
xTimerStart(xMyTimer, 0); // 启动定时器
}
}
RT-Thread软件定时器示例:
#include <rtthread.h>
static rt_timer_t timer1;
static void timeout_cb(void *parameter) {
rt_kprintf("Periodic timer tick.\r\n");
}
void rt_thread_timer_demo(void) {
// 创建并启动一个周期性软定时器
timer1 = rt_timer_create("timer1",
timeout_cb,
RT_NULL,
RT_TICK_PER_SECOND, // 周期1秒
RT_TIMER_FLAG_PERIODIC | RT_TIMER_FLAG_SOFT_TIMER);
if (timer1 != RT_NULL) {
rt_timer_start(timer1);
}
}
裸机环境下的软件定时器
实现原理:基于一个硬件定时器(如SysTick)中断,在该中断服务函数中维护多个软件定时器结构的倒计时,从而实现“一个硬件定时器,多个软件定时器”的功能。
轻量级实现示例:
#include <stdint.h>
#include <stdbool.h>
#define MAX_SOFT_TIMERS 8
typedef struct {
bool is_active;
bool is_periodic;
uint32_t countdown;
uint32_t reload_value;
void (*callback)(void);
} soft_timer_t;
static soft_timer_t timer_pool[MAX_SOFT_TIMERS];
static volatile uint32_t systick_ticks = 0;
// 在1ms的SysTick中断中调用
void soft_timer_tick_update(void) {
systick_ticks++;
for (int i = 0; i < MAX_SOFT_TIMERS; i++) {
if (timer_pool[i].is_active && timer_pool[i].countdown > 0) {
timer_pool[i].countdown--;
if (timer_pool[i].countdown == 0) {
// 定时器到期
if (timer_pool[i].callback) {
timer_pool[i].callback();
}
// 处理重载或停止
if (timer_pool[i].is_periodic) {
timer_pool[i].countdown = timer_pool[i].reload_value;
} else {
timer_pool[i].is_active = false;
}
}
}
}
}
// 创建一个软件定时器
int soft_timer_create(uint32_t period_ms, bool periodic, void (*cb)(void)) {
for (int i = 0; i < MAX_SOFT_TIMERS; i++) {
if (!timer_pool[i].is_active) {
timer_pool[i].reload_value = period_ms;
timer_pool[i].countdown = period_ms;
timer_pool[i].callback = cb;
timer_pool[i].is_periodic = periodic;
timer_pool[i].is_active = true;
return i; // 返回定时器ID
}
}
return -1; // 创建失败
}
// 启动/重启定时器
void soft_timer_start(int id) {
if (id >= 0 && id < MAX_SOFT_TIMERS) {
timer_pool[id].countdown = timer_pool[id].reload_value;
timer_pool[id].is_active = true;
}
}
Linux系统定时器
timerfd:高精度且易于集成的定时器
核心特性:
- 文件描述符抽象:将定时器抽象为一个文件描述符,可以像文件一样进行读写操作。
- 无缝集成I/O多路复用:能直接使用
select、poll或epoll监听定时事件,与其他socket或文件I/O统一处理。
- 高精度:支持纳秒级定时。
使用示例(结合epoll):
#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>
int create_timerfd(int interval_sec) {
int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
struct itimerspec its = {
.it_interval = {.tv_sec = interval_sec, .tv_nsec = 0}, // 周期
.it_value = {.tv_sec = interval_sec, .tv_nsec = 0} // 首次到期时间
};
timerfd_settime(tfd, 0, &its, NULL);
return tfd;
}
void timerfd_demo(void) {
int timer_fd = create_timerfd(1); // 创建1秒周期的timerfd
int epoll_fd = epoll_create1(0);
struct epoll_event ev, events[5];
ev.events = EPOLLIN;
ev.data.fd = timer_fd;
epoll_ctl(epoll_fd, EPOLL_CTL_ADD, timer_fd, &ev);
while (1) {
int n = epoll_wait(epoll_fd, events, 5, -1);
for (int i = 0; i < n; i++) {
if (events[i].data.fd == timer_fd) {
uint64_t exp_cnt;
read(timer_fd, &exp_cnt, sizeof(exp_cnt)); // 必须读走数据
printf("Timer fired. Count: %llu\n", (unsigned long long)exp_cnt);
// 执行定时任务...
}
}
}
close(timer_fd);
close(epoll_fd);
}
传统信号定时器:alarm 与 setitimer
核心特性:
- 基于信号:定时到期后向进程发送信号(如
SIGALRM)。
- 使用简单:API简洁,适合简单的超时功能。
- 精度与灵活性有限:信号处理上下文限制较多,且精度通常不如
timerfd。
alarm 示例:
#include <unistd.h>
#include <signal.h>
#include <stdio.h>
void sigalrm_handler(int sig) {
printf("Received SIGALRM.\n");
}
int main() {
signal(SIGALRM, sigalrm_handler);
alarm(5); // 设置5秒后发送SIGALRM信号
pause(); // 挂起进程等待信号
return 0;
}
如何选择合适的定时器?
决策流程图
[需要定时器]
|
v
[精度要求?]ns级/ms级/秒级
/ | \
/ | \
(ns级)通用TIM (ms级)SysTick (秒级)RTC
| | |
[功能需求?] [运行环境?] [功耗要求?]
/ | \ / \ / \
PWM 捕获 比较 RTOS 裸机 低功耗 常规
| | | | | |
通用TIM 通用TIM RTOS软件 裸机软件 LPTIM 通用TIM
定时器 定时器 或RTC
应用场景快速参考
| 应用场景 |
推荐定时器类型 |
核心理由 |
| RTOS内核调度 |
SysTick |
内核内置,零额外外设开销,为调度提供稳定心跳。 |
| 电机控制/PWM调光 |
通用TIM (TIM) |
硬件PWM生成,精度高、波形稳定,不占用CPU。 |
| 脉冲频率/宽度测量 |
通用TIM输入捕获 |
硬件直接测量,精度可达微秒或纳秒级,CPU干预少。 |
| 数据记录时间戳 |
RTC |
提供真实的日历时间,掉电不丢失,功耗极低。 |
| 低功耗设备周期性唤醒 |
LPTIM |
可在芯片深度睡眠模式下运行,是实现长待机的关键。 |
| RTOS应用层周期任务 |
RTOS软件定时器 |
使用RTOS标准API,管理方便,支持动态创建和删除。 |
| Linux应用定时 |
timerfd |
可融入epoll事件循环,精度高,编程模型优雅。 |
| 系统死机监控与恢复 |
看门狗 (WDT) |
独立硬件电路,能在软件完全死锁时强制复位系统。 |
总结
嵌入式系统中的定时器是连接软件逻辑与物理时间的桥梁。从最底层的硬件计数器到上层的软件抽象,选择时需综合权衡:
- 精度与功能:是否需要PWM、捕获等高级功能?对定时误差的容忍度如何?
- 系统环境:项目是基于裸机、RTOS还是Linux?这直接决定了可用定时器的类型。
- 资源与功耗:可用硬件定时器数量是否紧张?系统是否有严格的低功耗要求?
掌握各类定时器的工作原理和适用场景,是进行高效、可靠嵌入式系统开发的基本功。希望本文的解析和实战代码能帮助你在项目中更好地驾驭时间。