家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地。遥控器的基带通信协议很多,大概有几十种,常用的就有 ITT 协议、NEC 协议、Sharp 协议、Philips RC-5 协议、Sony SIRC 协议等。用的最多的就是 NEC 协议了,因此我们 KST-51 开发板配套的遥控器直接采用 NEC 协议,我们这节课也以 NEC 协议标准来讲解一下。

NEC 协议的数据格式包括了引导码、用户码、用户码(或者用户码反码)、按键键码和键码反码,最后一个停止位。停止位主要起隔离作用,一般不进行判断,编程时我们也不予理会。其中数据编码总共是 4 个字节 32 位,如图 16-7 所示。第一个字节是用户码,第二个字节可能也是用户码,或者是用户码的反码,具体由生产商决定,第三个字节就是当前按键的键数据码,而第四个字节是键数据码的反码,可用于对数据的纠错。


图 16-7  NEC 协议数据格式

这个 NEC 协议,表示数据的方式不像我们之前学过的比如 UART 那样直观,而是每一位数据本身也需要进行编码,编码后再进行载波调制。

  • 引导码:9ms 的载波+4.5ms 的空闲。
  • 比特值“0”:560us 的载波+560us 的空闲。
  • 比特值“1”:560us 的载波+1.68ms 的空闲。

结合图 16-7 我们就能看明白了,最前面黑乎乎的一段,是引导码的 9ms 载波,紧接着是引导码的 4.5ms 的空闲,而后边的数据码,是众多载波和空闲交叉,它们的长短就由其要传递的具体数据来决定。HS0038B 这个红外一体化接收头,当收到有载波的信号的时候,会输出一个低电平,空闲的时候会输出高电平,我们用逻辑分析仪抓出来一个红外按键通过HS0038B 解码后的图形来了解一下,如图 16-8 所示。


图 16-8  红外遥控器按键编码

从图上可以看出,先是 9ms 载波加 4.5ms 空闲的起始码,数据码是低位在前,高位在后,数据码第一个字节是 8 组 560us 的载波加 560us 的空闲,也就是 0x00,第二个字节是 8 组 560us的载波加 1.68ms 的空闲,可以看出来是 0xFF,这两个字节就是用户码和用户码的反码。按键的键码二进制是 0x0C,反码就是 0xF3,最后跟了一个 560us 载波停止位。对于我们的遥控器来说,不同的按键,就是键码和键码反码的区分,用户码是一样的。这样我们就可以通过单片机的程序,把当前的按键的键码给解析出来。

我们前边学习中断的时候,学到 51 单片机有外部中断 0 和外部中断 1 这两个外部中断。我们的红外接收引脚接到了 P3.3 引脚上,这个引脚的第二功能就是外部中断 1。在寄存器TCON 中的 bit3 和 bit2 这两位,是和外部中断 1 相关的两位。其中 IE1 是外部中断标志位,当外部中断发生后,这一位被自动置 1,和定时器中断标志位 TF 相似,进入中断后会自动清零,也可以软件清零。bit2 是设置外部中断类型的,如果 bit2 为 0,那么只要 P3.3 为低电平就可以触发中断,如果 bit2 为 1,那么 P3.3 从高电平到低电平的下降沿发生才可以触发中断。此外,外部中断 1 使能位是 EX1。那下面我们就把程序写出来,使用数码管把遥控器的用户码和键码显示出来。

Infrared.c 文件主要是用来检测红外通信的,当发生外部中断后,进入外部中断,通过定时器 1 定时,首先对引导码判断,而后对数据码的每个位逐位获取高低电平的时间,从而得知每一位是 0 还是 1,最终把数据码解出来。虽然最终实现的功能很简单,但因为编码本身的复杂性,使得红外接收的中断程序在逻辑上显得就比较复杂,那么我们首先提供出中断函数的程序流程图,大家可以对照流程图来理解程序代码,如图 16-9 所示。


