呼吸灯不是“调个PWM”那么简单:一个被低估的感知工程实践
你有没有注意过,AirPods盒盖打开时那抹柔和的白色微光?或者某款智能手表在待机状态下,LED像心跳一样缓缓明暗起伏?它们没有刺眼的闪烁,没有生硬的开关,只有一种让人下意识放松下来的节奏感——这背后,往往藏着一段不到200行、却融合了生理学、信号处理与系统调度的嵌入式代码。
很多人第一次实现呼吸灯,是在STM32 HAL库例程里改几行HAL_TIM_PWM_Start(),再套个sin()函数循环更新占空比。结果呢?LED在低亮度区“噗”地一跳就亮了,在高亮度区又拖沓得像卡顿的动画。用户没说哪里不对,但就是觉得“不够高级”。问题不在硬件,而在于我们常把呼吸灯当成一个电学控制问题,却忽略了它本质是一个人眼感知建模问题。
为什么正弦波直接驱动LED会“假”?
先看最直觉的做法:
// 常见误区:直接用正弦映射到PWM值 uint8_t val = (uint8_t)(127.5f + 127.5f * sinf(phase * 2.0f * PI / 512.0f)); __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, val);这段代码生成的是数学上完美的正弦曲线,但人眼并不买账。
原因在于:人眼对亮度变化的敏感度是非线性的。Weber-Fechner定律指出,人眼感知的亮度变化近似与光强的对数成正比。这意味着:
- 当LED从0%→10%占空比时,实际光强只增加了10%,但人眼会觉得“突然就亮了”;
- 而从90%→100%占空比时,光强增加了同样10%,人眼却几乎察觉不到变化。
换句话说:线性占空比 ≠ 线性感知亮度。
如果想让亮度在视觉上“匀速上升”,实际需要的占空比变化必须是指数型或幂函数型——越暗的地方,占空比要“挤得更密”;越亮的地方,“摊得更开”。
这就是伽马校正(Gamma Correction)的底层动机:它不是为显示器准备的,而是为人眼这个生物传感器定制的补偿算法。
📌 关键洞察:伽马值γ=2.2不是玄学参数,它是sRGB标准对CRT显示特性的历史继承,而巧合的是,它也意外贴合了人眼在中低照度下的平均响应曲线。对呼吸灯而言,我们真正需要的是反伽马映射(Inverse Gamma):把线性时间轴 → 映射为非线性