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

1464

积分

0

好友

216

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

嵌入式系统开发中,定时器是实现精准时间控制、周期性任务调度、延时操作的核心模块。无论是简单的裸机应用还是复杂的实时操作系统,对定时器的理解和熟练应用都至关重要。本文将系统梳理从硬件定时器到软件定时器的完整知识体系,并提供在STM32、RTOS及Linux等不同环境下的实战配置代码。

定时器分类概览

嵌入式定时器主要分为硬件定时器和软件定时器两大类,各自适用于不同的场景。

分类图表

嵌入式定时器
├── 硬件定时器
│   ├── 系统定时器(SysTick,系统滴答)
│   ├── 通用定时器(TIM,支持PWM/捕获/比较)
│   ├── 实时时钟(RTC)
│   ├── 低功耗定时器(LPTIM)
│   └── 看门狗定时器(WDT)
└── 软件定时器
    ├── RTOS软件定时器(如FreeRTOS、RT-Thread)
    ├── 裸机软件定时器(基于硬件定时器实现)
    └── Linux系统定时器(如timerfd、alarm)

特性对比

定时器类型 精度 功耗 主要功能 典型适用场景
SysTick 高 (1ms级) 系统节拍 RTOS任务调度、系统延时
通用TIM 很高 (ns级) PWM/输入捕获/输出比较 电机控制、信号测量
RTC 低 (秒级) 极低 日历/闹钟 时间戳记录、定时唤醒
LPTIM 极低 低功耗定时 电池供电设备、传感器采集
WDT 系统监控 故障检测与恢复
RTOS软件定时器 任务调度 周期性任务管理
timerfd 事件驱动定时 Linux应用开发

硬件定时器详解与配置

SysTick(系统滴答定时器)

特性

  • ARM Cortex-M内核内置的24位递减计数器。
  • 独立于外设,不占用额外的定时器资源。
  • 主要用于为操作系统(如FreeRTOS、RT-Thread)提供任务调度的基准时钟节拍。

工作原理

配置重装载值(LOAD) -> 启动定时器 -> 计数器递减 -> 计数到0触发SysTick中断 -> 执行中断处理程序(进行任务调度等)-> 自动重装载,周而复始。

STM32 HAL库配置示例(1ms中断)

#include "stm32f4xx.h"

// SysTick配置(1ms中断)
void SysTick_Config_1ms(void) {
    // 假设系统时钟为168MHz
    uint32_t sysclk = SystemCoreClock;
    uint32_t reload = sysclk / 1000; // 1ms对应的计数值

    SysTick->LOAD = reload - 1;      // 设置重装载值
    SysTick->VAL = 0;                // 清空当前计数值
    // 使能SysTick,使用系统时钟,并开启中断
    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

    // 用户自定义的节拍计数(例如用于HAL_Delay)
    static uint32_t tick_count = 0;
    tick_count++;
}

// 基于SysTick实现的毫秒延时函数
void delay_ms(uint32_t ms) {
    uint32_t start_tick = HAL_GetTick(); // 此函数依赖于SysTick
    while ((HAL_GetTick() - start_tick) < ms) {
        // 空循环等待
    }
}

应用场景:RTOS任务调度节拍、系统延时函数、提供HAL_GetTick()时间基准。

通用定时器(TIM)

特性

  • 功能极其丰富:支持基本定时、PWM生成、输入捕获、输出比较、编码器接口等。
  • 精度高:通过预分频配置,可实现纳秒级精度。
  • 多通道独立:一个定时器常可支持多路PWM输出或输入捕获。

功能模式

通用定时器 (TIM)
├── 定时模式:产生周期性中断或单次触发。
├── PWM输出模式:用于电机控制、LED调光、蜂鸣器驱动。
├── 输入捕获模式:精确测量脉冲宽度、频率或边沿检测。
├── 输出比较模式:产生精确的延时或特定波形。
└── 编码器模式:读取正交编码器信号。

PWM输出配置示例(1kHz,50%占空比)

#include "stm32f4xx_hal.h"
TIM_HandleTypeDef htim2;

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);

    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // 启动PWM输出
}

// 动态调整PWM占空比
void TIM2_SetDutyCycle(uint16_t duty) {
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, duty);
}

输入捕获配置示例(测量脉冲宽度)

TIM_HandleTypeDef htim3;
volatile uint32_t capture_start = 0, capture_end = 0, pulse_width_us = 0;

void TIM3_InputCapture_Init(void) {
    TIM_IC_InitTypeDef sConfigIC = {0};

    htim3.Instance = TIM3;
    htim3.Init.Prescaler = 168 - 1;      // 1MHz计数频率,1个计数值=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_start = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
    } else if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_2) {
        capture_end = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2);
        // 计算脉冲宽度(微秒),处理计数器溢出
        if (capture_end > capture_start) {
            pulse_width_us = capture_end - capture_start;
        } else {
            pulse_width_us = (0xFFFFFFFF - capture_start) + capture_end;
        }
    }
}

