拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STC15单片机红外发射:NEC协议与PCA0载波生成实战

简介面向嵌入式开发者的红外发射程序源码包基于STC15W408AS单片机采用NEC码协议并通过PCA0模块实现脉冲发送适合学习8051内核红外通信的开发者参考。压缩包共15个文件既包含Keil的uv2工程、lnp/plg/opt等编译辅助文件也提供C语言与汇编语言两套源码main.c与main.asm以及可直接烧录的hex固件整体仅34KB结构紧凑。目前已有536人学习。代码完整覆盖PCA0初始化、NEC编码生成、红外LED脉冲驱动等关键环节可直观理解曼彻斯特编码与地址/命令码的时序控制C与汇编双版本便于对照学习有助于从寄存器底层操作到通信协议实现的进阶。1. 红外发射程序为什么绕不开 NEC 码和 PCA0做 STC15 系列单片机的红外遥控最容易被卡住的不是“怎么把引脚拉高拉低”而是载波怎么持续输出、数据位怎么卡准时间。NEC 协议里每一位都要用 560us 的载波脉冲开头一帧数据里至少要切几十次载波。如果载波靠定时器中断里翻转 IO 来模拟CPU 基本被占死再来一个串口中断或者按键扫描脉冲宽度就会漂接收端直接解不出键值。把 38kHz 载波交给 PCA0 模块去生成是 15 系列上最常见的做法。PCA0 工作在 8 位 PWM 模式时引脚会自动输出方波发送程序只负责在 NEC 码规定的时间点切换“载波开/关”状态和延时逻辑上等同于用硬件隔离了高频率的载波生成和低频率的协议时序。这篇文章从 NEC 协议帧结构开始落到 STC15 系列 PCA0 的寄存器配置、发送函数和调试手段适合已经在用 8051 内核、需要把红外发射功能加进现有工程的开发者。2. NEC 协议时序与 15 系列 PCA0 的载波生成方式2.1 NEC 码帧结构与位编码NEC 协议一帧由引导码、地址码、地址反码、命令码和命令反码组成最后还有一个结束位。地址和命令都是 8bit低位先发。发射端要做的就是在特定时间长度内输出 38kHz 载波在空格时间保持静默。段时长说明引导码9000us 载波 4500us 空闲接收端靠它做自动增益控制地址码8bitLSB first一般表示设备地址地址反码8bit接收端校验用命令码8bitLSB first按键功能码命令反码8bit校验用结束位560us 载波整帧结束标记位编码是 NEC 最容易错的地方。逻辑 0 是 560us 载波 560us 空闲逻辑 1 是 560us 载波 1690us 空闲。也就是说不管是 0 还是 1载波部分长度相同区别只在后续的空闲时间。发送单字节时循环 8 次每次都先输出 560us 载波再根据当前比特位延时 560us 或 1690us。2.2 PCA0 的 8 位 PWM 如何产生载波STC15 系列的 PCA 模块有捕获、定时器、高速脉冲输出和 PWM 四种工作模式。做红外发射时PCA0 合适的工作方式是 8 位 PWM。这里不涉及复杂的比较中断只需要理解 PWM 输出脚的电平变化规律PCA 计数器从 0 计数到 255然后溢出回 0每一次计数都会拿当前计数值和 CCAP0H 做比较比较结果直接反映到 CCP0 引脚上。载波频率由 PCA 输入时钟决定。PCA 时钟为 11.0592MHz、8 位模式下分频 256得到的引脚翻转频率是 11059200 / 256 43.2kHz。这个值比标准 NEC 的 38kHz 高一点但一体化红外接收头HS0038、VS1838 等的检波带宽一般覆盖 33kHz 到 44kHz 这个范围实测能正常收到。后面第 4 章会专门说这个偏差对调试的影响。2.2.1 CCAP0H 决定占空比CCAP0H 是 PWM 比较值它直接控制输出高电平的时间比例。比较值 0x80 对应 128 / 256 50% 占空比0x3F 接近 25%0xC0 接近 75%。在 8 位 PWM 模式下改变 CCAP0H 只影响这一路 PWM 的占空比不影响 PCA 计数器本身运行所以可以在发送过程中随时调整。2.2.2 控制载波开关不需要启停 CR很多初学者会想通过 CR0 停止 PCA再 CR1 恢复从而控制载波有无。这个做法能工作但不推荐。PCA 停止后计数器清零重新启动时载波相位会从头开始波形在边界处不连续。更好的办法是让 PCA 一直运行需要载波时把 CCAPM0 设为 PWM 输出模式不需要时关闭 PWM 让引脚回到普通 IO 并拉低。这样切换速度快代码逻辑也更直观。提示NEC 接收端按包络解调对载波相位不敏感但保持载波连续可以减少接收端解码抖动的概率尤其是连续按键时。3. STC15 系列 PCA0 发送 NEC 码的完整实现3.1 初始化 PCA0 并映射到发射引脚不同封装的 STC15 型号PCA0 引脚位置不完全一样。以带 P_SW1 的型号为例CCP_S1:CCP_S0 设置为 01CCP0 映射到 P3.4。部分 8 脚封装没有 P_SW1PCA 引脚固定直接看数据手册确认即可。下面初始化代码按 P3.4 输出设计。void PCA0_Init(void) { ACC P_SW1; ACC ~0x18; // 清零 CCP_S0(bit3) 和 CCP_S1(bit4) ACC | 0x08; // CCP_S01CCP0 映射到 P3.4 P_SW1 ACC; CMOD 0x08; // PCA 时钟选择系统时钟CIDL0 CL 0x00; CH 0x00; PCA_PWM0 0x00; // 8 位 PWM 模式 CCAPM0 0x00; // 先不使能 PWM避免上电瞬间输出异常 CCAP0L 0x80; CCAP0H 0x80; // 占空比 50% CR 1; // 启动 PCA 计数器 }CMOD的 CPS 位在不同子系列里定义有差异代码里注释按“PCA 时钟 系统时钟”写实际移植时对照芯片数据手册确认。PCA_PWM0也是同理部分 STC15 老型号没有这个寄存器默认就是 8 位 PWM那一行可以直接注释掉。初始化后 CCAPM0 保持 0此时 P3.4 还是普通 IO载波不会自己跑出来。3.2 载波开关与字节发送函数发射引脚接红外的推荐接法P3.4 串一个 100Ω 电阻接红外发射管阳极阴极接 GND。这样关闭载波时 P3.4 拉低LED 是熄灭状态。如果电路用了三极管反向驱动载波关闭的常量就要反过来。void IR_Carrier_Start(void) { CCAP0H 0x80; // 先设比较值 CCAPM0 0x42; // ECOMPWM使能 8 位 PWM 输出 } void IR_Carrier_Stop(void) { CCAPM0 0x00; // 关闭 PWMP3.4 回到普通 IO P34 0; // 拉低引脚熄灭红外 LED }CCAPM0 0x42的含义是启用 PCA0 的 PWM 功能CCAPM0 0x00相当于把该通道从 PCA 上摘下来引脚控制权交还给端口数据寄存器。这一步比修改 CCAP0H 更可靠因为不管比较值怎么设8 位 PWM 输出总会在单个周期内出现至少一个高电平计数点模拟不出严格的常低电平。字节发送函数按“先载波后空闲”的顺序写注意移位方向是低位在前void IR_Send_Byte(uint8_t dat) { uint8_t i; for (i 0; i 8; i) { IR_Carrier_Start(); Delay_us(560); IR_Carrier_Stop(); if (dat 0x01) Delay_us(1690); // 逻辑 1长空闲 else Delay_us(560); // 逻辑 0短空闲 dat 1; } }这段函数里载波开的时间固定是 560us发不发“1”只影响后续空格长度。如果发现接收端解出来的字节是反转的先查移位方向不要把问题归结到延时不准。3.3 发送完整帧地址和命令的反码是 NEC 接收端校验的重要手段不能省略。发送顺序为地址、地址反码、命令、命令反码。void IR_Send_Frame(uint8_t addr, uint8_t cmd) { EA 0; // 发送过程中关总中断避免时序被拉长 IR_Carrier_Start(); Delay_us(9000); // 引导码载波 IR_Carrier_Stop(); Delay_us(4500); // 引导码空格 IR_Send_Byte(addr); IR_Send_Byte(~addr); IR_Send_Byte(cmd); IR_Send_Byte(~cmd); IR_Carrier_Start(); // 结束位 Delay_us(560); IR_Carrier_Stop(); EA 1; }发送一帧大约需要 60ms 到 70ms。如果中途被中断打断常见表现是接收端偶尔丢按键或者连续按键时只有第一次响应。关总中断是省事且有效的方法。如果项目里中断任务很紧急可以改成“发送过程中只禁止会使能度超过 10us 的中断”否则不建议打开。3.4 延时函数与频率系数上面代码依赖精确延时。用循环_nop_()凑延时在 11.0592MHz 下误差很大不如直接用定时器。STC15 的 Timer0 可以用 12T 模式计数频率为 921.6kHz每个计数周期约 1.085us计算 9ms 延时只需要装载 8294远小于 65535 的 16 位上限。void Delay_us(uint16_t us) { uint16_t ticks; uint32_t tmp; tmp ((uint32_t)us * 9216UL) / 10000UL; // us 换算成 12T 计数个数 ticks (uint16_t)tmp; TMOD (TMOD 0xF0) | 0x01; // Timer0 模式 116 位 AUXR ~0x80; // Timer0 工作在 12T 模式 TR0 0; TH0 (uint8_t)((65536UL - ticks) 8); TL0 (uint8_t)(65536UL - ticks); TF0 0; TR0 1; while (!TF0); TR0 0; }系数 9216 / 10000 来源于 11059200 / 12 / 1000000也就是 1us 需要 0.9216 个计数周期。560us 换算后是 5161690us 是 15589000us 是 8294误差在 0.5% 以内。换晶振或者改内部 IRC 频率时只需要改这个系数。4. 红外发射程序调试载波频率偏差与波形验证方法4.1 先看引导码确认载波链路通不通拿到一块新板子第一步不要在协议上纠结直接发引导码并测量。把逻辑分析仪接到 P3.4采样率设置 1MHz 以上按下发送键后应该看到一段约 9ms 的连续方波包络然后是一段约 4.5ms 的低电平。这里看的是“有没有载波”不是看协议数据。如果引导码这段都不稳定后面调位时序没有意义。载波部分在逻辑分析仪上通常会显示成致密的矩形带因为 43.2kHz 的周期只有约 23us而 NEC 数据位最小长度是 560us一个载波脉冲组里包含约 23 个方波周期。只要包络宽度对接收头就能正常输出解调信号。4.2 位间隔与反码校验引导码正常后发现按键无反应接着抓完整帧观察地址码和命令码的反码。接收端如果检测到地址和地址反码不一致会直接丢弃整帧遥控器表现为“完全没反应”而不是“偶尔失灵”。检查项预期值常见异常引导码载波9000us ± 500us不足 7ms多半是延时系数偏小逻辑 1 空格1690us ± 150us被中断拉长表现为偶发丢键逻辑 0 空格560us ± 80us计算错误时反码校验过不了结束位560us 载波缺失时接收端可能收不到最后一字节4.3 载波 43.2kHz 是否真的可用11.0592MHz 主频下 PCA 8 位 PWM 得到 43.2kHz和标准 38kHz 差了约 13%。这不是程序 bug而是选型取舍。市场上常见的红外接收头内部带自动增益控制和带通滤波器带宽普遍覆盖 33kHz 到 44kHz43.2kHz 落在可响应范围。实测距离会略短但不会解不出。如果产品要求更严需要把载波拉回 38kHz 附近有两条路。第一换主频19.44MHz 晶振配合 PCA 时钟二分频19.44MHz / 2 / 256 37.97kHz非常接近。第二用 PCA 比较中断翻转引脚计算匹配间隔为 146 个时钟周期输出 11059200 / 2 / 146 37.9kHz。第二种方式会占用 PCA 中断但发送代码结构不用改。4.4 发射距离短和环境干扰排除了时序和协议问题距离短十有八九是驱动电路和供电。3.3V 供电下红外管正向压降约 1.2V限流电阻 100Ω 时峰值电流约 20mA50% 占空比下平均电流 10mA近距离 5 米内够用。想拉远距离把限流电阻降到 47Ω红外管峰值电流可以到 40mA 以上平均功耗仍然不高。注意这时要确认发射管最大脉冲电流规格不要按平均电流选型。环境光干扰通常表现为“不按键时也能收到信号”。先检查 P3.4 关闭载波时是否稳定为低电平再用示波器看电源纹波红外接收头对电源噪声很敏感。5. 结尾技巧15 系列上把载波占空比调到可用的上界5.1 通过 CCAP0H 调整占空比改善接收裕量前面初始化里 CCAP0H 设为 0x80占空比 50%。如果实测距离不够可以试着把占空比调到 60% 到 70%也就是 CCAP0H 设为 0x99 到 0xB3。占空比升高意味着红外管平均发光时间变长接收端解调出的脉冲包络更扎实。需要注意接收头内部 AGC 有一个上限超过 70% 后部分型号会把持续的强载波误判为“环境光背景”反而丢码。建议从 0x80 开始以 0x0D 为步进向上调每个档位用 5 米距离实测一次。这里有一个容易被忽略的问题STC15 的 8 位 PWM 比较值是即时生效的不要在载波开启过程中改写 CCAP0H否则当前未完成的高电平或低电平周期会被截断。正确做法是先调用IR_Carrier_Stop()修改 CCAP0H再重新IR_Carrier_Start()。不过在一帧 NEC 数据内部频繁切换载波载波起始瞬间会有最多一个周期的误差属于正常现象接收端不会因此误码。5.2 连续按键时控制帧间隔NEC 协议允许同一按键重复发送但不能一帧接一帧连发。接收端解码完成到下次接收需要约 20ms 的恢复时间工程上可以在IR_Send_Frame返回前自动追加一段低电平等待void IR_Send_Frame_WithGap(uint8_t addr, uint8_t cmd) { IR_Send_Frame(addr, cmd); Delay_us(20000); // 帧间隔 20ms连续按键更稳定 }如果目标是和原装遥控器按键行为完全一致则需要实现 NEC 的重复帧9ms 载波 2.25ms 空格 560us 载波而不是重发完整数据帧。长按场景下前 110ms 发完整帧之后每 110ms 发一次重复帧这个节奏可以让大多数空调、机顶盒接收端正确进入连续调节模式。5.3 验证同一个按键稳定触发 50 次确认整套程序可交付前用一个固定地址和命令比如IR_Send_Frame(0x00, 0x45)循环发送 50 次在接收端记录每次解码结果。50 次里有 1 次失败优先怀疑延时函数被中断拉长连续失败超过 5 次优先怀疑载波频率落在接收头带宽边缘。把这两条路查完NEC 码发射程序就可以从开发板挪到正式产品里了。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门