图 16-9  红外接收程序流程图
  1. /***************************Infrared.c 文件程序源代码*****************************/
  2. #include <reg52.h>
  3. sbit IR_INPUT = P3^3; //红外接收引脚
  4. bit irflag = 0; //红外接收标志,收到一帧正确数据后置 1
  5. unsigned char ircode[4]; //红外代码接收缓冲区
  6. /* 初始化红外接收功能 */
  7. void InitInfrared(){
  8. IR_INPUT = 1; //确保红外接收引脚被释放
  9. TMOD &= 0x0F; //清零 T1 的控制位
  10. TMOD |= 0x10; //配置 T1 为模式 1
  11. TR1 = 0; //停止 T1 计数
  12. ET1 = 0; //禁止 T1 中断
  13. IT1 = 1; //设置 INT1 为负边沿触发
  14. EX1 = 1; //使能 INT1 中断
  15. }
  16. /* 获取当前高电平的持续时间 */
  17. unsigned int GetHighTime(){
  18. TH1 = 0; //清零 T1 计数初值
  19. TL1 = 0;
  20. TR1 = 1; //启动 T1 计数
  21. while (IR_INPUT){ //红外输入引脚为 1 时循环检测等待,变为 0 时则结束本循环
  22. //当 T1 计数值大于 0x4000,即高电平持续时间超过约 18ms 时,
  23. //强制退出循环,是为了避免信号异常时,程序假死在这里。
  24. if (TH1 >= 0x40){
  25. break;
  26. }
  27. }
  28. TR1 = 0; //停止 T1 计数
  29. return (TH1*256 + TL1); //T1 计数值合成为 16bit 整型数,并返回该数
  30. }
  31. /* 获取当前低电平的持续时间 */
  32. unsigned int GetLowTime(){
  33. TH1 = 0; //清零 T1 计数初值
  34. TL1 = 0;
  35. TR1 = 1; //启动 T1 计数
  36. while (!IR_INPUT){ //红外输入引脚为 0 时循环检测等待,变为 1 时则结束本循环
  37. //当 T1 计数值大于 0x4000,即低电平持续时间超过约 18ms 时,
  38. //强制退出循环,是为了避免信号异常时,程序假死在这里。
  39. if (TH1 >= 0x40){
  40. break;
  41. }
  42. }
  43. TR1 = 0; //停止 T1 计数
  44. return (TH1*256 + TL1); //T1 计数值合成为 16bit 整型数,并返回该数
  45. }
  46. /* INT1 中断服务函数,执行红外接收及解码 */
  47. void EXINT1_ISR() interrupt 2{
  48. unsigned char i, j;
  49. unsigned char byt;
  50. unsigned int time;
  51. //接收并判定引导码的 9ms 低电平
  52. time = GetLowTime();
  53. //时间判定范围为 8.5~9.5ms,
  54. //超过此范围则说明为误码,直接退出
  55. if ((time<7833) || (time>8755)){
  56. IE1 = 0; //退出前清零 INT1 中断标志
  57. return;
  58. }
  59. //接收并判定引导码的 4.5ms 高电平
  60. time = GetHighTime();
  61. //时间判定范围为 4.0~5.0ms,
  62. //超过此范围则说明为误码,直接退出
  63. if ((time<3686) || (time>4608)){
  64. IE1 = 0;
  65. return;
  66. }
  67. //接收并判定后续的 4 字节数据
  68. for (i=0; i<4; i++){ //循环接收 4 个字节
  69. for (j=0; j<8; j++){ //循环接收判定每字节的 8 个 bit
  70. //接收判定每 bit 的 560us 低电平
  71. time = GetLowTime();
  72. //时间判定范围为 340~780us,
  73. //超过此范围则说明为误码,直接退出
  74. if ((time<313) || (time>718)){
  75. IE1 = 0;
  76. return;
  77. }
  78. //接收每 bit 高电平时间,判定该 bit 的值
  79. time = GetHighTime();
  80. //时间判定范围为 340~780us,
  81. //在此范围内说明该 bit 值为 0
  82. if ((time>313) && (time<718)){
  83. byt >>= 1; //因低位在先,所以数据右移,高位为 0
  84. //时间判定范围为 1460~1900us,
  85. //在此范围内说明该 bit 值为 1
  86. }else if ((time>1345) && (time<1751)){
  87. byt >>= 1; //因低位在先,所以数据右移,
  88. byt |= 0x80; //高位置 1
  89. }else{ //不在上述范围内则说明为误码,直接退出
  90. IE1 = 0;
  91. return;
  92. }
  93. }
  94. ircode[i] = byt; //接收完一个字节后保存到缓冲区
  95. }
  96. irflag = 1; //接收完毕后设置标志
  97. IE1 = 0; //退出前清零 INT1 中断标志
  98. }