RTC(实时时钟)

特性

  • 提供完整的日历功能(年、月、日、时、分、秒)。
  • 功耗极低,通常由纽扣电池在系统掉电后维持运行。
  • 支持闹钟和自动唤醒功能。

应用场景:为数据记录添加时间戳、在特定时刻触发任务、将系统从低功耗模式唤醒。

STM32 RTC配置示例

#include "stm32f4xx_hal.h"
RTC_HandleTypeDef hrtc;

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);
}

// 设置闹钟(每天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("RTC Alarm Triggered!\r\n");
    // 执行预定任务
}

LPTIM(低功耗定时器)

特性

  • 设计用于低功耗模式(Stop、Standby),可在核心时钟关闭时运行。
  • 由低速内部时钟(LSI)或外部低速晶体(LSE)驱动。
  • 是电池供电设备实现周期性唤醒的关键部件。

STM32 LPTIM配置示例(1秒低功耗定时)

#include "stm32l4xx_hal.h"
LPTIM_HandleTypeDef hlptim1;

void LPTIM1_Init(void) {
    hlptim1.Instance = LPTIM1;
    hlptim1.Init.Clock.Source = LPTIM_CLOCKSOURCE_APBCLOCK_LPOSC; // 使用LSI
    hlptim1.Init.Clock.Prescaler = LPTIM_PRESCALER_DIV128; // LSI=32kHz, 分频后250Hz
    hlptim1.Init.Trigger.Source = LPTIM_TRIGSOURCE_SOFTWARE;
    HAL_LPTIM_Init(&hlptim1);
}

void LPTIM1_Start_Timeout(uint32_t timeout_ms) {
    // 计算计数值:250Hz -> 每4ms计数1次
    uint32_t count = (timeout_ms / 4);
    HAL_LPTIM_TimeOut_Start_IT(&hlptim1, 0xFFFF, count);
}

void HAL_LPTIM_TimeOutCallback(LPTIM_HandleTypeDef *hlptim) {
    // 定时器超时,唤醒系统或执行传感器采样
    printf("LPTIM Wakeup from low-power mode.\r\n");
}

看门狗定时器(WDT)

特性

  • 用于监控系统运行状态,防止程序跑飞或陷入死循环。
  • 需在超时前定期“喂狗”,否则将触发系统复位。
  • 分为独立看门狗(IWDG)和窗口看门狗(WWDG)。

STM32 独立看门狗(IWDG)配置

#include "stm32f4xx_hal.h"
IWDG_HandleTypeDef hiwdg;

void IWDG_Init(void) {
    hiwdg.Instance = IWDG;
    hiwdg.Init.Prescaler = IWDG_PRESCALER_64;   // 预分频
    hiwdg.Init.Reload = 625;                    // 重装载值: (625*64)/32kHz ≈ 1.25秒
    HAL_IWDG_Init(&hiwdg);
}

void IWDG_Feed(void) {
    HAL_IWDG_Refresh(&hiwdg); // “喂狗”,重置计数器
}

int main(void) {
    IWDG_Init(); // 初始化看门狗
    while (1) {
        // 执行主要任务
        do_critical_task();
        // 在预期时间内喂狗,证明程序运行正常
        IWDG_Feed();
        HAL_Delay(500);
    }
}

软件定时器原理与应用

RTOS软件定时器

特性

  • 基于RTOS的系统节拍(Tick)实现。
  • 支持创建、启动、停止、修改周期、单次或周期性触发。
  • 通过回调函数执行定时任务,简化了并发任务的管理。

FreeRTOS软件定时器示例

#include "FreeRTOS.h"
#include "timers.h"

TimerHandle_t xPeriodicTimer, xOneShotTimer;

void vPeriodicTimerCallback(TimerHandle_t xTimer) {
    printf("Periodic timer fired.\r\n");
    // 执行周期性任务,如状态上报
}

void vOneShotTimerCallback(TimerHandle_t xTimer) {
    printf("One-shot timer fired.\r\n");
    // 执行一次性任务,如延时启动
}

void SoftwareTimer_Init(void) {
    // 创建周期性定时器,周期1000ms
    xPeriodicTimer = xTimerCreate(
        "PeriodicTimer",
        pdMS_TO_TICKS(1000),
        pdTRUE,                 // 自动重载
        (void *)0,
        vPeriodicTimerCallback
    );
    // 创建单次定时器,5000ms后触发
    xOneShotTimer = xTimerCreate(
        "OneShotTimer",
        pdMS_TO_TICKS(5000),
        pdFALSE,                // 单次触发
        (void *)1,
        vOneShotTimerCallback
    );

    if (xPeriodicTimer != NULL) xTimerStart(xPeriodicTimer, 0);
    if (xOneShotTimer != NULL) xTimerStart(xOneShotTimer, 0);
}

