2. 通用定时器PWM输出呼吸灯

任务目标

使用STM32通用定时器TIM3的PWM输出通道,驱动LED实现呼吸灯效果。通过不断调整PWM占空比,使LED亮度从暗到亮再回到暗,循环往复,从而理解PWM调光的基本原理。

硬件与依赖

  • 硬件:STM32F103系列开发板(如最小系统板)、LED(接在TIM3_CH1对应的PB4引脚,需串联220Ω限流电阻)
  • 软件:STM32标准外设库(或HAL库,本案例使用标准库)、Keil MDK或IAR编译环境
  • 时钟:系统时钟72MHz,APB1定时器时钟72MHz(若APB1分频为2,则定时器时钟为72MHz)

实现原理

TIM3的CH1输出PWM波形,通过修改捕获/比较寄存器CCR1的值来改变占空比。呼吸灯效果需要占空比按正弦或三角波规律变化。本案例采用三角波方式:占空比从0%逐步增加到100%,再逐步减小到0%,循环执行。使用TIM3的更新中断来定时调整CCR1,实现平滑过渡。

关键完整代码

// main.c 核心代码
#include "stm32f10x.h"

// PWM周期 = 72MHz / (72-1) / (500-1) ≈ 2kHz
#define PWM_PERIOD     500
#define STEP_DELAY     10      // 每步延时(ms),控制呼吸速度

volatile uint16_t pwm_duty = 0;
volatile uint8_t direction = 1;  // 1=增亮, 0=变暗

void TIM3_PWM_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    
    // 使能时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    
    // PB4配置为复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // TIM3时基配置:72MHz/(72-1)=1MHz, 1MHz/500=2kHz
    TIM_TimeBaseStructure.TIM_Period = PWM_PERIOD - 1;
    TIM_TimeBaseStructure.TIM_Prescaler = 72 - 1;
    TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
    
    // PWM模式1,输出极性高
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC1Init(TIM3, &TIM_OCInitStructure);
    TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
    
    // 更新中断配置
    TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
    NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    TIM_Cmd(TIM3, ENABLE);
}

// 简单延时函数(不精确,仅用于主循环节奏控制)
void Delay_ms(uint32_t ms) {
    uint32_t i;
    while(ms--) {
        for(i = 0; i < 8000; i++);
    }
}

int main(void) {
    TIM3_PWM_Init();
    
    while(1) {
        // 每10ms调整一次占空比,步进1/500
        if(direction) {
            pwm_duty++;
            if(pwm_duty >= PWM_PERIOD) direction = 0;
        } else {
            pwm_duty--;
            if(pwm_duty == 0) direction = 1;
        }
        Delay_ms(STEP_DELAY);
    }
}

// TIM3更新中断:更新CCR1值
void TIM3_IRQHandler(void) {
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
        TIM_SetCompare1(TIM3, pwm_duty);
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

编译与运行

  1. 在Keil中创建工程,选择对应型号的STM32芯片,添加上述代码及标准外设库源文件。
  2. 编译工程,通过ST-Link或J-Link下载到开发板。
  3. 确认PB4引脚已连接LED(正极接PB4,负极经电阻接地)。

验证方法

观察LED亮度变化:应呈现从熄灭逐渐变亮到最亮,再逐渐变暗到熄灭的循环过程,每个完整周期约1秒(500步×10ms×2)。若LED常亮或常灭,检查PB4引脚连接是否正确;若亮度无变化但LED亮着,检查TIM_SetCompare1是否在中断中被正确调用;若LED完全不亮,用示波器或逻辑分析仪测量PB4引脚是否有2kHz的PWM波形输出。

常见问题

  • LED不亮:检查GPIO时钟是否使能、PB4是否被JTAG占用(PB4默认是JTAG引脚,需禁用JTAG或使用SWD模式)。在GPIO初始化前添加GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);。
  • 呼吸频率过快/过慢:调整STEP_DELAY的值,增大则变慢,减小则变快。
  • 亮度跳变不连续:确认TIM_SetCompare1在中断中执行,且中断优先级未被其他中断阻塞。