Chinaunix首页 | 论坛 | 博客
  • 博客访问: 138687
  • 博文数量: 35
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 289
  • 用 户 组: 普通用户
  • 注册时间: 2010-06-21 20:39
文章分类

全部博文(35)

文章存档

2010年(35)

我的朋友

分类: 嵌入式

2010-06-30 08:29:08

一.         心得的概述部分
最近,尝试制作了一个简单的学习型万能遥控器的prototype,期间发现有关资料并不是很好找,这里将我的制作过程分享出来,希望对大家能有所帮助。
首先要提到的是万能遥控器。相信大部分人应该都听过这个东西,分类上讲,传统可分为编码型和学习型。编码型的遥控器内部有各大电器制造厂商生产的遥控器的控制码编码格式,使用时只要选定机型,再遥控时,遥控器会将其编码提取出来,再和38khz的周期方波或正弦波进行ASK调制就可以驱动红外线发射管进行控制信号发射了。缺点是,使用的时候需要有一个冗长的机器型号手册来查。另外一种学习型遥控器的机理就相对比较好理解了,对遥控器发出的信号进行记录,换句话说这类遥控器还要有红外线型号的接受部分。将遥控器发出的信号进行记录(当然是解调后的信号,否则信号频率较高,要记录的话会需要更快速度和更多的存储空间)。待控制时,只要将这些记录的点原样再生再调制就可以实现和遥控器一样的功能了。
传统的实现方法,或者说我之前在网上查到的一些资料都是用单片机来实现的。我手头有的两块实验板,一块是单片机MSP430F449,还有一块是FPGACYCLONE2—EP2C5Q208C8。实现的方案当然有两种,一种是用MSP430的定时模块(basic timertimerAtimerB,看门狗定时器),另一种就是直接用FPGA的时钟进行分频来实现各种信号的产生。老实说,这样的顺序型的控制,用单片机会将整个控制变得简单很多,但由于好久不用了,用到其定是模块时,查资料,发现其各类寄存器CCR0,TACTL,DC0….有关系统时钟频率设置的,有关定时器时钟选择的一大堆寄存器,感觉特别烦,特别是和FPGA比起来实在是麻烦太多了(FPGA直接用内部的10Mhz信号进行分频就能得到我想要的时钟频率)。当即决定用FPGA实现。
实际制作起来第一个要考虑的问题就是红外线的接收和发射。查到的文章写得比较好的一篇是《红外线遥控原理》(具体作者不详)。还有一篇是匠人的一篇文章《红外遥控器信号的接收和转发》,这篇文章其实已经给出了学习新遥控器的制作方法。有关于红外线的接收,其详细的电路模型和参数都给出了。用的CX20106这个带有前置放大,滤波,增益自调整的接收芯片配合一个红外线接收管,就实现了带有38khz载频红外线信号解调。其输出直接就是方波(TTL电平)。但我在实际接好电路后,发现CX20106的输出端即使没有输入也一直有比较乱的方波输出。这样检测起来就会很麻烦(虽然在有遥控器信号输入时,输出会变的很规整和遥控器的标准输出完全吻合)。但对于信号的检测,就必须不断地检测是否有前置码到来(一个交替的4.5ms高低电平,有关前置码的内容后面会提到)。会让一个简单的问题变得复杂了,因为前置码随遥控器的种类不同而变化。
二.基础知识
以上使一些概述部分,接下来进入正题。为了能将整个过程说明白,首先介绍一下遥控器的编码。这里会借用以上提到的两篇文章中的一些话和图片,具体部分我会加入我的理解。
红外线遥控是目前使用最广泛的一种通信和遥控手段。由于红外线遥控装置具有体积小、功耗低、功能强、成本低等特点,因而,继彩电、录像机之后,在录音机、音响设备、空凋机以及玩具等其它小型电器装置上也纷纷采用红外线遥控。工业设备中,在高压、辐射、有毒气体、粉尘等环境下,采用红外线遥控不仅完全可靠而且能有效地隔离电气干扰。
 
1 红外遥控系统
 
通用红外遥控系统由发射和接收两大部分组成,应用编/解码专用集成电路芯片来进行控制操作,如图1所示。发射部分包括键盘矩阵、编码调制、LED红外发送器;接收部分包括光、电转换放大器、解调、解码电路。
 
编码的方式来说其实每个厂家都不尽相同。这里给出两个例子。首先是日本NECuPD6121G。他们的编码采用的脉宽编码方式。也就是对于逻辑‘1’和逻辑‘0’,对应的方波信号高低电平时间不同来加以区分。
 
