11706浏览
查看: 11706|回复: 20

[项目] 红外遥控 Arduino 实例

[复制链接]

先来说下制作这个DEMO的所经历的曲折以及知识点:

  • 学习NEC协议;
  • 学习AVR的定时与中断,因为Arduino是在AVR的基础上实现的;
  • 编程实现NEC解码;
  • 解码未成功,最终使用Ken Shirriff的解码类库,成功实现DEMO;

下面进入正题

1. 首先介绍下红外接收头

红外接收头有三个引脚如下图

三个引脚含义上图标的非常清晰:VOUT接模拟口,GND接GND,VCC接电源。

红外遥控器发出的信号是一连串的二进制脉冲码。为了使其在无线传输过程中免受其他红外信号的干扰,通常都是先将其调制在特定的载波频率上,然后再经红外发射二极管发射出去,而红外线接收装置则要滤除其他杂波,只接收该特定频率的信号并将其还原成二进制脉冲码,也就是解调.

工作原理:内置接收管将红外发射管发射出来的光信号转换为微弱的电信号,此信号经由IC内部放大器进行放大,然后通过自动增益控制、带通滤波、解调发、波形整形后还原为遥控器发射出的原始编码,经由接收头的信号输出脚输入到电器上的编码识别电路。


2. NEC协议

要想对某一遥控器进行解码必须要了解该遥控器的编码方式。我们的这个DEMO使用的遥控器的编码方式为:NEC协议。

特点:
(1)8位地址位,8位命令位
(2)为了可靠性地址位和命令位被传输两次
(3)脉冲位置调制
(4)载波频率38khz
(5)每一位的时间为1.125ms或2.25ms

逻辑0 和1 的定义如下图:


按键按下立刻松开的发射脉冲:


上面的图片显示了NEC的协议典型的脉冲序列。注意:这是首先发送LSB(最低位)的协议。在上面的脉冲传输的地址为0x59命令为0x16。一个 消息是由一个9ms的高电平开始,随后有一个4.5ms的低电平,(这两段电平组成引导码)然后由地址码和命令码。地址和命令传输两次。第二次所有位都取 反,可用于对所收到的消息中的确认使用。总传输时间是恒定的,因为每一点与它取反长度重复。如果你不感兴趣,你可以忽略这个可靠性取反,也可以扩大地址和 命令,以每16位!

按键按下一段时间才松开的发射脉冲:


一个命令发送一次,即使在遥控器上的按键仍然按下。当按键一直按下时,第一个110ms的脉冲不上图一样,随后每110ms重复代码传输一次。这个重复代码是由一个9ms的高电平脉冲和一个2.25ms低电平和560μs的高电平组成。

·重复脉冲:

注意:脉冲波形进入一体化接收头以后,因为一体化接收头里要进行解码、信号放大和整形,故要注意:在没有红外信号时,其输出端为高电平,有信号时为低电平,故其输出信号电平正好和发射端相反。接收端脉冲大家可以通过示波器看到,结合看到的波形理解程序。


3. 实现的效果以及器材

器材及数量:
     红外遥控器:1个;
     红外接收头:1个;
     LED灯:1个;
     220Ω电阻:1个;
     多彩面包线:若干;

实现效果:按下遥控器的EQ键盘LED亮,按下电源键LED灭。


4. 编码实现

根据NEC 特点和接收端的波形,将接收端的波形分成四部分:引导码(9ms 和4.5ms 的脉冲)、地址码16 位(包括8 位的地址码和8 位的地址的取反)、命令码16 位(包括8 位命令位和8 位命令位的取反)、重复码(9ms、2.25ms、560us 脉冲组成)。

利用定时器对接收到的波形的高电平段和低电平段进行测量,根据测量到的时间来区分:逻辑“0”、逻辑“1”、引导脉冲、重复脉冲。引导码和地址码只要判断是正确的脉冲即可,不用存储,但是命令码必须存储,因为每个按键的命令码都不同,根据命令码来执行相应的动作。

