51单片机红外解码与数码管显示:NEC协议时序测量实战

发布时间:2026/9/16 12:46:00

51单片机红外解码与数码管显示:NEC协议时序测量实战 简介一份面向51单片机初学者的红外解码与数码管显示综合实例来源于51单片机教程系列第117讲。资源围绕红外接收、协议解码、动态扫描显示等核心知识点展开适合入门者巩固定时器、中断和I/O控制技术。压缩包仅15KB共11个文件包含C源码、Keil工程文件.uv2、编译生成的.hex可执行文件以及.obj、.lst、.m51等过程文件可帮助对照源码理解编译和烧录流程。已有291人学习。学习者可从中掌握NEC等红外协议的解码思路了解七段数码管静态与动态驱动方法并参考工程设计文件完成从代码编写、编译调试到硬件验证的全过程为后续嵌入式开发打下基础。1. 51单片机红外解码与数码管显示为什么值得动手做一遍在51单片机的学习路线里点亮LED、驱动数码管、做电子钟这类实例解决的是“怎么让引脚输出电平”而红外解码解决的是“怎么把一段承载数据的波形读出来”。前者是写后者是读两者交叉在一起才算是真正碰过硬件时序。这个教程实例的主题很明确用51单片机接收红外遥控器发出的NEC协议信号解码出遥控码值并实时显示在数码管上。它的价值不只是“看懂一个遥控器”而是把外部中断、定时器计数、脉冲宽度测量、动态扫描显示这几个模块串成一条完整的数据链路。做一遍这个实例等于把51单片机最核心的中断系统和定时器系统都过了一遍之后再去看串口、看PWM、看I2C时序都会轻松不少。适合做这个实例的人有两类一类是刚学完中断和定时器、想找一个综合练习的初学者另一类是正在做课程设计或项目预研需要把红外遥控功能集成进产品里的开发者。下面的内容按“协议原理 → 解码实现 → 数码管显示 → 仿真与排错”的顺序展开代码基于Keil5编写仿真环境用Proteus。2. NEC协议与时序测量红外解码前必须搞懂的三个细节2.1 NEC协议的数据帧格式与时间参数绝大多数市售红外遥控器使用NEC协议原因是它实现简单、抗干扰能力够用。NEC协议每一帧数据由引导码、地址码、地址反码、命令码、命令反码组成总共32位数据位。数据段长度说明引导码9ms高电平 4.5ms低电平标志一帧数据的开始地址码8位通常表示设备类型比如电视、空调地址反码8位地址码取反用于校验命令码8位按键对应的功能码命令反码8位命令码取反用于校验引导码之后是32位数据每一位的持续时间都是固定的区别在于高电平的脉宽。逻辑“0”由560us高电平和560us低电平组成总时长约1.125ms逻辑“1”由560us高电平和1.69ms低电平组成总时长约2.25ms。接收端一体化红外接收头在没有信号时输出高电平收到载波时输出低电平。还有一类特殊信号叫连发码当遥控器按键持续按住时每隔110ms左右会发送一组9ms高电平 2.25ms低电平的脉冲中间不跟数据位。解码程序必须能识别连发码否则按住按键时数码管会不停地重复刷新。2.2 为什么用外部中断加定时器而不是轮询读引脚最容易犯的错误是用主循环不停地读P3.2引脚电平然后靠软件延时测脉宽。这种做法的缺点是主循环里还要做数码管扫描一旦在延时函数里被中断打断或是在测量过程中被其它代码抢走CPU测出来的时间就会偏大或偏小解码偶尔成功偶尔失败极难排查。正确做法是让外部中断0INT0负责捕捉引脚下降沿定时器0负责计时。接收头空闲时输出高电平收到红外信号后输出低电平信号结束后恢复高电平。因此每出现一次下降沿就意味着一个电平段的结束同时是下一个电平段的开始。在下降沿中断服务函数里读出定时器当前值再和上一次的值做差就能得到一段精确的脉冲宽度。这样做的好处是测量过程与主循环无关无论主程序正在扫描数码管还是处理其它逻辑都不会影响时间精度。2.3 量化判断脉冲宽度误差容限与分界值红外接收头输出的波形边沿会存在几十微秒的抖动加上定时器中断本身有响应延迟所以不能要求测量结果精确等于协议标称值。常见的做法是设计一个分界阈值低于某个值判为逻辑“0”相关的短脉宽高于某个值判为逻辑“1”相关的长脉宽。引导码的9ms低电平是最好识别的标志但接收头输出的引导码低电平长度并不严格等于协议标称值实际范围可能在8.5ms到9.5ms之间。我的判断逻辑是脉宽大于8ms判定为引导码低电平进入数据接收状态脉宽在1.2ms到2.5ms之间判定为逻辑“1”脉宽在0.3ms到1.1ms之间判定为逻辑“0”脉宽约2.0ms且前面已经收到32位完整数据判定为连发码。3. 用Keil5写51单片机红外解码程序定时器初值与状态机实现3.1 硬件连接与定时器参数计算实验板上的红外接收头一般是三引脚器件OUT引脚连接到单片机的外部中断0输入脚。常见的连接方式如下接收头引脚连接目标说明VCC5V供电GNDGND接地OUTP3.2 (INT0)信号输出我习惯把定时器0配置为16位模式工作在方式1。系统晶振频率12MHz时定时器每1us计数一次一个16位周期最多计时65536us即65.5ms完全够用。定时器初值计算方式方式1是16位计数器从初值加到65536溢出。如果初值设为0最大计时约65.5ms如果每次中断后重装初值可以把计时窗口限制在固定周期内。这里由于要测量的是最长9ms的引导码我选择不重装、直接让定时器从0开始数溢出自然结束。/* 定时器0初始化16位模式允许中断 */ void Timer0_Init(void) { TMOD 0xF0; // 清零定时器0的配置位 TMOD | 0x01; // 定时器0工作在方式116位定时器 TH0 0x00; // 初值高8位 TL0 0x00; // 初值低8位 ET0 1; // 开启定时器0中断 EA 1; // 开启总中断 } /* 外部中断0初始化下降沿触发 */ void EX0_Init(void) { IT0 1; // INT0下降沿触发 EX0 1; // 开启外部中断0 EA 1; // 开启总中断 }这段代码把定时器0和外部中断0同时打开。16位模式意味着TH0和TL0合并为一个16位计数器每个机器周期自动加1。12MHz晶振下机器周期为1us计数器加1表示1us过去。初值为0的好处是读取到的数值就是从上次清零到现在的微秒数不需要额外的换算。3.2 外部中断服务函数记录临界值并切换状态中断服务函数是解码的核心。每一次下降沿进入中断时要先判断上一个电平段的宽度属于什么类型然后更新状态机。这里需要三个全局变量一个存储上次中断的定时器值一个存储当前接收状态一个存储正在累积的数据字节。/* 全局变量定义 */ unsigned int time_old 0; // 上次下降沿的定时器计数值 unsigned char ir_state 0; // 0:等待引导码 1:接收数据位 2:数据接收完毕 unsigned char ir_data[4]; // 存放地址码、地址反码、命令码、命令反码 unsigned char ir_bit_count 0; // 已接收的数据位个数 unsigned int ir_code 0; // 组合后的完整编码值 unsigned char ir_ok 0; // 一帧数据接收完成标志 /* 外部中断0服务函数 */ void EX0_ISR(void) interrupt 0 { unsigned int time_now; unsigned int pulse_width; time_now (TH0 8) | TL0; // 读取当前计数器值 pulse_width time_now - time_old; // 计算上一段电平时长 time_old time_now; // 更新基准点 TH0 0x00; // 定时器归零开始测下一段 TL0 0x00; if (ir_state 0) { /* 等待引导码9ms左右的低电平 */ if (pulse_width 8000 pulse_width 10000) { ir_state 1; ir_bit_count 0; } } else if (ir_state 1) { /* 接收32位数据位 */ if (pulse_width 1200 pulse_width 2500) { ir_data[ir_bit_count / 8] 1; // 高位在前先右移腾位置 ir_data[ir_bit_count / 8] | 0x80; } else if (pulse_width 300 pulse_width 1100) { ir_data[ir_bit_count / 8] 1; // 逻辑0 } else { ir_state 0; // 脉宽异常放弃本帧 } ir_bit_count; if (ir_bit_count 32) { ir_state 2; ir_ok 1; } } else if (ir_state 2) { /* 连发码约2.25ms的低电平 */ if (pulse_width 1800 pulse_width 2800) { /* 连发码不改变编码值只做按键持续响应的标记 */ } else { ir_state 0; // 回到等待引导码状态 } } }中断服务函数有几个细节值得说明。首先是高位在前还是低位在前的问题NEC协议先发送最高位。这里用右移操作配合置位操作实现高位优先的排列每收到一位数据先让当前字节右移一位再根据脉宽决定是否把最高位置1。第二次右移时第一次的数据自然落在bit6位置依次类推。其次是定时器归零的位置。我把TH0和TL0清零放在代码前半段这样time_now读到的值没有经过清零干扰时序更准确。读取计数器值时先读TH0再读TL0因为16位计数器在边界处可能出现进位如果顺序反了会读到错误的高低位组合。3.3 主循环中的数据处理策略中断服务函数只负责记录脉宽和填充数组真正对数据进行校验和转换的工作不放在中断里是因为中断函数里不适合做复杂的除法或查表操作也容易因为执行时间过长而漏掉下一次中断。主循环发现ir_ok标志为1后再从数组里取出数据。void main(void) { unsigned char user_code, cmd_code; Timer0_Init(); EX0_Init(); while (1) { if (ir_ok 1) { ir_ok 0; if (ir_data[0] (unsigned char)~ir_data[1]) { if (ir_data[2] (unsigned char)~ir_data[3]) { user_code ir_data[0]; cmd_code ir_data[2]; /* 将数据交给显示模块 */ Display_Code(user_code, cmd_code); } } ir_state 0; } } }校验逻辑很直观地址码和地址反码按位取反后应相等命令码和命令反码同理。如果不相等说明传输过程中有错误直接丢弃。注意这里的取反运算在C语言里是对整个字节取反本质等同于255减去原值和NEC协议发送端的取反方式一致。4. 数码管动态扫描显示解码结果与Proteus仿真配合4.1 4位共阴数码管的段码表与位选控制解码得到的编码值通常是一帧32位数据但数码管只有4位能显示的信息有限。在实例中显示前8位地址码和后8位命令码比较常见即4位数码管左边两位显示用户码右边两位显示命令码都以十六进制呈现。常用的4位共阴数码管段码表如下这个表是按“a段对应P0.0b段对应P0.1依此类推”的顺序排列的。/* 共阴数码管段码表 */ unsigned char code seg_table[] { 0x3F, // 0 0x06, // 1 0x5B, // 2 0x4F, // 3 0x66, // 4 0x6D, // 5 0x7D, // 6 0x07, // 7 0x7F, // 8 0x6F, // 9 0x77, // A 0x7C, // b 0x39, // C 0x5E, // d 0x79, // E 0x71 // F };段码表的数值来自共阴数码管的发光原理要让某一段亮起来需要在该段对应的引脚上输出高电平。以数字0为例需要点亮a、b、c、d、e、f六段g段不亮所以段码是0x3F。如果不是共阴而是共阳数码管段码需要取反用0xC0对应数字0。4.2 动态扫描刷新周期与消隐处理数码管动态扫描的基本思想是逐位点亮每位数码管点亮一小段时间依靠视觉暂留让人感觉四位数码管同时亮。扫描间隔如果太长会看到明显闪烁太短则亮度不足。我一般把扫描周期控制在4ms以内也就是每一位亮1ms左右。时间基准从定时器0的中断里获得并不现实因为解码用的定时器0已经承担了脉宽测量任务。解决办法是利用主循环中的软件延时来实现扫描或者再开一个定时器1专门做显示刷新。用定时器1的好处是显示扫描不受主循环影响缺点是多占一个中断资源。在只做解码和显示这个场景里main函数里写一个非阻塞的扫表逻辑就够了。void Display_Code(unsigned char user_code, unsigned char cmd_code) { unsigned char i; unsigned char temp; for (i 0; i 4; i) { switch (i) { case 0: temp user_code 4; break; // 用户码高4位 case 1: temp user_code 0x0F; break; // 用户码低4位 case 2: temp cmd_code 4; break; // 命令码高4位 case 3: temp cmd_code 0x0F; break; // 命令码低4位 } P2 0x01 i; // 位选选中第i位数码管 P0 seg_table[temp]; // 段选送入该位要显示的字形 Delay_Short(); // 保持点亮时间 P0 0x00; // 消隐熄灭该位防止拖影 } } void Delay_Short(void) { unsigned char i 200; while (i--); }位选信号通过P2口的低4位控制P2.0选中最右边一位P2.3选中左边一位这样解码结果显示顺序和日常阅读习惯一致。段选和位选之间有极短的切换时间如果位选切换后段选还没更新上一位的字形会短暂显示在当前位上产生“拖影”。所以每次切换后先消隐也就是给P0写0再选通下一位。4.3 Proteus仿真红外遥控与波形检查在Proteus中做这个实例的仿真需要放置的元件有AT89C51单片机、4位共阴数码管、红外接收头模型以及一个IR Remote控件。IR Remote控件在Proteus的“Virtual Instruments”菜单里可以找到它模拟了一个手持红外遥控器的按键行为。放置IR Remote后双击它进入属性设置。关键的一步是把IR Remote的OUT引脚连接到单片机的P3.2引脚同时需要一个红外接收头做中间转换。Proteus里的红外接收头模型会把IR Remote发出的信号转换成单片机可识别的TTL电平。Proteus按键命令码说明00x00数码管显示00 0010x01数码管显示00 0120x02数码管显示00 02CH0x10频道上翻VOL0x18音量加大仿真时钟频率需要设置为12MHz与代码中定时器参数计算保持一致。如果仿真时发现解码不稳定优先检查单片机属性里的晶振频率是否和代码中的定时器初值匹配。5. 解码失败的排查方法与一个实用技巧连发码去抖动5.1 数码管乱码或没反应时的排查顺序遇到解码显示不出结果先别急着改代码。红外解码的排查入口是有顺序的按下面的路径走一遍大部分问题都能定位到具体环节。第一步看接收头输出波形。把示波器探头接到接收头OUT引脚按遥控器按键正常波形应该是一串密集的方波脉冲。如果波形几乎水平、没有明显跳变说明接收头没收到信号或供电有问题。第二步看单片机是否进入了外部中断。在中断服务函数第一行加一个翻转LED的语句按遥控器时观察LED是否闪动。LED不闪说明中断没触发问题在硬件连接或配置LED闪了但显示不对问题在定时器参数或数据判断逻辑。第三步用串口打印调试信息。如果开发板有USB转串口芯片可以每隔一段时间把检测到的pulse_width值通过串口发出来直接在电脑上观察每组脉宽的实际数值比在代码里反复调阈值高效得多。5.2 连发码去抖让长按只触发一次显示刷新遥控器持续按住时NEC协议会以110ms为间隔发送连发码其内容不携带键值。如果不做处理最常见的现象是短按按键数码管正常显示长按后显示内容每隔110ms闪烁一次。这个问题的本质是连发码没有被识别程序把连发码的2.25ms低电平误判为数据位又触发了一次完整解码。处理方式很直接记录上一次成功解码的命令码和当前时间。在中断里识别到连发码时直接丢弃数据不置ir_ok标志在主循环的显示部分如果检测到连续两次命令码相同且间隔小于150ms就跳过刷新。/* 主循环里增加连发码过滤逻辑 */ unsigned int last_cmd 0xFF; unsigned int last_display_time 0; unsigned int current_time; if (ir_ok 1) { ir_ok 0; current_time Get_System_Time(); if (!(ir_data[2] last_cmd (current_time - last_display_time) 150)) { /* 间距足够大或键值不同才刷新显示 */ Display_Code(ir_data[0], ir_data[2]); } last_cmd ir_data[2]; last_display_time current_time; }这里的关键是current_time - last_display_time用的unsigned int差值两个值都是从同一个系统时钟计数值里取出来的即使系统时钟溢出回绕差值运算仍然能得到正确结果不需要额外的溢出判断。150ms这个阈值比连发码的110ms间隔大一些保证长按时第二帧连发码到达时被拦截同时不会误伤快速双击等操作。这套解码和显示框架里中断函数保持轻量、只做状态记录主循环做校验和呈现。把时间测量与数据处理分离这个思路同样适用于红外发射端的脉宽生成、超声波测距的回波时间测量以及更多依赖精确时序的传感器驱动场景。数码管显示的具体字形刷新节拍也可以在这套框架上扩展为LCD1602显示或串口上报电源和引脚占用需要重新规划的地方就是下一轮硬件设计的事了。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/9/16 12:46:00

23款AI写作工具实测:学术论文高效写作全攻略

1. 论文写作工具测评背景与价值作为一名经历过自考论文写作全过程的过来人,我深刻理解学术写作中遇到的三大痛点:文献检索效率低、格式规范难把握、写作过程缺乏智能辅助。去年撰写教育管理专业毕业论文时,我耗时两个月测试了市面上23款AI写作…

2026/9/16 12:46:00

北方苍鹰优化算法(NGO)的Matlab实现与改进

1. 北方苍鹰优化算法(NGO)概述北方苍鹰优化算法(Northern Goshawk Optimization,简称NGO)是近年来提出的一种新型元启发式算法,灵感来源于北方苍鹰在自然界中的捕猎行为。这种猛禽以其卓越的狩猎策略闻名—…

2026/9/16 13:41:07

基于Proteus的无线温度采集报警系统仿真设计与实现

简介:一套基于51单片机与Proteus的无线温度采集报警系统完整设计方案,适合单片机学习者、课程设计开发者以及想了解DS18B20温度采集与LCD1602显示的读者。系统包含温度采集端与显示报警端两大部分,采集端完成温度检测与模拟无线发送&#xff…

2026/9/16 13:41:07

AT89C51驱动DAC0832输出正弦波:Proteus仿真与C代码详解

简介:面向单片机初学入门者与电子设计爱好者,实例基于AT89C51与DAC0832构建正弦波输出电路,配套Proteus仿真源文件与C语言程序,可在Proteus 8.6中直接打开和仿真运行,直观展示D/A转换、查表法生成正弦波以及运放OP07信…

2026/9/16 13:41:07

轻量级智能交通系统:Java SE实现的可调试交通流建模骨架

简介:本资源是一套完整可运行的智能交通系统(ITS)项目源码,面向计算机类专业学生及初级开发人员,适用于课程设计、大作业、毕业设计与项目立项演示等实践场景。压缩包共850个文件,涵盖219个Java核心业务逻辑…

2026/9/16 13:41:07

基于Verilog的FPGA AES加密实现与仿真验证

简介:一套基于Verilog HDL实现AES加密算法的完整Quartus II工程,面向FPGA开发者和信息安全学习者,适合希望深入理解对称加密硬件实现及数字电路设计的读者。包内共214个文件,包含硬件描述语言源码(v/vhd)、…

2026/9/16 12:52:37

拯救者Y7000黑屏故障排查与维修实战指南

1. 项目概述:一台黑屏的拯救者Y7000,到底卡在哪一步? 联想拯救者Y7000系列笔记本,从2018年第一代搭载i5-8300H开始,到后来的i7-9750H、i7-10750H、i5-11400H,再到2023年款的R7-7840HS,它始终是学…

2026/9/16 0:04:09

PHP源码部署实战:从环境配置到运行情侣游戏全攻略

简介:这是一套面向情侣互动场景的PHP完整源码,集成情侣飞行棋、真心话大冒险、情趣骰子等玩法,并内置完整分销制度,可自定义多种返佣比例,源码完全开源无加密,支持微信无感自动授权登录与第三方授权&#x…

2026/9/15 14:22:53

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/15 21:31:11

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/15 11:42:23

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码