呼吸灯效果在电子产品中非常常见,从手机通知灯到各种智能设备的指示灯,都能看到它的身影。这个项目基于STM32F103C8T6这款经典的Cortex-M3内核微控制器,利用其内置的PWM功能实现LED亮度渐变效果。相比简单的LED闪烁,呼吸灯更能提升产品的视觉体验和品质感。
STM32F103C8T6是STMicroelectronics推出的"蓝精灵"系列中的一员,虽然定位入门级,但性能足够强大。它内置多达4个通用定时器,其中TIM1是高级定时器,TIM2-TIM4是通用定时器,都可以用来产生PWM信号。我们将利用其中一个定时器的PWM功能,通过调节占空比来改变LED亮度。
提示:选择STM32F103C8T6不仅因为其性价比高,还因为HAL库提供了完善的硬件抽象层,大大简化了开发流程,特别适合初学者快速上手。
呼吸灯的基本电路非常简单,关键在于PWM信号的产生和控制。具体连接方式如下:
注意:限流电阻必不可少,直接连接LED到GPIO可能会因电流过大损坏LED或MCU。220Ω电阻在3.3V供电下,LED电流约10mA,既保证亮度又安全。
STM32的GPIO引脚大多具有复用功能,我们需要将PA8配置为TIM1_CH1的PWM输出功能。在STM32F103C8T6上,TIM1_CH1的默认复用引脚就是PA8,这简化了我们的设计。
HAL(Hardware Abstraction Layer)库是ST提供的硬件抽象层,它封装了底层寄存器操作,提供了统一的API接口。本项目主要用到以下几个HAL功能:
PWM(Pulse Width Modulation)即脉冲宽度调制,通过调节脉冲的高电平时间(占空比)来控制平均电压。对于LED来说,占空比越大,亮度越高;占空比越小,亮度越低。
在STM32中,PWM由定时器产生。定时器不断计数,当计数值小于比较值时输出高电平,大于比较值时输出低电平,如此循环形成PWM波。
我们需要配置以下几个关键参数:
这样配置得到的PWM频率为1kHz,既高于人眼可察觉的范围(避免闪烁),又不会给MCU带来太大负担。
呼吸灯效果需要LED亮度平滑变化,通常采用以下两种方式:
我们选择正弦变化,因为它看起来更加自然。实现方法是在主循环中计算正弦值并映射到PWM比较寄存器。
c复制/* 全局变量 */
uint16_t pwmVal = 0;
uint8_t dir = 1; // 1:增加, 0:减少
/* 主循环中调用 */
void Breath_LED_Process(void)
{
static uint32_t lastTime = 0;
if(HAL_GetTick() - lastTime < 10) return; // 10ms更新一次
lastTime = HAL_GetTick();
if(dir) {
pwmVal += 5;
if(pwmVal >= 1000) dir = 0;
} else {
pwmVal -= 5;
if(pwmVal <= 0) dir = 1;
}
// 使用正弦曲线计算亮度,效果更自然
float radian = pwmVal * 3.1415926f / 1000.0f;
uint16_t brightness = (uint16_t)(sinf(radian) * 500 + 500);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, brightness);
}
c复制int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM1_Init();
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
while (1)
{
Breath_LED_Process();
}
}
c复制static void MX_TIM1_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
TIM_OC_InitTypeDef sConfigOC = {0};
htim1.Instance = TIM1;
htim1.Init.Prescaler = 71;
htim1.Init.CounterMode = TIM_COUNTERMODE_UP;
htim1.Init.Period = 999;
htim1.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim1.Init.RepetitionCounter = 0;
htim1.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim1) != HAL_OK) {
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim1, &sClockSourceConfig) != HAL_OK) {
Error_Handler();
}
if (HAL_TIM_PWM_Init(&htim1) != HAL_OK) {
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim1, &sMasterConfig) != HAL_OK) {
Error_Handler();
}
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 0;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
if (HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1) != HAL_OK) {
Error_Handler();
}
HAL_TIM_MspPostInit(&htim1);
}
利用STM32的多个定时器通道,可以同时控制多个LED,实现更复杂的灯光效果。例如:
c复制// 初始化多个PWM通道
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // LED1
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_2); // LED2
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // LED3
// 分别控制不同LED
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, brightness1);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, brightness2);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, brightness3);
对于更精确的时序控制,可以使用定时器中断:
c复制// 在定时器初始化后启用更新中断
HAL_TIM_Base_Start_IT(&htim1);
// 实现中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM1) {
Breath_LED_Process();
}
}
呼吸灯可以与其他功能结合,例如:
我在实际项目中发现,呼吸灯效果虽然简单,但对提升产品质感很有帮助。通过调整参数和算法,可以获得各种不同的视觉效果。例如,将正弦曲线改为指数曲线,可以让亮度变化在低亮度区域更平缓,高亮度区域变化更快,更符合人眼的感知特性。