尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

自制Arduino红外遥控器,驱动乐高Power Functions火车

自制Arduino红外遥控器,驱动乐高Power Functions火车 如果你玩过乐高火车一定对车头上那个小小的红外接收器不陌生轨道铺好电池装上遥控器一推马达就转。但原厂遥控器用久了痛点会越来越明显——固定四个通道、速度拨杆手感一般、想加个自动进站刹车基本没门、遥控距离还受摆放角度影响。于是就有了这个项目用单片机、红外LED和一个入门级三极管自己做一个完全可控的IR-Based LEGO Train Controller。这篇是Part 1先把发射端完整跑通让自制的Arduino遥控器能驱动乐高官方Power Functions接收器和火车马达后面再谈多通道切换、速度曲线、自动控制这些进阶玩法。适合乐高玩家、硬件DIY爱好者也适合想了解红外遥控原理的初学者参考。1. 项目背景与整体方案设计1.1 为什么放着原厂遥控器不用非要自己做玩乐高火车的老玩家基本都会遇到类似问题原厂的红外遥控器本身没什么不好但它是个封闭系统你没法往里面加自己的逻辑。想实现一个“火车接近站台自动减速”的功能或者想用手机App控制火车前进后退原厂遥控器就完全无能为力了。自制控制器最大的价值不是省钱而是把控制权拿到自己手里。用单片机做发射端之后你可以随意扩展功能接几个按钮做手动控制接旋转编码器模拟油门接蓝牙模块让手机控制甚至接上一堆传感器让火车自己跑。这套思路本质上就是把“遥控器”从一个固定硬件变成一个可以无限折腾的开放式平台。我之前也见过不少人在乐高火车上加蓝牙或者2.4G模块但那样需要在车头改装接收端而且和乐高官方PF接收器不兼容。而红外的方案有一个天然优势乐高官方Power Functions接收器本身就是红外接收的你只要把发射端做出来车头完全不用动放上电池就能跑。这也是我这次选红外而不是蓝牙的根本原因——先做到“官方接收器能直接听懂我发出的指令”剩下的事都好办。1.2 整体技术选型Arduino 红外LED PF协议整套系统分为三个部分信号源、发射电路、接收端。信号源我用Arduino Nano原因很简单5V逻辑电平适合直接驱动三极管电路16MHz主频足够产生精确的红外载波社区资料多到随便搜一下就有参考而且很便宜。发射电路是“三极管开关电路 红外LED”用单片机的普通IO控制三极管的通断让IR LED在需要的时候以100mA以上的脉冲电流发光。接收端直接用乐高官方的Power Functions红外接收器它接收38kHz载波信号并驱动马达。有人会问为什么不用555定时器或者纯逻辑芯片来做发射确实可以做但灵活性太差。Arduino方案的好处是改一个速度、加一个功能只需要改代码不用重新焊电路。而且后续做解码、做多通道管理、做自定义协议全都依赖单片机现在就把平台搭好后面会省很多事。整套系统的数据流大概是这样的Arduino根据你设定的通道和指令在内存里拼出一帧符合乐高PF协议的数据然后通过IO口控制三极管让红外LED以38kHz载波的形式把这帧数据发出去乐高PF接收器收到并解码后把PWM信号输出给马达。整个过程看似简单但里面有几个点非常关键接下来逐个拆开讲。2. IR通信原理与乐高协议解析2.1 38kHz红外载波为什么是38kHz红外遥控的本质是用肉眼不可见的近红外光传递信息。但如果你直接把数据信号接到LED上让它一直亮着或者一直灭着接收端根本分不清这是不是周围环境光的干扰。所以所有红外遥控系统都会用到一个技巧先把数据信号“抬”到某个固定频率的载波上再用载波的有无来表达数据。乐高PF接收器用的载波频率是38kHz。这个频率差不多是红外遥控领域的“通用语言”——电视遥控器、空调遥控器、各种家电遥控器很多都用38kHz或者非常接近的频率。选择38kHz有实际考量这个频率既处于红外接收头灵敏度较高的区间又能较好地避开室内照明尤其是50Hz/100Hz交流电光源带来的低频干扰。“OOK”这个词你可能见过意思是On-Off Keying就是载波存在时表示一种状态不存在时表示另一种状态。可以把它想象成用闪光灯发摩斯电码灯快速闪烁的次数代表点还是划而平时灯不亮就没有信号。38kHz载波就是那个“快速闪烁”每个载波周期大约26.3微秒一亮一灭接收端的一体化接收头比如TSOP38238会把这串高频闪烁解调成普通的数字电平变化输出给后端的解码电路。2.2 乐高Power Functions IR协议帧结构乐高PF协议并不是简单的高低电平编码它用的是脉冲位置调制PPM。每个数据位又分成前后两个阶段通过载波脉冲出现在哪个阶段来区分0和1。这样做的好处是抗干扰能力比较强接收端对脉冲的绝对长度没那么敏感而是关注两个脉冲之间的相对位置。一帧完整的PF命令包括起始位、16位数据、停止位。起始位是一个较长的载波脉冲作用是让接收端知道“命令开始了”。16位数据里包含了通道号、操作模式和速度/方向信息。乐高官方遥控器有四个通道可切换操作模式有好几种控制火车最常用的是PWM模式也就是直接输出不同占空比的PWM信号给马达调速。当你按住原厂遥控器上的某个按键不放时它会把这帧命令重复发送多次通常是六次以上。接收端不是收到一帧就立刻执行而是连续收到内容相同的命令帧之后才动作。这么做是为了防止遥控器按键抖动或者环境干扰造成的误触发。我们自己写发射端代码时也要模仿这个行为不然火车会时不时“抽搐”一下。这里必须提一个很实在的建议网上各种资料里给出的PF协议时序参数很多是从不同版本、不同地区的设备上逆向出来的细节上可能有差异。最靠谱的做法是如果你手边有原厂8879遥控器用逻辑分析仪抓一下它发出的波形对照着调自己代码里的参数。没有逻辑分析仪的话至少也要先用手机摄像头确认IR LED确实在发光再逐步排查距离和响应问题。2.3 两个IR DropLED压降与信号衰减“IR Drop”这个词在电路和红外通信里其实有两层含义都对这个项目影响很大。第一层是电子学里的IR Drop指电流流过电阻或者半导体器件时产生的电压降。具体到这个项目就是红外LED导通时的正向压降Forward Voltage Drop。别小看这零点几伏它直接决定你限流电阻怎么选。多数940nm波长的红外LED正向压降在1.2V到1.5V之间如果拿5V电源直接驱动实际落在限流电阻上的电压只有3.5V左右而不是5V。忽略这个压降算出来的电流会偏大不少。第二层是物理层面的信号衰减。红外光在空间传播时遵循平方反比定律距离增加一倍光照强度会降到原来的四分之一。所以你会发现遥控器离接收器近一点信号很强稍微远一点就急剧变差。这个衰减没法完全消除只能通过增大发射电流、优化LED透镜、选择合适波长来缓解。理解了这个“IR Drop”再去看后面限流电阻优化和遥控距离测试就知道每项操作到底在解决什么问题了。3. 硬件设计与发射电路搭建3.1 元器件清单与选型理由做这个红外火车控制器的发射端需要的器件非常少大部分你家里可能已经有了。我列了一份我当时用的清单以及为什么要选这些型号。元器件推荐型号/规格选型理由单片机Arduino Nano5V逻辑体积小直接插面包板USB供电方便红外LED波长940nm5mm与常见红外接收头光谱匹配肉眼几乎看不到光省电NPN三极管S8050或2N2222便宜、电流能力足够饱和压降小基极限流电阻1kΩ限制单片机IO口输出电流保护引脚发射限流电阻10Ω22Ω决定红外LED峰值电流影响遥控距离面包板/跳线标准面包板初期调试方便后面再做PCB或洞洞板选IR LED时常见两个波长850nm和940nm。850nm的红外光在通电时会带一点点暗红色肉眼能隐约看到调试时很直观。940nm则基本完全不可见更接近乐高官方的做法功耗也更低。我建议初学者先用850nm方便确认LED是否在工作等电路稳定了再换940nm。三极管型号不用太纠结S8050、2N2222、BC337都行。关键是选那种集电极电流能到500mA以上的管子因为我们要让红外LED脉冲电流到100mA以上普通小信号管虽然也能用但余量不够长时间工作容易发热。3.2 发射电路为什么必须加三极管第一次做红外遥控的玩家最容易踩的坑就是把红外LED直接接到Arduino的IO引脚上。这样接在近距离测试时往往也能工作但你会发现遥控距离顶多几十厘米稍微远一点火车就没反应了。原因很简单Arduino的一个IO引脚最大输出电流只有20mA左右绝对极限40mA而要让红外LED在开放空间里达到几米的有效距离通常需要100mA以上的脉冲电流甚至更高。直接用IO引脚推不动大电流于是需要三极管来做“小电流控制大电流”的开关。Arduino的IO引脚只在基极上提供约5mA的电流来控制三极管的导通而集电极回路上允许流过上百毫安的电流给红外LED供电。你可以把它理解成一个水龙头IO引脚只是轻轻转动了阀门的手柄真正流过大水流的是水管本身。连接方式Arduino的D3引脚通过1kΩ电阻接到三极管的基极三极管发射极接地IR LED的正极接5V电源负极接三极管的集电极。这样当IO引脚输出高电平时三极管导通IR LED通电发光输出低电平时三极管断开LED熄灭。注意IR LED的极性不能接反长脚是阳极接5V那一边。整个回路里还有一个容易被忽略的点乐高PF接收器内部已经有解调电路和自动增益控制它对发射端的载波频率和信号强度都有要求。载波频率偏一点问题不大但电流太小就会导致距离明显缩短。所以这个三极管电路不是可选项而是保证性能的刚需。3.3 限流电阻计算把IR Drop算进去限流电阻的取值是整个硬件部分最需要动笔算的地方。很多人直接套公式RV/I拿5V除以想要的100mA得50Ω结果换来的却是IR LED亮度不足。问题就出在没把IR Drop算进去。实际电路里的电压分配是这样的电源电压5V减去IR LED的正向压降约1.4V减去三极管饱和导通时的集电极-发射极压降约0.2V剩下的电压才是加在限流电阻上的。于是公式变成R (Vcc - Vf - Vce_sat) / I代进去R (5 - 1.4 - 0.2) / 0.1 34Ω取标称值33Ω。如果你想要更大电流比如150mAR 3.4 / 0.15 ≈ 22.7Ω取22Ω。很多人一开始算出来的电阻值偏大就是少了“减掉IR Drop”这一步。如果想把两个IR LED串联来增强发射功率Vf会翻倍变成2.8V同样电流下限流电阻就得重新算。串联的好处是两个LED发光的区域更大但电源电压只有5V串联后留给电阻的压降空间就小了电流提升受限。并联是另一种思路但每个LED都得有独立的限流电阻否则电流分配不均一个亮一个暗。实测经验我在面包板阶段用33Ω电阻IR LED的峰值电流大约100mA3到4米距离内控制乐高火车很稳定。把电阻换成22Ω后电流到150mA距离能明显拉到5米以上但IR LED在持续发送时会有一点温升。这个电压电流的事情后面测试时会直接影响你的体验所以别嫌烦老老实实算一遍。4. 软件实现让Arduino开口讲红外话4.1 软件流程与扩展思路有了硬件基础接下来就是让Arduino按照乐高PF协议把红外数据发出去。软件的整体流程不复杂系统初始化之后进入主循环读取你想发送的命令Part 1里先用固定命令后面可以接按钮或串口然后把这个命令组帧、调制、重复发送若干次最后等待一段时间再进入下一轮。代码结构上建议把“协议”和“控制逻辑”分开。协议相关代码负责把通道、方向、速度这些参数变成一帧红外数据并且发送出去控制逻辑负责决定当前要发什么命令。这样一来你后续加按钮、加蓝牙、加传感器都只需要改控制逻辑那一层协议层完全不用动。我自己的习惯是先写一个小的状态机框架初始化状态、待机状态、发送状态。待机状态下不断检查是否有新的控制命令有就进入发送状态命令发完再回到待机。这个框架看起来简单但后面想加“自动发送多帧”“按键消抖”“速度渐变”这些功能时会非常顺手。4.2 核心代码发送乐高PF命令下面的代码基于Arduino环境用普通IO翻转的方式产生38kHz载波。这种方式的优点是原理直观、代码好懂缺点是digitalWrite函数有额外开销导致载波频率不是严格38kHz但对于控制乐高火车来说完全够用。想要更精确的载波可以改用定时器的PWM输出区别我在下一节细说。#define IR_PIN 3 // 发送一帧乐高PF命令简化版 // data: 16位数据channel: 0~3 void sendPFCommand(uint16_t data, uint8_t repeatCount) { for (int i 0; i repeatCount; i) { sendStartPulse(); for (int bit 15; bit 0; bit--) { sendPFBit((data bit) 0x01); } sendStopPulse(); delayMicroseconds(1000); // 帧间隔 } } // 起始位较长的38kHz载波脉冲 void sendStartPulse() { for (int i 0; i 45; i) { digitalWrite(IR_PIN, HIGH); delayMicroseconds(13); digitalWrite(IR_PIN, LOW); delayMicroseconds(13); } delayMicroseconds(566); } // 一个数据位通过载波脉冲在前/后阶段区分0和1 void sendPFBit(bool bit) { if (bit) { // 1: 前阶段无载波后阶段有载波 delayMicroseconds(800); sendCarrierBurst(15); delayMicroseconds(400); } else { // 0: 前阶段有载波后阶段无载波 sendCarrierBurst(15); delayMicroseconds(800); delayMicroseconds(400); } } // 发送一段固定数量的38kHz载波脉冲 void sendCarrierBurst(int cycles) { for (int i 0; i cycles; i) { digitalWrite(IR_PIN, HIGH); delayMicroseconds(13); digitalWrite(IR_PIN, LOW); delayMicroseconds(13); } } // 停止位 void sendStopPulse() { delayMicroseconds(1000); } void setup() { pinMode(IR_PIN, OUTPUT); // 给串口调试留个口子发送间隙再打印 Serial.begin(9600); } void loop() { // Part 1固定发送通道1、前进、速度3 // 0x0103 只是示例不同接收器映射可能有差异 sendPFCommand(0x0103, 6); delay(800); // 后续可以改成读按钮、读串口、读电位器来决定命令 }这段代码的核心思路就是把数据帧拆成一个个bit然后按照PF协议每个bit内部的时序控制IR LED发光或者不发光。需要注意代码里的时间参数是我根据开源资料整理的参考值不一定和你手上的接收器完全匹配。强烈建议用逻辑分析仪抓一下官方遥控器的波形然后把这些delay值校准到和原厂一致。4.3 时序细节与中断处理如果你实测下来发现火车偶尔反应、经常没反应大概率出在时序上。乐高PF协议对帧内的时间精度要求不算变态但也不能差太多尤其是起始位和数据位的相对位置。如果你在发送帧的过程中Arduino突然去处理一个中断比如串口收到一个字节就会导致某一位的时间被拉长接收端就解不出来了。解决方法是发送期间临时关闭中断。你可以在sendPFCommand函数开头调用cli()结束时调用sei()这样在整帧发送过程中除了复位等不可屏蔽中断其他中断都被暂时挂起时序就不会被破坏。代价是发送期间串口接收和定时器中断会暂停但对于一帧不到20毫秒的发送时间来说影响微不足道。另一个细节是delayMicroseconds的精度。在16MHz的Arduino上delayMicroseconds的典型误差在几微秒以内对38kHz载波来说可以接受。但如果你用了delay(1)这种毫秒级的延时实际误差会更大因为Arduino库函数内部用了一个中断计数器而你在发送期间已经关了中断所以毫秒级延时会变得不可靠。发送帧内部尽量全用delayMicroseconds帧间隔用宏定义控制不要依赖delay。用普通IO翻转载波的方案有个小缺陷digitalWrite函数每次调用有额外的指令开销导致实际载波频率会偏低一些可能在36到38kHz之间。幸运的是市面上大多数红外接收头在36到40kHz范围内都能正常工作所以一般不影响。但如果你追求极致稳定可以改用定时器PWM输出把38kHz载波交给硬件生成代码里只需要控制PWM的开关。两种方案我都在不同项目里用过对于乐高火车控制来说IO翻转方案已经足够没必要为了几个百分点去折腾定时器。5. 实操过程从面包板到测试台5.1 面包板搭建步骤面包板阶段别急着焊电路先把它完整跑通了再说。我的搭建顺序是这样的第一步把Arduino Nano插在面包板一侧确认GND、5V、D3引脚都方便引线。Nano的引脚间距是标准2.54mm直接跨在面包板中间的凹槽上即可。第二步接三极管。先确定三极管的引脚排列S8050从正面看从左到右是E、C、B2N2222常见排列是E、B、C一定要先看数据手册再用万用表确认接错引脚是整个项目最隐蔽的坑。我一开始用S8050就被那个“EBC”排列坑过一次还以为是三极管烧了。第三步把D3引脚通过1kΩ电阻接到三极管基极。IR LED的阳极长脚接到5V阴极短脚接到三极管的集电极发射极直接接GND。这一整套连接完成后再从头到尾沿着电流路径检查一遍从5V出发经过IR LED经过三极管C-E回到GND控制回路是从D3出发经过1kΩ电阻经过三极管B-E回到GND。两条回路都通电路才算接对。第四步接电源。Nano用USB线连电脑供电5V引脚会和USB的5V直接连通。注意如果你的IR LED脉冲电流比较大USB供电一般扛得住但如果是电池供电要确认电池能输出足够电流不然电压会被拉低红外LED反而变暗。5.2 烧录与实测方法代码上传之后先做一个最直观的检查把手机摄像头对准IR LED按下按键或者手动触发发送函数你会看到屏幕上出现一个淡紫色或者白色的小光点这就是红外LED在工作。如果你看不到光点先检查代码有没有烧进去再检查IR LED有没有接反最后才考虑三极管电路的问题。接下来拿乐高PF接收器和马达做联调。把接收器装上电池马达插到接收器的输出口上接收器拨到通道1或代码里对应的通道然后发送命令看马达是否转起来。如果转到一半方向不对或者速度档位不对就去检查代码里的数据位写的是什么。很多情况下不是协议时序错了而是“1”和“0”的映射反了。实测时建议先近距离测比如IR LED对着接收器30厘米左右确认能控制再逐步拉远距离。如果近距离正常、远距离失控问题基本出在发射电流或者环境光干扰上不会是你的协议不对。如果近距离都不响应那就回到逻辑分析仪或者手机摄像头这一步看看载波到底有没有发出去。5.3 距离优化与注意事项距离是这个项目里最直观的体验指标。我自己的实测记录是室内普通照明环境下33Ω限流电阻、100mA电流稳定控制距离3到4米换成22Ω电阻、150mA电流后距离能到5米以上。如果是在阳光直射或者强光环境下接收端容易饱和距离会急剧缩短这是所有红外遥控的共性不是你的电路问题。优化距离的手段有好几个加大发射电流是最直接的办法但要注意别让IR LED长时间超过额定电流加透镜或者反光杯可以聚拢光线把散射的红外光集中到一个方向把IR LED升高一点让发射和接收尽量在一条直线上也能明显改善。还有一个很多人不知道的点IR LED的发射角度有宽有窄宽带LED适合近距离大范围覆盖窄带LED适合远距离定向控制你可以根据实际使用场景换着试。还有一个容易被忽略的因素就是乐高PF接收器本身有一定的输入灵敏度范围。信号太弱不行但信号太强也可能让接收头的自动增益控制失效导致近距离反而失控。如果你遇到“贴脸反而没反应、拉远一点反而正常”的情况可以在发射LED前面贴一小块半透明的塑料片或者胶带把光强稍微压一压通常能解决。6. 常见问题与排查技巧实录6.1 常见问题速查表做这个项目的过程中我前后遇到过不少问题也帮朋友排查过几回大部分都集中在下面这张表里。现象可能原因排查方法解决办法马达完全没反应IR LED没亮或载波没发出去手机摄像头看IR LED有无光点检查接线、代码是否烧录成功只有贴近接收器才响应发射电流太小或限流电阻太大测量IR LED实际电流按IR Drop计算减小限流电阻距离小于1米电源电压被拉低或LED波长不匹配用万用表测5V引脚电压换成电流能力更强的电源或电池偶尔能控制偶尔失灵帧重复次数太少或时序被中断干扰发送期间关闭中断增加重复次数使用cli()/sei()包裹发送多个通道互相串扰通道数据位配置错误用逻辑分析仪对照原厂波形校准通道映射近距离反而没反应接收头自动增益控制饱和远距离是否正常在IR LED前加半透明遮挡持续发送时LED很烫峰值电流太大或占空比太高摸一下IR LED温度调大限流电阻降低发送频率这张表是个很好的排查起点但具体情况还是要具体分析。比如“偶尔能控制”这个问题我见过有人调了半天代码最后发现是面包板上的跳线松动虚接了。所以排查时先看机械连接再怀疑代码顺序不能反。6.2 独家避坑经验最后分享几个属于“不做不知道”的避坑细节这些都是我实际踩过的坑照着做能省下大把时间。第一发帧之前先确认供电。IR LED脉冲电流到100mA以上之后如果用Nano的板载稳压器供电瞬间压降可能很明显。我在一次测试中遇到过诡异现象IR LED明明在闪但火车就是不给反应后来用示波器一看5V引脚在发送瞬间被拉到了4.2V载波波形整个变形了。换成外部5V供电后就正常了。所以如果你发现距离始终上不去先量电压不要只盯着电流。第二代码里一定要留一个串口调试输出。发送前把通道、方向、速度、重复次数这些信息通过Serial打印出来实测排查会轻松很多。但要注意把打印放在帧与帧之间不要放在发送帧的内部否则串口中断会打断红外时序。实在不行就只在发送完成后统一打印一次。第三手头没有逻辑分析仪的话手机摄像头就是最好的红外检测工具。除了能看IR LED是否亮还能观察闪烁模式。把手机摄像头对准LED按下遥控器你会看到一串有规律的闪烁。不同按键对应不同闪烁序列虽然肉眼分辨不了细节但至少能判断“代码是否在跑”。这个技巧在我给代码调通道映射的时候帮了大忙。第四如果你准备长期用这套系统面包板阶段验证之后尽快转到洞洞板或者PCB上。面包板的接触电阻和寄生电容在低频下无所谓但在38kHz载波和高脉冲电流场景下偶尔会出现莫名其妙的信号劣化。焊到洞洞板上之后很多“时好时坏”的问题会自己消失。这套发射端做完之后我最大的体会是红外遥控看起来就是“发光、接收”四个字但真正把一套协议跑通并稳定工作涉及的是电路计算、时序控制、干扰排查这些工程细节。Part 1到这里你已经有了一个能实际控制乐高火车的自制遥控器。下一步我打算在Part 2里加入多按钮控制和速度渐变逻辑让火车不仅“能开”还能开得更像真的。
返回列表