代码如下:
  • #define LED 7//LED灯
  • #define IR_IN 8 //红外接收
  • int Pulse_Width=0;//存储脉宽
  • int ir_code=0x00;//命令值
  • void timer1_init(void)//定时器初始函数
  • {
  •         TCCR1A = 0X00;
  •         TCCR1B = 0X05;//给定时器时钟源
  •         TCCR1C = 0X00;
  •         TCNT1 = 0X00;
  •         TIMSK1 = 0X00; //禁止定时器溢出中断
  • }
  • void remote_decode(void)//译码函数
  • {
  •         TCNT1=0X00;
  •         while(digitalRead(8))//是高就等待
  •         {
  •                 if(TCNT1>=1563) //当高电平持续时间超过100ms,表明此时没有按键按下
  •                 {
  •                         ir_code = 0xff00;
  •                         return;
  •                 }
  •         }
  •         //如果高电平持续时间不超过100ms
  •         TCNT1=0X00;
  •         while(!(digitalRead(8))); //低等待
  •         Pulse_Width=TCNT1;
  •         TCNT1=0;
  •         if(Pulse_Width>=140&&Pulse_Width<=141)//9ms
  •         {
  •                 while(digitalRead(8));//是高就等待
  •                
  •                 Pulse_Width=TCNT1;
  •                 TCNT1=0;
  •                 if(Pulse_Width>=68&&Pulse_Width<=72)//4.5ms
  •                 {
  •                         pulse_deal();
  •                         return;
  •                 }else if(Pulse_Width>=34&&Pulse_Width<=36)//2.25ms
  •                 {
  •                         while(!(digitalRead(8)));//低等待
  •                         Pulse_Width=TCNT1;
  •                         TCNT1=0;
  •                         if(Pulse_Width>=7&&Pulse_Width<=10)//560us
  •                         {
  •                                 return;
  •                         }
  •                 }
  •         }
  • }
  • void pulse_deal()//接收地址码和命令码脉冲函数
  • {
  •         int i;
  •         //执行8个0
  •         for(i=0; i<8; i++)
  •         {
  •                 if(logic_value() != 0) //不是0
  •                 return;
  •         }
  •         //执行6个1
  •         for(i=0; i<6; i++)
  •         {
  •                 if(logic_value()!= 1) //不是1
  •                 return;
  •         }
  •        
  •         //执行1个0
  •         if(logic_value()!= 0) //不是0
  •                 return;
  •        
  •         //执行1个1
  •         if(logic_value()!= 1) //不是1
  •                 return;
  •        
  •         //解枂遥控器编码中的command指令
  •         ir_code=0x00;//清零
  •         for(i=0; i<16;i++ ){
  •                 if(logic_value() == 1){ir_code |=(1<<i);}
  •         }
  • }
  • void remote_deal(void)//执行译码结果函数
  • {
  •         switch(ir_code)
  •         {
  •                 case 0xff00://停止
  •                 digitalWrite(LED,LOW);//LED亮
  •                 break;
  •                 case 0xfe01://VOL+
  •                 digitalWrite(LED,HIGH);//LED灭
  •                 break;
  •         }
  • }
  • char logic_value()//判断逻辑值“0”和“1”子函数
  • {
  •         while(!(digitalRead(8))); //低等待
  •         Pulse_Width=TCNT1;
  •         TCNT1=0;
  •        
  •         if(Pulse_Width>=7&&Pulse_Width<=10)//低电平560us
  •         {
  •                 while(digitalRead(8));//是高就等待
  •                 Pulse_Width=TCNT1;
  •                 TCNT1=0;
  •        
  •                 if(Pulse_Width>=7&&Pulse_Width<=10)//接着高电平560us
  •                         return 0;
  •                 else if(Pulse_Width>=25&&Pulse_Width<=27) //接着高电平1.7ms
  •                 return 1;
  •         }
  •         return -1;
  • }
  • void setup()
  • {
  •         unsigned char i;
  •         pinMode(LED,OUTPUT);//设置与LED连接的引脚为输出模式
  •         pinMode(IR_IN,INPUT);//设置红外接收引脚为输入
  • }
  • void loop()
  • {
  •         timer1_init();//定时器初始化
  •         while(1)
  •         {
  •                 remote_decode(); //译码
  •                 remote_deal(); //执行译码结果
  •         }
  • }
代码中的脉髋Pulse_Width比较值如7和10是如何算出来的呢?

引用Atommann的解释:

代码中用了 AVR 的 16 位 Timer/Counter 1,它的工作行为受几个寄存器的控制,这个可以在下面这个初始化函数里进行了设置: void timer1_init(void)//定时器初始函数 { TCCR1A = 0X00; TCCR1B = 0X05;//给定时器时钟源 TCCR1C = 0X00; TCNT1 = 0X00; TIMSK1 = 0X00; //禁止定时器溢出中断 } 其中把 TCCR1B 设置为 0x05,你看 atmega88/168/328 的数据手册第 131/132 页对这个寄存器的描述: 15.11.2 TCCR1B - Timer/Counter1 Control Register B 0x05 把 Timer/Counter 1 的时钟源设置成 clk_IO/1024,Arduino 的时钟频率是 16MHz,这里的 clk_IO 的频率也应当是 16MHz,Timer/Counter 1 的时钟频率就是 16MHz/1024 想象一下 Timer/Counter 的工作行为,它按照前面设定的时钟源进行计数,TCNT1 就是它的计数值,我们最开始把它清 0,然后开启它,它就开始数数。事实上我们已经知道了 Timer/Counter 1 的时钟频率,那就可以算出它的周期(就是数字每加 1 的时间有多长) 时钟频率 = 16MHz/1024 = 16000000/1024 周期 = 1/时钟频率 = 1/(16000000/1024) 上面的时间单位是秒,乘上 1000000 就把单位换成 us 周期 = 1000000/(16000000/1024) = 64us 上面的例子程序里有下面的语句: if(Pulse_Width>=7&&Pulse_Width<=10)//低电平560us Pulse_Width 就是取的 TCNT1 的值 64us*7 = 448us 64us*10 = 640us 560us 正好介于两者之间,就是这样算的。
以上知识点需要看点AVR的中断以及定时器相关知识。