大家在阅读这个程序时,会发现我们在获取高低电平时间的时候做了超时判断 if(TH1 >= 0x40),这个超时判断主要是为了应对输入信号异常(比如意外的干扰等)情况的,如果不做超时判断,当输入信号异常时,程序就有可能会一直等待一个无法到来的跳变沿,而造成程序假死。

另外补充一点,遥控器的单按按键和持续按住按键发出来的信号是不同的。我们先来对比一下两种按键方式的实测信号波形,如图 16-10 和 16-11 所示。


图16-10   红外单次按键时序图
 

图 16-11  红外持续按键时序图

单次按键的结果 16-9 和我们之前的图 16-8 是一样的,这个不需要再解释。而持续按键,首先会发出一个和单次按键一样的波形出来,经过大概 40ms 后,会产生一个 9ms 载波加2.25ms 空闲,再跟一个停止位的波形,这个叫做重复码,而后只要你还在按住按键,那么每隔约 108ms 就会产生一个重复码。对于这个重复码我们的程序并没有对它单独解析,而是直接忽略掉了,这并不影响对正常按键数据的接收。如果你日后做程序时需要用到这个重复码,那么只需要再把对重复码的解析添加进来就可以了。

  1. /*****************************main.c 文件程序源代码******************************/
  2. #include <reg52.h>
  3. sbit ADDR3 = P1^3;
  4. sbit ENLED = P1^4;
  5. unsigned char code LedChar[] = { //数码管显示字符转换表
  6. 0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
  7. 0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
  8. };
  9. unsigned char LedBuff[6] = { //数码管显示缓冲区
  10. 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
  11. };
  12. unsigned char T0RH = 0; //T0 重载值的高字节
  13. unsigned char T0RL = 0; //T0 重载值的低字节
  14. extern bit irflag;
  15. extern unsigned char ircode[4];
  16. extern void InitInfrared();
  17. void ConfigTimer0(unsigned int ms);
  18. void main(){
  19. EA = 1; //开总中断
  20. ENLED = 0; //使能选择数码管
  21. ADDR3 = 1;
  22. InitInfrared(); //初始化红外功能
  23. ConfigTimer0(1); //配置 T0 定时 1ms
  24. //PT0 = 1; //配置 T0 中断为高优先级,启用本行可消除接收时的闪烁
  25. while (1){
  26. if (irflag){ //接收到红外数据时刷新显示
  27. irflag = 0;
  28. LedBuff[5] = LedChar[ircode[0] >> 4]; //用户码显示
  29. LedBuff[4] = LedChar[ircode[0]&0x0F];
  30. LedBuff[1] = LedChar[ircode[2] >> 4]; //键码显示
  31. LedBuff[0] = LedChar[ircode[2]&0x0F];
  32. }
  33. }
  34. }
  35. /* 配置并启动 T0,ms-T0 定时时间 */
  36. void ConfigTimer0(unsigned int ms){
  37. unsigned long tmp; //临时变量
  38. tmp = 11059200 / 12; //定时器计数频率
  39. tmp = (tmp * ms) / 1000; //计算所需的计数值
  40. tmp = 65536 - tmp; //计算定时器重载值
  41. tmp = tmp + 13; //补偿中断响应延时造成的误差
  42. T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
  43. T0RL = (unsigned char)tmp;
  44. TMOD &= 0xF0; //清零 T0 的控制位
  45. TMOD |= 0x01; //配置 T0 为模式 1
  46. TH0 = T0RH; //加载 T0 重载值
  47. TL0 = T0RL;
  48. ET0 = 1; //使能 T0 中断
  49. TR0 = 1; //启动 T0
  50. }
  51. /* LED 动态扫描刷新函数,需在定时中断中调用 */
  52. void LedScan(){
  53. static unsigned char i = 0; //动态扫描索引
  54. P0 = 0xFF; //关闭所有段选位,显示消隐
  55. P1 = (P1 & 0xF8) | i; //位选索引值赋值到 P1 口低 3 位
  56. P0 = LedBuff[i]; //缓冲区中索引位置的数据送到 P0 口
  57. if (i < sizeof(LedBuff)-1){ //索引递增循环,遍历整个缓冲区
  58. i++;
  59. }else{
  60. i = 0;
  61. }
  62. }
  63. /* T0 中断服务函数,执行数码管扫描显示 */
  64. void InterruptTimer0() interrupt 1{
  65. TH0 = T0RH; //重新加载重载值
  66. TL0 = T0RL;
  67. LedScan(); //数码管扫描显示
  68. }

