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);
}
}
编译与运行
- 在Keil中创建工程,选择对应型号的STM32芯片,添加上述代码及标准外设库源文件。
- 编译工程,通过ST-Link或J-Link下载到开发板。
- 确认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在中断中执行,且中断优先级未被其他中断阻塞。