1. 项目概述与核心价值
最近在折腾一个智能家居的小项目,需要用一个非常小巧的控制器来发射红外遥控信号,控制家里的老电视和空调。市面上常见的方案要么是ESP8266这类Wi-Fi模块,体积和功耗对于嵌入式场景来说还是偏大;要么就是专用的红外发射芯片,功能又太单一。于是我把目光投向了Microchip(原Atmel)的PIC10F206这颗MCU。它只有6个引脚,8位架构,价格低廉,但用来实现RC5和SIRC这两种最主流的红外协议,却是绰绰有余。这个项目,就是基于PIC10F206,设计并实现一个通用的红外遥控发射器。
简单来说,这个项目能让你用一颗比米粒大不了多少的芯片,制作出一个万能遥控器的“心脏”。它特别适合集成到一些DIY的智能开关、自定义遥控面板,或者需要隐蔽控制传统红外家电的项目中。比如,你可以把它塞进一个智能插座里,让插座不仅能通断电源,还能通过红外遥控你家的空调,实现“插座+红外遥控”二合一的功能,这正是当前“红外遥控智能插座”这类热门创意的硬件核心。无论你是电子爱好者、嵌入式开发者,还是智能家居的DIY玩家,通过这个项目,你都能彻底掌握从协议分析、单片机编程到硬件调试的红外遥控全链路知识。
2. 红外协议核心:RC5与SIRC深度解析
在动手写代码和画电路图之前,我们必须先吃透要实现的两种红外协议:飞利浦的RC5和索尼的SIRC。这是整个项目的基石,协议理解错了,后面所有工作都是白费。
2.1 飞利浦RC5协议:曼彻斯特编码的典范
RC5协议以其简洁和强抗干扰性著称,它采用双相相位编码,也就是我们常说的曼彻斯特编码。它的每一位数据都用一个电平跳变来表示,具体规则是:逻辑“1”用“高-低”跳变表示,逻辑“0”用“低-高”跳变表示。这种编码方式的优点是,无论信号持续多长时间,每个位中间必然有一次跳变,便于接收端同步时钟,而且对载波占空比不敏感。
一个完整的RC5帧是14位,其结构如下:
- 两位起始位(Start Bits):总是逻辑“1”,用于帧同步。
- 一位控制位(Toggle Bit):每次按下同一个键,这一位会翻转(0变1或1变0)。这个设计非常巧妙,用于区分是“长按”还是“重复按下”。如果接收端连续收到两个相同的帧(控制位相同),则认为是长按;如果控制位翻转了,则认为是新的按键动作。
- 五位系统地址(System Address):用来区分不同类型的设备,比如电视、音响等。范围是0-31。
- 六位指令(Command):具体的操作指令,比如音量加、频道减等。范围是0-63。
RC5的位时长是固定的1.778ms,载波频率通常是36kHz或38kHz。这意味着,每一位数据,无论它是1还是0,其持续时间都是1.778ms。在这个时间内,会用36kHz的方波对红外LED进行调制。例如,要发送一个逻辑“1”,就在前0.889ms发射36kHz载波,后0.889ms关闭;发送逻辑“0”则相反,前0.889ms关闭,后0.889ms发射载波。整个帧的发送时间大约是14 * 1.778ms ≈ 24.89ms。
注意:在编程时,定时器的精度至关重要。1.778ms这个时间需要尽可能精确,否则接收设备可能无法正确解码。对于PIC10F206的内部4MHz RC振荡器,需要进行校准,或者使用外部晶振来保证时序。
2.2 索尼SIRC协议:脉冲宽度编码的代表
索尼的SIRC协议与RC5完全不同,它采用脉冲宽度编码。逻辑“1”和逻辑“0”通过高电平脉冲的宽度来区分,而低电平的间隔是固定的。
SIRC协议有多种格式,最常见的是12位、15位和20位格式。我们以最普遍的12位格式为例:
- 起始脉冲(Start Pulse):一个长达2.4ms的高电平脉冲,后面跟随一个0.6ms的低电平,标志着帧的开始。
- 7位命令(Command):首先发送命令字节的LSB(最低有效位)。
- 5位设备地址(Device Address):接着发送设备地址的LSB。
那么如何区分“1”和“0”呢?规则是:
- 逻辑“1”:高电平脉冲宽度为1.2ms,随后是0.6ms的低电平。
- 逻辑“0”:高电平脉冲宽度为0.6ms,随后是0.6ms的低电平。
可以看到,SIRC协议中,每一位的周期是固定的(1.2ms+0.6ms=1.8ms,或0.6ms+0.6ms=1.2ms?这里需要澄清:实际上,无论是1还是0,其总周期并不严格固定,因为高电平宽度不同。但低电平间隔固定为0.6ms。因此,一个“1”位总时长1.8ms,一个“0”位总时长1.2ms)。SIRC的载波频率通常是40kHz。
SIRC协议没有像RC5那样的Toggle位,所以如果按住按键不放,接收端会重复收到完全相同的帧。在编程实现时,我们需要精确控制2.4ms、1.2ms、0.6ms这几个关键时间点。
2.3 协议对比与选型思考
为什么选择同时实现这两种协议?因为它们几乎覆盖了市面上80%以上的消费电子设备。飞利浦的RC5协议被众多欧洲品牌的电视、音响采用,而索尼的SIRC协议则是日系设备(索尼、松下等)的标配。实现一个双协议发射器,其通用性会大大增强。
从实现难度上看,RC5协议因为采用曼彻斯特编码,对时序的对称性要求极高,编程上需要更精细的定时器控制。SIRC协议看似简单,但需要生成三种不同宽度的高电平脉冲(2.4ms,1.2ms,0.6ms),对定时器的灵活性要求更高。对于资源极其有限的PIC10F206来说,如何用一种高效、准确的方式同时驾驭这两种协议,是本次设计的核心挑战。
3. 硬件系统设计与核心器件选型
硬件设计的目标是在满足功能的前提下,做到极致的简单、小巧和低成本。PIC10F206的极限资源(768字节程序存储器,64字节RAM,6个I/O口)决定了我们必须精打细算。
3.1 主控芯片:PIC10F206的极限挖掘
PIC10F206是一颗6引脚(实际可用I/O为4-5个)的8位单片机,核心资源如下:
- 程序存储器:768 Words(约1.5KB机器码),对于我们这个项目,代码需要高度优化。
- 数据存储器:64字节RAM,这意味着我们不能定义大的数组,变量要尽可能用全局变量,并谨慎使用函数调用栈。
- 定时器:一个8位定时器(TMR0)和一个看门狗定时器。红外协议对时序要求苛刻,TMR0将是我们最宝贵的资源。
- 振荡器:支持内部4MHz RC振荡器和外部振荡器。为了获得更稳定的红外载波和位时序,强烈建议使用外部陶瓷谐振器或晶振。这里我们选择价格低廉的4MHz陶瓷谐振器,配合芯片内部的PLL倍频电路是不支持的,所以系统时钟就是4MHz,指令周期为1μs(4个时钟周期)。
引脚分配方案:
- GP0:配置为输出,连接红外发射LED的驱动三极管基极。这是我们的信号输出引脚。
- GP1:配置为输入,可以连接一个轻触按键,用于触发发射或切换协议。
- GP2:配置为输入,可以连接一个拨码开关,用于选择RC5或SIRC协议。
- GP3:此引脚是MCLR(主复位),通常上拉电阻接VDD,用作复位输入,不能作为普通I/O。
- GP4, GP5:可以配置为输入,用于连接更多的设置开关,比如选择设备地址。
3.2 红外发射电路:驱动与调制
红外发射部分的核心是将单片机GPIO输出的数字信号,转换为被高频载波调制的、足够强度的红外光信号。
典型电路如下:
- 单片机GPIO(GP0):输出已调制好的协议波形(即,在需要发射红外光时输出高频方波,在协议规定的低电平期间输出持续低电平)。
- 限流电阻R1:连接在GP0和NPN三极管(如S8050)的基极之间,限制基极电流,通常选择1kΩ。
- NPN三极管:作为开关使用。当基极为高电平时导通,为红外LED提供电流通路。
- 红外发射LED:建议使用专用的红外发射管,其波长一般为940nm,与绝大多数家电接收头匹配。不要用普通红色LED代替,波长不对无法工作。
- LED限流电阻R2:这是关键!它的阻值决定了红外LED的发射功率和距离。计算公式为:R2 = (Vcc - Vled - Vce_sat) / Iled。
- Vcc:电源电压,假设为3.3V或5V。
- Vled:红外LED正向压降,通常为1.2V-1.5V。
- Vce_sat:三极管饱和压降,约0.2V。
- Iled:期望的LED电流。普通5mm红外LED的连续工作电流建议在20-50mA。如果想获得更远的遥控距离,可以短暂地使用100mA甚至更高的脉冲电流(需注意LED和三级管的瞬时功耗)。
- 举例:Vcc=5V, Vled=1.3V, Vce_sat=0.2V, Iled=50mA。则 R2 = (5 - 1.3 - 0.2) / 0.05 = 70Ω。我们可以选择一个68Ω的常用电阻。此时LED功耗约为(5-1.3-0.2)0.05=0.175W,三极管功耗约为0.20.05=0.01W,均在安全范围内。
实操心得:为了提高遥控距离,我通常会采用“脉冲驱动”法。即选择一颗能承受较大脉冲电流的红外LED(比如脉冲电流可达1A的型号),然后减小R2阻值,让瞬时电流达到200-300mA。同时,在软件上要确保发射时间足够短(一帧红外信号也就几十毫秒),避免LED和三级管过热。实测这种方法可以将遥控距离从标准的5-8米轻松提升到15米以上,非常适合需要穿墙或远距离控制的应用。
3.3 电源与辅助电路
考虑到这是一个微型发射器,电源方案力求简单:
- 供电:采用两节AAA(7号)电池(约3V)或一颗CR2032纽扣电池(3V)。PIC10F206的工作电压范围是2.0V-5.5V,3V供电完全没问题,且有利于降低功耗。
- 去耦电容:在芯片的VDD和VSS引脚之间,务必就近放置一个0.1μF的陶瓷电容,用于滤除电源噪声,保证单片机稳定运行,尤其是发射高频载波时。
- 按键与开关:所有连接到GPIO的按键和拨码开关,另一端接地,并在GPIO口上启用内部弱上拉电阻。这样可以省去外部上拉电阻,进一步简化电路。
整个硬件原理图可以画得非常精简,核心就是MCU、红外驱动、电源和几个设置接口。PCB布局时,应将红外LED放置在板子边缘,避免被其他元件遮挡。
4. 软件架构与关键代码实现
软件是项目的灵魂,目标是在PIC10F206极其有限的内存和算力下,精准、高效地生成RC5和SIRC波形。我们将采用“状态机+精确定时器中断”的架构。
4.1 系统初始化与全局定义
首先,我们需要进行芯片的初始化配置,并定义一些全局变量和常量。
// PIC10F206 使用XC8编译器示例代码 #include <xc.h> // 芯片配置字设置:使用内部RC振荡器,看门狗关闭,代码保护关闭 #pragma config MCLRE OFF, CP OFF, WDTE OFF, FOSC_INTOSCIO // 定义协议类型 #define PROTOCOL_RC5 0 #define PROTOCOL_SIRC 1 // RC5协议常量 #define RC5_BIT_TIME 1778 // 单位:微秒 (us) #define RC5_CARRIER_HALF_CYCLE 13 // 载波半周期 @38kHz: 1/(38k*2) ≈ 13.16us // SIRC协议常量 (12位格式,40kHz载波) #define SIRC_START_HIGH 2400 // 起始位高电平 2.4ms #define SIRC_BIT_SPACE 600 // 位间隔低电平 0.6ms #define SIRC_BIT1_HIGH 1200 // 逻辑1高电平 1.2ms #define SIRC_BIT0_HIGH 600 // 逻辑0高电平 0.6ms #define SIRC_CARRIER_HALF_CYCLE 12 // 载波半周期 @40kHz: 1/(40k*2) = 12.5us // 全局变量 volatile unsigned char current_protocol; volatile unsigned int rc5_frame_data; // 存储14位RC5帧 volatile unsigned int sirc_frame_data; // 存储12位SIRC帧 volatile unsigned char bit_counter; // 已发送位数计数器 volatile unsigned char sending_state; // 发送状态机状态 volatile unsigned char carrier_toggle; // 载波翻转标志初始化函数void init(void)需要完成以下工作:
- 配置GPIO方向:GP0输出,GP1/GP2输入。
- 启用GPIO内部弱上拉(如果按键使用)。
- 配置TMR0定时器:选择预分频器,使其每1us或一个载波半周期产生一次中断。由于我们需要非常精细的时间控制(微秒级),这里将TMR0配置为1:2预分频,在4MHz系统时钟下,TMR0每2个指令周期(2μs)加1。我们可以通过软件计数来匹配不同的时间要求。
- 启用TMR0溢出中断和全局中断。
4.2 核心状态机与中断服务程序
整个红外发射过程由一个状态机驱动,在TMR0中断服务程序(ISR)中执行。这是最核心、最需要优化效率的部分。
状态机设计:
- IDLE状态:等待发送命令。检测到按键后,根据拨码开关读取
current_protocol,加载相应的帧数据(rc5_frame_data或sirc_frame_data),初始化bit_counter和状态变量,进入START状态。 - START状态:发送协议的起始部分。对于RC5,是第一个1.778ms的位(包含载波调制);对于SIRC,是2.4ms的高电平脉冲。发送完成后,进入DATA状态。
- DATA状态:循环发送数据位。根据当前协议和当前要发送的位(0或1),设置下一个时间片应该输出高电平还是低电平,以及持续时间。每发送完一位,
bit_counter加1。当所有位发送完毕,进入STOP状态。 - STOP状态:发送完成,清理状态,返回IDLE状态。
中断服务程序(ISR)伪代码逻辑:
void __interrupt() isr(void) { if (T0IF) { // TMR0溢出中断 T0IF = 0; // 清除中断标志 // 更新一个精细的微秒计数器(用于测量更长间隔) us_counter++; // 载波生成逻辑(如果当前处于需要发射载波的状态) if (carrier_enable) { carrier_toggle ^= 1; // 翻转载波状态 if (carrier_toggle) { GP0 = 1; // 输出高电平(产生红外光) } else { GP0 = 0; // 输出低电平(关闭红外光) } // 重置TMR0,使其在半个载波周期后再次中断 TMR0 = 256 - CARRIER_HALF_CYCLE_COUNT; } else { // 非载波期间,处理协议状态机 switch (sending_state) { case STATE_START: if (us_counter >= START_DURATION) { us_counter = 0; sending_state = STATE_DATA; // 准备发送第一位数据 prepare_next_bit(); } // 在此期间,GP0根据协议要求保持高或低 break; case STATE_DATA: if (us_counter >= CURRENT_BIT_DURATION) { us_counter = 0; bit_counter++; if (bit_counter >= TOTAL_BITS) { sending_state = STATE_STOP; } else { prepare_next_bit(); } } break; case STATE_STOP: GP0 = 0; // 确保LED关闭 sending_state = STATE_IDLE; carrier_enable = 0; break; } // 根据状态机决定TMR0的下次中断时间(用于测量us_counter) // 可能设置为一个较长的值(如50us),以减少中断频率,节省CPU TMR0 = 256 - 25; // 例如,约50us中断一次来检查时间 } } }关键技巧:这里有一个矛盾点。载波频率很高(38kHz对应26.3μs周期),需要频繁中断(每13μs)来翻转GPIO。而协议位时间很长(几百微秒到几毫秒),不需要这么高的中断频率。我的解决方案是双定时器模式:在需要发射载波的阶段,TMR0专注于产生精确的载波方波,
us_counter由软件在载波中断中累加(每次中断加13μs)。在不需要载波(低电平间隔)或发送SIRC的固定电平时,则切换TMR0到一个较长的定时间隔,用于更新us_counter和检查状态机。这需要对TMR0进行动态重载,增加了编程复杂度,但这是在小资源MCU上实现高精度混合时序的实用方法。
4.3 协议数据帧的组装函数
我们需要编写函数,将用户想要发送的设备地址和命令,组装成符合RC5或SIRC标准的帧数据。
对于RC5,需要处理Toggle位。可以定义一个全局变量rc5_toggle来记录状态。
unsigned int assemble_rc5_frame(unsigned char address, unsigned char command) { static unsigned char toggle = 0; unsigned int frame = 0; frame = 0x3FFF; // 先假设所有位为1,方便后续操作 // 清除起始位位置,然后设置起始位为1(实际上就是保持为1) // 设置控制位 if (toggle) { frame &= ~(1 << 11); // 第11位(从0开始计)为控制位,设为0 } else { // 控制位为1,默认已是1,无需操作 } toggle ^= 1; // 翻转 // 设置5位系统地址 (bit10-bit6) address &= 0x1F; // 确保5位 for (char i=0; i<5; i++) { if (address & (1 << i)) { // 对应位为1,保持为1 } else { frame &= ~(1 << (10-i)); // 对应位清0 } } // 设置6位命令 (bit5-bit0) command &= 0x3F; // 确保6位 for (char i=0; i<6; i++) { if (command & (1 << i)) { // 对应位为1 } else { frame &= ~(1 << (5-i)); } } return frame; }对于SIRC,组装相对简单,注意命令和地址都是先发送LSB。
unsigned int assemble_sirc_frame(unsigned char address, unsigned char command) { unsigned int frame = 0; // SIRC 12位格式:低7位是命令,接着5位是地址 // 我们需要构建一个12位的数,方便后续逐位发送 frame = (command & 0x7F); // 低7位是命令 frame |= ((address & 0x1F) << 7); // 接着5位是地址 return frame; }5. 调试、优化与实测问题排查
软件硬件搭建好后,真正的挑战才刚刚开始。调试红外项目,一台红外接收管、一个示波器(或逻辑分析仪)是必不可少的。
5.1 调试工具与方法
- 红外接收管测试:最简单的方法是用一个常见的红外接收头(如VS1838B,三个引脚:VCC, GND, OUT),将其OUT引脚连接到一个LED或通过一个三极管放大后接扬声器。当接收到正确的红外信号时,LED会闪烁或扬声器会发出“哒哒”声。这能快速验证你的发射器是否有信号发出。
- 示波器/逻辑分析仪观测:这是最准确的调试手段。将示波器探头接在发射器GP0引脚或红外LED的阳极上。
- 观测载波:放大时间轴,你应该能看到频率为38kHz(RC5)或40kHz(SIRC)的方波。
- 观测协议波形:拉开时间轴,你应该能看到完整的、符合RC5或SIRC标准的脉冲序列。将测量光标放在脉冲上,检查高电平、低电平的宽度是否与协议规定一致(如RC5的1.778ms, SIRC的1.2ms/0.6ms)。误差最好控制在5%以内。
- 手机摄像头辅助观测:大多数手机摄像头的CMOS传感器对红外光敏感。在黑暗环境下,用手机摄像头对着工作的红外LED,你会在屏幕上看到紫色的光点。这可以用来定性判断LED是否在闪烁,但无法分析波形。
5.2 常见问题与解决方案实录
在实际制作中,我踩过不少坑,这里总结出来供你参考:
| 问题现象 | 可能原因 | 排查步骤与解决方案 |
|---|---|---|
| 完全无反应,接收头不动作 | 1. 红外LED装反或损坏。 2. 三极管驱动电路错误(NPN/PNP搞混,电阻值过大)。 3. 单片机根本没运行(电源、复位问题)。 4. GPIO输出模式配置错误。 | 1. 用万用表检查LED正向压降。 2. 测量三极管基极电压,发射时应有0.7V左右变化。 3. 检查VDD电压,用示波器看GP0是否有任何输出。 4. 确认TRIS寄存器配置正确。 |
| 有反应但距离非常近(<1米) | 1. 红外LED驱动电流太小(R2阻值太大)。 2. 载波频率偏差太大。 3. 红外LED型号不对或质量差。 | 1. 计算并减小R2阻值,增大驱动电流(注意不要超过LED和三极管极限)。 2. 用示波器测量载波频率,调整定时器重载值。PIC内部RC振荡器误差可能达5%,建议换用外部晶振。 3. 更换为专用的、高发射功率的红外LED。 |
| 某些设备能控制,某些不能 | 1. 协议选择错误(用RC5发给了只认SIRC的设备)。 2. 设备地址设置错误。 3. 波形时序误差在临界点,某些接收芯片容错性差。 | 1. 确认目标设备使用的协议。可以网上搜索设备型号+“红外协议”。 2. 尝试不同的地址码。通常电视地址是0,音响是1等,需要查阅协议文档或抓取原装遥控器码值。 3. 用示波器精调位时序,确保误差最小。 |
| 遥控时灵时不灵 | 1. 电源干扰。发射时大电流导致MCU复位或程序跑飞。 2. 软件Bug,状态机在某些条件下卡死。 3. 环境光干扰(强烈的日光灯或太阳光)。 | 1. 在MCU的VDD和GND之间并接一个100μF的电解电容,稳定电源。 2. 加强软件看门狗和异常状态恢复机制。 3. 避免强光直射接收头,或给接收头加装遮光罩。 |
| 功耗过高,电池消耗快 | 1. 红外LED持续微小漏电。 2. 单片机未进入低功耗模式。 | 1. 确保在空闲状态,驱动三极管完全截止(GP0输出低电平)。 2. 在 IDLE状态,让单片机进入SLEEP模式,仅靠外部按键中断唤醒,这是省电的关键。 |
5.3 性能优化与功能扩展
在基础功能实现后,可以考虑以下优化和扩展:
- 低功耗优化:如前所述,在非发射状态,将PIC10F206置于
SLEEP模式,功耗可降至个位数微安级别,使纽扣电池供电成为可能,非常适合长期待机的遥控器。 - 学习功能:增加一个红外接收头,让这个发射器能够学习并存储其他遥控器的信号。这需要额外的存储空间(可使用外置EEPROM,如24C02),并编写复杂的解码程序。对于PIC10F206来说,资源非常紧张,实现完整的通用学习功能比较困难,但可以针对固定的一两种协议进行简化学习。
- 多按键支持:通过GP1、GP2、GP4、GP5连接多个按键矩阵,实现一个多键遥控器。需要在软件中增加按键扫描和不同键值对应不同红外码的功能。
- 无线触发:将GPIO连接的轻触按键,换成无线接收模块(如常见的2262/2272编码解码模块,或更高级的蓝牙、Wi-Fi模块的IO口)。这样,这个红外发射器就变成了一个受无线信号控制的“红外转发器”,是智能家居中控的常见形态。
这个基于PIC10F206的红外遥控发射器项目,虽然芯片简单,但“麻雀虽小,五脏俱全”,它涵盖了嵌入式开发中硬件选型、电路设计、低层驱动、状态机编程、精确定时和调试排错等核心环节。成功实现它,不仅能让你获得一个实用的万能遥控核心模块,更能让你对红外通信和微型单片机开发有深刻的理解。当你拿着自己做的这个小板子,成功控制客厅电视换台的那一刻,那种成就感是无可替代的。