1. 项目概述从“接收”到“理解”的跨越搞嵌入式开发尤其是和STM32这类单片机打交道玩转各种通信协议是基本功。红外遥控这个几乎家家户户都有的“古董”级无线技术至今在智能家居、小家电控制等领域依然活跃。其背后的NEC协议以其简单、可靠、成本低廉的特点成为了事实上的标准。很多新手拿到一个红外遥控器和接收头接上STM32发现能收到一堆高低电平变化但怎么把这堆“乱码”翻译成有意义的按键值就成了第一个拦路虎。这个项目的核心就是让STM32这颗“大脑”学会理解红外遥控器发出的“语言”——NEC协议。这不仅仅是简单的电平检测而是一个完整的信号解码过程。你需要从硬件连接开始理解红外载波、理解NEC协议独特的编码规则引导码、用户码、数据码及其反码然后利用STM32的定时器精准捕获脉冲宽度最后通过状态机逻辑将一串时间序列还原成8位或16位的按键命令。整个过程是对你单片机GPIO输入捕获、定时器应用、中断处理以及状态机编程思维的一次综合训练。我最初接触时也卡在如何区分逻辑“0”和“1”的脉冲宽度上用示波器抓了波形才恍然大悟。网上很多例程只给代码不讲为什么这个时间阈值是1.68ms而不是1.65ms导致稍微换个遥控器或者环境光干扰大点就解码失败。这篇文章我会把NEC协议从物理层到数据层掰开揉碎结合STM32的HAL库和寄存器两种方式把解码的每一步原理、踩过的坑和调优技巧都讲清楚。无论你是刚学完STM32基础想找个综合小项目练手还是工作中需要快速实现红外遥控功能这篇都能给你一套可直接复用、且知其所以然的方案。2. NEC协议深度解析不只是0和1在动手写代码之前必须吃透协议本身。NEC协议的精妙之处在于它的自校验和抗干扰设计理解这些是写出稳定解码程序的前提。2.1 物理层与信号构成红外通信不是直接发送高低电平。为了抗干扰主要是防止环境光如日光灯的影响它采用了一种叫“载波调制”的技术。以常见的38kHz载波为例遥控器内部的晶振会产生38kHz的方波这个方波信号再去驱动红外发射管。当需要发送“有效信号”时载波方波被输出当需要发送“空闲”或“无效”时载波关闭。我们使用的红外接收头如HS0038B、VS1838B是一个集成化的模块它内部已经完成了滤波、放大、解调的过程。它输出给单片机的就是解调后的数字信号有载波时输出低电平无载波时输出高电平。这里有个关键点容易搞反接收头输出是反相的。即发射管发光有38kHz载波时接收头输出低电平不发光时输出高电平。所以我们在示波器或逻辑分析仪上看到的是一个低电平代表“有信号”高电平代表“无信号”的波形。2.2 数据链路层帧结构详解一帧完整的NEC协议数据由以下几部分组成引导码Start Code这是一个9ms的低电平脉冲紧接着是一个4.5ms的高电平。这个独特的“长低长高”组合就像电报里的“开始监听”指令用于唤醒接收端并帮助其校准后续脉冲的判断基准。所有符合NEC协议的设备都以此开头。用户码Custom Code通常为8位或16位用于区分不同厂商或不同设备类型的遥控器。比如你电视的遥控器不会控制空调就是靠这个码区分的。标准NEC是8位用户码但很多厂商如索尼扩展为16位。数据码Data Code8位代表具体的按键值。这是解码后我们最关心的部分。数据反码Inverted Data Code数据码按位取反后的8位。这是NEC协议简单的校验机制。接收端在解码时会同时计算数据码的反码并与接收到的反码进行对比。如果不一致则认为本次接收有误丢弃该帧数据。这能有效避免因单个脉冲干扰导致的误触发。2.3 逻辑定义脉冲宽度编码NEC协议采用脉冲位置调制PPM的一种变体逻辑“0”和“1”由两个脉冲之间的时间间隔来区分。逻辑‘0’一个560µs的低电平脉冲后跟一个560µs的高电平。总周期为1.125ms。逻辑‘1’一个560µs的低电平脉冲后跟一个1.69ms约560µs * 3的高电平。总周期为2.25ms。这里就是解码的核心算法所在我们不是去测量低电平的宽度它们都是560µs而是去测量高电平的持续时间。如果高电平持续约560µs则判定为逻辑‘0’如果持续约1.69ms则判定为逻辑‘1’。注意协议文档上的时间是理论值。实际中由于晶振误差、电路延迟等原因脉冲宽度会有偏差。例如引导码的4.5ms高电平实测可能在4.2ms到4.8ms之间。因此在代码中我们必须使用一个合理的时间容差范围比如±20%进行判断而不是精确匹配某个值。这是解码稳定性的关键。2.4 连发码机制长按遥控器按键时遥控器不会重复发送完整的数据帧那样效率低且耗电。NEC协议定义了“连发码Repeat Code”。在首次发送完整帧后如果按键保持按下遥控器会每隔约110ms发送一个特殊的连发码。 连发码由9ms低电平和2.25ms高电平再加一个560µs的低电平作为下一次连发的起始构成。解码程序需要识别这种特殊波形并重复上一次有效的按键值以实现长按功能。3. 硬件设计与连接搭建可靠的信号通道理论清楚了我们来看看如何用STM32搭建一个能“听到”红外信号的硬件环境。这部分看似简单但连接不当会直接导致解码失败。3.1 核心器件选型与原理红外接收头推荐使用VS1838B或HS0038B。它们都是通用的38kHz一体化接收头有三只引脚VCC3.3V/5V、GND、OUT信号输出。关键参数是载波频率必须匹配市面上绝大多数遥控器都是38kHz所以选这个频率的接收头准没错。STM32单片机任何带有定时器输入捕获功能的STM32型号都可以比如STM32F103C8T6蓝色小板、STM32F407等。我们主要利用其GPIO的外部中断功能和定时器的捕获/计数功能。遥控器任意一个使用NEC协议的家电遥控器即可比如旧电视、机顶盒、风扇的遥控器。如果想标准化测试可以购买通用的NEC编码红外遥控器模块上面通常标有按键值。3.2 电路连接方案连接非常简单接收头VCC- STM32的3.3V或5V电源引脚注意接收头工作电压范围通常3.3V-5V都行5V时接收距离可能更远。接收头GND- STM32的GND。接收头OUT信号引脚- STM32的某个GPIO引脚例如PA0。这里有一个至关重要的细节上拉电阻。红外接收头内部输出电路通常是集电极开路Open Collector或漏极开路Open Drain结构。这意味着在无信号时它的输出引脚是悬空高阻态的而不是稳定的高电平。如果直接连接到STM32的GPIO在空闲时引脚电平会不稳定容易受到干扰产生误触发。 因此必须在接收头的OUT引脚和VCC之间连接一个上拉电阻阻值通常在4.7kΩ到10kΩ之间。这个电阻的作用是当接收头内部三极管关闭无信号输出高阻态时通过上拉电阻将引脚电平拉到VCC高电平当接收头内部三极管导通有信号输出低电平时引脚被强行拉低。这样就能得到干净、稳定的数字信号。很多开发板上的红外接收模块已经集成了这个上拉电阻。如果你是自己焊接千万别忘了它。我早期调试时就因为省了这个电阻解码时灵时不灵排查了半天硬件。3.3 利用工具“看见”信号示波器与逻辑分析仪在编写和调试解码程序时有一个可视化工具事半功倍。如果你有示波器可以将探头接到接收头的OUT引脚按下遥控器观察波形。你应该能清晰地看到9ms低、4.5ms高的引导码以及后续一串脉冲。 对于嵌入式开发者一个几十块钱的USB逻辑分析仪配合Sigrok/PulseView软件是更经济高效的选择。它能长时间记录波形并自带协议分析器。在PulseView中你可以直接添加“红外 NEC”解码器它能够自动将波形翻译成用户码和数据码是你验证硬件连接和后续解码算法是否正确的最直观工具。先确保硬件层面能收到正确的波形再进入软件调试阶段。4. 软件解码策略与实现状态机是灵魂硬件信号通路畅通后剩下的就是教STM32如何解读这个波形。核心思路有两种外部中断定时器计数和定时器输入捕获模式。前者理解起来更直观适合入门后者更精准资源利用率高。我将详细讲解第一种并提及其优劣和第二种的实现思路。4.1 方案一外部中断 基本定时器通用方法这是最常用、最易于理解的方法。我们利用GPIO的外部中断来感知信号边沿下降沿或上升沿在中断服务函数中读取一个自由运行的定时器的计数值通过计算两次中断之间的时间间隔来判断脉冲宽度。4.1.1 外设初始化配置GPIO与外部中断配置将连接红外接收头的GPIO如PA0配置为浮空输入或上拉输入如果硬件已上拉浮空即可。开启该GPIO对应的外部中断线EXTI Line。配置中断触发方式。这里有个选择是触发下降沿还是双边沿仅下降沿触发NEC协议中无论是引导码还是数据位都是以560µs的低电平开始。我们可以在每次下降沿信号由高变低代表脉冲开始时进入中断记录时间。然后我们需要在主循环或定时器中断中去查询高电平持续了多久这增加了复杂性。双边沿触发推荐配置为上升沿和下降沿都触发。这样每次电平变化都会进入中断。在中断里我们根据当前是上升沿还是下降沿结合上次边沿的时间就能直接计算出前一个电平的持续时间。逻辑更清晰解码过程可以完全在中断服务函数中完成。我们采用这种方式。基本定时器配置选择一个基本定时器如TIM2、TIM3配置为向上计数模式时钟源为内部时钟。关键点是设置定时器的预分频器PSC和自动重载值ARR使得定时器计数一次的时间计时精度适合我们测量脉冲宽度。计算示例假设系统主频APB1为72MHz定时器挂在APB1上。我们希望计时精度为1µs即计数器每加1代表1µs。定时器时钟 72MHz / (PSC 1)。令 PSC 71则定时器时钟 72MHz / 72 1MHz计数器每1/1MHz 1µs加1。设置ARR为最大值如0xFFFF让定时器自由溢出。我们只关心两次中断之间的计数值差溢出不影响差值计算因为计数值是32位变量存储的。启动定时器。4.1.2 解码状态机设计这是解码程序的核心逻辑。我们不能在中断里简单地判断时间然后存数据因为一帧数据有多个边沿。我们需要一个状态机来跟踪当前解码到哪一步了。我们可以定义以下几个状态typedef enum { IR_IDLE, // 空闲状态等待引导码 IR_LEADER_CODE, // 已收到引导码下降沿等待引导码高电平结束 IR_REPEAT_CODE, // 可能处于连发码判断中 IR_RECEIVING_DATA // 正在接收用户码和数据码 } IR_State_t;在双边沿外部中断服务函数中我们大致这样处理获取当前定时器计数器的值CurrentTime。计算与上一次中断的时间差Duration CurrentTime - LastTime注意处理定时器溢出的情况。更新LastTime CurrentTime。判断当前引脚电平是上升沿中断还是下降沿中断。根据当前状态和检测到的边沿以及持续时间Duration进行状态转移和数据记录。状态转移逻辑详解初始状态IR_IDLE当检测到一个下降沿且下降沿之前的高电平持续时间Duration非常长比如 5ms这是为了过滤掉干扰和连发码后的低电平我们暂时认为这可能是一个引导码的开始但先不确认。状态切换到IR_LEADER_CODE。引导码判断状态IR_LEADER_CODE在这个状态下我们等待一个上升沿。当上升沿到来时测量这个低电平的持续时间Duration。如果Duration在9ms左右如8ms-10ms那么确认这是引导码的低电平部分。状态可以切换到IR_RECEIVING_DATA并重置数据索引。但更严谨的做法是继续等待引导码的高电平结束下一个下降沿判断高电平是否为4.5ms左右这样能进一步确认。接收数据状态IR_RECEIVING_DATA在此状态下我们只关心上升沿因为数据位的高电平宽度决定了是0还是1。每次上升沿到来计算前一个低电平的持续时间应该是560µs左右用于同步也可以做校验。然后根据这个上升沿之前的高电平持续时间即本次上升沿与上一个下降沿之间的时间来判断数据位如果高电平持续时间 ~560µs则为逻辑‘0’。如果高电平持续时间 ~1.69ms则为逻辑‘1’。将判断出的位存入缓冲区。接收完所有位如32位16位用户码8位数据码8位数据反码后进行反码校验。校验通过则一帧数据解码完成保存到全局变量并置位一个“数据就绪”标志位。状态回归IR_IDLE。连发码处理如果在IR_IDLE状态检测到下降沿且下降沿前的高电平持续时间约为2.25ms则可能是一个连发码。进入IR_REPEAT_CODE状态等待后续的560µs低电平结束下一个上升沿确认后即可触发“重复按键”事件而无需重新解析全部32位数据。4.1.3 代码实现要点与避坑指南// 示例变量定义 volatile uint32_t ir_last_time 0; volatile IR_State_t ir_state IR_IDLE; volatile uint8_t ir_data_buf[4]; // 存储4字节数据 volatile uint8_t ir_bit_idx 0; volatile uint8_t ir_data_ready 0; volatile uint32_t ir_key_value 0; // 外部中断服务函数 (以PA0为例双边沿触发) void EXTI0_IRQHandler(void) { if(__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) ! RESET) { uint32_t current_time __HAL_TIM_GET_COUNTER(htim2); // 获取定时器当前值 uint32_t duration 0; // 处理计数器溢出简易方法使用32位变量记录时间差值计算自动处理 static uint32_t last_time_static 0; duration (current_time last_time_static) ? (current_time - last_time_static) : (0xFFFF - last_time_static current_time 1); last_time_static current_time; uint8_t pin_state HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0); // 状态机处理 switch(ir_state) { case IR_IDLE: if(pin_state GPIO_PIN_RESET) { // 下降沿 // 下降沿前是高电平检查高电平持续时间是否很长5ms可能是新引导码开始 if(duration 5000) { // 单位是微秒 ir_state IR_LEADER_CODE; ir_bit_idx 0; memset((void*)ir_data_buf, 0, 4); } // 如果高电平持续时间 ~2.25ms可能是连发码 else if(duration 2000 duration 2500) { ir_state IR_REPEAT_CODE; } } break; case IR_LEADER_CODE: if(pin_state GPIO_PIN_SET) { // 上升沿引导码低电平结束 if(duration 8500 duration 9500) { // 判断9ms低电平 // 引导码低电平OK可以开始准备接收数据 // 也可以再等下一个下降沿判断4.5ms高电平这里简化处理 ir_state IR_RECEIVING_DATA; } else { ir_state IR_IDLE; // 不是有效的引导码复位 } } break; case IR_RECEIVING_DATA: // 在接收数据状态我们只关心上升沿用来判断前一个高电平宽度 if(pin_state GPIO_PIN_SET) { // 首先前一个低电平应该是560us左右这里可以做个粗略校验 // if(duration 400 || duration 700) { ir_state IR_IDLE; break; } // 可选校验 // 重点判断本次上升沿之前的高电平宽度即本次duration // 注意这里需要记录上一个下降沿的时间来计算高电平宽度。 // 更完整的实现需要记录上一次边沿的类型和时间。为了清晰这里用简化逻辑说明原理。 // 实际代码中需要用一个变量记录上一次是上升沿还是下降沿以及时间。 // 假设我们通过另一个变量计算出了高电平宽度 high_duration uint32_t high_duration get_high_level_duration(); // 伪函数需自己实现逻辑 if(high_duration 400 high_duration 800) { // 逻辑‘0’ // 将0存入ir_data_buf的对应位 ir_data_buf[ir_bit_idx / 8] ~(1 (ir_bit_idx % 8)); ir_bit_idx; } else if(high_duration 1400 high_duration 1900) { // 逻辑‘1’ // 将1存入ir_data_buf的对应位 ir_data_buf[ir_bit_idx / 8] | (1 (ir_bit_idx % 8)); ir_bit_idx; } else { // 宽度异常复位状态机 ir_state IR_IDLE; break; } // 检查是否接收完32位 if(ir_bit_idx 32) { // 进行反码校验 if(ir_data_buf[2] (uint8_t)(~ir_data_buf[3])) { // 数据码与数据反码校验 ir_key_value (ir_data_buf[2] 8) | ir_data_buf[3]; // 合并数据码和反码实际通常只取数据码 // 更常见的是用户码在buf[0]和buf[1]数据码在buf[2] ir_key_value (ir_data_buf[0] 8) | ir_data_buf[1]; // 16位用户码 ir_key_value (ir_key_value 8) | ir_data_buf[2]; // 加上8位数据码 ir_data_ready 1; // 置位标志位 } ir_state IR_IDLE; } } // 如果是下降沿在接收数据状态可以记录时间用于计算下一个高电平宽度 break; case IR_REPEAT_CODE: // 处理连发码确认 if(pin_state GPIO_PIN_SET duration 500 duration 600) { // 确认是连发码后的560us低电平结束 // 触发重复按键事件例如重复上一次有效的ir_key_value ir_data_ready 2; // 用不同的标志表示连发 } ir_state IR_IDLE; break; default: ir_state IR_IDLE; break; } __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); } }避坑指南中断服务函数ISR要快进快出不要在ISR里做复杂计算、打印或长时间操作。只做最简单的状态判断、时间记录和标志位设置。解码完成后的数据处理如根据按键值控制LED应放在主循环中检查ir_data_ready标志位来进行。时间容差是关键协议时间是理论值。务必设置合理的容差范围如±20%。例如判断1.69ms的逻辑‘1’范围可以设定在1.35ms到2.0ms之间。容差太小容易误判太大容易串码。需要根据实际遥控器和接收头微调。变量共享与volatile在中断和主循环之间共享的变量如ir_data_ready,ir_key_value必须用volatile关键字修饰防止编译器优化导致数据不一致。处理定时器溢出如果定时器计数值可能在一个脉冲周期内溢出对于1µs精度65535µs即65ms溢出而NEC一帧最长也就几十ms有可能溢出必须在计算时间差时考虑溢出。上面的示例代码提供了一种简易的溢出处理方法。消抖与抗干扰红外信号易受日光灯、节能灯等光源的干扰可能会产生毛刺。可以在GPIO初始化时配置一个小的硬件滤波如果MCU支持或者在软件上对于持续时间极短如200µs的脉冲边沿予以忽略。4.2 方案二定时器输入捕获模式更精准的方案这种方法更专业资源占用更少。它将所有工作交给一个定时器完成利用定时器的输入捕获功能直接测量脉冲宽度。配置将连接红外接收头的GPIO引脚配置为对应定时器的输入捕获通道如TIM2_CH1。原理设置定时器在输入信号的两个边沿都触发捕获。当边沿发生时硬件会自动将当前计数器的值锁存到捕获/比较寄存器CCR中并产生中断。优势时间测量由硬件完成精度极高不占用CPU在中断中计算时间差。CPU只在捕获事件中断时读取CCR值即可中断频率减半只在边沿触发而不是每次边沿都需计算。实现在捕获中断中读取CCR值计算与上一次捕获值的差值即为脉冲宽度。后续的状态机逻辑与方案一类似。这种方式代码更简洁可靠性更高是产品级项目的首选。5. 调试技巧与问题排查实录即使按照上述步骤操作第一次解码失败也是常态。以下是几个常见的“翻车”现场和排查思路。5.1 常见问题速查表现象可能原因排查方法完全无反应按键后无任何中断触发1. 硬件连接错误VCC/GND接反或未接。2. 接收头损坏。3. GPIO模式或外部中断未正确配置。4. 未加上拉电阻引脚电平不稳定。1. 用万用表检查电压。2. 更换接收头。3. 用简单LED闪烁程序测试GPIO输出功能是否正常再检查中断配置。4. 示波器或逻辑分析仪观察接收头OUT引脚波形看按键时是否有信号变化。能进入中断但解码出的数据全是0或全是1或乱码1. 时间判断阈值设置不合理容差太小或太大。2. 定时器时钟配置错误计时单位不准确。3. 状态机逻辑有bug状态切换条件错误。4. 中断处理中变量被意外修改。1.用逻辑分析仪抓取波形实测引导码、逻辑0、逻辑1的实际宽度据此调整代码中的阈值。2. 检查定时器PSC和ARR配置计算实际计时精度。3. 在中断中添加调试变量通过串口打印状态、时间差等信息跟踪状态机运行流程。4. 检查volatile关键字和临界区保护。同一个按键每次解码值不同1. 干扰严重如强光直射接收头。2. 电源噪声大。3. 中断服务函数执行时间过长丢失边沿。1. 避免强光环境或给接收头加遮光罩。2. 在VCC和GND之间并联一个10-100µF的电解电容和一个0.1µF的瓷片电容进行滤波。3. 优化ISR代码确保其执行时间远小于最短脉冲560µs。长按无效无法识别连发码1. 未实现连发码识别逻辑。2. 连发码的时间阈值判断错误。1. 在状态机中添加对连发码9ms低2.25ms高560µs低的识别分支。2. 用逻辑分析仪抓取长按时的波形确认连发码的精确时间。解码距离很短1. 接收头供电电压不足尝试5V供电。2. 遥控器电池电量低。3. 接收头性能不佳或方向性太强。1. 将接收头VCC接到5V确保STM32 IO口能容忍5V输入或使用电平转换。2. 更换遥控器电池。3. 调整接收头角度使其正对遥控器。5.2 调试心得逻辑分析仪是你的最佳搭档对于通信协议调试逻辑分析仪比示波器更高效。以PulseView为例连接好逻辑分析仪通道到接收头的OUT引脚。设置合适的采样率如2MHz足够。开始录制然后按下遥控器。停止录制后添加“红外 NEC”解码器。如果硬件和信号基本正确解码器会直接在波形下方显示出解码出的用户码和数据码十六进制。你可以对照解码器显示的结果和你程序解码的结果快速定位是哪个脉冲判断错了。5.3 软件滤波与稳定性提升在工业环境或存在可控硅调光等干扰的场景下可以加入简单的软件滤波宽度滤波在中断中如果测量到的脉冲宽度小于一个最小值如100µs则认为是毛刺干扰直接忽略本次边沿事件不进行状态转移。一致性校验除了协议规定的数据反码校验可以对引导码的9ms和4.5ms进行双重校验只有两者都符合条件才进入数据接收状态。帧间隔保护解码成功一帧后设置一个几十毫秒的“沉默期”在此期间忽略所有中断防止同一帧信号因回波或干扰被重复解码。6. 项目进阶与扩展思路一个稳定的NEC解码器做出来后你可以在此基础上做很多有趣的扩展这能让你的项目从“能用”变成“好用”。6.1 制作通用红外学习与发射模块解码是接收你还可以让STM32学习红外码值并发射出去实现一个万能遥控器。发射电路需要一个NPN三极管如8050驱动红外发射管。STM32的GPIO输出电流有限需要三极管进行电流放大。在发射管上串联一个100Ω左右的限流电阻。发射原理与接收相反。你需要用STM32的PWM或GPIO翻转模拟出38kHz的载波。发射逻辑‘0’时输出560µs的38kHz脉冲然后停止560µs发射逻辑‘1’时输出560µs的38kHz脉冲然后停止1.69ms。关键在于精确的定时可以使用定时器产生38kHz PWM再用另一个定时器控制发送时序。学习功能将解码程序获取到的原始时间序列每个高/低电平的持续时间存储到Flash或EEPROM中。发射时直接按这个时间序列控制载波的开关即可。这样就能学习并复制任何红外遥控信号不限于NEC协议。6.2 集成到更大的应用系统中解码得到的按键值可以作为输入事件集成到你的项目里状态机框架将红外解码作为一个独立的任务或模块通过消息队列或全局变量与主应用通信。例如在基于RTOS如FreeRTOS的项目中解码成功后向任务队列发送一个包含按键值的消息。与显示设备结合将解码出的用户码和键值显示在OLED或LCD屏幕上方便调试和观察。智能家居控制用STM32解码空调、电视的遥控信号然后通过Wi-Fi如ESP8266或蓝牙模块将控制命令转发到手机App或云平台实现手机远程控制老家电。6.3 探索其他红外协议NEC只是红外协议家族的一员。掌握了它的解码方法你就有了理解其他协议的基础。可以尝试解码RC-5协议飞利浦采用双相调制每位数据中间必有翻转抗干扰能力强。SIRC协议索尼采用脉冲宽度编码但逻辑定义与NEC不同。空调专用协议通常数据量更大包含温度、模式等信息编码也更复杂。解码这些协议无非是调整状态机中对脉冲宽度的判断逻辑和帧结构的解析规则。核心技能——利用定时器测量时间、设计状态机——是完全通用的。红外NEC解码是一个经典的嵌入式入门项目它麻雀虽小五脏俱全。从硬件连接到软件协议解析从定时器使用到中断处理再到状态机设计和抗干扰考虑完整地走一遍这个流程你对STM32的理解会深刻很多。最关键的是要动手去做用逻辑分析仪去观察根据实际波形去调整参数。当你按下遥控器STM32终于能正确识别出那个独一无二的键值并点亮一个LED或者发送一串串口信息时那种成就感就是学习嵌入式最大的乐趣。