基于ATtiny28的RC5红外遥控发射器设计与实现 1. 项目缘起为什么是ATtiny28和RC5最近在整理工作室的旧零件箱翻出来几片ATtiny28这玩意儿现在可不多见了。它属于AVR家族里比较“古典”的一位引脚少资源也有限但胜在结构简单价格低廉当年。看着它我就在想能不能用它来做点实用又有趣的东西顺便重温一下AVR的编程手感。正好手头有个老式的飞利浦音响遥控器早就不知所踪而它用的正是经典的RC5红外协议。一个念头就冒了出来用这片“古董”单片机做一个RC5红外遥控发射器。这个想法听起来有点“杀鸡用牛刀”毕竟现在随便一个几毛钱的专用编码芯片或者更强大的MCU都能轻松实现。但我觉得用ATtiny28来实现RC5恰恰是一个绝佳的练手项目。它能让你深刻理解几个关键点一是如何在资源极其有限的MCU上用软件精准地模拟一个标准的通信协议时序二是如何利用AVR单片机的底层硬件如定时器来解放CPU实现高效、稳定的信号生成三是如何从原理图到代码完整地走通一个嵌入式小系统的设计流程。这比单纯调用一个现成的红外库要有趣和深刻得多。RC5协议本身也是一个经典。它由飞利浦制定采用了双相位编码Biphase coding或者叫曼彻斯特编码。这种编码方式的好处是自带时钟信息抗干扰能力强。一个完整的RC5帧包含14个比特包括起始位、控制位、地址位和命令位。它的载波频率是36kHz占空比通常是1/3或1/4。我们的任务就是让ATtiny28的某个IO口输出完全符合这个规范的脉冲波形。2. 硬件设计极简主义的艺术ATtiny28的引脚资源非常紧张只有28个引脚其中能用的IO口就更少了。这迫使我们的设计必须走向极简。2.1 核心电路与电源首先是最小系统。ATtiny28支持内部RC振荡器最高可以跑到8MHz在5V供电下。对于红外发射这种对时序精度要求高的应用我强烈建议使用外部晶振。我选择了一个便宜的4MHz石英晶体配合两个22pF的电容连接到XTAL1和XTAL2引脚。这样能提供更稳定、更精准的时钟源为后续用定时器生成精确的36kHz载波和比特时序打下坚实基础。电源部分由于红外发射管的工作电流较大瞬间可达100mA以上而ATtiny28本身功耗极低所以整个系统可以用两节AAA电池3V或者一个CR2032纽扣电池3V供电。需要注意的是ATtiny28的工作电压范围是2.7V - 5.5V3V供电完全没问题但此时其IO口输出高电平的电压也接近3V。为了确保红外发射管有足够的驱动电流和发射距离我增加了一个简单的NPN三极管如2N3904或8050作为开关驱动。具体的连接方式是ATtiny28的一个IO口例如PB0连接到三极管的基极通过一个1kΩ的限流电阻。红外发射管的阳极连接到电源正极VCC阴极连接到三极管的集电极。三极管的发射极接地。这样当PB0输出高电平时三极管导通红外管有电流流过而发光PB0输出低电平时三极管截止红外管熄灭。这种接法利用了三级管的电流放大作用让单片机微弱的IO驱动能力通常20mA左右能够控制上百毫安的红外管电流。注意红外发射管是电流型器件其正向压降通常在1.2V-1.5V。计算限流电阻时公式为 R (VCC - Vf - Vce_sat) / If。假设VCC3V Vf1.3V 三极管饱和压降Vce_sat0.2V 期望电流If100mA 则R (3 - 1.3 - 0.2) / 0.1 15Ω。实际中我会选择一个10Ω的电阻确保有足够电流。这个电阻需要一定的功率P I²R 0.1² * 10 0.1W所以选用1/4W的电阻即可。2.2 用户输入与功能扩展既然是遥控器就得有按键。ATtiny28的IO口支持内部上拉电阻这为我们省去了外部上拉电阻。我计划用4个按键分别连接到PB1, PB2, PB3, PB4并配置这些引脚为输入模式且使能内部上拉。当按键按下时引脚被拉低到地松开时内部上拉电阻将其拉高到VCC。通过轮询或外部中断的方式即可检测按键状态。为了指示工作状态比如按键按下或发射成功可以增加一个LED。将其连接到另一个IO口如PB5同样通过一个限流电阻330Ω-1kΩ接地采用低电平驱动点亮。至此一个最基础的硬件框架就搭好了MCU、晶振、红外发射驱动电路、按键、状态LED。所有元件都可以塞进一个很小的空间里非常适合做成一个迷你型的万能遥控器或者学习型遥控器的发射端。3. 软件核心用定时器“雕刻”时间硬件是骨架软件才是灵魂。整个项目的软件核心就是如何用ATtiny28的定时器精准地生成RC5协议所需的各种时间信号。ATtiny28有一个8位的定时器/计数器0我们将用它来产生36kHz的载波和测量比特时间。3.1 生成36kHz载波RC5协议规定逻辑“1”和“0”都是用一段36kHz的脉冲串来表示的区别在于脉冲串的相位。每个比特周期是1.778ms64个载波周期载波周期就是1/36000 ≈ 27.78µs。我们可以利用定时器0的CTC比较匹配清零模式来生成这个频率的方波。假设系统时钟是4MHz定时器预分频设为1不分频。那么定时器的计数时钟频率就是4MHz。要产生27.78µs的周期我们需要定时器在计数到某个值OCR0时清零并触发中断。计算OCR0的值OCR0 (F_CPU / (2 * F_CARRIER * PRESCALER)) - 1。这里乘以2是因为我们要生成占空比1/2的方波实际常用1/3但1/2更容易实现且对接收影响不大。代入OCR0 (4,000,000 / (2 * 36,000 * 1)) - 1 ≈ 55.56 - 1 54.56取整为55。在CTC模式下当计数器TCNT0的值增加到与OCR0相等时TCNT0会被清零并且OC0引脚在ATtiny28上通常是PB0但需要查数据手册确认是否支持输出比较可以配置为在比较匹配时取反。这样我们就能在PB0上得到一个频率为F_CPU / (2 * (OCR01)) 4,000,000 / (2*56) ≈ 35,714Hz的方波接近36kHz误差在可接受范围内。但是ATtiny28的OC0功能可能受限或者我们想用其他引脚。更通用的方法是使用定时器溢出中断或比较匹配中断在中断服务程序ISR中手动翻转一个IO口比如我们之前连接红外驱动三极管的PB0。// 假设使用定时器0比较匹配中断OCR055 ISR(TIMER0_COMP_vect) { PORTB ^ (1 PB0); // 翻转PB0产生载波 }初始化代码需要设置定时器模式、预分频和比较值并开启中断。3.2 实现RC5比特编码生成了载波下一步是控制这个载波的“门”即按照RC5的规则在正确的时间打开或关闭载波输出以形成双相位编码。RC5的每个比特位持续1.778ms64个载波周期。在比特周期的中间点0.889ms信号必须发生一次跳变从有载波到无载波或者相反。逻辑“1”和逻辑“0”的区别在于第一个半周期的电平状态。如果第一个半周期前0.889ms为高有载波第二个半周期为低无载波则表示逻辑“0”反之则表示逻辑“1”。因此我们需要一个更高层级的定时来控制这1.778ms的比特窗口和其中的0.889ms半周期切换点。由于1.778ms相对较长我们可以用循环延时但更优雅和准确的方式是使用另一个定时器或者复用定时器0但用不同的预分频。一个巧妙的方法是我们仍然使用定时器0来产生载波中断每27.78µs一次但在这个中断服务程序中我们维护一个比特周期计数器和一个半周期标志。思路如下定义一个全局变量bit_phase_counter每次载波中断加1从0计数到127因为1.778ms / 27.78µs ≈ 64 两个半周期就是128次中断。当bit_phase_counter为0时代表一个新的比特周期开始。根据当前要发送的比特是1还是0设置一个first_half_level标志高或低。在载波中断中判断bit_phase_counter的值如果bit_phase_counter 64说明在第一个半周期。此时红外输出电平应等于first_half_level。如果bit_phase_counter 64说明在第二个半周期。此时红外输出电平应等于!first_half_level取反。当bit_phase_counter达到127时表示这个比特发送完毕重置计数器为0并准备发送下一个比特。这样我们就在一个载波中断里同时完成了载波生成和比特编码的时序控制。红外输出的最终控制就是根据上述逻辑在中断里设置或清除驱动引脚的电平。注意这里说的“电平”是指是否允许载波通过。当输出电平为“高”时我们让定时器驱动的载波翻转生效即PB0不断翻转当输出电平为“低”时我们强制PB0输出低电平关闭载波。这需要在中断服务程序里做一点逻辑判断。4. 协议层实现与数据组帧有了底层的比特发送引擎上层的数据组帧就相对简单了。RC5的一帧数据是14位格式如下两位起始位总是为1。一位控制位Toggle bit。每次按下同一个键这一位会翻转用于区分是长按还是新的按键。五位系统地址位Address用来区分不同设备类型比如电视、音响等。六位命令位Command代表具体的操作如音量加、播放等。我们需要在内存中构建一个14位的缓冲区。当有按键按下时根据按键映射填充地址和命令字段。控制位需要非易失性存储比如EEPROM来记录上一次的状态以实现翻转功能。ATtiny28有64字节的EEPROM足够存储这个状态。发送一帧数据的流程如下按键检测在主循环中轮询或通过中断。从EEPROM读取上一次的控制位状态取反后作为本次的控制位并写回EEPROM。结合固定的起始位、计算出的控制位、预设的地址位和按键对应的命令位组装成一个14位的整数。启动发送引擎。将14位数据放入一个队列或缓冲区并设置当前发送比特索引为0。在载波中断服务程序中除了处理载波和半周期还需要根据当前比特索引从数据缓冲区中取出对应的比特值1或0并用它来决定first_half_level的初始值比特为1则first_half_level为低比特为0则first_half_level为高。每发送完一个比特bit_phase_counter从127回到0比特索引加1。当14个比特全部发送完毕关闭发送引擎可以停止定时器中断或者将红外输出强制置低等待下一次按键。为了确保接收端能正确接收通常一个按键按下需要连续发送多帧相同的数据帧与帧之间有一定的间隔。RC5标准规定一帧结束后至少等待24个比特周期约42.67ms再发送下一帧。我们可以在主循环中用一个状态机和定时器来实现自动重复发送的功能。5. 调试与实测中的坑与技巧理论很美好但把代码烧录进ATtiny28后实际测试时可能会遇到各种问题。问题一红外发射距离极短甚至无法控制设备。这是最常见的问题。首先用手机摄像头对准红外发射管观察。当按下按键时你应该能看到发射管发出微弱的白光手机CMOS对红外光敏感。如果看不到说明根本没有载波输出检查定时器配置和驱动电路。 如果能看到闪烁但距离很短问题通常出在驱动电流上。用万用表测量红外发射管两端的电压在发射时其阴极接三极管集电极电压应该被拉低到接近0.2V三极管饱和压降。如果这个电压较高比如还有1V以上说明三极管没有完全饱和驱动电流不足。可以尝试减小基极限流电阻比如从1kΩ降到470Ω或者更换放大倍数更高的三极管。 另一个可能是载波频率偏差太大。用示波器测量驱动引脚波形看其周期是否是接近27.8µs。如果偏差超过1kHz可能会超出接收头通常是38kHz但有一定带宽的接收范围。调整OCR0的值进行微调。问题二设备反应不稳定时灵时不灵。这可能是比特时序不准确导致的。1.778ms的比特周期和0.889ms的半周期点必须非常精确。检查你的系统时钟是否稳定。如果使用内部RC振荡器其误差可能高达10%这会导致协议时序完全错乱必须换用外部晶振。 用示波器观察红外接收头解码后的输出信号接收头通常有三个引脚VCC GND OUT。OUT脚在无信号时为高电平收到正确信号时会输出解码后的数字波形。对比你发送的命令和接收头输出的波形看其高低电平的持续时间是否符合RC5标准。调试时可以发送一个固定的命令如地址0x0 命令0x0用示波器单次触发捕捉接收头的输出仔细测量每个脉冲的宽度。问题三按键响应有延迟或连发。这是软件去抖动和发送状态机处理不当。机械按键在按下和释放的瞬间会产生抖动可能持续10-50ms。如果直接在检测到低电平时就触发发送可能会误触发多次。必须加入软件去抖动。一个简单有效的方法是检测到按键状态变化后延时20-50ms再次检测如果状态稳定才认为是有效按键。 对于连发要确保在一帧数据发送完成前不会响应新的按键检测。同时重复发送的机制也要设计好比如按下超过500ms后开始每秒重复发送10次直到按键释放。一个实用的调试技巧制作一个“逻辑分析仪”如果没有示波器可以用另一个AVR单片机比如Arduino来模拟简易逻辑分析仪。将红外接收头的OUT引脚连接到这个“分析仪”的输入引脚编写一段程序以尽可能高的速度采样这个引脚的电平并将时间戳和电平状态通过串口发送到电脑。在电脑上用串口绘图工具或者自己写个小程序解析这些数据就能还原出红外信号的波形对于调试协议时序非常有帮助。6. 从原型到产品优化与扩展当基本功能实现后可以考虑进一步优化和扩展让它更像一个真正的产品。功耗优化遥控器大部分时间处于待机状态。ATtiny28支持多种休眠模式。在无按键时可以让单片机进入空闲Idle模式甚至掉电Power-down模式此时定时器停止功耗可以降到微安级。通过按键引脚的外部中断来唤醒单片机。唤醒后初始化定时器发送信号发送完毕后再进入休眠。这样可以极大地延长电池寿命。学习功能让这个发射器不仅能发射固定码还能学习其他遥控器的编码。这需要增加一个红外接收头如VS1838B。当进入学习模式时单片机切换角色用定时器输入捕获功能精确测量接收头输出波形的脉冲和间隔时间记录下整个帧的时序数据并将其存储在EEPROM中。发射时则按照学习到的时序数据原样复现。这可以支持非RC5的其他红外协议如NEC、Sony SIRC等。用户接口改进4个按键太少了。可以利用ATtiny28的ADC功能配合一个电阻分压网络和多个按键实现多个按键的检测即ADC按键。也可以使用串行接口连接一个小的OLED显示屏显示当前模式和命令但这对ATtiny28来说资源就非常紧张了可能需要升级到ATmega系列。外壳与结构使用3D打印为它制作一个小巧的外壳。将电池、电路板、红外发射管固定好。红外发射管最好略微凸出外壳并且前面不要有深色或厚实的塑料遮挡以免衰减信号。完成这个项目你收获的不仅仅是一个能控制老音响的小工具更是一整套在资源受限环境下进行嵌入式开发、精准时序控制、协议实现和硬件调试的实战经验。这些经验在你日后面对更复杂的MCU和更庞大的系统时会显得格外珍贵。它让你明白无论芯片多么简单只要理解其原理并善用其资源就能创造出解决实际问题的作品。