如何在定时器中断中实现PWM波形切换?

目录

[1. 方法一:软件模拟 PWM(适用于无硬件PWM或低频场景)](#1. 方法一:软件模拟 PWM(适用于无硬件PWM或低频场景))

[2.方法二:硬件PWM + 更新中断(推荐,精度高)](#2.方法二:硬件PWM + 更新中断(推荐,精度高))

3.两种方案对比


在中断中实现 PWM 波形切换,核心思路是利用定时器的中断功能,在精确的时间点通过软件来控制 GPIO 引脚的电平高低,从而"手动"合成出 PWM 波形。

主要有两种实现方式,它们的复杂度和精度有所不同。

1. 方法一:软件模拟 PWM(适用于无硬件PWM或低频场景)

这种方法完全依靠中断和软件来翻转 IO 口。你需要配置一个定时器,让它以一个固定的、较短的时间间隔(我们称之为"节拍")产生中断。

核心逻辑:

  1. 定义参数: 设定 PWM 的总周期(total_time)和高电平持续时间(high_time),单位都是"节拍"数。
  2. 设置中断: 配置定时器,使其每隔一个"节拍"就触发一次中断。
  3. 在中断中判断: 在中断服务函数(ISR)里,使用一个计数器(pwm_counter)来记录当前处于周期的哪个位置。
    • 如果 pwm_counter 小于 high_time,则将 GPIO 设置为高电平。
    • 否则,将 GPIO 设置为低电平。
  4. 周期重置: 每次中断后 pwm_counter 加 1。当 pwm_counter 达到 total_time 时,将其重置为 0,开始下一个 PWM 周期。

代码示例 (以 51 单片机风格为例):

复制代码
sbit PWM_PIN = P1^0; // 定义PWM输出引脚
unsigned int pwm_counter = 0;
unsigned int high_time = 500;   // 高电平持续500个节拍
unsigned int total_time = 1000; // 总周期1000个节拍

// 定时器中断服务程序,假设每10us中断一次(一个节拍)
void Timer_ISR(void) interrupt 1 
{
    // 1. 根据计数器判断输出高电平还是低电平
    if(pwm_counter < high_time) 
    {
        PWM_PIN = 1; // 输出高电平
    } 
    else 
    {
        PWM_PIN = 0; // 输出低电平
    }
    
    // 2. 计数器递增
    pwm_counter++;
    
    // 3. 检查是否完成一个完整周期
    if(pwm_counter >= total_time) 
    {
        pwm_counter = 0; // 周期结束,重置计数器
    }
}

通过修改 high_time 的值,就可以在不改变频率(total_time 不变)的情况下,动态调整 PWM 的占空比。

2.方法二:硬件PWM + 更新中断(推荐,精度高)

这是更优的方案。它利用 MCU 内置的硬件 PWM 模块来生成稳定的波形,而中断仅用于在特定时刻(如每个周期结束时)更新 PWM 的参数(如占空比或频率)。

核心逻辑:

  1. 配置硬件PWM: 首先,配置定时器工作在 PWM 模式。设置好自动重装载值(ARR,决定频率)和初始的捕获/比较值(CCR,决定初始占空比)。启动 PWM 后,硬件会自动生成波形,无需 CPU 干预。
  2. 使能更新中断: 开启定时器的"更新中断"(Update Interrupt)。这个中断会在定时器计数器溢出(即一个 PWM 周期结束)时触发。
  3. 在中断中更新参数: 在中断服务函数中,你不再操作 GPIO,而是修改定时器的 CCR 寄存器(或 ARR 寄存器来变频)。
    • 修改 CCR:可以平滑地改变下一个周期的占空比。
    • 修改 ARR:可以改变 PWM 的频率。

代码示例 (以 STM32 HAL库为例):

复制代码
TIM_HandleTypeDef htim3; // 假设使用 TIM3

// 1. 初始化并启动硬件PWM (在主函数中调用)
void PWM_Init() {
    // ... (此处省略定时器和PWM通道的详细初始化代码)
    // 假设已配置好:频率1kHz, 初始占空比50% (ARR=999, CCR=500)
    
    // 使能更新中断
    __HAL_TIM_ENABLE_IT(&htim3, TIM_IT_UPDATE);
    
    // 启动PWM
    HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
}

// 2. 更新中断服务函数
void TIM3_IRQHandler(void) 
{
    // 检查是否是更新中断
    if (__HAL_TIM_GET_FLAG(&htim3, TIM_FLAG_UPDATE) != RESET) 
    {
        // 清除中断标志
        __HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);
        
        // --- 在此处实现你的波形切换逻辑 ---
        // 例如:动态改变占空比,实现呼吸灯效果
        static uint16_t new_duty = 500;
        new_duty = (new_duty + 10) % 1000; // 占空比在0-100%之间变化
        
        // 安全地更新CCR值,改变下一个周期的占空比
        __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, new_duty);
    }
}

3.两种方案对比

特性 软件模拟 PWM 硬件PWM + 更新中断
CPU占用率 高,每个节拍都需进入中断 极低,仅周期更新时进入中断
波形精度 较低,受中断延迟和抖动影响 非常高,由硬件定时器保证
实现复杂度 简单,逻辑直观 稍复杂,需理解硬件PWM配置
适用场景 无硬件PWM模块、低频控制 电机控制、精密电源等绝大多数应用

除非你的 MCU 没有硬件 PWM 功能,或者对频率要求极低(如几赫兹),否则强烈推荐使用方法二。它能以最小的 CPU 开销获得最稳定、最精确的 PWM 波形。

相关推荐
La Pulga1 小时前
【FreeRTOS源码学习笔记】任务切换
单片机·mcu·学习·嵌入式·freertos·实时操作系统
女神下凡4 小时前
芯参谋(85):Linux_PPI_NAND_驱动开发指南
linux·运维·arm开发·驱动开发·单片机·嵌入式硬件·设计规范
Zw-awa6 小时前
从 CH340 到 HC-05 和 HC-06:让小车脱离电脑
stm32
硅基手札6 小时前
【Linux 内核专栏 19】20 · 嵌入式子系统 — GPIO·pinctrl·clock·regulator
linux·运维·单片机·linux内核·嵌入式开发·调试定位
何粥学8 小时前
Vivado 2024.2 开发记录(AC820 Zynq-7020)——软件使用,点亮led灯
单片机·嵌入式硬件·zynq
Sakura_j_h_9 小时前
51 单片机驱动四位数码管:动态扫描、限流电阻与消影的取舍
单片机·嵌入式硬件·51单片机·嵌入式·数码管·动态扫描·限流电阻
HANDY0920010 小时前
从“不照人”到“剂量达标”:扇叶遮挡式UV-C空气消毒的工程边界与可验证评估框架
开发语言·人工智能·嵌入式硬件·智能家居
飞凌嵌入式13 小时前
千次静电放电试炼,守住飞凌嵌入式硬件可靠底线
嵌入式硬件
蹊黎21 小时前
Linux 下用 C 语言实现进度条
linux·c语言·单片机
Zw-awa1 天前
从 CPU 搬运到自动搬运:DMA、空闲中断和两块缓冲
stm32