4、定时器与PWM应用:SysTick系统滴答定时器、通用定时器中断、PWM输出控制电磁锁蜂鸣器、输入捕获测量刷卡时长

定时器,说白了就是单片机的“心跳”。没有它,很多时序相关的功能都玩不转。我刚开始做门禁系统时,觉得定时器不就是计个数嘛,有啥难的?结果真到项目里,才发现这里面的门道不少。今天咱们就把定时器这块彻底捋清楚。

4.1 SysTick系统滴答定时器:操作系统的“节拍器”

SysTick,也叫系统滴答定时器。它是 Cortex-M 内核自带的,24位向下计数。你想想看,它最大的好处是什么?——不占用通用定时器资源。

我个人习惯,只要跑 RTOS,SysTick 必开。它负责给操作系统提供时间基准。比如 FreeRTOS 里的 vTaskDelay(),底层就是靠 SysTick 来计时的。

核心配置要点:

  • 时钟源:一般选内核时钟(HCLK/8 或直接 HCLK)
  • 重装载值:决定中断频率。比如 72MHz 主频,8分频后是 9MHz,要产生 1ms 中断,重装载值就是 9000-1
  • 中断优先级:建议设成最低,别影响关键任务
// SysTick 配置示例(1ms 中断)
void SysTick_Init(void)
{
    // 72MHz / 8 = 9MHz, 9MHz / 9000 = 1KHz = 1ms
    if (SysTick_Config(SystemCoreClock / 8 / 1000))  
    {
        // 配置失败,死循环
        while(1);
    }
}

// 中断服务函数
void SysTick_Handler(void)
{
    static uint32_t tick = 0;
    tick++;
    // 这里可以放系统心跳任务
    // 比如:按键扫描、LED闪烁计时等
}

避坑指南:我曾经在 SysTick 中断里放了一个延时函数,结果系统直接卡死。记住,中断服务函数里千万别放阻塞操作!SysTick 中断频率高,稍微卡一下,整个系统就崩了。

4.2 通用定时器中断:精准定时的“瑞士军刀”

通用定时器比 SysTick 灵活得多。它有多种工作模式:向上计数、向下计数、中央对齐。我项目中用得最多的是向上计数模式。

为什么需要通用定时器?因为 SysTick 只有一个,而且精度有限。比如你要产生一个 10us 的中断,SysTick 就搞不定了。这时候就得请出通用定时器。

参数 SysTick 通用定时器
位数 24位 16位/32位
精度 ms级 us级甚至ns级
通道数 1个 4个(可配置PWM/捕获)
适用场景 系统心跳 PWM、输入捕获、精准延时
// 通用定时器中断配置(TIM3,1ms中断)
void TIM3_Init(void)
{
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    
    // 72MHz / 72 = 1MHz, 1MHz / 1000 = 1KHz = 1ms
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;      // 预分频
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;       // 自动重装载
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // 使能中断
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM3, ENABLE);
}

void TIM3_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        // 用户代码:比如蜂鸣器状态翻转
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

注意:预分频器和自动重装载值的计算要仔细。我见过有人把预分频设成 0,结果定时器直接按 72MHz 跑,1ms 中断变成了 0.013us,CPU 全耗在中断里了。

4.3 PWM输出:控制电磁锁与蜂鸣器

PWM,脉冲宽度调制。说白了就是通过调节占空比来控制输出功率。在门禁系统里,PWM 有两个典型应用:电磁锁的电压控制和蜂鸣器的音调控制。

电磁锁这玩意儿,你想想看,它需要持续供电才能保持吸合。但长时间满功率供电,线圈发热严重。我之前的项目就遇到过,锁体温度能到 70 度,差点把外壳烤变形。

解决方案?用 PWM 控制!上电瞬间给 100% 占空比,确保锁舌吸合。稳定后降到 50% 占空比,保持吸力同时大幅降低功耗和发热。

// PWM 输出配置(TIM2_CH1,PA0)
void PWM_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置 PA0 为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 定时器配置:72MHz / 72 = 1MHz, 周期 = 1000, PWM频率 = 1KHz
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // PWM1 模式:CNT < CCR 时输出有效电平
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 500;  // 初始占空比 50%
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    
    TIM_Cmd(TIM2, ENABLE);
}

