移植易,基于stm8红外遥控器解码程序,并介绍如何快速移植.

2019-12-16 22:38发布

本帖最后由 i7gly 于 2014-5-8 21:04 编辑

学习单片机三年,闷骚型,一直都是自己捣鼓,这是第二次写红外解码程序,第一次写是两年前,不堪入目;好吧,废话多了.
此程序针对快速移植,并功能齐全,可判断遥控按键长按状态;
当然,如果你对ROM,RAM是非常吝啬的,那就没必要看了,因为这个程序没有对任何CPU做优化,不算是最精简的.用了很多预定义宏和注释,看起来很复杂,其实真的很复杂的说.
程序主要原理步骤:
用单片机内部计时器产生0.5ms时间片轮,用此时间基数进行红外解码判断,
所以无论是什么单片机,你只需要会用计时器做个0.5ms中断就可以用此程序解码了.
代码运行环境:STM8S903K3,8M晶振,4M工作周期
计时器用TIMER6,红外接收管脚在PA3;
程序编译环境:IAR FOR STM8
上程序,
首先是头文件 IR.h的内容
  1. #ifndef _IR_H_
  2. #define _IR_H_

  3. //功能定义,如不需此功能,则注释掉该行预定义
  4. //#define _IR_EN_HOLD_CHEAK        //按键长按检查
  5. #define _IR_EN_UN_CHEAK       //反码检查  
  6. //#define _IR_EN_USER_CHEAK     //指定用户码检查,如果你开启了它,代表你还要定义IR_USER_CODE的内容

  7. //遥控器的用户码,用户反码,判断是否为当前指定遥控器操作
  8. //如果开启红外用户码检查,则需定义
  9. #define IR_USER_CODE 0x00

  10. //接收多个重复码才触发HOLD状态,一个重复码大概为100ms
  11. //这是第一次触发HOLD的时间,默认大概1s,也就是按着遥控器不放1s后才触发HOLD状态
  12. #define IR_SET_HOLD_TIME_FIRST 10
  13. //重复触发HOLD状态的时间,默认为一个重复码的时间
  14. #define IR_SET_HOLD_TIME_AGAIN 1


  15. extern unsigned char ir_status ;
  16. //共5种红外接收状态,直接使用if判断  例:if(IR_OK) {...}
  17. //注意,读取到需要的状态后应调用Read_Ir_Code函数取走按键值
  18. //Read_Ir_Code函数会清除红外接收状态
  19. #define IR_NO_STATUS (ir_status == 0)
  20. #define IR_ING     (ir_status & 0x02)
  21. #define IR_OK      (ir_status & 0x04)
  22. #define IR_HOLD    (ir_status & 0x08)
  23. #define IR_ERROR   (ir_status & 0x10)


  24. void Ir_Init(void);
  25. //调用时小心,函数返回值为一次性读取,请不要多次用if判断
  26. //正确使用方法为用新变量读取按键值再多次判断
  27. unsigned char Read_Ir_Code(void);
  28. void Read_Ir_All(unsigned char* Pstr);

  29. #endif
复制代码

----------------------------------------------------------------------------------------------------------------------------------------------

