基于PIC10F206单片机的通用红外遥控发射器设计与实现

📅 2026/6/17 0:43:34
基于PIC10F206单片机的通用红外遥控发射器设计与实现
1. 项目概述与核心价值最近在整理工作室的旧设备发现一堆不同品牌、不同型号的电视机、机顶盒和音响遥控器多得能开个“遥控器博物馆”。每次想开个投影仪都得在抽屉里翻半天体验极差。这让我想起了红外遥控这个看似古老却无处不在的技术它依然是控制家电最直接、最可靠的物理方式之一。于是我萌生了一个想法能不能自己动手做一个通用的、可编程的红外遥控发射器它要足够小巧成本要低还得能兼容市面上主流的红外协议比如飞利浦的RC5和索尼的SIRC。这样一个硬件就能“通吃”大部分设备再配合简单的编程就能实现自定义的宏命令比如一键开启“观影模式”打开投影、降下幕布、关闭主灯。这个项目的核心就是围绕一颗非常经典的8位单片机——PIC10F206来展开。你可能觉得在ARM Cortex-M满天飞的今天还用这种老古董是不是有点“复古”但恰恰相反对于红外遥控这种对时序要求极其苛刻、功能单一且对成本极度敏感的应用PIC10F206这类极简架构的MCU有着不可替代的优势它功耗极低外围电路简单价格便宜而且经过几十年的市场检验稳定可靠。通过这个项目我们不仅能深入理解RC5和SIRC这两种主流红外协议的精髓更能掌握在资源极度受限的MCU上如何用软件精准模拟复杂时序的硬核技巧。这不仅仅是做一个遥控器更是一次对嵌入式系统底层编程和时序控制的深度实战。2. 红外遥控协议核心RC5与SIRC深度解析要发射红外信号首先得搞清楚我们要“说”哪种“语言”。红外遥控协议就是设备间的通信语言不同的厂商定义了不同的语法。我们这个项目重点实现两种最经典、应用最广泛的协议RC5和SIRC。2.1 飞利浦RC5协议曼彻斯特编码的典范RC5协议由飞利浦公司制定其最大特点是采用了曼彻斯特编码。这种编码方式的好处是它自带时钟信息抗干扰能力强接收端更容易同步。一个完整的RC5帧长度为14位其结构如下两位起始位Start Bits总是为逻辑‘1’用于标志帧的开始。一位控制位Toggle Bit这是RC5协议的一个巧妙设计。每次按下同一个键这一位就会翻转0变1或1变0。接收端通过判断这一位是否变化可以区分是“长按”还是“重复按下”。如果键被一直按住接收芯片会周期性地重复发送上一帧但其中的Toggle Bit保持不变这样设备就知道这是“长按”动作而不是多次独立的“短按”。五位系统地址System Address用于区分不同类型的设备比如电视机、音响、机顶盒等。标准RC5定义了0-31共32个系统地址。六位命令码Command代表具体的按键功能如音量加、频道减、电源开关等共64个命令。RC5的载波频率固定为36kHz这是红外接收头最常用的中心频率之一。每一位数据的传输时间位周期是固定的1.778ms。曼彻斯特编码规定每一位数据中间必须有一次电平跳变从高到低的跳变代表逻辑‘0’从低到高的跳变代表逻辑‘1’。因此每一位都会被这个跳变点分成两个等长的半周期各889μs在半周期内载波信号36kHz方波要么持续发射代表高电平要么静默代表低电平。注意理解曼彻斯特编码是理解RC5的关键。它不是简单地用“有载波”和“无载波”来代表1和0而是用“跳变方向”来代表。这要求我们的发射程序必须非常精确地控制载波开关的时机确保跳变发生在每一位的正中间。2.2 索尼SIRC协议脉冲宽度编码的代表索尼的SIRC协议采用了另一种思路脉冲宽度编码。它用不同宽度的脉冲来代表逻辑‘1’和‘0’结构上通常比RC5更简单但不同版本的SIRC帧长不同。我们以实现最普遍的12位SIRC为例其结构如下一个起始脉冲Start Pulse一个长达2.4ms的高电平脉冲期间发射载波后跟一个0.6ms的低电平间隙。这个长脉冲非常显眼用于唤醒接收端并同步时钟。7位设备地址Device用于区分索尼旗下的不同产品线。5位命令码Command代表具体的按键。SIRC的载波频率通常为40kHz部分早期设备使用38kHz或其它频率但40kHz最为通用。它的编码规则是逻辑‘0’一个0.6ms的高电平脉冲载波后跟一个0.6ms的低电平间隙。总时长1.2ms。逻辑‘1’一个1.2ms的高电平脉冲载波后跟一个0.6ms的低电平间隙。总时长1.8ms。可以看到逻辑‘1’的脉冲宽度是逻辑‘0’的两倍接收端通过测量高电平脉冲的宽度来区分数据。这种编码方式对发射端的时序精度要求同样很高但解码逻辑相对曼彻斯特编码更直观一些。2.3 协议对比与选型思考为什么选择同时实现这两种协议因为它们代表了两种主流的编码流派覆盖了绝大多数消费电子品牌。飞利浦、汤姆逊、诺基亚等众多品牌使用或兼容RC5而索尼及其关联品牌则使用SIRC。实现这两个协议基本上就能控制工作室里八九成的老设备。从实现难度上看SIRC协议因为其简单的脉冲宽度编码在编程上稍微容易一些。RC5的曼彻斯特编码需要更精细的时序控制特别是在位中间进行电平翻转对中断响应和延时函数的精度挑战更大。但正因为有挑战实现后才更能体现我们对MCU的掌控力。3. 硬件核心PIC10F206最小系统与电路设计硬件部分的目标是极简、低成本、高可靠性。PIC10F206是一款仅有6个引脚的8位MCU但“麻雀虽小五脏俱全”它内部集成了振荡器、定时器、比较器等外设非常适合本项目。3.1 PIC10F206资源盘点与引脚分配我们先看看这颗MCU的家底程序存储器仅有768个指令字。这意味着我们的代码必须极其精简不能有任何冗余。RAM64字节。所有的变量、数组、堆栈都在这64字节里必须精打细算。时钟内部4MHz RC振荡器。这是我们所有时序的基准。虽然精度不如外部晶振但对于红外遥控误差通常在±5%以内可接受来说通过软件校准完全够用。外设一个8位定时器TMR0一个模拟比较器一个可编程的弱上拉GPIO。基于以上资源我们的引脚分配方案如下GP0配置为输出连接红外发射二极管IR LED的驱动三极管基极。这是我们的“信号发射引脚”。GP1配置为输入可连接一个轻触按键用于切换协议或发送测试信号。在实际产品中这里可以接一个编码器或多个按键来扩展功能。GP2这个引脚比较特殊可以复用为T0CKI定时器0外部时钟或比较器输出。在本项目中我们暂时不用配置为输出可以接一个状态指示灯LED用于调试指示。GP3仅能作为输入且是主复位引脚MCLR。我们必须将其使能为GPIO输入并连接上拉电阻用于模式选择例如通过短路到地选择RC5或SIRC模式。VDD/VSS电源和地。工作电压范围很宽2.0V-5.5V我们可以用两节AAA电池3V或一个CR2032纽扣电池3V供电追求极致小巧。3.2 红外发射电路设计要点红外发射部分的核心是将MCU输出的数字信号0/1转换为被38kHz或40kHz载波调制的红外光信号。电路并不复杂但有几个关键点需要注意驱动电路GP0引脚的驱动能力有限通常几个mA无法直接驱动IR LED发出足够强的光需要100mA左右的峰值电流。因此必须使用三极管如常见的8050 NPN三极管进行电流放大。GP0连接三极管基极通过一个限流电阻如1kΩ。IR LED和一个小阻值的限流电阻如10Ω串联后接在电源和三极管的集电极之间。发射管导通时电流流经IR LED使其发光。载波生成38kHz或40kHz的载波必须由软件生成。我们的策略是在需要发射“高电平”即发送载波时让GP0引脚以载波频率快速翻转输出方波在需要“低电平”时则让GP0保持低电平。这就要求我们的延时函数必须能产生非常精确的载波半周期延时例如对于38kHz周期约26.3μs半周期约13.15μs。电源去耦在MCU的VDD和VSS引脚之间尽可能靠近引脚放置一个0.1μF的陶瓷电容用于滤除电源噪声确保MCU在快速切换输出时工作稳定。LED限流电阻计算假设我们使用3V电源IR LED的正向压降Vf约为1.2V三极管饱和压降Vce_sat约为0.2V。期望的LED峰值电流If为100mA。那么限流电阻R (Vcc - Vf - Vce_sat) / If (3 - 1.2 - 0.2) / 0.1 16Ω。我们可以选择一个15Ω或18Ω的电阻。务必注意这个电流是脉冲式的平均电流很小所以普通1/8W的电阻足够了但IR LED本身要选择能承受100mA峰值电流的型号。3.3 低功耗设计考量既然是遥控器低功耗是必须的。PIC10F206在这方面有天然优势。睡眠模式在待机时MCU可以进入SLEEP模式此时功耗可降至1μA以下。我们可以通过GP1或GP3引脚上的按键中断将MCU唤醒。关闭无用外设在初始化时关闭模拟比较器、弱上拉等不需要的功能模块。优化软件发射信号时全速运行发射完毕立即进入睡眠。整个发射过程在几十毫秒内完成平均功耗极低一对电池用上一年毫无压力。4. 软件架构与核心代码实现在768个指令字的限制下写代码每一行都必须物尽其用。我们不能使用标准库甚至要谨慎使用函数调用因为涉及堆栈操作。我们将采用“轮询精确定时”的主循环架构。4.1 系统初始化与时钟校准首先进行最精简的初始化// PIC10F206 配置位设置 (根据编译器不同写法可能不同) #pragma config MCLRE OFF // 将GP3作为输入而非复位引脚 #pragma config CP OFF // 关闭代码保护 #pragma config WDT OFF // 关闭看门狗需要低功耗时可开启 void main() { // 1. 设置振荡器为内部4MHz OSCCAL 0xFF; // 校准到最高频率实际项目中可根据需要调整 // 2. 配置GPIO TRIS 0b111010; // GP0输出GP1输入GP2输出GP3输入 GPIO 0x00; // 初始输出全低 // 3. 关闭模拟功能所有引脚为数字IO CMCON0 0x07; // 关闭比较器 // ... 其他初始化 }时钟校准是关键。内部RC振荡器有误差我们可以通过测量一个已知时间比如用定时器产生一个1秒的闪烁与真实时间的偏差来微调OSCCAL寄存器的值或者更实际一点在软件延时函数中进行补偿。例如如果我们发现实际延时比理论值慢2%就在延时循环中减少相应的循环次数。4.2 精确定时与载波生成函数我们没有硬件PWM所以38kHz载波必须用软件“捏”出来。我们将使用汇编语言或高度优化的C语言内联汇编来编写最核心的延时函数。// 假设系统时钟为4MHz指令周期为1μs。 // 生成13.15μs延时38kHz载波的半周期 void delay_13us() { // 这是一个高度简化的示例实际需要精确计算指令周期 _asm NOP // 1 cycle NOP // 1 cycle // ... 更多NOP或循环 RETURN // 2 cycles _endasm } // 发射一个载波脉冲高电平 void send_carrier_pulse(unsigned int duration_us) { unsigned int count duration_us / 13; // 计算需要多少个半周期 for(unsigned int i0; icount; i) { GPIObits.GP0 1; // 输出高 delay_13us(); GPIObits.GP0 0; // 输出低 delay_13us(); } }对于SIRC协议send_carrier_pulse(600)就是发送0.6ms的载波逻辑‘0’的脉冲部分send_carrier_pulse(1200)就是发送1.2ms的载波逻辑‘1’的脉冲部分。发送完毕后再调用一个delay_600us()函数产生低电平间隙。对于RC5协议我们需要在一个位周期1.778ms内根据数据位是0还是1在中间点889μs进行一次电平翻转。这需要更精细的状态控制。4.3 RC5协议发送函数实现RC5的发送逻辑是状态机驱动的。我们需要先构建要发送的14位数据帧包含起始位、控制位、地址、命令然后逐位发送。void send_rc5(unsigned char address, unsigned char command, unsigned char toggle) { unsigned int frame 0; // 构建帧起始位(2b) 控制位(1b) 地址(5b) 命令(6b) frame (0x03 12) | ((toggle 0x01) 11) | ((address 0x1F) 6) | (command 0x3F); // 发送14位数据从最高位开始 for(char i13; i0; i--) { char bit (frame i) 0x01; send_rc5_bit(bit); } } void send_rc5_bit(char bit_value) { // RC5曼彻斯特编码位中间跳变 // 如果bit_value1则前半周期低后半周期高 // 如果bit_value0则前半周期高后半周期低 if(bit_value) { // 前半周期低电平无载波 delay_889us(); // 后半周期高电平发送载波 send_carrier_for_duration(889); } else { // 前半周期高电平发送载波 send_carrier_for_duration(889); // 后半周期低电平无载波 delay_889us(); } }这里的delay_889us()和send_carrier_for_duration(889)都需要用之前提到的精确定时方法来实现。关键在于send_carrier_for_duration函数内部不是简单地让GP0持续高电平889μs而是要以38kHz的频率不断开关GP0持续889μs。4.4 SIRC协议发送函数实现SIRC的实现相对直白就是按照时序发送起始脉冲和后续的数据位。void send_sirc12(unsigned char device, unsigned char command) { // 1. 发送起始脉冲2.4ms高 0.6ms低 send_carrier_pulse(2400); delay_us(600); // 低电平间隙 // 2. 发送7位设备地址LSB first for(char i0; i7; i) { if((device i) 0x01) { send_sirc_bit(1); // 发送逻辑‘1’ } else { send_sirc_bit(0); // 发送逻辑‘0’ } } // 3. 发送5位命令码LSB first for(char i0; i5; i) { if((command i) 0x01) { send_sirc_bit(1); } else { send_sirc_bit(0); } } } void send_sirc_bit(char bit_value) { if(bit_value) { send_carrier_pulse(1200); // 逻辑‘1’脉冲 } else { send_carrier_pulse(600); // 逻辑‘0’脉冲 } delay_us(600); // 每位后的固定低电平间隙 }4.5 主循环与协议选择主程序非常简单大部分时间MCU都在睡眠。void main() { sys_init(); while(1) { if(按键被按下) { // 检测GP1或GP3 // 防抖延时 delay_ms(20); if(按键确认按下) { // 根据GP3的电平选择协议 if(GPIObits.GP3 1) { // 假设高电平选择RC5 send_rc5(0x00, 0x0C, toggle_bit); // 例如发送电视电源命令 toggle_bit ^ 1; // 翻转控制位 } else { // 低电平选择SIRC send_sirc12(0x01, 0x15); // 例如发送索尼电视电源命令 } // 等待按键释放 while(按键仍按下); delay_ms(20); // 释放防抖 } } SLEEP(); // 进入睡眠模式等待中断唤醒 } }5. 调试、优化与常见问题排查软件写好了电路焊完了第一次按下按键对面的设备很可能毫无反应。别急调试阶段才是学习的精华。5.1 硬件调试眼见为实没有示波器没关系我们可以用智能手机摄像头做一个简易的“红外探测器”。打开手机的相机APP将IR LED对准摄像头按下发射键。在手机屏幕上你应该能看到IR LED发出微弱的紫色或白色光点因为手机摄像头的CMOS对红外光敏感。如果看不到闪烁说明驱动电路或MCU根本没有控制IR LED发光检查三极管是否接反、限流电阻是否过大、GP0引脚是否配置为输出。如果有条件强烈建议使用示波器或逻辑分析仪探头连接到GP0引脚。你可以清晰地看到发送的波形对于SIRC应该能看到2.4ms的密集方波载波起始脉冲以及后续宽度不同的数据脉冲。对于RC5应该能看到周期为1.778ms中间有跳变的曼彻斯特编码波形。通过测量脉冲宽度和间隔可以精确判断我们的时序是否准确。5.2 软件时序校准这是最可能出问题的地方。内部RC振荡器的频率偏差、函数调用开销、循环指令的周期数都会影响最终延时。校准方法粗调写一个测试程序让GP0引脚每1秒翻转一次控制一个普通LED闪烁。用秒表或手机计时器测量实际闪烁周期。如果偏差较大调整OSCCAL寄存器值如果MCU支持或修改延时函数的基准循环次数。细调针对红外协议。可以找一个已知能正常工作的商业遥控器用逻辑分析仪捕获其波形测量其位周期、脉冲宽度。然后调整我们的delay_us函数使我们发出的波形参数尽可能接近商业遥控器。通常误差在±5%以内接收端都能正确解码。5.3 发射距离与角度问题如果设备只在很近的距离或特定角度才有反应问题通常出在发射功率上。检查驱动电流用万用表测量发射时流过IR LED的电流是否达到设计值如100mA。如果太小减小LED的串联限流电阻。检查IR LED型号确保使用的是大功率、窄角度的红外发射二极管而不是普通的小功率LED。窄角度的LED能量更集中射程更远。注意发射方向红外光基本是直线传播且容易被障碍物遮挡。确保发射管直接对准设备的红外接收窗口。接收窗口通常是一个深色通常是黑色的半透明塑料片。5.4 协议兼容性问题设备没反应也可能是协议或地址/命令码不对。确认协议查阅设备说明书或通过网络搜索确认你的设备到底支持RC5还是SIRC或者是NEC、松下等其他协议。确认地址和命令码同一个协议下不同品牌的设备可能使用不同的系统地址。命令码也未必通用。例如“电源”键在RC5协议中电视的地址可能是0x00命令是0x0C而音响的地址可能是0x10。你需要找到对应设备的正确码值。可以尝试用手机红外APP如果手机带红外发射学习原装遥控器的码值或者在网上搜索红外码库。5.5 常见问题速查表问题现象可能原因排查步骤LED完全不亮手机摄像头看不到1. 电源未接通2. MCU未工作/程序未运行3. 三极管驱动电路错误4. IR LED或电阻损坏1. 检查电源电压测量VDD。2. 检查GP2指示灯程序是否运行。3. 用万用表测GP0电压按键时应有变化。测三极管各极电压。4. 更换元件。LED亮但设备无反应1. 载波频率偏差太大2. 协议、地址或命令错误3. 发射功率不足距离近才有效4. 波形时序严重错误1. 用示波器测量载波频率调整延时函数。2. 确认设备支持的协议和码值。3. 测量LED电流换用大功率窄角度LED。4. 用逻辑分析仪对比商业遥控器波形。反应不灵敏时好时坏1. 电源电压不足或波动大2. 软件防抖处理不好误触发3. 环境有强红外干扰如日光灯、太阳光1. 更换新电池在VDD加滤波电容。2. 增加按键防抖延时和释放判断。3. 避开强光或尝试重复发送几次信号。只能控制一种协议设备1. 协议选择电路GP3故障2. 软件中协议判断逻辑错误1. 检查GP3的上拉电阻和接地开关。2. 调试时分别固化RC5和SIRC代码测试。6. 项目扩展与进阶玩法实现基础功能只是开始基于这个框架我们可以玩出很多花样变身“万能学习型遥控器”这是最有价值的升级。增加一个红外接收头如VS1838B连接到MCU的另一个引脚。先让MCU进入“学习模式”用原装遥控器对准接收头发射信号MCU记录下波形的时间序列并压缩存储到有限的RAM或外置EEPROM中。需要时再将这些波形原样发射出去。这需要更复杂的代码来处理不同协议的自适应学习但对PIC10F206的编程能力是极大的锻炼。增加OLED/LCD显示虽然PIC10F206引脚和资源有限但通过软件模拟I2C驱动一个128x32的OLED小屏幕是可能的。可以显示当前模式、设备名称、自定义宏命令名称等让遥控器更具可玩性。这需要将项目迁移到资源稍多的MCU如PIC12F1840。接入智能家居平台增加一个蓝牙模块如HC-05或Wi-Fi模块如ESP-01S让这个红外发射器具备网络连接能力。你可以通过手机APP、语音助手集成到Home Assistant等平台来控制传统红外设备实现“老设备智能化”。此时PIC10F206可以作为网络模块的协处理器专门负责高精度的红外信号生成。宏命令与场景联动利用MCU的存储空间预存多组红外码序列。按一个键依次发送“开电视-切换HDMI1-开音响-音量调至20%”等一系列命令实现一键场景切换。这个基于PIC10F206的红外遥控发射器项目从硬件到软件充满了“螺蛳壳里做道场”的乐趣。它强迫你去思考每一个字节、每一个时钟周期的价值去理解最底层的时序逻辑。当你最终按下自己制作的遥控器成功点亮对面那台老电视时那种成就感是直接用现成模块无法比拟的。它不仅仅是一个工具更是一个理解嵌入式系统如何与物理世界对话的绝佳范例。