// 设置占空比函数
void PWM_SetDuty(uint16_t duty)
{
    if (duty > 1000) duty = 1000;
    TIM_SetCompare1(TIM2, duty);
}

经验之谈:蜂鸣器的 PWM 频率建议设在 2KHz-4KHz 之间。太低会有“哒哒哒”的噪音,太高人耳听不见。我一般用 3KHz,占空比 50%,声音清脆又不刺耳。

4.4 输入捕获:测量刷卡时长

输入捕获,这功能很有意思。它能精确测量外部信号的脉宽或周期。在门禁系统里,我用来测量刷卡时长。

为什么测刷卡时长?因为有些用户刷卡太快,读卡器还没反应过来就移开了。通过输入捕获,我们可以统计每次刷卡的有效时长,低于某个阈值就提示“请重新刷卡”。

工作原理很简单:定时器检测到上升沿时,把当前计数值存到捕获寄存器。检测到下降沿时,再存一次。两次的差值就是高电平脉宽。

// 输入捕获配置(TIM4_CH1,PB6)
void InputCapture_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_ICInitTypeDef TIM_ICInitStructure;
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    // 配置 PB6 为浮空输入
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 定时器配置:72MHz / 72 = 1MHz, 每个计数 = 1us
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;
    TIM_TimeBaseStructure.TIM_Period = 0xFFFF;  // 最大周期
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
    
    // 配置输入捕获通道1
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 上升沿捕获
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
    TIM_ICInitStructure.TIM_ICFilter = 0x0;
    TIM_ICInit(TIM4, &TIM_ICInitStructure);
    
    // 使能捕获中断
    TIM_ITConfig(TIM4, TIM_IT_CC1, ENABLE);
    TIM_Cmd(TIM4, ENABLE);
}

// 中断服务函数
uint32_t capture_value = 0;
uint8_t capture_flag = 0;

void TIM4_IRQHandler(void)
{
    if (TIM_GetITStatus(TIM4, TIM_IT_CC1) != RESET)
    {
        capture_value = TIM_GetCapture1(TIM4);
        capture_flag = 1;
        TIM_ClearITPendingBit(TIM4, TIM_IT_CC1);
    }
}

// 主循环中判断刷卡时长
void CheckCardSwipeDuration(void)
{
    if (capture_flag)
    {
        capture_flag = 0;
        // capture_value 单位是 us
        // 比如要求刷卡时长 > 50ms = 50000us
        if (capture_value < 50000)
        {
            // 刷卡太快,提示重新刷卡
            Buzzer_Beep(3);  // 蜂鸣器响3声提示
        }
        else
        {
            // 刷卡有效,执行开门逻辑
            Door_Unlock();
        }
    }
}

曾经踩过的坑:输入捕获的极性配置要特别注意。我一开始设成了下降沿捕获,结果测出来的脉宽全是 0。后来才发现,信号过来时第一个边沿是上升沿,捕获根本没触发。记住:先确定信号的有效边沿,再配置极性。

4.5 综合应用:定时器与PWM的协同工作

实际项目中,这些定时器功能往往是配合使用的。我给你说个典型场景:

  1. SysTick 提供系统心跳,每 1ms 扫描一次按键状态
  2. 通用定时器 产生 100Hz 中断,用于电磁锁的 PWM 占空比渐变控制(上电时从 0% 平滑升到 100%)
  3. PWM 输出 直接驱动电磁锁和蜂鸣器
  4. 输入捕获 监测刷卡信号,判断刷卡是否有效

你想想看,如果没有定时器,这些功能全得靠软件延时来实现。CPU 会被占得死死的,啥也干不了。有了定时器,CPU 就能腾出手来处理更复杂的逻辑,比如联网通信、数据加密等。

总结一下:

  • SysTick:系统级定时,适合做 RTOS 心跳或简单延时
  • 通用定时器:灵活多变,PWM、输入捕获、精准中断都能干
  • PWM:控制功率输出,省电又高效
  • 输入捕获:测量外部信号,精度可达微秒级

记住一句话:能用硬件定时器解决的问题,千万别用软件延时。这是嵌入式开发的黄金法则。

好了,定时器这块就聊到这儿。下一章咱们开始讲串口通信,这可是门禁系统和上位机沟通的桥梁,到时候见。