然后是IR.c文件的内容,有点长
  1. /*
  2. 使用环境建议单片机机器周期大于1M,
  3. 也就是如果你单片机是传统的51,则晶振最好大于等于12M
  4. 虽然接收值通过全局变量定义的,但是数值只能通过函数调用才能读取
  5. 此方法为了防止读取数值时忘记清除IR状态,造成多次触发
  6. */
  7. #include <iostm8s903k3.h>
  8. #include "IR.h"

  9. //遥控码值储存;
  10. unsigned char ir_code[4]={0,0,0,0};

  11. //接收的用户码,用户反码,指令码,指令反码.这只是定义给你们看的,没有任何函数用到,可以删除
  12. #define user_code        (ir_code[0])
  13. #define un_user_code     (ir_code[1])
  14. #define command_code     (ir_code[2])
  15. #define un_command_code  (ir_code[3])

  16. //下面是定时需要用到的变量,常量的定义
  17. unsigned char ir_timer;  //计时基数,
  18. #define ZERO_TIMER ir_timer = 0

  19. //下面是接收状态的变量,宏定义
  20. unsigned char ir_status = 0;
  21. //共5种状态,分别为:接收预备,接收中,接收完成,按键长按,接收错误(没有用到),所有条件互斥
  22. #define RESET_IR_STATUS ir_status = 0
  23. #define SET_IR_ING     ir_status = 0; ir_status |= 0x02
  24. #define SET_IR_OK      ir_status = 0; ir_status |= 0x04
  25. #define SET_IR_HOLD    ir_status = 0; ir_status |= 0x08
  26. #define SET_IR_ERROR   ir_status = 0; ir_status |= 0x10

  27. #ifdef _IR_EN_HOLD_CHEAK
  28. //还有,IR_HOLD状态判断使能变量,本想精简到局部变量的,,,还是没能,,,
  29. unsigned char ir_hold_status;
  30. //初次触发时间跟再次触发时间软件做成不一样,所以要定义两个判断状态
  31. #define IR_HOLD_STATUS_FIRST 1
  32. #define IR_HOLD_STATUS       2
  33. #define SET_IR_HOLD_STATUS_FIRST ir_hold_status = IR_HOLD_STATUS_FIRST
  34. #define SET_IR_HOLD_STATUS ir_hold_status = IR_HOLD_STATUS
  35. #define RESET_IR_HOLD_STATUS ir_hold_status = 0
  36. #endif

  37. //码值中断间断时间定义,单位为0.5ms,取整数,最好不要修改了
  38. //起始时间,9+4.5ms = 13.5ms
  39. #define IRT_START 27
  40. //逻辑1,  0.56+1.68 = 2.25ms
  41. #define IRT_LOGIC_1 4      
  42. //逻辑0,  0.56+0.56 = 1.12ms
  43. #define IRT_LOGIC_0 2
  44. //连发码,连发码为9ms+2.5ms+0.56ms+97.94ms,这里我们只取前面一段,也就是9+2.5=11.5ms
  45. #define IRT_HOLD    20

  46. void Decode(void);
  47. void Timer_Add(void);

  48. //接收红外IO配置为下降沿中断
  49. //配置计时器每0.5ms中断一次
  50. void Ir_Init(void)
  51. {
  52.   RESET_IR_STATUS;
  53.   asm("sim");         //禁止中断,进入中断3级状态;设置中断时必须进入3级状态
  54.   PA_DDR &= 0xF7;       //PA3为红外接收中断输入脚
  55.   PA_CR1 &= 0xF7;      
  56.   PA_CR2 |= 0x08;

  57.   EXTI_CR1 &= 0xfc;
  58.   EXTI_CR1 |= 0x02;     //PD口中断为下降沿中断
  59.   
  60.   //计数器配置
  61.   CLK_PCKENR1 |= 0x10; //使能TIM6时钟
  62.   TIM6_PSCR = 0x05;    //32分频,预分频值,需更新事件计数器才有效
  63.   TIM6_CR1 = 0x80 ;   //
  64.   TIM6_IER = 0x01 ;   //允许更新中断
  65. //  TIM6_CNTR = 256-125;     //计数器值,125个数为0.5ms,由于是八位自动重装计数,所以为256-125
  66.   TIM6_ARR  = 256-125;     //自动重装值
  67.   TIM6_CR1 |= 0x01;        //开启计时器
  68.   asm("rim");         //使能中断
  69. }

  70. //计数器中断
  71. //用到STM8的TM6更新中断
  72. //直接调用Timer_Add()函数
  73. #pragma vector=0x19
  74. __interrupt void TM6(void)
  75. {
  76.   TIM6_SR_UIF = 0;
  77.   Timer_Add();
  78. }

  79. //红外管脚中断
  80. //下降沿中断,只需要在中断里调用Decode函数就可以了,方便吧
  81. #pragma vector=5
  82. __interrupt void PD0_IR(void)
  83. {
  84. Decode();
  85. }

  86. //下面这两个函数是给外部文件调用的.
  87. //读接收值,注意注意,一次有效,如果读取值操作成功,,则IR接收状态将会清零,HOLD状态也不例外
  88. //返回值为指令码
  89. unsigned char Read_Ir_Code(void)
  90. {
  91.     RESET_IR_STATUS;
  92.     return ir_code[2];
  93. }

  94. //好吧,我们再做一个函数,用于读取所有的接收值
  95. //使用说明,调用后将一如既往清除接收状态
  96. //形参指针应该指向4个连续的unsigned char内存,最简单的放大,定义一个数组,把首地址给它
  97. //不要问返回值在哪,,,
  98. void Read_Ir_All(unsigned char* Pstr)
  99. {
  100.     RESET_IR_STATUS;
  101.     (*Pstr) = ir_code[0];
  102.     Pstr++;
  103.     (*Pstr) = ir_code[1];
  104.     Pstr++;
  105.     (*Pstr) = ir_code[2];
  106.     Pstr++;
  107.     (*Pstr) = ir_code[3];
  108. }


  109. /*********************下面的这解码函数就不需要修改了************************/
  110. //解码函数,过程冗长
  111. void Decode (void)
  112. {
  113.   #define READ_SIZE_MAX 32          //接收数据长度,8位地址码,8位地址反码,8位用户码,8位用户反码
  114.   static unsigned char read_size;   //接收长度暂存,用于计算现接收数据的长度
  115.   unsigned char* P_ir_code;           //接收用的指针
  116. #ifdef _IR_EN_HOLD_CHEAK
  117.     static unsigned char ir_hold_time;  //  长按状态累计基数变量
  118. #endif
  119. #ifdef _IR_EN_UN_CHEAK
  120.   unsigned char cheak1,cheak2;        //IAR环境下不能直接用数组进行判断,只能新建两个变量了,用于进行接收验证
  121. #endif
  122.   
  123.   //哈哈,开始吧
  124.   //接收中状态,接收码值并赋值给变量
  125.   if(IR_ING && (read_size < READ_SIZE_MAX))
  126.   {
  127.     //定义指针位置
  128.     P_ir_code = ir_code;
  129.     P_ir_code += (read_size/8);
  130.     //逻辑1
  131.     if((ir_timer>IRT_LOGIC_1-1)&&(ir_timer<IRT_LOGIC_1+2))
  132.     {
  133.       (*P_ir_code) |= (1<<(read_size%8));
  134.     }
  135.     //接收位置+1;逻辑0就不判断了,非1既0
  136.     read_size ++;
  137.   }
  138.   /************************************************************/
  139.   //如果超过了接收长度,那就接收完成了,接收完成还要判断数值对不对
  140.   if(IR_ING && (read_size > READ_SIZE_MAX - 1 ) )
  141.   {
  142. #ifdef _IR_EN_USER_CHEAK
  143.     if(ir_code[0] == IR_USER_CODE)
  144.       {SET_IR_OK;}
  145.     else
  146.       {RESET_IR_STATUS;}
  147. #endif

  148. #ifndef _IR_EN_USER_CHEAK
  149.     SET_IR_OK;
  150. #endif
  151.    
  152. #ifdef _IR_EN_UN_CHEAK
  153.     cheak1 = ir_code[0];
  154.     cheak2 = ~ir_code[1];
  155.    if(cheak1 != cheak2)
  156.     {RESET_IR_STATUS;}
  157.     cheak1 = ir_code[2];
  158.     cheak2 = ~ir_code[3];
  159.    if(cheak1 != cheak2)
  160.     {RESET_IR_STATUS;}
  161. #endif
  162.   }
  163. /***********************************************************/
  164. //起始码判断,如果收到起始码了,接收走起
  165.   if((IR_NO_STATUS || IR_HOLD) && (ir_timer > IRT_START-3) && (ir_timer < IRT_START+3 ) )
  166.   {
  167.     SET_IR_ING;
  168.     read_size = 0;
  169.     ir_code[0]=ir_code[1]=ir_code[2]=ir_code[3]=0;
  170.   }
  171. /**********************************************************/
  172. #ifdef _IR_EN_HOLD_CHEAK
  173. //  好吧,接下来是长按状态了,,,最长最臭的就是它了,还要到处插入代码
  174. //  连发码为9ms+2.25ms+0.56ms+97.94ms,这里我们只取前一段,也就是12.5ms
  175.   //下面两句用于长按值判断
  176.   if(IR_OK)
  177.   {
  178.       SET_IR_HOLD_STATUS_FIRST;
  179.       ir_hold_time = 0;
  180.   }
  181.   if(ir_hold_status && (ir_timer > IRT_HOLD-3) && (ir_timer < IRT_HOLD+3 ) )  
  182.   {
  183.     //判断到了连发码时间
  184.     ir_hold_time++;
  185.     //初次触发状态
  186.     if( (ir_hold_status == IR_HOLD_STATUS_FIRST) && (ir_hold_time == IR_SET_HOLD_TIME_FIRST) )
  187.     {
  188.       ir_hold_time = 0;
  189.       SET_IR_HOLD;
  190.       SET_IR_HOLD_STATUS;
  191.     }
  192.     //多次触发状态
  193.     if((ir_hold_status == IR_HOLD_STATUS) && (ir_hold_time == IR_SET_HOLD_TIME_AGAIN))
  194.     {
  195.       ir_hold_time = 0;
  196.       SET_IR_HOLD;
  197.     }
  198.    //判断遥控没有长按了,要清除IR_HOLD状态判断清零,
  199.   //共有一句,插在了计时器中断中了,,,,
  200.   }
  201. #endif
  202. /*************************************************************************/
  203.    ZERO_TIMER;  //计时器基数清零
  204. }


  205. void Timer_Add(void)
  206. {
  207.   ir_timer++;
  208. #ifdef _IR_EN_HOLD_CHEAK
  209.   //如果判断超时,就不再判断HOLD状态;真不好意思,语句要插这里才能实现
  210.   if(ir_timer > 254)
  211.     RESET_IR_HOLD_STATUS;
  212. #endif
  213. }

