
做电子DIY这么多年彩虹LED灯算是我玩过最上头也最值回票价的项目。别小看这几颗灯珠想让RGB LED呈现出那种从红色缓缓流向紫罗兰的自然彩虹渐变背后牵扯到PWM调光、混色算法、MOS驱动电路甚至还有电源安全方面的一堆细节。这篇文章我会把整个Rainbow LEDs项目的方案选型、硬件参数计算、STM32调光实现以及调试过程中踩过的雷全部捋一遍。不管你是刚玩单片机的新手还是已经摸过WS2812想回头搞懂底层原理的老手这里都有一套可以直接照抄、也能灵活改动的方案。1. 项目整体设计与方案选型1.1 彩虹效果的本质用PWM混色先说清楚一个概念彩虹渐变不是什么玄学本质就是RGB三通道按照HSV色环的规律平滑变化。人的眼睛只会感知到光强不关心灯光里的具体波形所以只要让红、绿、蓝三颗灯珠或者一颗内置三色芯片的RGB灯珠分别以不同占空比发光混出来的颜色就能覆盖很大范围的色域。当色相H从0度走到360度RGB各通道的占空比跟着绘制出一条连续的轨迹灯珠呈现出来的就是彩虹流动的效果。这个原理听起来简单但落地时有两个关键控制量必须把握好一个是PWM频率至少要1kHz以上不然快速移动的彩虹会出现明显闪烁感另一个是占空比的分辨率建议至少8位也就是0~255颜色过渡才能细腻。很多新手做出来颜色一卡一卡的多半就是PWM定时器分频算错或者分辨率被压得太低。1.2 驱动方案选型驱动芯片、WS2812还是分立MOS开始动手之前最纠结的就是驱动方案。市面上做彩色LED灯条有很多现成路子比如WS2812灯珠内部自带驱动IC一根信号线就能串联几百颗写个简单的编码协议就能出彩虹流动效果确实非常爽。但我的建议是如果你想真正理解LED驱动和调光原理不要一上来就上WS2812它把太多细节封装死了。另外一种常用方案是用专门的LED驱动芯片比如MBI5024、STP16CPP05这类恒流驱动芯片它们适合LED显示屏、大面积灯板这类需要大规模并行驱动的场景通过SPI或串行接口把数据移位进去配合PWM扩展引脚实现灰度控制。但这个方案的画板、布线复杂度和成本都上来了做桌面氛围灯有点小题大做。最终我选的是最朴素的方案普通共阳RGB LED灯珠加STM32用三路定时器PWM直接驱动。这个组合的好处是每一个环节都透明可见电阻怎么算、MOS管怎么接、PWM怎么更新全是你能掌控的出问题也好排查。而且它省掉了一堆外围芯片原理图简单焊起来也快非常适合个人DIY和原型验证。1.3 为什么选STM32而不是51单片机或Arduino主控我用了STM32F103C8T6也就是大家常说的蓝 pills 板子。选它有几个具体理由首先是定时器资源多F103C8T6有4个通用定时器加1个高级定时器映射出三路PWM轻而易举其次是PWM分辨率能做得比较高72MHz主频下分频后依然能把占空比精度推到16位颜色过渡的细腻度远超普通8位单片机再者就是它足够流行踩坑资料一大把就算遇到问题也不至于困在原地。当然你要是只有Arduino也能做只是占空比更新和中断控制写起来不如STM32这么顺手。我的整体思路是用TIM2的三个通道分别输出三路PWM分别通过三个N-MOS管驱动RGB灯珠的三条负极通路。这种方式叫低边驱动在单电源、共阳LED的场合下非常经典比高边驱动简单几乎不存在电平匹配的问题。2. 硬件电路设计与关键参数计算2.1 N-MOS低边驱动电路先聊驱动电路。RGB LED灯珠如果是共阳结构阳极统一接电源正极三个阴极R、G、B各自需要一条独立的回路去拉电流。常见的方式是MCU的GPIO直接接三极管或MOS管基极/栅极用GPIO控制管子的导通和关断。这里我选用N-MOS而不是NPN三极管原因是MOS管没有基极电流消耗问题导通时压降也更小在大电流下发热不明显。以常用的AO3400为例它的栅极开启电压典型值只有1.4V左右而3.3V的MCU引脚电压就能轻松把它完全导通。导通后漏源电阻Rds(on)只有几十毫欧在这个电路中几乎不产生额外压降可以忽略不计。电路接法是STM32的PA6、PA7、PB0分别作为三路PWM输出经过1kΩ栅极电阻接到AO3400的栅极源极接地漏极接LED灯珠的阴极。栅极电阻的作用有两个一是限制MOS管寄生电容充放电电流减少MCU引脚瞬时负载二是和栅源间并联的10kΩ下拉电阻配合防止MCU上电瞬间引脚悬空导致MOS管误导通。这个话题我说得细一点因为这个下拉电阻是很多新手最容易漏掉的。如果漏掉它单片机复位或刚上电的几十毫秒里引脚电平不确定灯珠可能闪一下运气不好的话直接烧掉。2.2 限流电阻计算与LED灯珠电压检测限流电阻是整个硬件里最需要认真算的参数。LED灯珠不能直接接电源必须串电阻限流否则电流会指数级上涨灯珠瞬间冒烟。计算限流电阻前先要查清楚灯珠的正向压降VF。这里必须注意RGB灯珠里三种颜色的VF值不一样一般来说红色VF约1.8~2.2V绿色和蓝色VF约2.8~3.2V差异还挺大的。我的电源用的是5V假设目标电流是20mA那么限流电阻的算法就是红色通道串联电阻R (5V - 2.0V) / 0.02A 150Ω 绿色通道串联电阻R (5V - 3.0V) / 0.02A 100Ω 蓝色通道串联电阻R (5V - 3.0V) / 0.02A 100Ω注意这只是理论值实际还要把MOS管的导通压降算进去但AO3400完全导通时压降只有0.02V左右对电流影响很小可以直接忽略。真正要提醒的是每个颜色的电阻不能图省事共用同一个阻值否则做出来的彩虹明显偏色红色亮得刺眼绿色蓝色暗沉沉的。那怎么确认灯珠的实际VF呢这里涉及一个实操技巧用恒流源给LED通电然后用万用表测两端电压。不要直接用万用表的二极管挡测因为二极管挡输出电流太小且不确定测出来的VF误差很大和20mA下工作时的真实压降差距明显。你可以找一颗或者一块恒流LED驱动板把输出电流调到20mA然后接上灯珠量电压这个数据才可靠。稍微讲究一点的做法是用三极管搭一个简单的恒流源电路基极电压固定发射极电阻决定电流这样电流精确可控后面我会在故障排查部分详细展开。2.3 SELV在LED电源中的安全意义聊到电源顺便把安全这块讲清楚。有一类LED电源上会标注SELV这三个字母是Safety Extra-Low Voltage的缩写意思是安全特低电压。它规定的是在正常和单故障条件下输出电压都不会超过一定的安全阈值比如直流60V以下。在这个范围内人即使直接接触带电部分一般也不会产生危险的触电电流。为什么这个和DIY相关因为很多成品的LED驱动板是直接输入市电的输出端如果是非隔离的恒流驱动那输出地线上可能和输入端有电气联系并不满足SELV要求。你在测试这种板子的时候如果手碰到了输出端的金属裸露部分理论上存在危险电压传导的风险。所以我自己的原则是凡是涉及市电的驱动板测试时一律加隔离变压器或者用12V/24V的SELV电源模块把整个工作区域控制在安全电压下。你做得是氛围灯不是工业设备完全没有必要把自己暴露在危险电压底下。2.4 3个IO口控制4个LED的查理复用一般控制几个LED就是几个IO口但有一种玩法可以让IO口数量省着用叫查理复用英文叫Charlieplexing。它的原理是一个IO口有三种输出状态高电平、低电平和输入态高阻那么两个IO口之间就能组成两个不同方向的电流通路进一步利用LED的单向导电性就能做到用3个IO口控制最多6个LED4个自然不在话下。具体怎么做呢比如有A、B、C三个IO口要控制LED1到LED4可以通过把其中一个IO设为高另一个设为低第三个设为高阻这样只有连接在这两个IO之间且正方向匹配的那颗LED会亮。下个瞬间切换组合就能点亮另一颗。只要切换速度足够快肉眼看到的就是多颗LED同时亮着只是亮度会比直接驱动低一些因为导通时间被切碎了。这个技巧在IO口紧张时很有用但我也得说实话如果你只是做三色RGB彩虹灯没必要上查理复用老老实实三路PWM驱动就行。查理复用的价值更多是让你理解高阻态这个IO模式以及LED单向导电性如何在电路设计中被巧妙利用。3. 软件实现与效果调试3.1 STM32CubeMX配置LED引脚输出PWM拿下硬件后软件这块先从STM32CubeMX的配置说起。很多新手卡在最开始是因为不知道PWM输出引脚在CubeMX里怎么配。记住一个关键点PWM不是普通的GPIO输出它必须配置成复用功能Alternate Function并且要挂在某个定时器的通道上。我用的是TIM2的三个通道。在STM32CubeMX里左侧选TIM2然后依次勾选Channel1、Channel2、Channel3为PWM Generation CH1/CH2/CH3。系统会自动分配引脚如果和你的原理图引脚不一致你要手动改。定时器时钟源选内部时钟分频系数PSC设置为71自动重载值ARR设置为999这样PWM频率就是72MHz / (72×1000) 1kHz这个频率下彩虹流动的画面完全无闪烁。生成代码后记得在main函数里启动PWM输出HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_1); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_2); HAL_TIM_PWM_Start(htim2, TIM_CHANNEL_3);修改占空比的函数是__HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_1, duty_red); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_2, duty_green); __HAL_TIM_SET_COMPARE(htim2, TIM_CHANNEL_3, duty_blue);注意ARR值设成999那占空比的有效范围就是0到999。如果想用0到255的整数来表示颜色分量那就做个线性映射比如duty (color_value * 999) / 255。这里有个容易踩的坑PWM分辨率越高占空比更新带来的亮度变化越细腻但也不要随便把ARR设到65535因为定时器更新中断的频率会下降低亮度时反而容易出现可见的台阶感。3.2 彩虹渐变核心算法HSV转RGB要让颜色沿着彩虹色环走直接在RGB空间做线性插值会很别扭比如红到绿中间会经过灰黄色甚至暗色。正确做法是在HSV色彩空间里让H分量从0循环到360度饱和度S和明度V保持恒定再把HSV转成RGB值最后把RGB分量映射到三个PWM通道的占空比上。HSV到RGB的转换是这个项目里最核心的代码逻辑。我用的是下面的C语言实现void HSV_to_RGB(float h, float s, float v, uint8_t *r, uint8_t *g, uint8_t *b) { float c v * s; float hp h / 60.0f; float x c * (1.0f - fabsf(fmodf(hp, 2.0f) - 1.0f)); float r1, g1, b1; if (hp 1.0f) { r1 c; g1 x; b1 0; } else if (hp 2.0f) { r1 x; g1 c; b1 0; } else if (hp 3.0f) { r1 0; g1 c; b1 x; } else if (hp 4.0f) { r1 0; g1 x; b1 c; } else if (hp 5.0f) { r1 x; g1 0; b1 c; } else { r1 c; g1 0; b1 x; } float m v - c; *r (uint8_t)((r1 m) * 255.0f); *g (uint8_t)((g1 m) * 255.0f); *b (uint8_t)((b1 m) * 255.0f); }然后在主循环或者定时器中断里每过一段时间让h加一个小步长比如每10ms让h加1彩虹走一圈需要3.6秒速度很舒服。代码里把得到的r、g、b值映射到占空比再把占空比写入三个通道就可以了。这里有个性能上的细节fmodf和fabsf都是浮点函数在F103上跑速度尚可但如果用的是低端51单片机或者想做得更极致可以改用查表法。预先算好360个HSV对应RGB值存成一个静态数组运行时只查表响应速度会快很多也省掉大量浮点运算。3.3 呼吸灯效果与gamma校正彩虹渐变只是让颜色流动呼吸灯则是让整块灯板的亮度像呼吸一样缓慢起伏。呼吸灯的实现并不复杂核心是让PWM占空比随时间按特定曲线变化。不过这里很多教程都做得比较粗糙直接让占空比从0线性升到999再线性降回0。实测效果你会觉得灯速在两端慢、中间快尤其是在最高亮度附近整段过程显得特别愣。原因在于人眼对光的感知不是线性的而是近似幂函数关系。同样的亮度增量暗区明显亮区几乎感知不到。所以要给人柔和的感觉不能线性变化而是使用正弦曲线uint32_t step HAL_GetTick() % breath_period; float phase 2.0f * 3.14159f * (float)step / (float)breath_period; float brightness 0.5f * (1.0f - cosf(phase)); // brightness 范围是 0~1再做一次 gamma 校正后映射到占空比上面的brightness已经比线性好很多但如果追求更自然的效果还需要加一道gamma校正。因为LED的PWM占空比与发光亮度的关系近似线性可人眼感知的亮度是物理亮度的0.4~0.5次方关系。所以想让感知亮度线性增长就要让物理亮度按2.2次方的关系走uint32_t pwm_duty (uint32_t)(powf(brightness, 2.2f) * 999.0f);这个校正做完呼吸过程会明显均匀柔和。可能有人会觉得这步没必要但说实话彩虹灯这类项目最终拼的就是人眼观感一个gamma校正能让质感提升一个档次做产品的时候这就是差异点。另外呼吸周期我建议设在2~4秒之间太快会像报警灯太慢会觉得灯坏了。3.4 光敏传感器控制LED亮灭光敏传感器控制LED亮灭是一个很常见也很实用的扩展需求。最简单的方案是用光敏电阻加一个固定电阻做分压把分压点接到STM32的ADC引脚通过读取电压判断环境亮度。环境光强时光敏电阻阻值变小分压点电压变化超过某个阈值后就让三路PWM输出变到最小灯就灭了环境光变暗时阈值翻转彩虹呼吸灯恢复工作。用ADC之前要在CubeMX里把对应引脚配置为ADC模拟输入并开启一个ADC通道。读取值可以用阻塞式转换函数十分方便HAL_ADC_Start(hadc1); HAL_ADC_PollForConversion(hadc1, 10); uint16_t lux_raw HAL_ADC_GetValue(hadc1);光敏电阻做出来的效果只能说够用因为它的响应曲线是非线性的而且不同批次差异很大。想做得好一点可以用BH1750这种数字光敏传感器走I2C接口直接读回光照度的绝对数值单位是勒克斯。程序里就能写更直观的阈值比如环境光大于200勒克斯时熄灭小于50勒克斯时点亮中间亮度渐变。系统里还可以顺便设置一颗状态LED用不同颜色表示当前工作状态这个就叫状态指示别看灯小调试的时候能救大命。比如红色代表初始化失败绿色代表正常运行蓝色代表光敏控制已启用。4. 常见问题与故障排查实录4.1 LED驱动板测试时X2安规电容炸了这个是我觉得最值得拿出来说的一个故障。测试某块LED驱动板时通电一瞬间输入端的X2安规电容直接炸了外壳崩开周围一股焦糊味。X2电容是跨接在交流电源线之间的安规电容主要作用是抑制电磁干扰按常理不该那么容易炸。我拆下来一看电容规格标的是275VAC按说够用。但为什么还会炸后来排查发现问题不在电容本身而在于整块驱动板的输入端没有布置任何限流措施。通电瞬间巨大浪涌电流直接冲击电容加上电容本身容量选得偏大瞬间存储的能量远超它能承受的冲击功率。简单说就是浪涌把电容打穿了。那怎么避免我的经验是测试任何市电直接输入的驱动板在输入端串联一个白炽灯泡或者使用带软启动的隔离调压器。白炽灯在冷态时电阻很低但通电瞬时如果电路里有浪涌它会被迫限流灯泡瞬间亮一下保护后面的电路。这个土办法在工作室里非常实用。顺便说一句这类故障不要觉得换个大电容就完事电容大了反而更容易吸收更高的浪涌能量炸得更响。正确方向是查输入电路里有没有接负温度系数热敏电阻NTC或PTC保险丝没有的话就要在设计上补上。4.2 LED灯珠电压检测电路怎么做前面提过VF检测这里详细讲讲怎么搭一个可靠的LED灯珠电压检测小电路。目标很简单给LED通一个稳定的20mA电流然后测正负极之间的电压。可以用一个三极管恒流源实现。电路如下用两个电阻对3.3V分压给NPN三极管基极提供一个固定电压比如2V。三极管发射极串联一个10Ω电阻到地发射极电压约等于基极电压减去一个PN结压降约0.7V也就是2V - 0.7V 1.3V。那么发射极电流就是1.3V / 10Ω 130mA不对这个电流太大了。调整一下想让电流是20mA就让10Ω变成65Ω或者把基极电压降低。具体计算时发射极电阻取100Ω电流就是(2V - 0.7V)/100Ω 13mA比较接近目标。再把LED接到三极管的集电极回路里电流就被稳定住了。用万用表测LED两端的压降这个时候读到的数值才是它在接近实际工作电流下的真实VF。这个数据直接拿去算限流电阻结果会靠谱很多。我实际测过同一批号的RGB灯珠红色VF在1.95V到2.08V之间浮动绿色VF在2.9V到3.1V之间浮动差异虽然不大但在要求色温一致的项目里足够影响成品效果了。4.3 双LED交替闪烁电路设计另一个常见需求是双LED交替闪烁工程上最经典的实现是两个三极管组成的无稳态多谐振荡器电路不需要写任何代码。电路大概结构是两个NPN三极管交叉耦合一个管子的集电极通过电容连到另一个管子的基极两个LED分别接在两个集电极上。由于参数的微小不对称上电后两只管子交替导通和截止两个LED就交替闪烁。振荡周期可以用公式估算T ≈ 0.7 × R × C。假设取R为10kΩC为10μF那每个状态的持续时间为0.7 × 10kΩ × 10μF 0.07秒两个状态加一起周期约0.14秒也就是一秒闪7下左右闪烁频率有点快。如果想让闪烁更舒缓可以把C加到47μF周期就变成0.66秒看起来舒服很多。PCB布局上两个三极管尽量对称两个充放电容也要靠近对应管子走线短一点不容易出现一边灯亮、另一边微亮的异常。如果这个功能已经跑在STM32项目里了那就没必要再用模拟电路直接在GPIO中断里交替翻转两个引脚就可以。要注意的是别用HAL_Delay这类阻塞延时来做否则整个系统都会卡在那里其他任务全部受影响。正确的做法是在定时器中断里设置一个状态标志主循环里根据标志去翻转两个GPIO。4.4 红外/超声波传感器联动LED与蜂鸣器最后聊聊传感器联动这个场景很常见有人靠近灯亮人走了灯灭或者检测到障碍物后LED闪烁加蜂鸣器报警。硬件上可以用红外人体感应模块HC-SR501或者超声波测距模块HC-SR04。两者思路完全不同。HC-SR501是数字输出检测到人体活动时输出高电平STM32这边直接配一个GPIO输入读到高电平就点灯。这个模块有个特点它输出的是TTL 3.3V/5V电平给STM32用比较安全但如果用的是5V供电版建议确认一下输出电平是否兼容必要时加两个电阻分压。HC-SR04就要复杂一点。它需要你给Trig引脚发一个至少10μs的高电平脉冲然后模块会输出一个与距离成正比的高电平脉冲时间长度就是声波往返时间。距离等于时间乘以声速340m/s再除以2。我一般直接用HAL库的输入捕获功能来测这个高电平脉冲宽度而不是干等轮询这样不会卡死主循环。检测到距离小于某个阈值后就执行LED变红并快速闪烁、蜂鸣器鸣叫的三级联动动作。这里说的三级是指传感检测、视觉反馈、听觉反馈。先用传感器给出触发信号然后MCU处理并分别驱动LED和蜂鸣器。逻辑上注意加一个消抖延时不然超声波数据抖动会导致LED和蜂鸣器频繁开关观感和体验都很差。我通常的做法是连续读到三次低于阈值才算真正触发这个在项目里叫软件消抖比硬件电路加电容滤波要灵活得多。踩过几次坑之后我的体会是彩虹LED这玩意儿看着不起眼真要从电路原理到调光算法一点点抠细节能把嵌入式底层的很多基本功都串起来。最后分享一个实用的个人心得做彩虹渐变的时候在代码里对PWM占空比做一次gamma校正你会明显发现肉眼看到的过渡均匀很多这个细节很多教程不会告诉你。想继续扩展的话可以在这个基础上把灯板做成LED数字时钟用彩虹呼吸光做底色或者加个蓝牙模块手机调色发挥空间非常大。