main.c 文件的主要功能就是把获取到的红外遥控器的用户码和键码信息,传送到数码管上显示出来,并且通过定时器 T0 的 1ms 中断进行数码管的动态刷新。不知道大家经过试验发现没有,当我们按下遥控器按键的时候,数码管显示的数字会闪烁,这是什么原因呢?单片机的程序都是顺序执行的,一旦我们按下遥控器按键,单片机就会进入遥控器解码的中断程序内,而这个程序执行的时间又比较长,要几十个毫秒,而如果数码管动态刷新间隔超过10ms 后就会感觉到闪烁,因此这个闪烁是由于程序执行红外解码时,延误了数码管动态刷新造成的。

如何解决?前边我们讲过中断优先级问题,如果设置了中断的抢占优先级,就会产生中断嵌套。中断嵌套的原理,我们在前边讲中断的时候已经讲过一次了,大家可以回头再复习一下。那么这个程序中,有 2 个中断程序,一个是外部中断程序负责接收红外数据,一个是定时器中断程序负责数码管扫描,要使红外接收不耽误数码管扫描的执行,那么就必须让定时器中断对外部中断实现嵌套,即把定时器中断设置为高抢占优先级。定时器中断程序,执行时间只有几十个 us,即使打断了红外接收中断的执行,也最多是给每个位的时间测量附加了几十 us 的误差,而这个误差在最短 560us 的时间判断中完全是容许的,所以中断嵌套并不会影响红外数据的正常接收。在 main 函数中,大家把这行程序“//PT0 = 1;”的注释取消,也就是使这行代码生效,这样就设置了 T0 中断的高抢占优先级,再编译一下,下载到单片机里,然后按键试试,是不是没有任何闪烁了呢?而中断嵌套的意义也有所体会了吧。