复制代码

--------------------------------------------------------------------------------------------------------------------------------------------

好吧,其实程序注释写的很清楚了,这里再简要介绍一下哪些是可以修改,哪些最好不要修改:
首先,头文件的
//#define _IR_EN_HOLD_CHEAK        //按键长按检查
#define _IR_EN_UN_CHEAK       //反码检查  
//#define _IR_EN_USER_CHEAK     //指定用户码检查,如果你开启了它,代表你还要定义IR_USER_CODE的内容
三个预定义可以选择功能,如果你需要改功能则定义它,如果不需要就注释掉,

#define IR_SET_HOLD_TIME_FIRST 10
#define IR_SET_HOLD_TIME_AGAIN 1
这两个常量定义用于定义按键长按时的触发时间
他们的单位是连发码的时间,也就是108ms
上面是第一次触发长按状态的时间定义,遥控器不可能你一按下就触发长按状态了,需要个延时,这个就相当于你按下了1s后才触发长按状态
下面多次触发长按状态的时间,第一次按下需要延时久一点,但有了第一次后,,,哈哈,就可一快点了,,,,同样,单位也是108ms

#define IR_USER_CODE 0x00
这个是用户码的定义,遥控是有个用户码的,将这个码值定义成你的遥控用户码,在开启#define _IR_EN_USER_CHEAK,这样接收机就是你遥控专属了.
如果不开启#define _IR_EN_USER_CHEAK,那可以忽略这个常量.

