本帖最后由 i7gly 于 2014-5-8 21:04 编辑
学习单片机三年,闷骚型,一直都是自己捣鼓,这是第二次写红外解码程序,第一次写是两年前,不堪入目;好吧,废话多了.
此程序针对快速移植,并功能齐全,可判断遥控按键长按状态;
当然,如果你对ROM,RAM是非常吝啬的,那就没必要看了,因为这个程序没有对任何CPU做优化,不算是最精简的.用了很多预定义宏和注释,看起来很复杂,其实真的很复杂的说.
程序主要原理步骤:
用单片机内部计时器产生0.5ms时间片轮,用此时间基数进行红外解码判断,
所以无论是什么单片机,你只需要会用计时器做个0.5ms中断就可以用此程序解码了.
代码运行环境:STM8S903K3,8M晶振,4M工作周期
计时器用TIMER6,红外接收管脚在PA3;
程序编译环境:IAR FOR STM8
上程序,
首先是头文件 IR.h的内容
- #ifndef _IR_H_
- #define _IR_H_
- //功能定义,如不需此功能,则注释掉该行预定义
- //#define _IR_EN_HOLD_CHEAK //按键长按检查
- #define _IR_EN_UN_CHEAK //反码检查
- //#define _IR_EN_USER_CHEAK //指定用户码检查,如果你开启了它,代表你还要定义IR_USER_CODE的内容
- //遥控器的用户码,用户反码,判断是否为当前指定遥控器操作
- //如果开启红外用户码检查,则需定义
- #define IR_USER_CODE 0x00
- //接收多个重复码才触发HOLD状态,一个重复码大概为100ms
- //这是第一次触发HOLD的时间,默认大概1s,也就是按着遥控器不放1s后才触发HOLD状态
- #define IR_SET_HOLD_TIME_FIRST 10
- //重复触发HOLD状态的时间,默认为一个重复码的时间
- #define IR_SET_HOLD_TIME_AGAIN 1
- extern unsigned char ir_status ;
- //共5种红外接收状态,直接使用if判断 例:if(IR_OK) {...}
- //注意,读取到需要的状态后应调用Read_Ir_Code函数取走按键值
- //Read_Ir_Code函数会清除红外接收状态
- #define IR_NO_STATUS (ir_status == 0)
- #define IR_ING (ir_status & 0x02)
- #define IR_OK (ir_status & 0x04)
- #define IR_HOLD (ir_status & 0x08)
- #define IR_ERROR (ir_status & 0x10)
- void Ir_Init(void);
- //调用时小心,函数返回值为一次性读取,请不要多次用if判断
- //正确使用方法为用新变量读取按键值再多次判断
- unsigned char Read_Ir_Code(void);
- void Read_Ir_All(unsigned char* Pstr);
- #endif
复制代码
----------------------------------------------------------------------------------------------------------------------------------------------
然后是IR.c文件的内容,有点长
- /*
- 使用环境建议单片机机器周期大于1M,
- 也就是如果你单片机是传统的51,则晶振最好大于等于12M
- 虽然接收值通过全局变量定义的,但是数值只能通过函数调用才能读取
- 此方法为了防止读取数值时忘记清除IR状态,造成多次触发
- */
- #include <iostm8s903k3.h>
- #include "IR.h"
- //遥控码值储存;
- unsigned char ir_code[4]={0,0,0,0};
- //接收的用户码,用户反码,指令码,指令反码.这只是定义给你们看的,没有任何函数用到,可以删除
- #define user_code (ir_code[0])
- #define un_user_code (ir_code[1])
- #define command_code (ir_code[2])
- #define un_command_code (ir_code[3])
- //下面是定时需要用到的变量,常量的定义
- unsigned char ir_timer; //计时基数,
- #define ZERO_TIMER ir_timer = 0
- //下面是接收状态的变量,宏定义
- unsigned char ir_status = 0;
- //共5种状态,分别为:接收预备,接收中,接收完成,按键长按,接收错误(没有用到),所有条件互斥
- #define RESET_IR_STATUS ir_status = 0
- #define SET_IR_ING ir_status = 0; ir_status |= 0x02
- #define SET_IR_OK ir_status = 0; ir_status |= 0x04
- #define SET_IR_HOLD ir_status = 0; ir_status |= 0x08
- #define SET_IR_ERROR ir_status = 0; ir_status |= 0x10
- #ifdef _IR_EN_HOLD_CHEAK
- //还有,IR_HOLD状态判断使能变量,本想精简到局部变量的,,,还是没能,,,
- unsigned char ir_hold_status;
- //初次触发时间跟再次触发时间软件做成不一样,所以要定义两个判断状态
- #define IR_HOLD_STATUS_FIRST 1
- #define IR_HOLD_STATUS 2
- #define SET_IR_HOLD_STATUS_FIRST ir_hold_status = IR_HOLD_STATUS_FIRST
- #define SET_IR_HOLD_STATUS ir_hold_status = IR_HOLD_STATUS
- #define RESET_IR_HOLD_STATUS ir_hold_status = 0
- #endif
- //码值中断间断时间定义,单位为0.5ms,取整数,最好不要修改了
- //起始时间,9+4.5ms = 13.5ms
- #define IRT_START 27
- //逻辑1, 0.56+1.68 = 2.25ms
- #define IRT_LOGIC_1 4
- //逻辑0, 0.56+0.56 = 1.12ms
- #define IRT_LOGIC_0 2
- //连发码,连发码为9ms+2.5ms+0.56ms+97.94ms,这里我们只取前面一段,也就是9+2.5=11.5ms
- #define IRT_HOLD 20
- void Decode(void);
- void Timer_Add(void);
- //接收红外IO配置为下降沿中断
- //配置计时器每0.5ms中断一次
- void Ir_Init(void)
- {
- RESET_IR_STATUS;
- asm("sim"); //禁止中断,进入中断3级状态;设置中断时必须进入3级状态
- PA_DDR &= 0xF7; //PA3为红外接收中断输入脚
- PA_CR1 &= 0xF7;
- PA_CR2 |= 0x08;
-
- EXTI_CR1 &= 0xfc;
- EXTI_CR1 |= 0x02; //PD口中断为下降沿中断
-
- //计数器配置
- CLK_PCKENR1 |= 0x10; //使能TIM6时钟
- TIM6_PSCR = 0x05; //32分频,预分频值,需更新事件计数器才有效
- TIM6_CR1 = 0x80 ; //
- TIM6_IER = 0x01 ; //允许更新中断
- // TIM6_CNTR = 256-125; //计数器值,125个数为0.5ms,由于是八位自动重装计数,所以为256-125
- TIM6_ARR = 256-125; //自动重装值
- TIM6_CR1 |= 0x01; //开启计时器
- asm("rim"); //使能中断
- }
- //计数器中断
- //用到STM8的TM6更新中断
- //直接调用Timer_Add()函数
- #pragma vector=0x19
- __interrupt void TM6(void)
- {
- TIM6_SR_UIF = 0;
- Timer_Add();
- }
- //红外管脚中断
- //下降沿中断,只需要在中断里调用Decode函数就可以了,方便吧
- #pragma vector=5
- __interrupt void PD0_IR(void)
- {
- Decode();
- }
- //下面这两个函数是给外部文件调用的.
- //读接收值,注意注意,一次有效,如果读取值操作成功,,则IR接收状态将会清零,HOLD状态也不例外
- //返回值为指令码
- unsigned char Read_Ir_Code(void)
- {
- RESET_IR_STATUS;
- return ir_code[2];
- }
- //好吧,我们再做一个函数,用于读取所有的接收值
- //使用说明,调用后将一如既往清除接收状态
- //形参指针应该指向4个连续的unsigned char内存,最简单的放大,定义一个数组,把首地址给它
- //不要问返回值在哪,,,
- void Read_Ir_All(unsigned char* Pstr)
- {
- RESET_IR_STATUS;
- (*Pstr) = ir_code[0];
- Pstr++;
- (*Pstr) = ir_code[1];
- Pstr++;
- (*Pstr) = ir_code[2];
- Pstr++;
- (*Pstr) = ir_code[3];
- }
- /*********************下面的这解码函数就不需要修改了************************/
- //解码函数,过程冗长
- void Decode (void)
- {
- #define READ_SIZE_MAX 32 //接收数据长度,8位地址码,8位地址反码,8位用户码,8位用户反码
- static unsigned char read_size; //接收长度暂存,用于计算现接收数据的长度
- unsigned char* P_ir_code; //接收用的指针
- #ifdef _IR_EN_HOLD_CHEAK
- static unsigned char ir_hold_time; // 长按状态累计基数变量
- #endif
- #ifdef _IR_EN_UN_CHEAK
- unsigned char cheak1,cheak2; //IAR环境下不能直接用数组进行判断,只能新建两个变量了,用于进行接收验证
- #endif
-
- //哈哈,开始吧
- //接收中状态,接收码值并赋值给变量
- if(IR_ING && (read_size < READ_SIZE_MAX))
- {
- //定义指针位置
- P_ir_code = ir_code;
- P_ir_code += (read_size/8);
- //逻辑1
- if((ir_timer>IRT_LOGIC_1-1)&&(ir_timer<IRT_LOGIC_1+2))
- {
- (*P_ir_code) |= (1<<(read_size%8));
- }
- //接收位置+1;逻辑0就不判断了,非1既0
- read_size ++;
- }
- /************************************************************/
- //如果超过了接收长度,那就接收完成了,接收完成还要判断数值对不对
- if(IR_ING && (read_size > READ_SIZE_MAX - 1 ) )
- {
- #ifdef _IR_EN_USER_CHEAK
- if(ir_code[0] == IR_USER_CODE)
- {SET_IR_OK;}
- else
- {RESET_IR_STATUS;}
- #endif
- #ifndef _IR_EN_USER_CHEAK
- SET_IR_OK;
- #endif
-
- #ifdef _IR_EN_UN_CHEAK
- cheak1 = ir_code[0];
- cheak2 = ~ir_code[1];
- if(cheak1 != cheak2)
- {RESET_IR_STATUS;}
- cheak1 = ir_code[2];
- cheak2 = ~ir_code[3];
- if(cheak1 != cheak2)
- {RESET_IR_STATUS;}
- #endif
- }
- /***********************************************************/
- //起始码判断,如果收到起始码了,接收走起
- if((IR_NO_STATUS || IR_HOLD) && (ir_timer > IRT_START-3) && (ir_timer < IRT_START+3 ) )
- {
- SET_IR_ING;
- read_size = 0;
- ir_code[0]=ir_code[1]=ir_code[2]=ir_code[3]=0;
- }
- /**********************************************************/
- #ifdef _IR_EN_HOLD_CHEAK
- // 好吧,接下来是长按状态了,,,最长最臭的就是它了,还要到处插入代码
- // 连发码为9ms+2.25ms+0.56ms+97.94ms,这里我们只取前一段,也就是12.5ms
- //下面两句用于长按值判断
- if(IR_OK)
- {
- SET_IR_HOLD_STATUS_FIRST;
- ir_hold_time = 0;
- }
- if(ir_hold_status && (ir_timer > IRT_HOLD-3) && (ir_timer < IRT_HOLD+3 ) )
- {
- //判断到了连发码时间
- ir_hold_time++;
- //初次触发状态
- if( (ir_hold_status == IR_HOLD_STATUS_FIRST) && (ir_hold_time == IR_SET_HOLD_TIME_FIRST) )
- {
- ir_hold_time = 0;
- SET_IR_HOLD;
- SET_IR_HOLD_STATUS;
- }
- //多次触发状态
- if((ir_hold_status == IR_HOLD_STATUS) && (ir_hold_time == IR_SET_HOLD_TIME_AGAIN))
- {
- ir_hold_time = 0;
- SET_IR_HOLD;
- }
- //判断遥控没有长按了,要清除IR_HOLD状态判断清零,
- //共有一句,插在了计时器中断中了,,,,
- }
- #endif
- /*************************************************************************/
- ZERO_TIMER; //计时器基数清零
- }
- void Timer_Add(void)
- {
- ir_timer++;
- #ifdef _IR_EN_HOLD_CHEAK
- //如果判断超时,就不再判断HOLD状态;真不好意思,语句要插这里才能实现
- if(ir_timer > 254)
- RESET_IR_HOLD_STATUS;
- #endif
- }
复制代码
--------------------------------------------------------------------------------------------------------------------------------------------
好吧,其实程序注释写的很清楚了,这里再简要介绍一下哪些是可以修改,哪些最好不要修改:
首先,头文件的
//#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()函数就可以读取到指令码了,,,
如果我说得复杂了,那还是用代码说话吧:
- void main(void)
- {
- unsigned char munber;
- Ir_Init();
- while(1)
- {
- if(IR_OK)
- {
- number = Read_Ir_Code();
- }
- }
- }
复制代码
看吧,你需要做的仅仅是如此;
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为下降沿中断;
就是这样子:
- void Ir_Init(void)
- {
- EA = 1; //打开总中断
- //配置外部中断
- IT1 = 1; //下降沿触发
- EX1 = 1; //开启外部中断
- //配置计时器
- //计时器工作在16位模式,12T模式,12M晶振,则,,, 大概500个机器周期为0.5ms
- TL0 = 10; //255-(500%255)
- TH0 = 254; //255-(500/255)
- TMOD |= 0x01; //16位模式
- TR0 = 1; //开启计时器
- ET0 = 1; //允许计时器0中断
- }
复制代码
然后,写下中断响应函数,当然,STM8s那两个中断函数你要删除;
再说一次,你只需要在计时器中断里调用 Timer_Add();函数
在红外管脚中断里调用void Decode (void);函数;,仅此而已;
- void Tm0(void) interrupt 1
- {
- TL0 = 10;
- TH0 = 254;
- Timer_Add();
- }
- void Int1(void) interrupt 2
- {
- Decode();
- }
复制代码
好吧,不要问我为什么计时器中断里多了两句TL0 = 10; TH0 = 254;
好了,,,这样就可以在51中运行了这段解码程序了;
调用方法不变;
今天被客户否定了一个方案,心情不是很好,也许表述不是很清楚,不懂的可以盖楼;
附件是51单片机的文件,,,
STM8S207C8T6
一周热门 更多>