(注:这里的图相比原图的文章我修改了一下,因为原“1”的图有误)
这样,根据低电平的时间就可以判断一个信号究竟是“1”还是“0”了。通常一个控制信号是一串逻辑电平。上述“0”和“1”组成的32位二进制码经38kHz的载频进行二次调制以提高发射效率(这里的调制实际就是直接相称(ASK),当然38khz的信号并不一定非得是正弦波,方波也可以,虽然很多教材上都用的是正弦波)达到降低电源功耗的目的。然后再通过红外发射二极管产生红外线向空间发射,如图3所示。
 
可以看见,一个完整的控制信号,包括前导码,识别码,操作码。不同的遥控器,虽然会有差别,但通常都是有这几部分组成的。前导码通常都是一个高电平加一个低电平,比如这个是9ms的高电平加一个4.5ms的低电平。接下来是用户识别码,这款遥控器的码是8位的用户识别码。识别码的目的就是让被控制的设备(电视,空调等)接收到其他控制器的信号后能尽早丢弃掉,一旦检测识别码不同就没必要继续接收这个信号了。比如这里某电视的用户识别码是0x01,那接收到信号的电视会先检测该用户识别码是否为0x01,若不是就干脆不进行接收了。
接下来还会发送识别码的反码,其实对于接下来的操作码也会发送器反码。之所以发送的数据内容都会带上其反码我分析主要有两个作用。第一,着相当于发送两次数据内容,增加数据冗余,可以用来检测误码。第二,我们可以发现原码和反码加起来其时间是固定的。本来由于脉宽编码方式使得发送信号时间不固定的,但由于原码+反码的时间是固定的,使得整个控制信号的时间最终固定,这很有利于接收器的设计。试想,对于一个时间不固定的信号,和一个时间固定的信号,哪一个更好判断和接收处理呢。
这里再给出一个例子,是我这次制作的过程中的一台空调的编码分析。在我手头有一台美的空调R51D,所以此次制作过程方案确定都是依据它来进行的。
美的空调(R51D)协议 一次按键重复发两次,前后两次完全一样,上个图片: 
 
前、后两次完全一样,下面再发一次前面部分的图片: 

      可以看见,他同样也是由引导码、8位地址码、8位地址反码、前8位数据、前8位数据反码、后8位数据、后8位数据反码、后置单脉冲
其中地址码是:1011 0010B2)。这里所说的地址码也就是设备识别码。
 
下面就各部分分解说明:
引导码:4.5mS发送、4.5mS间隔:
 
 
 
1”:0.56uS发送、0.56*3uS间隔: 
 
 
0”:0.56uS发射、0.56uS间隔:
 
 
后置单脉冲:0.56发送、4.5mS间隔:
 
 
后置单脉冲的作用就是结束码,作为整个码序列的结尾。
附上几个按键的16位数据码:
摆风:6B E0
强劲:F5 A2
清新:F5 A3
数显:F5 A5
关:7B E0
开:根据温度不同数据码不同,下面一一列表:
17度:1F 08    18度:1F 18    19度:1F 38   
20度:1F 28    21度:1F 68    22度:1F 78  
23度:1F 58    24度:1F 48    25度:1F C8    
26度:1F D8    27度:1F 98    28度:1F 88   
29度:1F A8    30度:1F B8   
风向:1F 44
 
相信通过以上两个例子的分析,应该已经将遥控器有关的编码知识介绍的很清楚了。
 
三.电路设计部分
 
电路的设计,主要包括红外线接收和发送部分。
接收模块
之前在概述内容里我提到了,利用匠人给出的CX20106电路(其实很多有关红外下接受或超声波接收电路用的都是这个芯片),在输出会在无信号输入时仍有方波输出,造成干扰。产生的原因可能是外界干扰,也肯能是由于电路焊接工艺导致的,总之我花了不少功夫就是没能消掉这干扰。其实我选择这个芯片也主要是因为相关资料上都用的是它,也并没有什么具体的原因(网上连这个芯片的数据手册都找不到)。这款SONY公司的芯片其实已经很老了,但看来还算是个经典之作被传承了下来。在买器件时,发现接收管还有三个脚的。我印象里红外线接收管就是一个对红外线敏感材料制作而成的二极管,在又红外线照射时会产生电压形成接受信息。但后来了解到这种三个脚的接收管更方便,其内部包含了接收管,前置放大,滤波,等功能,输出就是TTL电平的方波。看起来虽然和一个普通的二极管不太一样,一般是方脑袋,如下图所示为一个例子。
 