好了,头文件你需要修改的就是这么多...

关于C文件
你只需要在Ir_Init()函数里,将红外接收管脚配置成下降沿中断,将计时器配置成0.5ms(500us)中断一次,仅此而已;
然后,有中断当然要写中断响应函数了;
你只需要在计时器中断里调用 Timer_Add();函数
在红外管脚中断里调用void Decode (void);函数;
然后,,,然后就可以赤裸裸的main函数了有木有~~~

mian函数里,调用Ir_Init()进行初始化工作;
然后用if语句判断是否有红外码接收成功了,如果有,只需要调用Read_Ir_Code()函数就可以读取到指令码了,,,
如果我说得复杂了,那还是用代码说话吧:

  1. void main(void)
  2. {
  3.         unsigned char munber;
  4.         Ir_Init();
  5.         while(1)
  6.         {
  7.                 if(IR_OK)
  8.                 {
  9.                         number = Read_Ir_Code();
  10.                 }
  11.         }
  12. }
复制代码

看吧,你需要做的仅仅是如此;
IR_OK为真的时候就表示有按键值接收成功了,你可以大方的读取了,
不用担心IR_OK长期为真造成多次触发,因为你一但调用Read_Ir_Code()函数,红外接收状态就会进行归零,也就是,IR NO OK了...所以你可以尽管使用
if(IR_OK)
{
        number = Read_Ir_Code();
}

