TSMP96000红外接收器:宽频带信号探测与代码学习实战

TSMP96000红外接收器:宽频带信号探测与代码学习实战 1. 项目概述为什么我们需要一个“代码学习”型红外接收器在嵌入式开发和智能家居改造中红外遥控几乎是绕不开的一环。无论是想用单片机控制家里的老电视、空调还是DIY一个万能遥控器第一步都是让设备能“听懂”红外遥控器在说什么。市面上最常见的方案是使用像VS1838B这类38KHz的通用红外接收头它们内部集成了光电二极管、前置放大器和解调电路输出的是已经“剥离”了38KHz载波的干净逻辑信号。这对于解码NEC、Sony、RC5这些标准协议非常方便直接接上单片机用现成的库就能读出来。但问题来了如果你手头有一个不知名的遥控器或者一个使用非标准频率比如40KHz、56KHz的专业设备这些通用接收头就束手无策了。因为它们内部固定解调在38KHz对于其他频率的信号要么接收不灵敏要么直接收不到。这就是所谓的“协议已知频率未知”或“频率与协议均未知”的场景。此时你需要的是一个能“看见”原始红外信号的“眼睛”而不是一个已经替你“翻译”好的“耳朵”。Adafruit TSMP96000红外接收器突破板就是为了解决这个问题而生。它不是一个解调器而是一个宽频带的红外信号探测器。它的核心是Vishay的TSMP96000传感器能够感应20KHz到60KHz范围内的红外载波信号并将这个原始的、包含载波的调制信号你可以理解为“带着波纹的水面”直接通过SIG引脚输出给你的单片机。这样一来你的单片机就获得了分析原始红外波形的能力可以通过软件算法来测量载波频率、解析脉冲宽度从而实现“代码学习”——即逆向工程任何红外遥控器的协议。这对于开发需要兼容多种未知遥控器的产品或者进行红外信号分析与研究是一个不可或缺的工具。2. 核心硬件解析TSMP96000板卡设计与接口详解拿到这块小巧的红色板卡你会发现它的设计非常“Adafruit”——简洁、实用且对开发者友好。我们把它拆开来看理解每一个部分的设计意图。2.1 核心传感器TSMP96000的特性与工作原理板卡中央那个黑色的、像一个小型晶体管的就是TSMP96000。它与普通接收头的最大区别在于输出信号的性质。普通接收头输出的是解调后的数字信号高低电平而TSMP96000输出的是经过内部放大器放大后的原始调制信号。这个信号是一个以GND为基准、振幅在0.3V到Vcc之间变化的模拟波形其包络形状与红外发射管发出的光信号一致内部包含了完整的载波信息。它的工作电压范围是2.5V到5.5V这意味着它可以与3.3V和5V逻辑的系统完美兼容。其接收角度大约为±45度在典型条件下比如室内光照接收距离可以达到十几米甚至更远具体取决于发射器的功率和环境红外噪声。板卡上已经集成了必要的电源去耦电容确保传感器工作稳定。2.2 电源与信号接口灵活连接的奥秘板卡提供了两种连接方式体现了模块化设计的思路。1. STEMMA JST PH接口3-Pin这是Adafruit推广的快速连接接口使用2mm间距的3芯JST PH端子。其引脚定义如下红色线 (V): 电源输入接3-5V。黑色线 (GND): 电源地。白色线 (Sig): 原始调制信号输出。使用配套的STEMMA JST PH电缆你可以真正做到“免焊接”连接特别适合快速原型验证和教学场景。插上就能用大大降低了入门门槛。2. 0.1英寸标准排针对于喜欢使用面包板或需要直接焊接的用户板卡边缘也预留了一组标准的0.1英寸间距排针其功能与JST接口一一对应。从左到右当板卡正面朝上JST接口在左时通常是GND、VIN、SIG。在焊接前务必用万用表确认一下丝印标注这是避免接反烧毁芯片的好习惯。2.3 状态指示灯与配置跳线调试好帮手板卡上有两个LED和两个对应的配置跳线这是非常实用的调试设计。绿色电源LED (ON): 接上电源即常亮直观指示板卡已上电。如果它不亮首先检查电源和接地。红色信号LED (Sig): 这是核心指示灯。当TSMP96000检测到有效的红外信号时这个LED会闪烁。注意它的闪烁频率与你遥控器按键的编码有关可能很快也可能是一串复杂的闪烁但它能第一时间告诉你“板卡收到光了”。两个LED旁边各有一个“跳线”Jumper。这实际上是一块PCB上的细铜箔。它的作用是串联在LED的供电回路中。如果你想禁用某个LED比如在低功耗应用或避免LED光干扰其他光敏元件只需要用美工刀或烙铁轻轻划断对应的跳线铜箔即可。这是一个不可逆的硬件修改但非常有效。如果你想重新启用则需要用一点焊锡将划断的跳线重新桥接起来。注意信号LED的闪烁是跟随原始调制信号的在某些编码下如快速的连续脉冲LED可能会呈现微亮或高频闪烁状态人眼可能无法清晰分辨。此时依靠示波器或代码分析来判断信号更为可靠。3. 软件核心Arduino频率测量代码深度剖析Adafruit提供的示例代码其核心目的不是解码具体协议而是测量并验证输入红外信号的载波频率。这是一个“代码学习”系统中最基础、最关键的第一步。只有知道了载波频率你才能用正确的频率去发射信号或者配置后续解码算法的采样参数。我们来逐部分解析这段代码。3.1 全局变量与中断服务程序#define IROUT 9 #define IRIN 2 volatile unsigned long pulseCount 0;IROUT 定义了一个输出引脚示例中接9号脚用于生成特定频率的方波模拟红外发射以测试系统。在实际的纯接收应用中你可能不需要这个引脚。IRIN 这是关键它定义了连接TSMP96000 SIG输出信号的输入引脚示例中接2号脚。必须选择支持外部中断的引脚。在Uno上引脚2和3支持外部中断。pulseCount 这是一个volatile修饰的变量用于在中断服务程序(ISR)中累加计数。volatile告诉编译器这个变量可能被异步修改比如被中断函数阻止编译器对其进行可能不安全的优化。中断服务程序非常简单void countPulse() { pulseCount; }这个函数被配置为在IRIN引脚的电平发生上升沿变化时触发。TSMP96000输出的原始信号是包含载波的模拟波形当它被单片机的数字输入引脚读取时会因施密特触发器作用被整形成数字方波。每一个上升沿就对应载波的一个周期或半个周期取决于占空比的开始。因此统计固定时间内的上升沿数量就能计算出频率。3.2 频率测试函数testFreq这是代码的逻辑核心它完成一次频率测量和判断。bool testFreq(uint32_t freq) { uint32_t temp 0; Serial.print(freq); Serial.println( Hz); setFrequency(freq); // 1. 设置发射频率 pulseCount 0; // 2. 清零计数器 delay(100); // 3. 等待100毫秒的采样窗口 temp pulseCount; // 4. 读取计数值 Serial.print(\tCounted ); Serial.print(temp); Serial.println( pulses); // 5. 判断计数值是否在预期频率的10%误差范围内 if ((temp (freq / 10) (freq / 100)) || (temp (freq / 10) - (freq / 100))) { return false; } return true; }工作流程与计算原理setFrequency(freq) 此函数控制IROUT引脚产生一个指定频率(freq)的方波模拟一个红外发射源。例如调用testFreq(38000)就会产生38KHz的方波。清零pulseCount为新的计数做准备。delay(100) 这是一个100毫秒的采样窗口。在这100ms内中断函数会持续对IRIN引脚上的上升沿进行计数。读取pulseCount到临时变量temp。关键判断逻辑计算temp是否在理论值的合理误差范围内。理论脉冲数 频率 × 采样时间 freq * 0.1(100ms 0.1秒)。代码中简化了计算用freq / 10来代表freq * 0.1。误差范围设定为理论值的 ±10% (freq / 100是理论值的1%这里上下各加/减了10个1%即±10%)。所以判断条件是temp是否在(freq/10 - freq/100)和(freq/10 freq/100)之间。为什么这样设计这个testFreq函数实际上是一个自检循环。它用IROUT引脚发射一个已知频率的信号例如38KHz然后用TSMP96000接收再通过IRIN引脚测量这个频率。如果测量值在预期范围内说明整个“发射-接收-测量”链路工作正常。在真正的“代码学习”场景中你需要移除发射部分只保留接收和测量逻辑去测量未知遥控器的频率。3.3 定时器PWM生成函数setFrequency这个函数负责在AVR如ATmega328P或RP2040如Raspberry Pi Pico架构上生成指定频率的方波。这是嵌入式编程中硬件定时器的经典应用。对于AVRArduino Uno/Nano等void setFrequency(unsigned long frequency) { #if defined(__AVR__) unsigned long ocrValue; byte csBits 0; noInterrupts(); // 关闭全局中断安全配置定时器 TCCR1A 0; TCCR1B 0; TCNT1 0; // 复位定时器1控制寄存器和计数器 TCCR1B | (1 WGM12); // 设置为CTC模式比较匹配时清零计数器 // 根据目标频率选择分频器和计算比较匹配值OCR1A if (frequency 4000) { csBits (1 CS10); // 无分频 (时钟16MHz) ocrValue 16000000 / (2 * frequency) - 1; } else if (frequency 500) { csBits (1 CS11); // 8分频 (时钟2MHz) ocrValue 2000000 / (2 * frequency) - 1; } else if (frequency 60) { csBits (1 CS11) | (1 CS10); // 64分频 (时钟250KHz) ocrValue 250000 / (2 * frequency) - 1; } else { csBits (1 CS12); // 256分频 (时钟62.5KHz) ocrValue 62500 / (2 * frequency) - 1; } if (ocrValue 65535) ocrValue 65535; if (ocrValue 1) ocrValue 1; OCR1A ocrValue; // 设置比较匹配寄存器 TCCR1B | csBits; // 应用分频器 TCCR1A | (1 COM1A0); // 设置OC1A引脚对应Arduino 9号脚在比较匹配时翻转 interrupts(); // 重新开启全局中断 #endif }计算原理以38KHz无分频为例AVR定时器在CTC模式下当计数器TCNT1的值达到比较匹配寄存器OCR1A的值时计数器清零并可以触发输出引脚翻转。引脚翻转的频率公式为f_out f_cpu / (2 * N * (1 OCR1A))其中f_cpu是CPU主频16MHzN是分频系数。为了得到目标频率f_out需要反推OCR1AOCR1A (f_cpu / (2 * N * f_out)) - 1。代码中16000000 / (2 * frequency) - 1正是这个公式在N1无分频时的应用。选择不同的分频系数是为了让OCR1A的值落在合理的范围内1~65535以提高精度和兼容不同频率。对于RP2040RP2040的PWM外设非常灵活。代码通过Arduino-Pico核心的底层硬件API进行配置直接设置PWM的周期wrap和比较电平chan_level来生成指定频率、占空比为50%的方波。其原理与AVR类似但寄存器操作被封装成了更易用的函数。3.4 主循环与串口输出loop()函数非常简单就是循环测试30KHz, 40KHz, 50KHz, 60KHz这四个频率点。打开串口监视器波特率115200当你用一块板子的IROUT接LED对着另一块板子的TSMP96000或者用能发射这些频率的遥控器对准接收器时你就能看到类似以下的输出---------------------- 30000 Hz Counted 3002 pulses 40000 Hz Counted 3998 pulses 50000 Hz Counted 5005 pulses 60000 Hz Counted 5993 pulses这证明系统工作正常能够准确测量载波频率。4. 从频率测量到代码学习实战应用拓展掌握了频率测量只是“代码学习”的第一步。真正的目标是解析出遥控器按键所代表的编码序列。这通常是一个由“高电平持续时间”和“低电平持续时间”组成的脉冲序列不同的协议如NEC、Sony定义了不同的引导码、数据位表示法和结束码。4.1 构建一个简单的脉冲宽度分析仪要解码首先需要记录下完整的原始波形。我们可以修改中断服务程序不再仅仅计数而是记录每个上升沿和下降沿之间的时间间隔脉冲宽度。#define MAX_PULSES 100 volatile unsigned long pulseTimings[MAX_PULSES]; volatile unsigned int pulseCount 0; volatile unsigned long lastTime 0; void recordPulse() { unsigned long currentTime micros(); // 获取当前时间微秒 if (pulseCount MAX_PULSES) { pulseTimings[pulseCount] currentTime - lastTime; // 存储距离上一个边沿的时间间隔 pulseCount; } lastTime currentTime; } void setup() { Serial.begin(115200); attachInterrupt(digitalPinToInterrupt(IRIN), recordPulse, CHANGE); // 注意触发模式改为CHANGE变化 }在loop()中当检测到pulseCount大于一定数量表示一次按键发送完成后将pulseTimings数组的数据打印到串口。你会得到一长串微秒值这些就是原始的红外编码时间序列。4.2 协议分析与解码尝试拿到时间序列后你需要人工或通过算法进行分析。以最常见的NEC协议为例引导码 一个9ms的高脉冲后跟一个4.5ms的低脉冲。数据位 每个位由560us的载波脉冲开始。逻辑“0” 560us高电平 560us低电平。逻辑“1” 560us高电平 1690us低电平。结束码 一个560us的脉冲。你可以在串口打印的数据中寻找符合这个模式的时间段。例如如果你看到一个约9000us的高电平紧接着一个约4500us的低电平那么后面很可能就是NEC协议的32位数据地址码命令码反码。实操心得 不同协议间的差异可能很微妙。Sony的SIRC协议使用更短的脉冲而RC5协议使用双相编码。在分析时建议先用一个已知协议的遥控器比如电视遥控器进行录制将得到的时间序列与协议文档对照验证你的采集和分析流程是否正确。这是理解“代码学习”过程的最佳方式。4.3 集成现有库进行高级解码手动分析低效且容易出错。幸运的是Arduino社区有强大的库支持。正如Adafruit指南中提到的Chris Young的IRLib2库是一个功能极其强大的红外编解码库。它不仅能解码数十种标准协议其核心的“原始码Raw”捕获模式正是为TSMP96000这类接收器准备的。你可以使用IRLib2的IRrecvPCI或IRrecvLoop示例并稍作修改将接收对象配置为“原始模式”它就会自动捕获IRIN引脚上的边沿变化并将其存储为一个原始时间数组。然后库内置的协议分析器会尝试匹配已知协议。如果匹配失败它也会完整地输出原始时间数组供你进一步分析或学习新的协议。接线与代码调整思路安装IRLib2库。在代码中定义一个IRrecvPCI对象并指定中断引脚接TSMP96000 SIG的引脚。在setup()中启用该对象并可能设置enableIRIn()为原始模式。在loop()中调用decode()函数。如果解码成功可以通过results.value获取按键值如果失败可以访问results.rawbuf和results.rawlen来获取原始脉冲数据。5. 常见问题、调试技巧与进阶思考在实际使用TSMP96000进行开发时你几乎一定会遇到一些挑战。以下是我从多次项目中总结出的经验。5.1 信号捕获不稳定或完全无信号这是最常见的问题通常不是板卡坏了而是环境或配置问题。检查供电与接地 确保VIN和GND连接牢固电压稳定3.3V或5V。电源噪声可能导致接收器工作异常。尝试在VIN和GND之间并联一个10uF~100uF的电解电容进行滤波。确认信号线连接 SIG线是否接到了单片机支持外部中断的引脚在Arduino Uno上优先使用引脚2或3。环境光干扰 强烈的日光、白炽灯或某些LED灯会发出丰富的红外线淹没微弱的遥控信号。尝试在较暗的环境中测试或者用纸筒等物体稍微遮挡接收器使其只对准遥控器方向。遥控器问题 遥控器电池是否电量充足尝试用手机摄像头大多数手机摄像头对红外光敏感查看遥控器发射管按下按键时应该能看到紫色光点。确保遥控器对准了接收器距离在1米以内开始测试。代码配置 中断服务函数是否过于复杂在ISR中应只做最简单的操作如记录时间、递增计数避免使用delay()、Serial.print()等耗时函数。确保中断触发边沿RISING或CHANGE设置正确。5.2 测量频率不准或脉冲计数漂移采样时间过短 示例代码中使用100ms采样。对于低频信号如20KHz100ms内只有2000个脉冲±1个脉冲的误差就是0.05%。但对于高频或需要更高精度时可以延长采样时间到500ms或1秒然后取平均。中断丢失 如果红外信号频率很高如60KHz意味着每16.7微秒就有一个脉冲。如果中断服务程序执行时间过长或者全局中断被关闭noInterrupts()时间太久可能会丢失脉冲。优化ISR代码是关键。信号波形不理想 TSMP96000输出的是模拟波形在电压阈值附近可能产生抖动导致一个物理脉冲产生多个边沿触发。可以在信号线SIG和地GND之间连接一个约10kΩ~100kΩ的下拉电阻或者在代码中引入简单的“去抖动”逻辑比如在中断中记录时间后短暂禁用中断几微秒再开启。5.3 从“学习”到“发射”构建完整红外中继系统“代码学习”的终极应用往往是“复制重放”。当你用TSMP96000分析并存储了一个遥控信号的所有参数载波频率、脉冲序列后你就可以用另一个红外发射管如TSAL6100将这些信号原样发射出去。系统构成学习端 TSMP96000 单片机A负责捕获和存储未知信号。发射端 红外发射管 单片机B或同一个单片机负责根据存储的参数生成PWM载波并调制上存储的脉冲序列。关键技术点载波生成 需要使用硬件定时器如setFrequency函数所做来产生精确的载波方波如38KHz。信号调制 用存储的脉冲序列高低电平时间去控制这个载波的输出与否。在“高电平”时段开启载波输出在“低电平”时段关闭载波输出。这可以通过定时器比较匹配中断或PWM的使能/禁用来实现。驱动能力 单片机IO引脚驱动电流有限通常20mA直接驱动红外发射管可能导致亮度不足、距离短。需要增加一个三极管如NPN型的8050或MOSFET作为开关来驱动为发射管提供100mA以上的电流从而获得更远的发射距离。5.4 项目构思与扩展方向掌握了TSMP96000这个工具你可以尝试许多有趣的项目万能红外学习遥控器 制作一个可以学习并存储多个遥控器按键的装置配合OLED屏幕和按钮实现一个物理万能遥控器。红外信号分析仪 结合SD卡模块将捕获到的原始红外脉冲序列以CSV格式保存下来导入电脑用Python或Excel进行更深入的波形分析和协议逆向。智能家居红外网关 将TSMP96000与ESP8266/ESP32这类Wi-Fi单片机结合搭建一个可以通过手机App或网页学习并控制传统红外家电空调、电视、风扇的网关让老旧设备融入智能家居生态。红外通信实验 利用两套TSMP96000和发射管实现简单的单片机之间的红外无线数据传输学习基本的串行通信和调制解调原理。红外通信的世界远比38KHz要广阔。Adafruit TSMP96000这块板卡就像一把钥匙为你打开了分析宽频段红外信号的大门。从简单的频率测量开始逐步深入到脉冲捕获、协议分析最终实现完整的信号学习与重放这个过程本身就是对嵌入式系统中断、定时器、信号处理等核心概念的绝佳实践。