简介:这份面向电子初学者的红外发射接收电路文档,围绕HS38(HS0038)接收集成模块展开,帮助读者理解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.1~1.4 V,峰值波长 940 nm 左右,脉冲电流可以做到 100~200 mA,但平均电流受占空比限制。直接把 LED 接在 5 V 和单片机引脚之间,要么电流太小打不远,要么把引脚拉坏。常见做法是用一只小功率 NPN 三极管做开关,单片机只负责输出 38 kHz 方波,LED 回路单独算限流电阻。
2.1 先算电流再选电阻:5 V 供电下的限流公式与三极管驱动
限流电阻按欧姆定律算:R = (VCC - VF - VCE(sat)) / IF_PULSE。VCC 取 5 V,VF 取 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 mA,39 Ω 电阻上的平均功耗约 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 mA,Ib 约 1 mA,5 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.7~5.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 V |
| VCC 滤波电容 | 100 µF | 吸收低频波动 | 漏电流大的电容不选 |
| OUT 上拉电阻 | 10 kΩ | 保证空闲高电平 | 长线可降到 4.7 kΩ |
| OUT 串联电阻 | 100 Ω | 抑制振铃 | 会轻微增加上升时间 |
3.2 接收头外围只加三个元件:RC 滤波、去耦与上拉
接线顺序建议先单独给接收头供电,不接单片机,测静态 OUT 是否接近 VCC。正常空闲高电平应在 4.5 V 以上;如果只有 2~3 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 不支持双边沿,可以用定时器输入捕获的两种极性交替配置。接收头输出边沿不是理想方波,近处强信号时上升沿可能有 10~50 µs 的斜坡,软件消抖不要小于 50 µs,否则会把正常脉宽切碎。接收头内部有延迟,典型 0.5~1.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 以上,抓一帧完整数据。先量第一个低脉冲,应该在 8~10 ms;紧接着的高电平在 4~5 ms;后面连续 32 组“低 560 µs + 高 560/1690 µs”。如果第一个低脉冲只有 500 µs,说明抓到的不是帧头,可能是重复码或噪声;如果低脉冲宽度整体翻倍,检查采样率是不是设成了 500 kHz 而软件按 1 MHz 算。接收头对脉宽有压缩和展宽,典型误差 ±100 µs,所以阈值不要卡在 560 µs 和 1.69 ms 的中间点,而是取 1.0~1.2 ms 作为 0/1 分界。
| 波形段 | 发射端 | 接收头 OUT | 判读阈值 |
|---|---|---|---|
| 引导码低 | 9 ms 载波 | 低 8~10 ms | 8000~10000 µs |
| 引导码高 | 4.5 ms 空闲 | 高 4~5 ms | 4000~5000 µs |
| 位起始低 | 560 µs 载波 | 低 400~800 µs | 300~800 µs |
| 数据 0 高 | 560 µs 空闲 | 高 400~800 µs | 小于 1000 µs |
| 数据 1 高 | 1.69 ms 空闲 | 高 1400~2000 µs | 大于 1000 µs |
4.2 单片机端输入捕获:记录高电平与低电平的计数值
用 1 MHz 计数时钟,1 个计数就是 1 µs,16 位定时器 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。解码函数先检查引导码,再逐位检查低电平是否在 300~800 µ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 判为 1,560 us 判为 0 code |= 1 print(f"解码结果: 0x{code:08X}")逻辑说明:edges里存的是每次跳变后的电平,所以lvl == 0的区间是低电平;low_us[0]和high_us[0]是引导码;后面 32 组按位移拼成 32 位数。参数边界:引导码低取 8000~10000 µs,高取 4000~5000 µs;数据位低取 300~800 µs;0/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.5~5 V,再测发射管两端在发射瞬间是否有 1 V 左右的脉冲压降;如果没有,测三极管基极是否有 0.6~0.7 V 的驱动电压。手机摄像头对着红外 LED 看,发射时能看到微弱紫光,说明 LED 在闪;看不到就查极性和电阻,不要先改解码代码。
现象二:LED 用万用表二极管档能亮,装上电路却不亮。原因是万用表提供的测试电流只有几毫安,而电路里 39 Ω 串联后脉冲电流虽然大,但三极管没有饱和,或者单片机引脚直接驱动导致电压被拉低。解决是把基极电阻从 10 kΩ 降到 1 kΩ,确认三极管发射极接地、集电极接 LED 负极、LED 正极经电阻接 VCC;如果是 PNP 高边驱动,极性全部反过来。用示波器看集电极波形,正常应是 38 kHz 方波被 9 ms 包络切开,而不是一条直线。
5.2 距离短和误触发:电流、载波频率、环境光三条线
现象三:近距离能收,超过 1 米就没反应。原因可能是脉冲电流只有十几毫安、红外管视角太窄且没有对准、载波频率偏到 35 kHz 以下、接收头前面被黑色滤光片或热缩管挡住。解决是先把脉冲电流提到 100~150 mA,限流电阻按 2.1 节重算;再用示波器测 PWM 频率,38 kHz 误差控制在 ±1 kHz 内;接收头与发射管不要完全同轴贴脸,近处直射会让 AGC 饱和,稍微偏 10~20 度反而更远。距离和电流不是线性关系,电流翻倍不一定距离翻倍,接收头灵敏度和发射管辐射强度才是瓶颈。
现象四:没人按键也偶尔解出一帧,或者按一次出多帧。原因是环境光里的 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.5~38.5 kHz | 不要用软件延时 |
验证方法不用很复杂:手机摄像头看发射管是否有紫色闪烁,只能证明“在发”;示波器看接收头 OUT 是否有 9 ms 低脉冲,才能证明“发对了”;逻辑分析仪抓一帧,用 4.3 节脚本解出码字,才算“收对了”。我一般先把接收头单独供电、确认静态高和收到载波时拉低,再回头调发射端,最后才碰解码阈值。红外这玩意儿有点玄学,但多数问题都能在电源和地线上找到痕迹,先把这两件事做扎实,再谈距离和抗干扰。希望帮到你。
本文还有配套的精品资源,点击获取