用51单片机改造USB风扇:红外遥控摇头小风扇DIY全攻略 夏天在书桌前写东西桌面那个小风扇永远只能对着一个方向吹。人坐偏一点风就吹不到坐远了又懒得伸手去调角度。后来我干脆做了个小项目用一块51单片机把普通USB小风扇改成了红外遥控摇头小风扇——遥控器可以开关、调速、摇头、定时彻底解放双手。这个项目难度不高但一次涵盖了硬件电路、电机驱动、红外通信、机械结构和状态程序设计非常适合刚学完51单片机、正在纠结“点灯之后还能做点什么”的同学练手。这篇文章我从方案选型一直写到最后踩过的坑包含所有能复现的接线方式、代码骨架和调试思路。如果你手里有一块51单片机学习板、一个坏了或闲置的小风扇大概率可以跟着做一个出来。1. 想法怎么落地从“手扶风扇”到“遥控摇头小风扇”1.1 为什么拿这个项目练手很多人学完51单片机之后第一个反应是“下一步干什么”。流水灯太无聊数码管钟表又看不到成就感。而这个红外摇头小风扇的好处就是它把单片机最常见的几个外设都用上了。我来数一下涉及的知识点外部中断红外接收信号输入、定时器PWM调速、时间测量、定时关机、IO输出控制电机驱动模块、状态机编程不同档位和摇头状态的切换、甚至还能加EEPROM掉电记忆。一个项目把这些全部串起来比单独学每一个例程有用得多。再加上机械部分风扇本身是现成的摇头机构可以自己搭成本不高但完成后的“这是一个真能用的东西”的感觉很强烈。尤其夏天放在桌边拿遥控器一按风对着人吹那个成就感完全不是跑个流水灯能比的。1.2 整体技术路线和模块拆分我的初步方案定得很简单先把功能列清楚遥控器开关机三档调速低、中、高摇头开启/关闭30/60/120分钟定时风扇关机时摇头自动复位系统其实就五大部分主控51单片机、红外接收模块、风扇电机驱动、摇头电机驱动、电源。通信方式我直接选了红外理由很现实成本低一个红外接收头几块钱家里电视遥控器就能拿来当发射端不需要再买遥控模块。蓝牙或者WiFi虽然听上去高级但配对、协议栈、供电都会增加一堆额外的工作量对一个“先跑起来”的项目来说没必要。摇头机构的选择我在后面专门讲因为这里有个很有意思的取舍看起来最简单的舵机方案实际做出来反而不是最好的。这是这个项目最容易踩的坑值得单独说。2. 硬件搭建电路设计和接线避坑2.1 核心器件清单和供电方案我实际用到的器件清单如下都是很常规的型号淘宝或者积攒的开发板零件盒里基本都能翻出来。模块常见型号/规格作用关键注意点主控51单片机开发板或最小系统板红外解码、逻辑控制、PWM输出选带内部EEPROM的型号更方便做掉电记忆红外接收头HS0038B / VS1838B接收遥控器38kHz红外信号电源端并联电容否则距离骤减H桥驱动模块L298N / DRV8833 双H桥模块驱动风扇调速和摇头正反转输入信号必须共地PWM脚和方向脚分开风扇电机5V直流有刷电机拆旧风扇带动扇叶启动电流远超额定电流电源要留余量摇头电机5V小型减速电机带动摇头机构需要正反转不能只用单路开关限位开关常开微动开关或干簧管检测摇头到端点接线要上拉安装位置决定摇头角度电源USB 5V 2A充电器或18650电池升压板整机供电前期调试建议直接用USB电池版本后面再说供电是整个项目里最容易埋雷的地方。我一开始觉得5V USB给一个小风扇供电绰绰有余实际一量就发现问题风扇电机和摇头电机同时启动的那一瞬间电流能冲到1.5A以上USB电源的电压会被瞬间拉下来单片机直接复位。所以后期我采用的是“一个5V节点但加足电容”的办法电源入口并一个大电解电容470uF到1000uF电机两端各并一个104陶瓷电容主控部分的供电线单独走线不要和电机线缠在一起。如果条件允许最好主控和电机分开供电只在电池负极做单点共地。这样电机电流波动不会把主控电压拽垮。2.2 红外接收头的接法红外接收头看起来只有三个引脚接错的人可不少。以HS0038B为例引脚排列是供电正、信号输出、地但也有个别型号引脚定义不同接线前一定要查数据手册或者用万用表确认。信号输出脚可以直接接单片机的INT0P3.2引脚利用外部中断做脉冲宽度测量。红外接收头输出的是解调后的数字电平空闲时是高电平收到38kHz载波时输出低电平所以我们其实是在测量一段低电平和一段高电平的组合宽度。这里要特别提醒红外接收头对电源纹波非常敏感。我第一次接的时候直接把接收头电源接到了电机驱动模块旁边的5V走线上结果遥控距离只有十几厘米还经常丢码。后来在接收头电源正负极之间并联了一个100uF电解电容和一个104陶瓷电容遥控距离立刻恢复正常站在两三米外都能稳定遥控。如果你手上有示波器或者逻辑分析仪建议把探头夹在接收头信号脚上看一眼波形。大部分“遥控不识别”问题其实在波形上一眼就能看出来要么信号幅度不够要么波形毛刺太多根本到不了解码那一步。2.3 电机驱动和PWM接线小风扇电机属于直流有刷电机调速方式是改变平均电压也就是PWM。摇头电机需要正反转才能实现来回摆头所以最简单的方案是直接用双H桥驱动模块一路接风扇电机一路接摇头电机。接线逻辑不要怕复杂按模块分清楚就很简单H桥A路输出接风扇电机PWM输入接单片机的PWM输出脚方向引脚固定为高电平或不用。H桥B路输出接摇头电机两个方向引脚分别接两个IO口程序里控制正转、反转、停止。很多L298N模块上的ENA、ENB引脚需要接高电平才让对应通道工作。如果你发现电机不转先查使能脚是不是被拉到3.3V或5V了。驱动模块和单片机之间必须共地不然输入信号完全乱套。风扇电机两端我建议直接并联一个104陶瓷电容可以吸收换向火花产生的干扰对单片机复位问题有很大缓解。摇头电机如果离主控近也照做。电源线尽量粗一点别用杜邦线飞几十厘米电机启动瞬间压降会让你怀疑人生。3. 摇头机构机械结构和控制逻辑3.1 三种摇头方案实测对比摇头是这个项目里最有意思的部分。我一开始想的很简单用舵机拉着风扇摆头不就行了后来实际试了才明白事情没那么简单。方案成本控制难度噪音寿命实际体验舵机如SG90低低抖动明显一般小扭矩带风扇头后易抖摆动速度不自然步进电机28BYJ-48低中低速噪音大较长能精确控制角度但转速慢、声音难听减速电机限位开关低中低低较长摆动自然结构简单成本低舵机方案的坑在于舵机是通过PWM信号控制角度的但普通廉价舵机在带负载的情况下会不断修正角度表现出来就是“咔咔咔”的抖动。风扇头本身不重可一旦转到某个角度风的反推力变化会让舵机很难受声音会很吵。步进电机方案我也试了28BYJ-48这种五线四相步进电机控制逻辑不复杂但光头转速本来就不高带动风扇头时只能用低速发出那种“滋滋”的声音放在桌面上非常烦躁。最后我选了“减速电机曲柄连杆两个限位开关”的方案。原理跟家里的电风扇摇头机构几乎一样一个小减速电机转动一个偏心曲柄连杆带动固定在转轴上的风扇底座来回摆动。电机本身不需要精确角度只是在两个端点之间来回转检测到端点就反转方向。3.2 减速电机和曲柄连杆的安装要点机械部分的制作我用了3D打印支架没有条件的话用亚克力板、甚至硬纸板加热熔胶也能做出来只是强度差点。尽量保证“风扇头固定在转轴上转轴通过轴承或铜套固定在底座上”这样摩擦小、不晃动。曲柄连杆的核心尺寸有两个曲柄长度决定摆动角度连杆长度决定运动平顺性。我这里参考常见做法曲柄半径10mm左右连杆长度45mm左右摇头角度大概60度人坐在桌前完全够用。角度大了会撞到空间限制角度小了又没意义。装配时有两个坑连杆和曲柄的关节要留一点间隙但不能太大。间隙太大每个来回都会“哐当”一声整机异响来源就在这里。要避免连杆运动到接近直线位置否则会出现“死点”电机明明在转风扇头却卡着不动可能一直堵转到发热。限位开关不是装在电机上而是装在风扇底座转轴的左右两端。风扇转到最左边转轴上的挡片压住左边开关转到最右边压住右边开关。程序读到开关信号后让电机反转往另一边走。3.3 限位逻辑与堵转保护限位开关的选择上我用过微动开关和干簧管。微动开关便宜、好买但有机械触点长期反复按压会疲劳干簧管配合转轴上的小磁铁更耐用没有物理接触不过安装位置要精确磁铁要离得够近。程序里的逻辑不复杂但一定要加堵转保护。我的做法是记录电机启动的时间如果超过3秒还没读到对应的限位信号就强制断电并设置一个故障标志。因为一旦限位开关失灵电机就会一直堵转电流飙升不用几分钟就烫手。另外摇头到端点之后不要直接让电机硬停在那个位置。正确流程是读到左限位信号后电机保持反转一小段时间比如200ms让风扇头稍微离开限位位置再停下。这样下次启动时不会因为刚好压在限位上而直接触发反转也能减少机械冲击。4. 程序实现红外解码与状态机4.1 NEC协议和中断解码思路红外遥控器大多使用NEC协议。我用的遥控器也是这种协议特点很简单引导码9ms载波 4.5ms空闲逻辑0560us载波 560us空闲逻辑1560us载波 1.69ms空闲重复码9ms载波 2.25ms空闲 560us载波红外接收头输出的是反相之后的电平空闲为高收到载波为低。所以我的解码思路是用单片机的外部中断INT0监视接收头输出脚每次下降沿触发中断在中断里读取定时器0的计数值得到两个下降沿之间的时间宽度再按宽度判断是什么码。为什么用下降沿间隔而不是直接测量高低电平宽度因为每次数据位结束时下一个数据位的低电平开始也是一个下降沿两次下降沿之间的总时间恰好等于“当前位的低电平时间高电平时间”逻辑0大约是1120us逻辑1大约是2250us区分度很高。实测下来比逐段测量高电平宽度更稳定。核心代码骨架大概是这样void Timer0_Init(void) { TMOD 0xF0; TMOD | 0x01; // 定时器016位模式 TH0 0; TL0 0; TR0 1; } void IR_Init(void) { IT0 1; // INT0下降沿触发 EX0 1; // 开启外部中断 EA 1; }在12MHz晶振、经典51的12T模式下定时器每1us计数一次16位最多数到65535us而NEC协议最长的一段引导码也不到14ms完全够用不需要额外处理溢出。中断服务函数里做时间测量void IR_ISR(void) interrupt 0 { unsigned int us (TH0 8) | TL0; TH0 0; TL0 0; HandleIRWidth(us); }然后根据测量到的时间宽度判断是引导码、重复码、逻辑0还是逻辑1。因为NEC是LSB先发所以先收到的bit放最低位static unsigned char bitCount 0; static unsigned long codeValue 0; static unsigned char state 0; void HandleIRWidth(unsigned int us) { if (us 10000 us 16000) { state 1; bitCount 0; codeValue 0; return; } if (us 10000 us 13000) { gRepeatFlag 1; // 重复码可用于按键长按 return; } if (state 1 bitCount 32) { if (us 2200 us 2800) { codeValue | (1UL bitCount); bitCount; } else if (us 900 us 1500) { bitCount; } else { state 0; bitCount 0; return; } if (bitCount 32) { gIrCode codeValue; gIrReady 1; state 0; bitCount 0; } } }这里的时间窗口我留得比较宽。实际用示波器测量时遥控器晶振误差会让脉冲宽度漂一点窗口太窄容易误判。4.2 主循环状态机和PWM调速程序主体不要写成一堆if散在外层我习惯用一个简单状态机来管风扇状态typedef enum { OFF, LOW, MID, HIGH } FanState_t; FanState_t fan OFF; bit swingOn 0;主循环里只是不断查标志位然后更新输出void main(void) { SystemInit(); while (1) { if (gIrReady) { HandleIrCmd(gIrCode); gIrReady 0; } if (swingOn fan ! OFF) { SwingTask(); } else { StopHeadMotor(); } } }遥控命令处理的部分把命令码对应到具体功能。以我用的遥控器为例通常有独立的电源键、风速键、摇头键、定时键。命令码的具体数值取决于遥控器可以先写一个临时代码在串口打印出来记录下每个按键对应的码值再填进映射表里。void HandleIrCmd(unsigned long code) { unsigned char addr (unsigned char)(code 0xFF); unsigned char addrInv (unsigned char)((code 8) 0xFF); unsigned char cmd (unsigned char)((code 16) 0xFF); unsigned char cmdInv (unsigned char)((code 24) 0xFF); if ((addr addrInv ! 0xFF) || (cmd cmdInv ! 0xFF)) return; switch (cmd) { case IR_POWER: if (fan OFF) fan LOW; else { fan OFF; swingOn 0; } break; case IR_SPEED: if (fan LOW) fan MID; else if (fan MID) fan HIGH; else if (fan HIGH) fan LOW; break; case IR_SWING: if (fan ! OFF) swingOn !swingOn; break; default: break; } }PWM调速我单独用一个定时器产生中断例如定时器1中断里维护一个计数变量和一个占空比变量void Timer1_ISR(void) interrupt 3 { static unsigned char cnt 0; cnt; if (cnt 100) cnt 0; FAN_PWM (cnt pwmDuty) ? 1 : 0; }这样相当于100Hz的PWM低档占空比设成40中档70高档直接100。如果你嫌电机啸叫明显可以把定时器周期改短把频率提到1kHz以上代价是中断更频繁对红外接收的解码稳定性会有一点影响所以我在实际项目里用了100Hz再配合电机两端电容效果完全能接受。4.3 定时、看门狗和掉电记忆定时功能一开始很容易写得乱。我的习惯是用一个定时器产生1ms时基在主循环里累加秒数再按设定时间判断是否到点。设置定时档位的时候把所有正常工作都保持原样只在主循环里判断if (timerSet 0) { timeCount; if (timeCount timerSet * 1000) { fan OFF; swingOn 0; timerSet 0; } }如果风扇关机了定时也要清零不然下次开机可能莫名其妙又自动关机。这个细节我调试时踩过莫名觉得“单片机自己乱来”其实只是定时状态没有随关机一起复位。看门狗要不要加我的建议是加。因为红外解码有时候会遇到异常波形程序如果没有正确处理可能卡死在中断里。开了单片机内部看门狗之后主循环里定期喂狗一旦程序跑飞几毫秒后自动复位比手动按复位键省心得多。掉电记忆是加分项。STC系列单片机的内部EEPROM可以直接用掉电前把当前档位和摇头状态存进去上电后再读出来。这样拔电重启之后风扇还是保持上次的设置不会每次开机都要重新调一遍。5. 调试排错那些让人抓狂的问题5.1 红外遥控距离短到怀疑人生第一个坑是红外接收距离。刚焊完电路时遥控器必须凑到红外接收头20cm以内才有反应远一点就完全没响应。我当时还以为是接收头坏了换了两个还是一样。排查思路其实不复杂先量电压接收头供电脚和地之间有没有足够稳定的5V再用逻辑分析仪看信号脚波形最后检查一下接收头电源脚旁边有没有滤波电容。我的问题就出在第三步——接收头电源直接并联在电机驱动模块的5V上纹波大得根本没法解码。加了一个100uF和104电容后遥控距离直接到了六七米。还有一次是遥控器本身的问题。家里电视遥控器的晶振频率偏得有点多解码偶尔成功偶尔失败换了另一把遥控器就非常稳定。如果你发现某个遥控器“很挑”先别急着怀疑代码换个遥控器试试。5.2 电机一启动单片机就复位这个现象非常经典风扇电机启动瞬间单片机整个重启数码管或指示灯闪一下又恢复。原因是电机启动电流非常大瞬间把电源电压拉到了单片机复位电压以下。电源线越长线阻越大压降越明显。我的解决步骤是第一步给总电源加一个1000uF大电解电容相当于给整个系统一个“蓄水池”。第二步电机两端并联104陶瓷电容吸收换向火花。第三步把电机电源线和主控电源线分开走从电源入口单点分开。第四步实在不行用光耦或者继电器隔离电机回路强电和弱电彻底分离。我的项目做到第三步就解决了。如果你用的还是那种质量很差的USB供电线线阻高这里也会成为压降来源换根粗短线效果立竿见影。5.3 摇头卡死、堵转发热摇头机构的堵转问题在初期几乎每天都能遇到。现象是风扇头转到最左边程序没有收到限位信号减速电机一直嗡嗡地想转但转不动过一会儿摸电机外壳明显发烫。根因基本都是限位开关的安装位置不对。我一开始把限位开关装得太靠外转轴上的挡片压不到开关的弹片导致开关永远不被触发。后来调整了位置把挡片加长了一点问题就消失了。软件层面的堵转保护也必须有。我的做法是记录本次摇头启动时间如果在3秒内都没有收到端点信号立刻停机并进入错误状态等遥控器再给一个“摇头”指令才重新恢复。自从加了这道保护即使限位开关坏了也不会烧电机。5.4 整机共振和异响整机异响在摇头风扇上特别明显因为摇头机构在运动任何一点间隙都会被放大成“哒哒哒”的声音。我处理过的异响来源有四个扇叶动平衡差、网罩松动、连杆关节间隙过大、底座太轻。扇叶拆下来换了一个或者在扇叶背面贴一小块橡皮泥做配重网罩螺丝全部拧紧连杆关节处加了尼龙垫片底座压了一个配重块。处理完之后整机噪音从“让人烦躁”变成了“可以接受”。这个环节没有捷径只能一个位置一个位置排查。我的习惯是先用耳朵听再用手指触碰各个部件摸到哪里振动最明显问题基本就在那里。6. 成品效果、参数与可复用的经验6.1 实测参数和日常使用感受项目跑稳定之后我简单测了一下整机参数。遥控距离实测在6到8米隔一堵墙会明显变差但正常使用足够三档调速里低档适合睡觉中档办公高档风力很大但噪音也明显。整机用USB 5V 2A供电跑中档加摇头大概0.4到0.5A如果用18650锂电池加升压板中档摇头模式能跑三个多小时够一下午使用了。日常用下来的真实感受是定时功能最实用。夏天不想让风扇吹一整晚设个60分钟到点自动关比买那种几十块钱的定时插座还顺手。摇头功能也很自然减速电机带动曲柄连杆的摆动速度和家用电风扇几乎一样不会像舵机那样一下一下地顿。6.2 后续升级方向和模块化思路这个项目做完之后我留了两个扩展方向。一个是换一个带硬PWM输出的增强型51单片机把软件PWM改成硬件PWM电机啸叫能进一步降低CPU负载也更小。另一个是加一个DS18B20温度传感器根据环境温度自动切档温度低用低档温度高直接高档把风扇变成“半自动”设备。如果你想继续玩还可以加离线语音识别模块喊一声就能开关风扇加一个OLED屏显示当前档位和剩余时间或者用带充电管理的18650电池模块让整机彻底摆脱USB线。每一步改动都不破坏原来的主循环因为在结构上我已经把红外解码、摇头控制、PWM调速拆成了独立模块后面加功能只是加一个模块的事。我个人体会是这个项目最值得参考的不是某一段代码而是“先选好方案再把方案拆成模块最后逐个调试”的节奏。很多同学一上来就写程序结果硬件没搭稳出问题之后分不清是程序错还是电路错。我的做法是先把硬件调到电机动起来、红外能解码再写上层逻辑这样每一层都有明确验证点排查问题快得多。如果让我再做一版我会直接用洞洞板代替杜邦线和面包板把接线做成焊接固定的整机会再可靠一个档次。