1. 项目概述从零打造一个红外电视遥控器如果你手头有一块Adafruit的Circuit Playground Express开发板并且对用代码控制现实世界的小玩意儿感兴趣那么今天这个项目绝对会让你兴奋。我们将一起动手把这块功能丰富的开发板变成一个能真正控制你家电视的红外遥控器。这不仅仅是复制一个按键功能而是深入理解红外通信协议、学习如何用CircuitPython与硬件交互并最终完成一个即插即用的可编程遥控设备。整个过程不需要焊接代码也相对直观非常适合嵌入式开发的初学者或者想给创客项目增加一点实用功能的爱好者。Circuit Playground Express后面我们简称CPX这块板子设计得非常友好集成了LED、按钮、传感器最关键的是它自带了一个红外发射器和接收器。这意味着我们不需要额外购买和连接任何红外模块直接用板载硬件就能完成信号的发送和接收。我们将使用CircuitPython进行编程这是一种基于Python的、为微控制器优化的编程语言语法简单库生态丰富特别适合快速原型开发。整个项目的核心逻辑分为两步第一步是“学习”即用CPX的红外接收器捕获并记录你原有电视遥控器发出的红外信号编码第二步是“模仿”即让CPX的红外发射器精确地复现这些编码从而骗过你的电视。下面我们就来一步步拆解这个过程。2. 硬件与软件环境准备在开始写代码之前确保手头有正确的硬件和配置好的软件环境是成功的第一步。这个环节的准备工作看似琐碎但每一步都关系到后续能否顺利进行。2.1 核心硬件选择与确认首先必须再次强调硬件型号的唯一性。Adafruit的Circuit Playground系列有好几款但只有Circuit Playground Express是本次项目的完美选择。Circuit Playground Classic较老的版本不支持CircuitPython因此无法运行本项目的代码。Circuit Playground Bluefruit新款主打蓝牙功能但为了给蓝牙天线腾出空间移除了红外发射和接收硬件。因此除非你外接红外模块否则无法直接使用。Circuit Playground Express集成了红外发射IR LED、红外接收IR sensor、加速度计、温度传感器、蜂鸣器、电容触摸按键等十多种外设并且原生支持CircuitPython、MakeCode和Arduino IDE。我们需要的红外收发功能正是它的标准配置。所以请确认你的板子正面印有“Circuit Playground Express”字样。除了主板你还需要一根数据线非仅充电线用于连接电脑以及一个3.7V锂电池组可选但推荐。电池组能让你的遥控器摆脱线缆束缚真正变成一个可移动的遥控设备。2.2 刷写CircuitPython固件CPX出厂可能运行着其他引导程序如MakeCode。我们需要先将它的“操作系统”换成CircuitPython。下载固件访问Adafruit的CircuitPython官网找到Circuit Playground Express的页面下载最新的.uf2格式固件文件。务必选择与“Express”型号匹配的文件。进入引导模式使用数据线将CPX连接到电脑。此时板子上的所有LED可能会亮起或闪烁。接着快速双击板子中央的复位RESET按钮。双击后所有LED会变成红色然后除了一个之外的其余LED都会熄灭。此时电脑上会出现一个名为CPLAYBOOT或ARDUBOOT的可移动磁盘。这个模式称为“引导加载程序模式”专门用于刷写新固件。拖放固件将刚才下载的.uf2文件直接拖拽或复制到CPLAYBOOT磁盘中。复制完成后磁盘会自动弹出板子会自动重启。重启后电脑上会出现一个新的名为CIRCUITPY的可移动磁盘。恭喜这说明CircuitPython固件已经刷写成功这个CIRCUITPY盘就是你的“代码硬盘”之后我们编写的Python代码文件code.py就放在这里板子上电后会自动运行它。2.3 安装必要的代码库与编辑器CircuitPython的强大在于其丰富的“库”这些库文件提供了驱动各种硬件的简单接口。对于红外功能我们需要一个专门的库。安装IRRemote库访问Adafruit的CircuitPython库包发布页面下载与你的CircuitPython版本匹配的完整库包通常是一个.zip文件。解压这个ZIP文件在其中找到名为adafruit_irremote.mpy的文件.mpy是CircuitPython的编译库格式。打开CIRCUITPY磁盘如果里面没有lib文件夹就新建一个。将adafruit_irremote.mpy文件复制到lib文件夹内。安全弹出硬件在Windows或macOS上务必使用“安全弹出硬件”操作来断开CIRCUITPY磁盘以确保所有文件写入完成。然后重新插拔USB线让板子重新挂载磁盘。安装Mu编辑器 虽然你可以用任何文本编辑器编写代码但Mu编辑器是Adafruit官方推荐的CircuitPython集成开发环境它集成了代码编辑、串口监视器REPL和文件管理能极大提升开发效率。前往Mu编辑器的官方网站下载对应你操作系统Windows, macOS, Linux的安装包。安装并运行Mu。首次启动时它会让你选择模式请选择“Adafruit CircuitPython”模式。提示为什么强调“安全弹出”和库文件因为直接拔线可能导致文件系统损坏尤其是小容量的CIRCUITPY磁盘。而库文件如果缺失或版本不匹配代码中的import语句就会失败导致程序无法运行。这些细节是嵌入式开发中保证稳定性的基础操作。3. 红外遥控原理与信号捕获实战在动手让板子发射信号之前我们必须先理解它要发射什么以及如何获取这个“什么”。这就是红外信号的学习过程。3.1 红外通信基本原理简析家用红外遥控器使用的是一种非常经典的脉宽调制PWM编码方式。它并不是发送一个简单的“开”或“关”光信号而是发送一连串长短不同的红外脉冲。这些脉冲的组合方式构成了不同的协议比如常见的NEC、Sony SIRC、RC-5等。每个协议定义了脉冲的基准时长、头码、逻辑“0”和逻辑“1”的表示方法。举个例子在NEC协议中一个完整的信号通常以一个9ms的高电平和4.5ms的低电平开始起始码然后跟着32位的数据地址码和命令码。每一位数据如果是“0”就用560µs高电平加560µs低电平表示如果是“1”则是560µs高电平加1690µs低电平。接收端电视的红外接收头会将这些光信号解调为电信号并由电视的主控芯片按照协议解码最终执行对应的操作如开机、调音量。对于我们这个项目我们不需要深入理解电视遥控器具体用的是哪种协议。我们可以采取一种更通用的“笨办法”原始信号录制与回放。即用CPX的高精度输入捕获功能把遥控器发出的原始脉冲序列高低电平的持续时间以微秒为单位完整地记录下来存储为一个数组。然后在需要的时候让CPX的红外发射器按照这个时间序列原样播放一遍。只要播放得足够精确电视就无法分辨这是原装遥控器还是我们的CPX。3.2 使用REPL交互模式捕获信号CircuitPython的REPLRead-Eval-Print Loop是一个交互式的Python环境我们可以通过串口直接向板子发送命令并立即看到结果。这是调试和进行一次性操作如信号捕获的利器。连接并打开REPL在Mu编辑器中点击顶部的“串行”按钮。编辑器底部会打开一个面板这就是串口监视器也是REPL的入口。如果面板显示空白或者提示连接失败尝试按一下CPX的复位键。正常情况下你会看到几行欢迎信息最后是提示符。输入捕获代码 在提示符后逐行输入以下代码。每输入一行按一次回车。这些代码的作用是初始化红外接收、准备解码器并开始监听红外信号。import board import pulseio import adafruit_irremote # 初始化红外接收器连接到板载的IR_RX引脚最大记录200个脉冲 pulses pulseio.PulseIn(board.IR_RX, maxlen200, idle_stateTrue) decoder adafruit_irremote.GenericDecode() # 清空可能存在的旧数据并开始监听 pulses.clear() pulses.resume()执行捕获并触发信号 接下来输入最关键的一行命令它将启动一次性的信号捕获pulse_data decoder.read_pulses(pulses)输入这行并回车后你会发现提示符消失了REPL似乎“卡住”了。这其实是好事说明程序正在board.IR_RX引脚上等待红外信号的到来。此时立刻拿起你的原装电视遥控器将其红外发射头对准CPX板上的红外接收器一个小黑色的方形元件通常旁边标有“IR”然后按下你想要学习的按键比如电源键。检查捕获结果 如果捕获成功REPL会重新出现提示符。现在输入变量名查看我们捕获到的数据pulse_data回车后你会看到输出一长串用方括号[]括起来的数字例如[9024, 4512, 560, 560, 560, 1690, 560, 560, ...]。这一串数字就是你的遥控器电源键红外信号的“指纹”每个数字代表一个高电平或低电平持续的微秒数。实操心得捕获信号时常见的坑与技巧距离与角度红外接收器有一定的接收角度。确保遥控器的发射头正对CPX的接收器距离在10-30厘米为宜。太远信号弱太近可能饱和。环境光干扰强烈的日光或某些LED灯可能包含红外成分干扰接收。尽量在室内普通灯光下操作避免阳光直射。多次尝试同一个按键每次按下的信号序列可能因遥控器电量、按键压力略有微小差异但整体模式一致。如果第一次捕获的数组非常短比如少于50个数字或看起来杂乱可能是捕获失败按CtrlC退出等待重新执行pulse_data decoder.read_pulses(pulses)再试一次。保存多个按键你可以重复上述过程捕获音量加、减、频道切换等不同按键的信号。每次捕获前最好先执行pulses.clear()和pulses.resume()来重置接收器。为每个按键的数据单独起一个变量名如vol_up_data、ch_next_data方便后续使用。4. 遥控器程序编写与功能集成捕获到原始信号数据后下一步就是将这些数据“教”给我们的主程序并设计一个交互方式让我们能通过CPX上的按钮来控制信号的发射。4.1 解析与集成捕获数据到主程序我们不会一直在REPL里操作最终需要一个能独立运行的code.py文件。我们需要把捕获到的数据数组粘贴到主程序的相应位置。创建主程序框架 在Mu编辑器中点击“加载”按钮打开CIRCUITPY磁盘上的code.py文件。如果文件是空的或者有其他内容将其清空然后粘贴以下基础框架代码import time import array import board import pulseio import adafruit_irremote from digitalio import DigitalInOut, Direction, Pull # --- 红外发射初始化 --- # 创建一个PWM输出对象连接到板载红外发射LED pwm pulseio.PulseOut(board.IR_TX, frequency38000, duty_cycle2**15) # 创建红外发射器对象 transmitter adafruit_irremote.GenericTransmit() # --- 按钮初始化 --- # 将板载的A按钮设置为输入并启用内部上拉电阻 button_a DigitalInOut(board.BUTTON_A) button_a.direction Direction.INPUT button_a.pull Pull.UP # 默认高电平按下时变为低电平 # --- 定义红外信号数组 --- # 这里需要粘贴你从REPL捕获的数据 power_signal array.array(H, []) # ‘H’表示数组存储无符号短整型(0-65535) # 示例你可以为其他按键定义更多数组 # volume_up_signal array.array(H, [ ... ]) # volume_down_signal array.array(H, [ ... ]) # --- 主循环 --- print(红外遥控器就绪按下A键发送电源信号。) while True: if not button_a.value: # 按钮被按下时值为False print(发送红外信号...) try: transmitter.transmit(pwm, power_signal) print(信号发送成功) except Exception as e: print(发送失败:, e) # 简单的防抖动延迟避免一次按下触发多次 time.sleep(0.5) time.sleep(0.01) # 短暂延迟降低CPU占用嵌入捕获的数据 找到代码中power_signal array.array(H, [])这一行。我们的目标是将REPL里pulse_data输出的那串数字放到这个方括号[]里面。回到REPL确保pulse_data变量里是你想要的信号数据。在REPL中输入pulse_data并回车让数据再次显示出来。用鼠标精确地选中从第一个方括号[到最后一个方括号]之间的所有内容包括括号本身。回到Mu编辑器的code.py文件中将光标定位到power_signal array.array(H, [])这行代码的两个方括号[]的中间。粘贴你刚才复制的内容。现在这行代码应该看起来像这样power_signal array.array(H, [9024, 4512, 560, 560, 560, 1690, ...])非常重要点击Mu编辑器顶部的**“保存”**按钮将修改后的代码保存到CIRCUITPY磁盘。保存后CPX会自动重启并运行新代码。4.2 扩展功能多按键与信号管理一个只有一个电源键的遥控器显然不够用。我们可以轻松地扩展它。捕获更多信号 重复第3.2节的过程在REPL中捕获其他按键如音量、音量-、频道的信号并分别保存到不同的变量例如vol_up_datavol_down_data。定义更多信号数组 在主程序code.py中像定义power_signal一样为每个新按键定义一个新的数组变量。power_signal array.array(H, [ ... ]) # 电源信号 volume_up_signal array.array(H, [ ... ]) # 音量加信号 volume_down_signal array.array(H, [ ... ]) # 音量减信号利用多个按钮触发 CPX有两个物理按钮A和B和7个电容触摸焊盘A1-A7 TX RX。我们可以用它们来触发不同的信号。使用B按钮初始化方式与A按钮完全相同。在循环中检查它的状态。使用电容触摸这需要引入touchio库。例如使用A1焊盘import touchio touch_a1 touchio.TouchIn(board.A1) while True: if touch_a1.value: print(A1被触摸发送音量信号) transmitter.transmit(pwm, volume_up_signal) time.sleep(0.3) # 触摸的防抖延迟可以稍长 # ... 检查其他按钮 time.sleep(0.01)优化代码结构 当按键多起来后用一堆if语句会显得混乱。一个好的实践是使用字典Dictionary来映射输入按钮/触摸和输出红外信号数组。# 定义一个映射字典 输入对象 - 对应的信号数组 control_map { button_a: power_signal, button_b: volume_up_signal, touch_a1: volume_down_signal, # ... 可以继续添加 } while True: for control, signal in control_map.items(): # 判断是否是触摸输入有‘value’属性还是按钮输入需要检查value为False if hasattr(control, value): # 触摸输入value为True时触发 if control.value: transmitter.transmit(pwm, signal) time.sleep(0.3) else: # 按钮输入value为False时触发 if not control.value: transmitter.transmit(pwm, signal) time.sleep(0.5) time.sleep(0.01)这样增加新的控制方式只需要在control_map字典里添加一行主循环逻辑无需改动代码更清晰、更易维护。5. 系统调试、优化与问题排查即使代码正确在实际使用中也可能遇到各种问题。这一部分记录了从原型到稳定可用的遥控器过程中可能遇到的典型问题及其解决方案。5.1 信号发送测试与角度调整保存好代码后CPX应该已经开始运行。将CPX通过电池或USB电源供电拿到电视附近。首次测试按下CPX上的A按钮。观察Mu编辑器底部的串口输出应该能看到“发送红外信号...”和“信号发送成功”的打印信息。这表示程序逻辑和发射电路是工作的。对准与角度将CPX板载的红外发射LED一个透明或浅灰色的LED对准电视的红外接收窗口通常位于电视正面下方或侧面是一个深色的小塑料片。由于这个发射LED功率较小且指向性较强你需要像使用普通遥控器一样找到一个合适的角度和距离通常10-50厘米内。可以缓慢地左右上下移动CPX同时反复按下A键。成功标志如果电视有反应开机/关机恭喜你如果没反应不要气馁这是调试的开始。5.2 常见问题与诊断流程当电视没有反应时可以按照以下排查树来定位问题问题现象可能原因诊断步骤与解决方案按下按钮串口无任何打印1. 代码未运行。2. 按钮接线或初始化错误。3. 串口未正确连接。1. 检查CIRCUITPY磁盘根目录下是否有code.py且内容正确。2. 按一下复位键观察串口是否输出初始信息“红外遥控器就绪”。3. 在REPL中手动执行import digitalio等语句检查是否有错误。有“发送成功”打印但电视无反应1.信号数据错误最常见。2. 发射LED未对准。3. 电视不支持原始信号回放罕见。4. 发射功率不足。1.核心检查在REPL中用len(power_signal)检查数组长度。如果少于50可能捕获不完整重新捕获。2. 在REPL中用transmitter.transmit(pwm, power_signal)手动发射一次同时用手机摄像头普通模式对准红外LED。按下按钮时在手机屏幕上应能看到发射LED发出微弱的紫色或白色光点。这是快速验证发射电路是否工作的好方法。3. 尝试极近距离5cm正对电视接收窗再测试。电视反应不稳定时灵时不灵1. 信号数据存在偶然误差。2. 电源干扰。3. 环境光干扰。1. 重新捕获几次同一个按键的信号比较它们的长度和大致模式。选择最稳定、长度最长的一组数据。2. 如果使用USB供电尝试换用电池供电排除电脑USB端口噪声干扰。3. 避开强烈的日光或节能灯直射。按下按钮电视执行错误操作信号数据被污染可能捕获到了其他信号如另一个遥控器的信号。在黑暗、安静的环境下确保只使用目标遥控器重新捕获信号。确保在read_pulses等待时没有其他红外源干扰。5.3 进阶优化与功能扩展在基本功能稳定后可以考虑以下优化让你的遥控器更好用、更专业增加视觉/听觉反馈 CPX板载了10个可编程的RGB NeoPixel LED和一个蜂鸣器。可以在信号发送成功时让LED闪烁绿色失败时闪烁红色或者让蜂鸣器发出短促的“嘀”声。这能提供直观的状态反馈尤其在无法看到串口输出时。import neopixel pixels neopixel.NeoPixel(board.NEOPIXEL, 10, brightness0.1) def send_signal_with_feedback(signal_array): try: transmitter.transmit(pwm, signal_array) pixels.fill((0, 20, 0)) # 成功亮绿色 time.sleep(0.1) pixels.fill((0, 0, 0)) except Exception as e: pixels.fill((20, 0, 0)) # 失败亮红色 time.sleep(0.5) pixels.fill((0, 0, 0))实现信号连发长按 电视遥控器通常支持长按音量键连续调节。我们可以修改按钮检测逻辑当检测到按钮被持续按下时以一定间隔重复发送信号。button_press_time 0 while True: if not button_a.value: if button_press_time 0: # 首次按下 send_signal_with_feedback(volume_up_signal) button_press_time time.monotonic() elif time.monotonic() - button_press_time 0.5: # 长按超过0.5秒 send_signal_with_feedback(volume_up_signal) time.sleep(0.15) # 连发间隔 else: button_press_time 0 # 按钮释放重置计时 time.sleep(0.01)省电优化 如果使用电池供电续航很重要。可以在长时间无操作后进入低功耗模式如降低CPU频率、关闭NeoPixel等或者利用板载的滑动开关将其配置为深度睡眠的唤醒源实现“物理关机”。外壳与便携性 为你的CPX遥控器设计一个3D打印外壳或者用一个合适的塑料盒来收纳。将电池组固定在外壳内部引出一个USB充电口。这样它就从一个开发板原型变成了一个可以放在茶几上的、像模像样的自制电子设备。从捕获一个飘忽的红外信号到稳定地控制客厅的电视这个过程充满了嵌入式开发特有的乐趣和挑战。它涉及了硬件交互、信号处理、状态机编程和实际调试。这个项目最大的价值不在于复制了一个遥控器而在于提供了一套方法论如何让微控制器“理解”并“模仿”一种简单的无线通信协议。掌握了这个方法你完全可以举一反三去控制空调、风扇、投影仪甚至组合多个设备的信号实现“一键关所有电器”的场景。硬件就在你手中逻辑由你定义剩下的就是发挥想象力去创造更多有趣的应用了。