1. 项目概述与核心思路最近在捣鼓一个基于FPGA的小玩意儿想用红外遥控来控制几个LED灯。手头正好有一套红外对射模块发射端是个红外LED接收端用的是IRM-3638这个解码芯片。听起来挺简单对吧不就是按个键发个信号对面收到就亮灯嘛。我一开始也是这么想的结果在Verilog实现上卡了好几天差点没把自己给整郁闷了。问题就出在我把红外通信想得太“数字”了以为就是简单的“发1收1发0收0”完全忽略了红外通信中“载波”这个关键角色。这个坑我相信很多从纯数字逻辑转向涉及简单模拟信号处理的工程师都可能会踩。IRM-3638是一个很常见的红外接收头内部集成了光电二极管、前置放大器、限幅器、带通滤波器、解调器和输出整形电路。它的核心任务是把被38kHz常见或其它频率如我用的33kHz载波调制的红外光脉冲信号解调成干净的数字电平输出。这意味着发送端不能直接发送高/低电平给红外LED而是必须用特定频率的载波去“携带”你的数据。对于IRM-3638而言其逻辑定义往往是反直觉的持续接收到对应频率的载波时它输出低电平没有载波或载波频率不对时它输出高电平。理解这一点是整个项目成败的第一个关键。我的项目目标很明确在FPGA开发板上用三个物理按键sw1, sw2, sw3作为输入当其中任意一个按键被按下时通过红外发射管发送信号接收端的IRM-3638收到信号后驱动三个LED灯led_d2, led_d3, led_d4同时点亮。这实际上实现了一个最简单的“红外遥控灯”功能是理解红外编解码通信原理的绝佳实践。2. 红外通信原理与IRM-3638工作机制深度解析2.1 为何需要载波抗干扰与提高驱动效率如果你直接用直流信号驱动红外LED发光来表示“1”熄灭来表示“0”会有什么问题首先环境中的自然光、日光灯都含有红外成分会造成极强的干扰接收端根本无法区分是信号还是噪声。其次红外LED需要一定的驱动电流才能获得足够的发射功率使其能在几米甚至十几米的距离被接收到。持续导通的大电流不仅耗电还可能损坏LED。引入载波调制完美解决了这两个问题。载波就是一个高频的方波信号例如33kHz。我们要发送的数据信号称为“基带信号”通过这个载波进行“调制”。最常见的调制方式是“幅移键控”ASK即发送“0”时关闭载波发送“1”时开启载波。这样红外LED实际上是在高频闪烁。对于接收端IRM-3638来说其内部的带通滤波器中心频率就设计在33kHz只有这个频率附近的信号才能被有效放大和解调环境中的直流红外噪声和频率不匹配的干扰都被极大地抑制了。同时LED工作在高频脉冲状态其平均电流远小于持续导通的电流既保证了发射强度又提高了效率和使用寿命。2.2 IRM-3638的输出逻辑与直觉相反这是最容易让人困惑的地方也是我最初栽跟头的原因。我们习惯性认为发送“有效信号”时接收端应该输出“有效电平”比如1。但在很多红外接收头包括IRM-3638的典型应用里逻辑是反的。查阅IRM-3638的数据手册Datasheet其输出部分通常会说明当接收到有效载波时输出端输出低电平当未接收到有效载波时输出端输出高电平通常通过内部上拉电阻实现。为什么这样设计一个合理的解释是与抗干扰有关。在空闲状态无信号输出保持高电平。当有载波信号即有效信号到来时才拉低。这样任何非载波引起的干扰如瞬间的强光都不会产生一个低电平脉冲从而降低了误触发的概率。你可以这样记忆载波 活动状态 输出低电平。因此在发送端当我们要向接收端传递一个逻辑‘0’让接收端输出高电平时我们应该持续发送载波。当我们要向接收端传递一个逻辑‘1’让接收端输出低电平时我们应该停止发送载波。在我的简单演示项目中逻辑被简化了只要按键按下就持续发送载波。那么根据上述规则接收端IRM-3638在收到载波后会输出低电平。我的代码里用这个低电平去控制LED点亮低电平有效或经反相后驱动。2.3 载波参数计算从系统时钟到精准脉冲我的FPGA板载晶振是50MHz周期为20ns。IRM-3638要求或适配的载波频率是33kHz。我们需要用Verilog生成一个尽可能接近33kHz的方波。计算载波周期33kHz信号的周期 T_carrier 1 / 33,000 Hz ≈ 30.303 μs。计算系统时钟周期数N_carrier T_carrier / T_clk 30.303 μs / 20 ns ≈ 1515.15。这不是整数我们需要取整。在代码中作者使用了cnt_1315这个11位计数器其比较值是1315这对应的是26.3μs周期即约38kHz的载波。这里可能存在一个选择是使用更常见的38kHz载波还是数据手册指定的33kHz有时为了兼容通用遥控器会选择38kHz。我们需要根据手头IRM-3638的具体型号决定。这是一个关键细节频率不对接收头可能无法解调或灵敏度大幅下降。计算占空比通常红外载波的占空比是1/3或1/2以降低平均功耗。例如周期26.3μs占空比1/3则高电平时间 ≈ 8.77μs低电平时间 ≈ 17.53μs。这对应计数器计到约4381315/3时翻转。注意务必根据你实际使用的IRM-3638型号的数据手册确认其中心频率Center Frequency和对应的载波周期、占空比要求。这是硬件驱动的基础绝不能想当然。3. Verilog代码逐模块详解与实现下面结合我最终调试成功的代码来详细分解每一个模块的设计思路和注意事项。我的系统时钟是50MHz。module topirda( input clk, // 50MHz 主时钟 input rst_n, // 低电平有效的复位信号 input irda_receive, // 来自IRM-3638的输出信号 output irda_send, // 驱动红外发射管的信号 input sw1, sw2, sw3, // 三个低电平有效的按键输入假设按下为0 output led_d2, led_d3, led_d4 // 三个LED灯低电平点亮 );3.1 按键消抖与检测模块机械按键在按下和弹起时会产生持续数毫秒的抖动会产生多个边沿必须进行消抖处理。// 20ms 消抖计数器 reg [19:0] cnt_20ms; // 50MHz时钟下计满1,000,000次为20ms (1,000,000 * 20ns 20ms) reg key_value; // 消抖后的稳定键值0表示有键按下 always (posedge clk or negedge rst_n) begin if (!rst_n) cnt_20ms 20d0; else cnt_20ms cnt_20ms 1b1; // 循环计数 end always (posedge clk or negedge rst_n) begin if (!rst_n) key_value 1b1; // 默认无按键按下 else if (cnt_20ms 20hfffff) // 每20ms采样一次按键状态 // 如果三个按键中任意一个为低电平按下则key_value为0 key_value sw1 sw2 sw3; end设计要点采样周期选择20ms是经验值能覆盖绝大多数机械按键的抖动时间。计数器cnt_20ms自由运行每到计满值这里用20hfffff近似代表20ms点时对按键状态进行一次采样。这种方法比检测到边沿后启动定时器更节省逻辑。按键逻辑key_value sw1 sw2 sw3;意味着只要sw1,sw2,sw3中有一个为0按下key_value就为0。这实现了“任意键按下”的逻辑。寄存器输出将消抖后的信号key_value锁存在寄存器中避免输出毛刺。3.2 33kHz/38kHz载波生成模块这是驱动红外LED的核心。我们需要产生一个占空比约为1/3的方波。// 载波周期计数器以26.3us (38kHz) 为例 parameter CARRIER_CNT_MAX 11d1315; // 26.3us / 20ns 1315 parameter CARRIER_HIGH_CNT 11d438; // 1315 / 3 ≈ 438, 高电平时间约8.77us reg [10:0] cnt_carrier; // 载波周期计数器 reg carrier_wave; // 生成的载波信号 always (posedge clk or negedge rst_n) begin if (!rst_n) cnt_carrier 11d0; else if (cnt_carrier CARRIER_CNT_MAX - 1) cnt_carrier cnt_carrier 1b1; else cnt_carrier 11d0; end // 生成载波方波 always (posedge clk or negedge rst_n) begin if (!rst_n) carrier_wave 1b0; else if (cnt_carrier CARRIER_HIGH_CNT) carrier_wave 1b1; // 计数小于438输出高电平 else carrier_wave 1b0; // 计数大于等于438输出低电平 end设计要点参数化设计使用parameter定义关键参数方便后期修改频率和占空比。例如如果要改为33kHz周期30.3us只需修改CARRIER_CNT_MAX 30.3us / 20ns 1515。占空比精度CARRIER_HIGH_CNT的计算决定了占空比。1/3占空比是常见选择但有些协议可能要求1/2。务必与接收头要求匹配。信号质量carrier_wave是一个纯净的、周期精确的方波它将作为调制用的载波。3.3 数据调制与发送模块这部分将消抖后的按键信号key_value调制到载波上。逻辑是有按键按下时发送载波无按键时停止发送输出恒低。reg irda_send_r; // 发送数据寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) irda_send_r 1b0; else if (!key_value) // 有键按下 irda_send_r carrier_wave; // 输出载波 else irda_send_r 1b0; // 无键按下输出恒定低电平 end assign irda_send irda_send_r; // 连接到输出端口设计要点与常见误区调制逻辑irda_send_r carrier_wave;这行代码实现了ASK调制。当key_value0有按键红外LED就会以38kHz的频率闪烁当key_value1无按键红外LED常灭。对应接收逻辑根据2.2节的解析当IRM-3638持续收到这个38kHz的闪烁信号即按键按下时其输出引脚irda_receive会变为低电平。当它收不到载波按键松开时irda_receive会变为高电平。一个关键检查点你的红外发射管驱动电路是否正确FPGA的IO口驱动能力通常只有几十毫安而红外LED需要更大的瞬间电流可达100mA才能有足够的发射距离。通常需要在FPGA输出引脚后接一个三极管如NPN型的8050或MOSFET来驱动红外LED并串联一个限流电阻如10-100Ω。直接连接FPGA引脚到LED很可能导致亮度不足、距离极短甚至损坏FPGA引脚。3.4 接收解码与LED控制模块接收端逻辑相对简单因为IRM-3638已经完成了复杂的解调工作输出了干净的数字电平。// 将IRM-3638的输出直接分配给三个LED assign {led_d2, led_d3, led_d4} (irda_receive 1b0) ? 3b000 : 3b111;设计要点逻辑电平匹配代码(irda_receive 1b0) ? 3b000 : 3b111意味着当irda_receive为低电平时三个LED输出低电平假设LED是低电平点亮灯亮。这正好对应了“按键按下 - 发送载波 - 接收头输出低电平 - LED亮”的逻辑链。反逻辑处理如果你的LED电路是高电平点亮或者你希望“无信号时灯亮有信号时灯灭”那么就需要将逻辑取反assign {led_d2, led_d3, led_d4} irda_receive ? 3b111 : 3b000;。原项目代码中使用的就是这种反逻辑。这完全取决于你的硬件电路设计务必根据原理图调整。信号稳定性IRM-3638的输出在信号稳定时很干净但在载波刚出现或消失的瞬间可能会有轻微抖动。对于LED控制这种应用无需处理但如果用于数据通信则必须进行边沿检测和同步处理。4. 系统集成、调试与深度问题排查将上述模块集成后就构成了完整的topirda模块。然而从代码到稳定工作的系统还有很长的调试之路。以下是我在调试过程中遇到的核心问题及解决方法。4.1 硬件连接检查清单在烧录代码前必须再三确认硬件连接很多“不工作”都是硬件问题。电源确保FPGA开发板、红外发射模块、红外接收模块供电正常且电压匹配通常是3.3V或5V。接地确保FPGA、发射模块、接收模块共地。这是信号参考的基础未共地会导致信号紊乱。发射端FPGA的irda_send引脚是否连接到了驱动三极管的基极通过一个限流电阻如1kΩ三极管的集电极是否通过一个限流电阻如47Ω连接到了红外LED的正极红外LED的负极是否连接到三极管的发射极并接地用手机摄像头初步测试肉眼看不见红外光但手机摄像头能感应到。在暗处用手机摄像头对准红外LED按下按键你应该能看到LED发出微弱的白光或紫光这是手机CMOS对红外光的成像。这是最快验证发射电路是否工作的办法。接收端IRM-3638的VCC、GND是否接对信号输出引脚是否直接连接到FPGA的irda_receive输入引脚通常不需要上拉电阻因为芯片内部已有上拉。注意引脚顺序IRM-3638常见封装有三个引脚顺序可能是从弧形凹槽或正面看输出、地、电源也可能是地、输出、电源。务必查阅数据手册或商家资料确认接反会烧毁芯片4.2 软件仿真与调试技巧即使硬件没问题代码逻辑也可能有误。使用仿真工具如ModelSim、Vivado Simulator是必不可少的。编写Testbench模拟时钟、复位和按键输入。观察irda_send信号。在按键按下期间irda_send应该是完美的38kHz方波。检查方波的周期和占空比是否与设计一致26.3μs周期高电平8.77μs。检查计数器溢出原代码if(cnt_1315 20hfffff)用于20ms采样但cnt_1315是11位寄存器最大值是204720hfffff是20位的最大值两者永远不相等这会导致key_value永远不被更新。这是一个致命的编码错误。应该改为if(cnt_20ms 20hfffff)或者更合理的if(cnt_20ms)检查所有位是否为1或使用一个特定的计数值如20‘d999_999对应20ms。使用内部逻辑分析仪像SignalTap II (Intel) 或 ILA (Xilinx) 这样的工具可以实时抓取FPGA内部信号。这是调试硬件-软件交互问题的神器。抓取key_value,carrier_wave,irda_send_r,irda_receive等信号。按下按键观察irda_send_r是否出现载波同时观察irda_receive是否在经过一个短暂延时后变低。这个延时就是IRM-3638的响应时间通常在几百微秒量级。4.3 常见故障现象与排查表故障现象可能原因排查方法LED毫无反应1. 供电或接地问题。2. 红外发射管未工作。3. IRM-3638损坏或接线错误。4. FPGA代码未正确运行或引脚分配错误。1. 用万用表测量各点电压。2. 用手机摄像头检查发射管。3. 更换接收头核对引脚顺序。4. 检查FPGA配置是否成功用逻辑分析仪看irda_send信号。LED常亮或常灭不受控制1.irda_receive引脚电平固定。可能是接收头损坏、电源问题或FPGA引脚模式设置错误应设为输入。2. 发送端载波频率严重偏离。1. 测量接收头输出端电压无信号时应为高电平如3.3V用金属物遮挡接收头电压应有变化。2. 用示波器测量irda_send引脚波形核对频率和占空比。控制不灵敏距离很短1. 发射管驱动电流不足。2. 载波占空比不合适。3. 环境红外干扰太强如阳光直射。4. 发射管与接收头未对准。1. 减小发射管限流电阻增加驱动电流注意不要超过器件最大值。2. 尝试调整载波占空比至1/2或1/3看灵敏度是否有变化。3. 在较暗环境下测试。4. 确保发射管正对接收头。按键松开后LED状态不稳定1. 按键消抖逻辑有误key_value信号抖动。2. IRM-3638输出在载波消失时有轻微抖动。1. 用逻辑分析仪观察key_value信号确保其干净稳定。2. 对于LED应用可忽略若需稳定可在接收端对irda_receive信号也做一次20ms左右的滤波。4.4 从演示到真正通信协议化的思考本项目只是一个简单的“通断”演示。真正的红外遥控如NEC、RC5协议要复杂得多它们包含了地址码、命令码、重复码、起始位和停止位并且通过脉冲宽度如560μs脉冲560μs间隔代表‘0’560μs脉冲1690μs间隔代表‘1’来编码数据。如果你想在此基础上实现标准协议需要发送端设计一个状态机将按键值映射成特定的数据帧如NEC协议的32位帧然后用这个数据流去控制irda_send_r是否输出载波即进行ASK调制。逻辑‘0’和‘1’对应不同的载波发射时长。接收端IRM-3638输出的是解调后的数据流即基带信号。你需要编写一个解码状态机检测起始位一个长低脉冲然后测量后续高电平的持续时间来区分‘0’和‘1’最后拼装出地址和命令码。同步与容错需要考虑帧间间隔、重复码处理以及一定的容错能力。这个简单的IRM-3638驱动项目正是理解这一切的基石。它让你搞清楚了最核心的部分载波是如何生成、调制并被接收头解调成数字信号的。掌握了这个再去研究NEC等具体协议就会豁然开朗。调试电子系统尤其是这种涉及数字和简单模拟混合的系统切忌一头扎进代码里。我的教训就是一开始太“想当然”。正确的顺序永远是理解器件原理数据手册- 设计硬件电路原理图- 编写驱动逻辑代码- 仿真验证 - 硬件调试仪器测量。每一步的扎实才能换来最后“灯亮”那一刻的顺畅。红外通信虽然简单但麻雀虽小五脏俱全它涵盖了时钟分频、信号调制、按键处理、状态控制等多个FPGA基础知识点是一个非常棒的练手项目。