裸机环境下的软件定时器

实现原理:利用一个硬件定时器(如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 uint32_t sys_tick = 0;

// 在SysTick中断(1ms)中调用此函数
void SoftTimer_TickUpdate(void) {
    sys_tick++;
    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 SoftTimer_Start(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].is_active = true;
            timer_pool[i].is_periodic = periodic;
            timer_pool[i].reload_value = period_ms;
            timer_pool[i].countdown = period_ms;
            timer_pool[i].callback = cb;
            return i; // 返回定时器ID
        }
    }
    return -1; // 创建失败
}

void SoftTimer_Stop(int id) {
    if (id >= 0 && id < MAX_SOFT_TIMERS) {
        timer_pool[id].is_active = false;
    }
}

Linux系统定时器

timerfd(高精度定时器文件描述符)

特性

  • 将定时器抽象为文件描述符,可以方便地集成到select/poll/epoll I/O多路复用机制中。
  • 提供纳秒级的高精度定时。
  • 是现代Linux应用中进行定时事件处理的推荐方式。

使用示例

#include <sys/timerfd.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <stdio.h>
#include <stdint.h>

int main() {
    // 1. 创建timerfd
    int tfd = timerfd_create(CLOCK_MONOTONIC, 0);
    struct itimerspec its = {
        .it_value = { .tv_sec = 1, .tv_nsec = 0 },    // 首次超时:1秒后
        .it_interval = { .tv_sec = 1, .tv_nsec = 0 } // 后续周期:1秒
    };
    timerfd_settime(tfd, 0, &its, NULL);

    // 2. 使用epoll监听timerfd
    int efd = epoll_create1(0);
    struct epoll_event ev = { .events = EPOLLIN, .data.fd = tfd };
    epoll_ctl(efd, EPOLL_CTL_ADD, tfd, &ev);

    struct epoll_event events[5];
    while (1) {
        int n = epoll_wait(efd, events, 5, -1);
        for (int i = 0; i < n; i++) {
            if (events[i].data.fd == tfd) {
                uint64_t exp_cnt;
                read(tfd, &exp_cnt, sizeof(exp_cnt)); // 必须读取,以清空事件
                printf("Timer triggered. Count: %lu\n", exp_cnt);
                // 执行定时任务...
            }
        }
    }
    close(tfd);
    close(efd);
    return 0;
}

传统的信号定时器 (alarm/setitimer)

特性:通过发送信号(如SIGALRM)来通知定时事件,实现简单但精度和灵活性不如timerfd

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void sigalrm_handler(int sig) {
    printf("Received SIGALRM.\n");
}

int main() {
    signal(SIGALRM, sigalrm_handler);
    alarm(5); // 5秒后发送SIGALRM信号
    pause(); // 挂起进程等待信号
    return 0;
}

如何选择合适的定时器?

选择定时器时,需要综合考虑以下四个维度,可以参考以下决策流程:

  1. 精度要求:是否需要纳秒/微秒级(通用TIM),还是毫秒级(SysTick,软件定时器),或是秒级(RTC)即可?
  2. 功能需求:是否需要PWM、输入捕获等特殊功能?
  3. 运行环境:是在裸机、RTOS还是Linux系统下运行?
  4. 资源与功耗:可用定时器外设数量是否紧张?是否有严格的低功耗要求?
应用场景推荐表 应用场景 推荐定时器 关键理由
RTOS任务调度与延时 SysTick 内核内置,零额外外设消耗
电机控制、LED调光 通用TIM (PWM模式) 硬件输出,精度高,性能稳定
测量脉冲频率/宽度 通用TIM (输入捕获) 硬件直接测量,结果精确
为数据添加时间戳 RTC 提供真实日历时间,掉电不丢失
低功耗设备周期性唤醒 LPTIM 在深度睡眠模式下仍可工作
RTOS内的周期性任务 RTOS软件定时器 管理方便,与任务系统集成度高
Linux应用定时任务 timerfd 精度高,可与I/O事件统一处理
防止系统死机 看门狗WDT 硬件级监控,保障系统可靠性

总结

嵌入式系统中的定时器是构建稳定、可靠、高效应用的基础。从硬件的精准控制到软件层面的灵活调度,开发者需要根据具体的精度功能环境功耗约束,在SysTick、通用TIM、RTC、WDT、LPTIM以及各类软件定时器之间做出合理选择。掌握其原理并熟练运用提供的代码框架,将能显著提升嵌入式开发中时间相关功能的实现效率与质量。




上一篇:Harbor镜像仓库Systemd服务配置指南:实现开机自启与稳定运行
下一篇:Java多线程深度解析:规避10大雷区与线程池优化实战
您需要登录后才可以回帖 登录 | 立即注册

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

GMT+8, 2025-12-24 20:53 , Processed in 0.274864 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

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