其三个管脚,分别为+5VGND,和输出端。在接电路时千万不要搞错,否则这东西会变的很烫,我第一次时就搞错了,但好在没有烧坏。其管脚定义一般如图所示。有了这个器件可方便多了,代替了之前的8个电路元件(CX20106,红外线接收管,3个电容,3个电阻)。最关键是效果很好,输出几乎是没有关绕的,如果你想输入更干净,可以用金属片或网来个屏蔽保护罩,效果会更佳。
这里有的人可能会有疑问,输出并没有解调但为什么会是方波呢,应该带有38khz的信号啊,其主要原因是低通滤波本身就可以作为ASK信号的解调方式之一,所以输出直接叫相当于解调后的信号了。
发送模块
电路相比之前简单多了,虽然可以用三极管来搭电路,但更简单的方式是直接用FPGA管脚与地之间串上一个发射用的红外线二极管。合起来中的电路模块示意图。
 
四.FPGA程序设计
 
有了以上的外围电路,就可以开始FPGA内部控制的设计了。其设计思想还是比较简单的,按下记录按钮后,可以用遥控器对准接收管按下想要录制功能的按键。按下播放按键后会持续发出之前录制的功能信号。
通常遥控器的控制信号都不会太长,大概100-200ms左右吧。当然也会有特殊情况(任何事物都是这样)。因此这里我们取比较常见的情况200ms,也就是说FPGA录制外界信号长达200ms。但究竟用多少频率的时钟作为采样信号呢(当然要采样,不可能将输入的方波连续的记录下来,但并不是指AD这样的采样)。
我们这里的信号里会有0.56ms这个持续时间。因此我最初准备采用0.01ms作为记录的点数的时钟,这意味着总共记录时间(4.5ms+4.5ms+(0.56*2+0.56*4)*24+4.5+0.56)*2=189.4ms 大概200ms吧,那就是20000个点。我准备用一个std_logic_vector(20000 downto 0)来记录这些点。根据计算,FPGA的容量完全可以记录下这些点。但真正操作的的时候问题就来了。Quartus2会用非常长的时间来进行编译(500个点用3分钟,1000个点用5分钟,1500电用13分钟,2000个点就是数个小时,可想而知20000个点会是什么时间数量级,可能一年吧~~~)。具体原因我猜想可能是要把大量的逻辑门转化为存储用(比如20000D触发器,加一大堆译码编码器来对存取地址进行控制)。因此我面临的问题就来了,要么我外加一块RAM(当然这是非常好的一劳永逸的方法,如果真的想制作一个可以记录多个功能的学习新遥控器,这样做是唯一的的选择),但对于我这个制作来说,我并没有想记录很多的点,因此扩展RAM(复杂的操作时序逻辑,占用很多管脚)就显得不太划算了。
另一个方法就是想办法减少要记录的点数。通过之前的介绍,我知道R51D的控制信号其实只要记录一半就够了,但即使这样10000个点的存储同样是无法接受的。另一方面这样的做法会使得最后作品通用性大大降低(并不是每款遥控器都是两次发送相同的内容)。看来,只能降低采样的点数上下功夫了。0.56ms的确是最低时间,但这并不是说就一定得用0.01ms来记录这个时间,试想比较极端的情况,对于非前导码和后置码,由于数据都是以0.56ms的时间为单位的,一次只要用0.56ms作为记录时用的时钟信号就足以了,这样点数就减少成为原来的56分之一了。但这样也不具备通用性,万一其他的遥控器并不是以0.56ms为基本单位呢,因此选择一个通用性较好的时钟更为重要。通常码的设置都是在百Hz的单位附近,也就是码元的宽度都在0.1ms附近。
记录点我最终选为1000个,共记录时间200ms,因此记录用的时钟就用200ms/1000=0.2ms的时钟。经过实验,完全可行。由于没有进行很多的实验,不知道对于其他的遥控器是否可以,不过即使不可以,调整一下记录用的时钟,和记录点数就一定可以。
 
 
 
说起来好像很麻烦其实主控制程序还是很简单的。
 
 
 
 
process(clk02ms) ---clk02ms是一0.02ms的时钟,由分频产生
variable countforout:integer range 0 to 1000; --用来计数
variable flag,stb,outflag:std_logic;    --一些标志位
begin
      if clk02ms'event and clk02ms='1'then
        if BTN(2)='0' then ---记录用的按键
                    flag:='1';
        end if;
 
             if BTN(3)='0' and flag='0' then ---播放用的按键,确保没在记录时启用
                    outflag:='1';
        end if;
       
             if outflag='1' then --播放时的代码
          recors:=ramforcontrol(countforout);
