找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1583

积分

0

好友

228

主题
发表于 4 天前 | 查看: 16| 回复: 0

在嵌入式系统开发中,实现精准的时间控制、周期性任务调度以及延时操作,都离不开定时器这一核心组件。无论是直接操作硬件的底层定时器,还是运行在操作系统之上的软件定时器,它们各自在裸机、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多路复用:能直接使用selectpollepoll监听定时事件,与其他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?这直接决定了可用定时器的类型。
  • 资源与功耗:可用硬件定时器数量是否紧张?系统是否有严格的低功耗要求?

掌握各类定时器的工作原理和适用场景,是进行高效、可靠嵌入式系统开发的基本功。希望本文的解析和实战代码能帮助你在项目中更好地驾驭时间。




上一篇:C++内存泄漏常见陷阱:从new/delete误用到智能指针循环引用
下一篇:K8s集群etcd数据库100%碎片化故障排查与恢复实录
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-24 19:13 , Processed in 0.270496 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表