5. 上面编码实现存在的问题

上述编码中并未真正实现预定的效果,主要原因是因为在对0和1的解码不成功,脉宽不匹配。我尝试使用串口输出调试,才发现这一问题。希望哪位兄弟给指点下,可联系我,非常感谢。

6. 使用 Ken Shirriff 的解码类库

Ken Shirriff的解码类库 IRremote,它在解码和发射红外线指令方面堪称一流,它尝试匹配不同生产厂商使用的标准,如NEC, Sony SIRC, Philips RC5, Philips RC6, 和 raw。下载IRremote

  • #include <IRremote.h>
  • int RECV_PIN = 8;
  • int LED=7;
  • IRrecv irrecv(RECV_PIN);
  • decode_results results;
  • void setup()
  • {
  •         Serial.begin(9600);
  •         irrecv.enableIRIn(); // Start the receiver
  •         pinMode(LED,OUTPUT);
  • }
  • void loop() {
  •         if (irrecv.decode(&results)) {
  •                 if(results.value==0xFFE01F){
  •                    digitalWrite(LED,HIGH);
  •                 }else if(results.value==0xFFA25D){
  •                    digitalWrite(LED,LOW);
  •                 }
  •                 irrecv.resume(); // Receive the next value
  •         }
  • }

参考资料:《基于Arduino的趣味电子制作》


Eric  初级技神

发表于 2014-12-20 21:42:46

好详细的应用啊~收藏了,谢了
回复

使用道具 举报

Grey  中级技匠

发表于 2014-12-21 11:17:37

感谢分享,Mark一下
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-21 16:22:31

Eric 发表于 2014-12-20 21:42
好详细的应用啊~收藏了,谢了

做出来的话 录个视频吧
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-21 16:22:44

Grey 发表于 2014-12-21 11:17
感谢分享,Mark一下

其他您的成功
回复

使用道具 举报

drink  中级技师

发表于 2014-12-21 16:40:28

钻研得好深啊
回复

使用道具 举报

野马草上飞  初级技师

发表于 2014-12-21 18:48:55

牛X啊,不能忍
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-21 19:07:24


都是大神的作品 我转来让大家伙学学:P
回复

使用道具 举报

社区活动向导  管理员

发表于 2014-12-21 21:51:49

谢谢科普啊。。。需要这样的科普帖,可怜我们这些小白
回复

使用道具 举报

Phoebe  高级技匠

发表于 2014-12-21 22:09:28

几年前我做过一个万能遥控器,那个时候玩了一段时间的红外,还是挺有意思的。改天我找下资料还在不,拿出来分享下哈哈
回复

使用道具 举报

Youyou  初级技匠

发表于 2014-12-21 23:49:55

第一种代码非常不错,配合上时序图,能深入了解红外协议。
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-22 13:30:52


做这个的大神分享的  转来大家看看
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-22 13:31:32

Youyou 发表于 2014-12-21 23:49
第一种代码非常不错,配合上时序图,能深入了解红外协议。

您也多多在本版块发帖 供大家学习
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-22 13:31:59

Phoebe 发表于 2014-12-21 22:09
几年前我做过一个万能遥控器,那个时候玩了一段时间的红外,还是挺有意思的。改天我找下资料还在不,拿出来 ...

期待您作品的分享 期待哦
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-22 13:32:31

社区活动向导 发表于 2014-12-21 21:51
谢谢科普啊。。。需要这样的科普帖,可怜我们这些小白

我也是小白哦 愿意为大家搜这样的帖子
回复

使用道具 举报

luna  初级技神

发表于 2014-12-23 10:25:08

非常认真的教程,学习起来
回复

使用道具 举报

Angelo  初级技匠

发表于 2014-12-23 10:36:41

LZ真得很会钻研啊, 要是我就直接用Arduino提供的库了~~:lol
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-23 18:29:38

luna 发表于 2014-12-23 10:25
非常认真的教程,学习起来

希望能有器材做起来
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-23 18:30:08

Angelo 发表于 2014-12-23 10:36
LZ真得很会钻研啊, 要是我就直接用Arduino提供的库了~~

根据这个可否用库 做补充呢
回复

使用道具 举报

Eric  初级技神

发表于 2014-12-24 21:35:50

大连林海 发表于 2014-12-21 16:22
做出来的话 录个视频吧

好的,这个东东我手头还有预料,哈哈哈,待弄好后上视频
回复

使用道具 举报

大连林海  初级技神
 楼主|

发表于 2014-12-25 18:05:29

Eric 发表于 2014-12-24 21:35
好的,这个东东我手头还有预料,哈哈哈,待弄好后上视频

期待您的视频分享 为本帖丰富内容哦
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

为本项目制作心愿单
购买心愿单
心愿单 编辑
[[wsData.name]]

硬件清单

  • [[d.name]]
btnicon
我也要做!
点击进入购买页面
上海智位机器人股份有限公司 沪ICP备09038501号-4

© 2013-2024 Comsenz Inc. Powered by Discuz! X3.4 Licensed

mail