NEC协议红外遥控器相关推荐

  1. 【物联网毕设基础】单片机:NEC 协议红外遥控器

    文章目录 NEC 协议红外遥控器 NEC 协议红外遥控器 家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地.遥控器的基带通信协议很多,大概 ...

  2. 红外遥控c语言,NEC协议红外遥控器

    家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地.遥控器的基带通信协议很多,大概有几十种,常用的就有 ITT 协议.NEC 协议.Shar ...

  3. 16.3 NEC协议红外遥控器

    家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地.遥控器的基带通信协议很多,大概有几十种,常用的就有 ITT 协议.NEC 协议.Shar ...

  4. NEC协议——红外遥控的使用

    NEC协议是众多红外遥控协议的其中一种,下面以蓝桥杯的单片机开发板实现红外解码. 相关芯片与元器件介绍 此图为跳线帽的解法,在做红外通信时应接3,5:4,6. 此图左为红外发射装置和HX1838集成芯 ...

  5. 基于定时器捕获功能的红外解码程序(NEC协议)

    文章目录 前言 一.红外协议简介 1. ITT Protocol 2. NEC 协议 3. Nokia NRC17 协议 二.红外解码程序(NEC协议) 1. 软硬件环境及红外解码状态图 2. 基于定 ...

  6. 单片机毕业设计 stm32万能红外遥控器

    文章目录 1 简介 2 主要器件 3 实现效果 4 部分实现代码 5 最后 1 简介 Hi,大家好,学长今天向大家介绍一个学长做的单片机项目 基于单片机的万能红外遥控器 大家可用于 课程设计 或 毕业 ...

  7. stm32毕业设计 单片机万能红外遥控器

    文章目录 1 简介 2 主要器件 3 实现效果 4 部分实现代码 1 简介 Hi,大家好,学长今天向大家介绍一个学长做的单片机项目 基于单片机的万能红外遥控器 大家可用于 课程设计 或 毕业设计 选题 ...

  8. 毕业设计 嵌入式 万能红外遥控器

    文章目录 1 简介 2 主要器件 3 实现效果 4 部分实现代码 5 最后 1 简介 Hi,大家好,学长今天向大家介绍一个学长做的单片机项目 基于单片机的万能红外遥控器 大家可用于 课程设计 或 毕业 ...

  9. 【字符设备驱动】 -- NEC红外通信原理|红外协议|红外驱动|红外编解码|红外遥控器...

    目录 1. 红外基本介绍: 红外线的特点 红外线发射和接收 2.NEC协议介绍: NEC的数据格式: NEC协议中数据(DATA)段编码: NEC协议典型脉冲链: NEC载波调制: NEC载波解调: ...

  10. STC15系列 8系列解码红外遥控器(NEC协议)(12mhz)

    本次解码过程,使用[外部中断0],[定时器0]相互组合 废话少续,直接上源代码,只需要简单修改,即可完成配置 /*=========================================== ...

最新文章

  1. 自学python的书籍逐级推荐-适合初学者和经验的十大最佳Python书籍-2018
  2. POJ 3320 尺取法,Hash,map标记
  3. 一种导致android开发时无法生成R.java文件的原因
  4. python第七周答案_马哥2016全新Linux+Python高端运维班第七周作业
  5. MFC:多窗口函数、变量调用
  6. 丢失__EVENTTARGET _dopost Asp.net自带隐藏域和脚本的现象
  7. 计算机硬件:关于CPU的12个硬核干货!
  8. ios html图片相对路径,iOS 下加载本地HTML/js/css/image 等路径问题
  9. archive for required library...
  10. kail linux更新源、挂载
  11. 测试使用navicat工具将MySQL格式SQL文件导入到MogDB数据库
  12. Linux uart程序
  13. 学计算机笔画,学汉字学笔顺电脑版
  14. Java锁synchronized关键字学习系列之CAS和对象头
  15. 用户画像 客户消费模型表
  16. 直连路由和静态路由(实验)
  17. 【转】家庭影院的音频线改如何布置
  18. CSS 常见样式 特殊用法 贯穿线徽章箭头
  19. intel vPro LMS模块 Linux环境编译
  20. Elasticsearch-8.4.2 集群安装

热门文章

  1. 【练习】canvas——flappyBird
  2. Android解决Can't create handler inside thread that has not called Looper.prepare()
  3. Test类中的@testSetup标注 测试环境数据准备
  4. Windows 10安装labelImg(有压缩包链接)标注弹孔,生成.xml文件。
  5. MFC之对于文档类的DeleteContents和OnNewDocument说明29
  6. html怎么直接修改,如何编辑运行HTML网页文件(HTML编辑工具使用介绍)
  7. Pointer Generator Network 和 PEGASUS
  8. 一次简单的PC游戏汉化
  9. 小秘谈币|币圈永远不缺机会,就怕缺你在场内
  10. Training data-efficient image transformers distillation through attention