当然,,,长按状态接收触发的使用原理也是一样,,,
if(IR_HOLD)
{
        number = Read_Ir_Code();
}
只要遥控长按了,且时间满足要求,则IR_HOLD就为真;

关于接收值的读取说明:
默认使用Read_Ir_Code();函数读取接收值,它的返回值就是指令码,这已经可以满足要求了;
当然,如果你贪心需要读取接收到的4个代码的数值,你可以调用void Read_Ir_All(void)读取,方法注释已经说的很清楚了,我不想多介绍,因为这只是满足部分人才写的;
好了,,,代码介绍完毕,没有介绍的你可以不用理也能使用,当然最好不要修改了;

------------------------------------------------------------------------------------------------------------------------------------------------------------

下面介绍如何快速移植,上面的代码是基于STM8的,我将一步一步介绍如何移植到51中;
单片机:STC89C52Rc
编译环境:Keil 3
红外接收管脚在P33(INT1)
计时器用TIMER0;
值得一提,低速单片机最好计时器中断优先级高于外部中断;
头文件是功能选择,可以先不管
直接修改C文件;
首先,修改包含的头文件        #include <iostm8s903k3.h>    更改成 #include "reg52.h"
然后,在void Ir_Init(void)函数中配置计时器为0.5ms中断一次,配置外部中断1为下降沿中断;
就是这样子:
  1. void Ir_Init(void)
  2. {
  3.         EA  = 1;  //打开总中断
  4.         //配置外部中断
  5.         IT1 = 1;  //下降沿触发
  6.         EX1 = 1;  //开启外部中断
  7.         //配置计时器
  8.         //计时器工作在16位模式,12T模式,12M晶振,则,,, 大概500个机器周期为0.5ms
  9.         TL0   =         10;        //255-(500%255)
  10.         TH0   =  254;        //255-(500/255)
  11.         TMOD |= 0x01;        //16位模式
  12.         TR0   = 1;                //开启计时器
  13.         ET0   = 1;                //允许计时器0中断

  14. }
复制代码

然后,写下中断响应函数,当然,STM8s那两个中断函数你要删除;
再说一次,你只需要在计时器中断里调用 Timer_Add();函数
在红外管脚中断里调用void Decode (void);函数;,仅此而已;

  1. void Tm0(void) interrupt 1
  2. {
  3.         TL0   =  10;
  4.         TH0   =  254;
  5.         Timer_Add();
  6. }

  7. void Int1(void) interrupt 2
  8. {
  9.         Decode();
  10. }
复制代码
好吧,不要问我为什么计时器中断里多了两句TL0   =  10;         TH0   =  254;

好了,,,这样就可以在51中运行了这段解码程序了;
调用方法不变;
今天被客户否定了一个方案,心情不是很好,也许表述不是很清楚,不懂的可以盖楼;
附件是51单片机的文件,,,
友情提示: 此问题已得到解决,问题已经关闭,关闭后问题禁止继续编辑,回答。