
简介这份面向电子初学者的红外发射接收电路文档围绕HS38HS0038接收集成模块展开帮助读者理解38kHz红外方波的发射、检测与示波器验证方法。内容涉及未设置限流电阻和三极管驱动的简易电路、20%至30%占空比对发射功率的影响以及用单片机、555定时器或信号发生器产生方波的基本思路文中还给出常见38kHz接收模块的引脚图并说明可用试触法辨别不明确引脚。压缩包仅含1个doc文件大小约47KB篇幅精简适合作为元件认知与入门实验的参考。文档已有1669人学习读者可从中获得HS38输出高低电平的判断依据、发射与遮挡实验的观察要点以及接收模块引脚识别和电路调试的入门线索。1. 从一块洞洞板开始简单的红外发射接收电路到底难在哪简单的红外发射接收电路看起来只是“一个红外 LED 加一个三脚接收头”但很多人第一次上电就卡在接收头输出恒高LED 明明焊了代码也跑了示波器却什么都没有。它要解决的是把一串数字码用 38 kHz 载波调制成红外光发出去再由一体化接收头解调、整形、还原成脉宽信号。适合做遥控、避障、红外码学习、单片机入门实验的人也适合已经能把灯点亮、但一上接收就翻车的从业者。真正难的不是电路规模而是发射电流、载波频率、接收头供电和阈值判读这几个参数必须同时对上。2. 发射端940 nm 红外 LED 的限流电阻与 38 kHz 载波怎么定发射端决定两件事红外 LED 有没有足够的脉冲辐射功率以及这个光有没有被 38 kHz 方波切开。红外 LED 不是普通指示灯它的正向压降通常在 1.11.4 V峰值波长 940 nm 左右脉冲电流可以做到 100200 mA但平均电流受占空比限制。直接把 LED 接在 5 V 和单片机引脚之间要么电流太小打不远要么把引脚拉坏。常见做法是用一只小功率 NPN 三极管做开关单片机只负责输出 38 kHz 方波LED 回路单独算限流电阻。2.1 先算电流再选电阻5 V 供电下的限流公式与三极管驱动限流电阻按欧姆定律算R (VCC - VF - VCE(sat)) / IF_PULSE。VCC 取 5 VVF 取 1.2 V三极管饱和压降按 0.2 V 估脉冲电流想做到 100 mA则 R (5 - 1.2 - 0.2) / 0.1 36 Ω取标准值 39 Ω 或 33 Ω。若忽略三极管压降R (5 - 1.2) / 0.1 38 Ω实际电流会略小仍在可用范围。脉冲占空比按 1/3 算平均电流约 33 mA39 Ω 电阻上的平均功耗约 0.13 W选 1/4 W 短脉冲能扛但连续发射时建议用 1/2 W避免电阻发热后阻值漂移。下面这段 Python 只是把电阻和功耗算清楚改 VCC、VF、IF_PULSE、DUTY 四个参数就能用于 3.3 V 或不同电流目标# 红外 LED 限流电阻与平均功耗估算 VCC 5.0 # 发射回路供电单位 V VF 1.2 # 940 nm 红外 LED 正向压降单位 V VCE_SAT 0.2 # 三极管饱和压降单位 V IF_PULSE 0.10 # 目标脉冲电流单位 A DUTY 1 / 3 # 38 kHz 载波占空比 R_ideal (VCC - VF - VCE_SAT) / IF_PULSE R_std 39.0 # 常用标准值按实际手头阻值改 I_actual (VCC - VF - VCE_SAT) / R_std P_avg I_actual ** 2 * R_std * DUTY print(f理论电阻: {R_ideal:.1f} ohm) print(f取 {R_std:.0f} ohm 后脉冲电流: {I_actual * 1000:.1f} mA) print(f电阻平均功耗: {P_avg:.3f} W)参数说明IF_PULSE 不要超过 LED 数据手册的峰值电流常见 5 mm 红外管连续电流 100 mA、脉冲电流 1 A 是极限值不代表可以长期跑DUTY 是载波占空比不是协议里引导码的占空比。三极管基极电阻按 Ib Ic / hFE 估假设 hFE 100、Ic 100 mAIb 约 1 mA5 V 下基极电阻取 1 kΩ 足够饱和基极再并 10 kΩ 下拉防止单片机复位时误发光。多个红外 LED 并联时每只都要串自己的限流电阻不能共用一只电阻否则 VF 分散会导致电流分配不均5 V 供电下两只串联再串电阻也可以但只剩 2.6 V 余量电流对 VF 变化很敏感。2.2 38 kHz 载波不是“随便方波”定时器参数与占空比接收头内部带通滤波器的中心频率常见为 38 kHz载波偏到 36 kHz 或 40 kHz 时距离会下降偏得更多就直接不响应。38 kHz 的周期是 26.3 µs半周期 13.16 µs占空比取 1/3 左右即高电平约 8.8 µs。占空比不是越小越省电太小会让接收头内部比较器难以识别1/3 到 1/2 是常见范围1/3 对平均电流更友好。用单片机定时器硬件 PWM 产生不要用delay_us()在主线里翻转引脚中断一多频率就飘接收头会时好时坏。以 8 MHz 定时器时钟、不分频为例周期计数值 8 000 000 / 38 000 ≈ 210比较值取 70实际频率 8 000 000 / 210 38.095 kHz误差 0.25%完全够用。16 MHz 时周期值取 421比较值取 140实际 38.005 kHz。下面是一段与寄存器无关的初始化骨架换成所用 MCU 的定时器 API 即可// 38 kHz 红外载波 PWM 初始化骨架 // 假设定时器计数时钟 8 MHz目标 38 kHz占空比约 1/3 #define IR_TIMER_CLK_HZ 8000000UL #define IR_CARRIER_HZ 38000UL #define IR_TIMER_TOP (IR_TIMER_CLK_HZ / IR_CARRIER_HZ) // 210 #define IR_DUTY_COMPARE (IR_TIMER_TOP / 3) // 70 void ir_carrier_init(void) { timer_set_prescaler(1); // 不分频 timer_set_period(IR_TIMER_TOP); // 周期 210 个计数 timer_set_compare(IR_DUTY_COMPARE); // 高电平 70 个计数 timer_pwm_enable(); timer_output_disable(); // 先不输出等发送时再开 }逻辑说明IR_TIMER_TOP决定载波频率IR_DUTY_COMPARE决定高电平宽度发送引导码时打开 PWM 输出 9 ms再关 4.5 ms数据位按 560 µs 载波加不同空闲宽度拼接。注意接收头输出是反相的有 38 kHz 载波时输出低没有载波时输出高。所以发射端“亮”的这段时间接收端看到的是低电平。调试时先把 PWM 连续开 1 秒用手机摄像头对着 LED 看能看到紫色闪烁说明载波在跑看不到不代表坏有些手机红外滤镜较强还是以接收头输出为准。3. 接收端三脚一体化接收头的供电、上拉与输出波形一体化接收头把光电二极管、前置放大、38 kHz 带通、解调、整形和输出级做在一个三脚封装里常见引脚是 VCC、GND、OUT。它的输出不是原始红外波形而是已经去掉载波的包络收到 38 kHz burst 时 OUT 拉低burst 结束后 OUT 回高。很多人以为接收头输出的是“红外光强弱”其实它输出的是“有没有符合 38 kHz 的调制光”所以发射端频率不对接收端再干净也没用。3.1 三脚接收头内部做了什么带通、解调与低电平有效接收头内部第一级是光电二极管把 940 nm 附近的光转成电流第二级是前置放大和自动增益控制弱信号放大、强信号压缩避免近处直射饱和第三级是 38 kHz 带通只让接近中心频率的交流分量通过最后是解调、整形和输出级。输出级常见是集电极开路或内部上拉结构空闲时 OUT 为高收到载波 burst 时 OUT 为低。这个“低有效”是后面解码时最容易搞反的地方示波器上看到的一串低脉冲宽度对应的是发射端载波 burst 的宽度不是空闲宽度。接收头对电源很敏感。VCC 常见 5 V部分型号支持 2.75.5 V静态电流不到 1 mA但内部放大器对纹波敏感。供电脚就近放 0.1 µF 去耦VCC 前面再串 100 Ω 加 100 µF 到地形成一个小 RC 滤波能压掉单片机 PWM 和电机换向带来的尖峰。100 Ω 上的压降按 1 mA 算只有 0.1 V不会影响工作但如果接收头附近有更大负载电阻要减小到 47 Ω 或 22 Ω否则 VCC 会跌破 4.5 V输出开始乱跳。OUT 脚外接 10 kΩ 上拉到 VCC 是常见做法即使接收头内部有上拉外接一只也能让边沿更干净长线时再串 100 Ω 并加 100 pF 到地但滤波电容不要大于 1 nF否则脉宽会被明显展宽。外围元件常见取值作用边界VCC 去耦电容0.1 µF抑制高频纹波必须就近放在接收头引脚旁VCC 滤波电阻100 Ω与 100 µF 组成 RC压降不能超过 0.5 VVCC 滤波电容100 µF吸收低频波动漏电流大的电容不选OUT 上拉电阻10 kΩ保证空闲高电平长线可降到 4.7 kΩOUT 串联电阻100 Ω抑制振铃会轻微增加上升时间3.2 接收头外围只加三个元件RC 滤波、去耦与上拉接线顺序建议先单独给接收头供电不接单片机测静态 OUT 是否接近 VCC。正常空闲高电平应在 4.5 V 以上如果只有 23 V先查上拉和 VCC不要急着写解码。然后把接收头 OUT 接到示波器或逻辑分析仪用发射端发一串 38 kHz burst看 OUT 是否出现低脉冲。如果没有低脉冲按“接收头 VCC → 发射管是否发光 → 载波频率 → 接收头朝向”的顺序排查不要一上来就改代码阈值。单片机侧初始化只做三件事把 OUT 脚设为输入、使能上拉或外部上拉、配置双边沿中断。下面这段代码是通用骨架IR_RX_PIN换成实际引脚// 接收头 OUT 引脚初始化输入 双边沿中断 void ir_rx_init(void) { gpio_set_input(IR_RX_PIN); gpio_enable_pullup(IR_RX_PIN); // 外部已有 10 kΩ 上拉时可省略 gpio_attach_interrupt(IR_RX_PIN, IR_EDGE_BOTH, ir_edge_isr); gpio_clear_interrupt(IR_RX_PIN); }逻辑说明双边沿中断用于记录每次电平跳变的时间戳后面用相邻时间差算低电平宽度和高电平宽度。IR_EDGE_BOTH不能少只抓下降沿会丢掉高电平间隔如果 MCU 不支持双边沿可以用定时器输入捕获的两种极性交替配置。接收头输出边沿不是理想方波近处强信号时上升沿可能有 1050 µs 的斜坡软件消抖不要小于 50 µs否则会把正常脉宽切碎。接收头内部有延迟典型 0.51.5 ms解码阈值要按实测调整不能拿发射端的 560 µs 直接当接收端的 560 µs 用。4. 从波形到码字用输入捕获和 Python 脚本解出一帧红外数据红外遥控常见的帧格式是脉冲宽度编码引导码由 9 ms 载波 burst 和 4.5 ms 空闲组成随后是 32 位数据每位先发 560 µs 载波 burst再发一段空闲空闲 560 µs 表示 0空闲 1.69 ms 表示 1。接收头把载波 burst 解成低电平所以 OUT 上看到的是“低 9 ms、高 4.5 ms”的引导码然后每位“低 560 µs、高 560 µs 或 1.69 ms”。不同厂家的协议在引导码和位宽上会变但判读方法一样先找引导码同步再按低电平宽度确认每一位的起始按高电平宽度判 0 或 1。4.1 先看引导码和位宽脉冲宽度协议的判读规则把逻辑分析仪采样率设到 1 MHz 以上抓一帧完整数据。先量第一个低脉冲应该在 810 ms紧接着的高电平在 45 ms后面连续 32 组“低 560 µs 高 560/1690 µs”。如果第一个低脉冲只有 500 µs说明抓到的不是帧头可能是重复码或噪声如果低脉冲宽度整体翻倍检查采样率是不是设成了 500 kHz 而软件按 1 MHz 算。接收头对脉宽有压缩和展宽典型误差 ±100 µs所以阈值不要卡在 560 µs 和 1.69 ms 的中间点而是取 1.01.2 ms 作为 0/1 分界。波形段发射端接收头 OUT判读阈值引导码低9 ms 载波低 810 ms800010000 µs引导码高4.5 ms 空闲高 45 ms40005000 µs位起始低560 µs 载波低 400800 µs300800 µs数据 0 高560 µs 空闲高 400800 µs小于 1000 µs数据 1 高1.69 ms 空闲高 14002000 µs大于 1000 µs4.2 单片机端输入捕获记录高电平与低电平的计数值用 1 MHz 计数时钟1 个计数就是 1 µs16 位定时器 65.5 ms 溢出一次一帧 108 ms 内会溢出所以中断里要维护溢出计数。更省事的做法是用 100 kHz 计数10 µs 分辨率16 位定时器可覆盖 655 ms脉宽误差 10 µs对 560 µs 和 1.69 ms 的区分足够。下面代码只保留逻辑骨架timer_get_count()返回当前计数值overflow_count在定时器溢出中断里累加#define IR_BUF_LEN 80 static volatile uint16_t edge_tick[IR_BUF_LEN]; static volatile uint8_t edge_cnt; static volatile uint32_t overflow_cnt; void ir_edge_isr(void) { uint16_t now timer_get_count(); if (edge_cnt IR_BUF_LEN) { edge_tick[edge_cnt] now; // 记录跳变时刻 } gpio_clear_interrupt(IR_RX_PIN); } // 将相邻边沿转成低电平宽度和高电平宽度单位 us uint8_t ir_build_widths(const uint16_t *tick, uint8_t n, uint16_t *low_us, uint16_t *high_us) { uint8_t pairs 0; for (uint8_t i 0; i 1 n; i) { uint16_t dt (uint16_t)(tick[i 1] - tick[i]); uint16_t us dt * 10; // 100 kHz 计时1 tick 10 us if (i % 2 0) low_us[pairs] us; else high_us[pairs] us; if (i % 2 1) pairs; } return pairs; }参数说明IR_BUF_LEN要能装下一帧 32 位加引导码至少 70 个边沿取 80 留余量tick是环形缓冲里按时间排序的跳变时刻如果边沿太密导致溢出说明有噪声或载波没被解调干净us dt * 10里的 10 来自 100 kHz 计时换成 1 MHz 就改成 1。解码函数先检查引导码再逐位检查低电平是否在 300800 µs高电平大于 1000 µs 记 1否则记 0。连续两帧结果一致才接受能挡掉大部分环境光误触发。4.3 逻辑分析仪 CSV 解码Python 脚本与阈值参数逻辑分析仪导出的 CSV 通常有两列时间秒和电平。下面脚本按“接收头 OUT 空闲高、载波低”的极性写换极性只需把lvl 0改成lvl 1。脚本先提取边沿再算低电平和高电平宽度最后按引导码和位宽解出 32 位码import csv from pathlib import Path # CSV 列名按实际导出改time_s 为秒level 为 0/1 rows [] with Path(ir_capture.csv).open() as f: for r in csv.DictReader(f): rows.append((float(r[time_s]), int(r[level]))) # 提取边沿电平变化的位置 edges [] for i in range(1, len(rows)): if rows[i][1] ! rows[i - 1][1]: edges.append((rows[i][0], rows[i][1])) low_us, high_us [], [] for i in range(len(edges) - 1): t0, lvl edges[i] t1, _ edges[i 1] width_us (t1 - t0) * 1e6 if lvl 0: low_us.append(width_us) # 接收头输出低对应载波 burst else: high_us.append(width_us) # 接收头输出高对应空闲 print(低电平宽度(us):, [round(x) for x in low_us[:8]]) print(高电平宽度(us):, [round(x) for x in high_us[:8]]) # 阈值不是固定值按接收头实测误差调整 if len(low_us) 33 and len(high_us) 33: if 8000 low_us[0] 10000 and 4000 high_us[0] 5000: code 0 for i in range(1, 33): if not (300 low_us[i] 800): raise ValueError(f第 {i} 位低电平异常: {low_us[i]:.0f} us) code 1 if high_us[i] 1000: # 1.69 ms 判为 1560 us 判为 0 code | 1 print(f解码结果: 0x{code:08X})逻辑说明edges里存的是每次跳变后的电平所以lvl 0的区间是低电平low_us[0]和high_us[0]是引导码后面 32 组按位移拼成 32 位数。参数边界引导码低取 800010000 µs高取 40005000 µs数据位低取 300800 µs0/1 分界取 1000 µs。如果实测 1 的高电平只有 1.3 ms分界要降到 900 µs如果接收头把 560 µs 拉宽到 900 µs低电平上限要放到 1000 µs。不要一套阈值打天下先抓三帧看分布再定。5. 避坑与排查红外接收头输出一直高、距离短、误触发的四个现场红外电路翻车大多不是“芯片坏了”而是供电、极性、载波和阈值四件事里有一件没对上。下面四条按现场现象、原因、解决写顺序也基本是排查顺序先让接收头输出动起来再谈距离再谈解码最后谈抗干扰。5.1 上电就翻车接收头输出恒高与 LED 不亮的排查顺序现象一接收头 OUT 一直是高电平发射时也不见低脉冲。原因通常是发射回路没电流红外 LED 极性焊反、限流电阻过大、三极管基极没驱动、单片机 PWM 没打开或者接收头 VCC 与 GND 接反。解决顺序是先用万用表测接收头 VCC 对 GND 是否 4.55 V再测发射管两端在发射瞬间是否有 1 V 左右的脉冲压降如果没有测三极管基极是否有 0.60.7 V 的驱动电压。手机摄像头对着红外 LED 看发射时能看到微弱紫光说明 LED 在闪看不到就查极性和电阻不要先改解码代码。现象二LED 用万用表二极管档能亮装上电路却不亮。原因是万用表提供的测试电流只有几毫安而电路里 39 Ω 串联后脉冲电流虽然大但三极管没有饱和或者单片机引脚直接驱动导致电压被拉低。解决是把基极电阻从 10 kΩ 降到 1 kΩ确认三极管发射极接地、集电极接 LED 负极、LED 正极经电阻接 VCC如果是 PNP 高边驱动极性全部反过来。用示波器看集电极波形正常应是 38 kHz 方波被 9 ms 包络切开而不是一条直线。5.2 距离短和误触发电流、载波频率、环境光三条线现象三近距离能收超过 1 米就没反应。原因可能是脉冲电流只有十几毫安、红外管视角太窄且没有对准、载波频率偏到 35 kHz 以下、接收头前面被黑色滤光片或热缩管挡住。解决是先把脉冲电流提到 100150 mA限流电阻按 2.1 节重算再用示波器测 PWM 频率38 kHz 误差控制在 ±1 kHz 内接收头与发射管不要完全同轴贴脸近处直射会让 AGC 饱和稍微偏 1020 度反而更远。距离和电流不是线性关系电流翻倍不一定距离翻倍接收头灵敏度和发射管辐射强度才是瓶颈。现象四没人按键也偶尔解出一帧或者按一次出多帧。原因是环境光里的 100 Hz 工频分量、电源纹波、接收头 AGC 被连续载波压制或者解码阈值太窄把噪声当位。解决是给接收头 VCC 加 100 Ω 100 µF 滤波OUT 加 10 kΩ 上拉和 100 pF 小电容软件上要求连续两帧码字一致才上报帧间隔至少 20 ms发射端不要连续发载波超过 10 ms引导码之后留足空闲否则接收头 AGC 会把增益压下去下一帧灵敏度下降。排查时先把发射头遮住看接收头是否还出脉冲如果还出就是环境光或电源问题不是协议问题。6. 把距离和可靠性再往前推一步三个可验证的调整技巧距离上不去时先别换接收头先把发射脉冲电流和载波频率这两个参数测准。限流电阻从 39 Ω 降到 22 Ω脉冲电流约 160 mA平均电流仍在 50 mA 左右短时间发射没问题但要让电阻功率留一倍余量载波用示波器或逻辑分析仪测实际频率38 kHz 偏到 36 kHz 时接收头带通衰减已经很明显。接收头供电用 100 Ω 100 µF 单独滤波地和单片机地单点连接长线时 OUT 串 100 Ω这些做法比换高价接收头更有效。调整目标先动哪个参数观察点边界距离从 2 m 提到 5 m脉冲电流 100→150 mA接收头低脉冲是否仍干净LED 平均功耗、电阻发热减少误触发连续两帧一致才接收误码帧是否消失响应延迟增加一帧抑制电源干扰VCC 串 100 Ω 100 µF静态高电平纹波压降不低于 4.5 V验证载波频率定时器周期值实测 37.538.5 kHz不要用软件延时验证方法不用很复杂手机摄像头看发射管是否有紫色闪烁只能证明“在发”示波器看接收头 OUT 是否有 9 ms 低脉冲才能证明“发对了”逻辑分析仪抓一帧用 4.3 节脚本解出码字才算“收对了”。我一般先把接收头单独供电、确认静态高和收到载波时拉低再回头调发射端最后才碰解码阈值。红外这玩意儿有点玄学但多数问题都能在电源和地线上找到痕迹先把这两件事做扎实再谈距离和抗干扰。希望帮到你。本文还有配套的精品资源点击获取