在嵌入式开发中,PWM(脉冲宽度调制)技术是实现LED亮度控制的核心方法。我最近在野火STM32开发板上完成了三通道PWM呼吸灯实验,效果相当惊艳。这个项目不仅适合初学者理解PWM工作原理,也是掌握STM32 HAL库定时器应用的经典案例。
呼吸灯的本质是通过PWM占空比的周期性变化来控制LED亮度。当占空比从0%逐渐增加到100%时,LED会从熄灭状态平滑过渡到最亮;反之则逐渐变暗。这种效果类似于人的呼吸节奏,因此得名"呼吸灯"。
我使用的是野火STM32F103指南者开发板,主控为STM32F103VET6。这个板子自带多个LED,我们选择连接在PA1、PA2、PA3引脚上的三个LED作为实验对象。值得注意的是,这块开发板的LED设计是低电平点亮,这意味着当引脚输出低电平时LED亮,高电平时LED灭。这个细节对后续PWM极性配置至关重要。
硬件连接非常简单:
提示:不同开发板的LED连接方式和极性可能不同,务必先查阅原理图确认。如果LED是高电平点亮,后续的PWM极性配置就需要反过来。
我选择TIM2作为PWM发生器,这是STM32F103系列的一个通用定时器,具有4个独立的通道。在CubeMX中的配置步骤如下:
定时器的核心参数有两个:Prescaler(预分频系数)和Counter Period(自动重装载值)。这两个值决定了PWM的频率和分辨率。
我的系统时钟是72MHz,配置如下:
这样计算出的PWM频率为:
PWM频率 = 系统时钟 / (Prescaler+1) / (Counter Period+1)
= 72MHz / 72 / 1000 = 1kHz
1kHz的频率对人眼来说非常合适,既不会出现闪烁,也不会因为频率太高导致亮度变化不明显。而1000的分辨率意味着我们可以有1000级亮度调节,足以实现平滑的呼吸效果。
由于开发板的LED是低电平点亮,我们需要将PWM极性设置为Low。这意味着:
在CubeMX中,这个配置位于每个通道的"CH Polarity"选项,需要分别设置为Low。
生成的初始化代码中,关键部分是TIM2的PWM初始化。CubeMX会自动生成这部分代码,我们只需要确保调用了正确的启动函数:
c复制HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_2);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_4);
这三个函数分别启动了TIM2的三个PWM通道。
呼吸灯效果的核心是通过不断改变CCR值(即占空比)来实现的。我在main.c中定义了以下变量:
c复制uint16_t pwm_val = 0; // 当前CCR值,范围0-1000
int8_t dir = 10; // 变化方向:+10表示变亮,-10表示变暗
在主循环中,我实现了以下逻辑:
c复制while (1) {
// 更新三个通道的CCR值
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, pwm_val);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, pwm_val);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_4, pwm_val);
// 调整CCR值
pwm_val += dir;
// 边界检查
if (pwm_val >= 1000) dir = -10; // 达到最大值后开始变暗
if (pwm_val <= 0) dir = 10; // 达到最小值后开始变亮
HAL_Delay(5); // 控制呼吸节奏
}
这个算法的工作原理是:
在实际调试中,我发现几个可以优化的点:
呼吸节奏:5ms的延迟产生了一个适中的呼吸效果。如果想加快呼吸,可以减少这个值;想减慢则增加。但要注意,太小的延迟可能导致亮度变化太快,失去平滑感。
亮度变化步长:dir的绝对值(10)决定了每次亮度变化的幅度。这个值越大,亮度变化越"跳跃";越小则越平滑。但太小会导致呼吸周期过长。
非线性变化:更高级的实现可以使用非线性函数(如正弦函数)计算pwm_val,这样产生的呼吸效果会更接近自然呼吸的节奏。
如果LED没有任何反应,或者一直亮/灭,可以按照以下步骤排查:
检查GPIO配置:确认引脚模式正确设置为"Alternate Function",而不是普通的输入/输出。
验证定时器时钟:确保TIM2的时钟已使能。可以在代码开头添加__HAL_RCC_TIM2_CLK_ENABLE()双重确认。
检查PWM启动:确认调用了HAL_TIM_PWM_Start()函数,并且参数正确。
测量引脚输出:用示波器或逻辑分析仪观察引脚输出波形,确认PWM信号是否正常产生。
如果LED亮度变化有跳跃感,可能是以下原因:
CCR变化步长太大:尝试减小dir的绝对值,比如从10改为5或2。
PWM频率过低:确保PWM频率至少100Hz以上,否则人眼会察觉到闪烁。
延时不稳定:HAL_Delay()依赖于SysTick,如果系统中有其他中断频繁发生,可能导致延时不准。可以考虑使用定时器中断来实现更精确的时间控制。
在这个项目中,三个LED的呼吸是完全同步的。如果需要实现不同步或不同节奏的效果,可以:
这个基础项目可以扩展出许多有趣的应用:
使用三个PWM通道分别控制RGB LED的红、绿、蓝三个颜色通道,通过不同的亮度组合可以产生各种颜色效果。更进一步,可以实现颜色渐变、彩虹效果等。
在实际产品中,呼吸灯常被用作状态指示。例如:
当前的实现是通过软件不断修改CCR值实现的。STM32的定时器还支持硬件自动改变CCR值的功能(通过重复计数和DMA),这样可以减轻CPU负担,实现更精确的控制。
通过光敏电阻或环境光传感器获取环境亮度,自动调整呼吸灯的亮度范围,使其在不同光照条件下都能有良好的视觉效果。
在完成这个项目的过程中,我深刻体会到STM32 HAL库的强大之处。它封装了底层硬件细节,让我们可以专注于应用逻辑的实现。同时,通过CubeMX的图形化配置,大大减少了初始化代码的工作量。不过,要真正掌握PWM技术,还是需要理解定时器的工作原理和各种参数的含义。