--这个ramforcontrol是向量用来记录点
                countforout:=countforout+1;
                if countforout=1000 then
              countforout:=0;
              outflag:='0';
           end if;
             end if;
 
             if flag='1' then
                 if stb='0' and b43='0' then –b43是输入的信号,解调出来是反码
                           stb:='1';            --实际上是按键允许后,等待外界有低
                           countlop:=0;        --电平才开始记录(说明有输入了)
                           flag:='0';
                    end if;
             end if;
            
             if stb='1' then
           flag:='0';
           if countlop<1000 then
                     ramforcontrol(countlop):=b43;
              countlop:=countlop+1;
           elsif countlop=1000 then
              countlop:=0;
              stb:='0';
           end if;
         end if;
   
    end if;
end process;
以下之前的一些全局变量的定义
shared variable ramforcontrol:std_logic_vector(0 to 1000);
shared variable countlop:integer range 0 to 1000;
shared variable recors:std_logic;
这是一些管脚信号,写在process
b43 <=B4_GIO(7); --输入信号
B4_GIO(3)<=(not recors) and clk38khz; --clk38khz是分频产生的38khz信号
 
五.写在最后
这次的制作过程很长一段时间都在用来解决那个超大的记录,花了34天的时间。另外红外的信号解调出来,很难说究竟是反码还是原码,具体情况完全可以焊接好电路再在软件上调整,录下来的信号要是反码输出时记得取个反就好。这个倒不是什么太大的问题。另外信号接收时,用手将那个三脚接收管后面罩上效果会更好。
这样的电路系统正如之前所说配上扩展ram就能以成品身份亮相了。这个主要是基于记录点的。当然有了那张协议码表,也可以自己产生准确的控制信号。这里给出一个VHDL程序。是我测试时用过的。也是针对R51D的,准确的产生关机信号,和开机信号,用按键进行功能切换。
process(clk001ms)
variable countms:integer range 0 to 104;
variable stb:std_logic ;
variable innercount:integer range 0 to 1000;
variable innerchange:std_logic;
variable ram:std_logic_vector(0 to 47):=x"B24d7B84E01F"; --关机信号
begin
if clk001ms'event and clk001ms='1'then
    if BTN(0)='0' and stb='0' then
          stb:='1';
          countms:=0;
          innercount:=0;
          innerchange:='0';
    end if;
    if BTN(1)='0' then
          if ram=x"B24d1FE028d7" then
             ram:=x"B24d7B84E01F";
          else
             ram:=x"B24d1FE028d7";     --开机信号
          end if;
          
    end if;
 
    if stb='1' then
       if countms=0 or countms=52 then
                    innercount:=innercount+1;
                    b4tep<='1';
          if innercount=450 then
                           countms:=countms+1;
                           innercount:=0;
          end if;
 
       elsif countms=1 or countms=53 then
                    innercount:=innercount+1;
                    b4tep<='0';
                    if innercount=450 then
                           countms:=countms+1;
                           innercount:=56;
                           innerchange:='0';
                    end if;
                   
       elsif countms=50 or countms=102 then
                    innercount:=innercount+1;
                    b4tep<='1';
                    if innercount=56 then
                           countms:=countms+1;
                           innercount:=0;
                    end if;      
                   
       elsif countms=51 or countms=103 then
                    innercount:=innercount+1;
                    b4tep<='0';
                    if innercount=450 then
                           countms:=countms+1;
                           innercount:=0;
                           if countms=104 then
                           stb:='0';
                        end if;
                    end if;                       
                   
                          
          elsif countms<50 then
             if innerchange='0' then
                b4tep<='1';
                innercount:=innercount-1;
                if innercount=0 then
                              innerchange:='1';
                              if (ram(countms-2)='0') then
                                  innercount:=56;
                              else
                                  innercount:=168;
                              end if;
                           end if;
                  else
                           b4tep<='0';
                innercount:=innercount-1;
                if innercount=0 then
                              innerchange:='0';
                              countms:=countms+1;
                              if countms=50 then
                                 innercount:=0;
                              else
                                   innercount:=56;
                              end if;
                           end if;
                    end if;
         elsif countms<102 then
                    if innerchange='0' then
                b4tep<='1';
                innercount:=innercount-1;
                if innercount=0 then
                              innerchange:='1';
                              if (ram(countms-54)='0') then
                                  innercount:=56;
                              else
                                  innercount:=168;
                              end if;
                           end if;
                  else
                           b4tep<='0';
                innercount:=innercount-1;
                if innercount=0 then
                              innerchange:='0';
                              countms:=countms+1;
                              if countms=102 then
                                 innercount:=0;
                              else
                                   innercount:=56;
                              end if;
                           end if;
                    end if;              
     end if;
      end if;
end if;
end process;
 
 
具体代码我这里不解释了。
文章写得很长,但看完后会对初学者很有帮助的。
阅读(13464) | 评论(0) | 转发(1) |
给主人留下些什么吧!~~