项目场景:
通过红外(NEC格式)控制外设,需要对引导码进行验证,并对客户码和键码进行记录
问题描述
如下是未处理引导码的初始代码
#ifndef _MAIN_C_
#define _MAIN_C_
#include "include/ca51f155_config.h"
#include "include/ca51f155sfr.h"
#include "include/ca51f155xsfr.h"
#include "include/gpiodef_f155.h"
#include "include/system_clock.h"
#include "include/uart.h"
#include "include/delay.h"
#include "include/pwm.h"
#include "include/lcd_led.h"
#include <intrins.h>
typedef unsigned int uint16; //对数据类型进行声明定义
typedef unsigned char uint8;
uint8 irtime;
uint8 startflag;
uint8 bitnum;
uint8 irdata[33];
uint8 irreceok;
uint8 ircode[4];
uint8 irprosok;
uint8 keynum;
uint8 keynum2;
uint8 dat1,dat2;
uint8 gsmg_code[]={0xfc,0x60,0xda,0xf2,0x66,0xb6,0xbe,0xe0,0xfe,0xf6};
uint8 g_code[]={0x04,0x02,0x01};
uint16 j=0;
int k=0;
enum
{
P10_INDEX = 0,
P11_INDEX = 1,
P12_INDEX = 2,
P13_INDEX = 3,
P14_INDEX = 4,
P15_INDEX = 5,
P16_INDEX = 6,
P17_INDEX = 7,
P20_INDEX = 8,
P21_INDEX = 9,
P22_INDEX = 10,
P23_INDEX = 11,
P24_INDEX = 12,
P25_INDEX = 13,
P26_INDEX = 14,
P27_INDEX = 15,
P30_INDEX = 16,
P31_INDEX = 17,
P32_INDEX = 18,
P33_INDEX = 19,
P34_INDEX = 20,
P35_INDEX = 21,
P36_INDEX = 22,
P37_INDEX = 23,
P40_INDEX = 24,
P41_INDEX = 25,
P42_INDEX = 26,
P43_INDEX = 27,
P44_INDEX = 28,
P45_INDEX = 29,
P46_INDEX = 30,
P47_INDEX = 31,
P50_INDEX = 32,
P51_INDEX = 33,
P52_INDEX = 34,
P53_INDEX = 35,
P54_INDEX = 36,
P55_INDEX = 37,
P04_INDEX = 38,
P05_INDEX = 39,
P06_INDEX = 40,
P07_INDEX = 41,
};
#define EPIE(n) (n<<7) //外部中断4
#define EPPL(n) (n<<5)
#define EPPSEL_L(n) ((n&0x1F)<<0)
#define EPPSEL_H(n) (n>>5)
#define PWMDIV_V (12000000/12000) //当PWM时钟为其他时钟频率时,需相应修改参数
#define PWMDUT_V (PWMDIV_V/2) //占空比为50%
//P3.0~P3.7端口为大灌电流管脚
//寄存器PnxC
#define SMIT_EN(N) (N<<6) //N=0~1, 1:SMIT使能,0:反相器使能
#define SINK(N) (N<<3) //灌电流强度选择;P30C~P37C的SINK设置位为两位(SINK[1:0] = 0~3)
#define SINK_EN(N) (N<<2) //N=0~1,大灌电流使能
#define DRV(N) (N<<1) //N=0~1,输出强度选择
#define SR(N) (N<<0) //N=0~1,输出斜率控制 0:最慢斜率控制 1:最快斜率控制
#define INT_TIME 10000 //定时时间,单位为us
#define TH_VAL (unsigned char)((0x10000 - (INT_TIME*(FOSC/1000))/12000)>>8)
#define TL_VAL (unsigned char)((0x10000 - (INT_TIME*(FOSC/1000))/12000))
bit int4_flag;
void PWM_Init(void)
{
INDEX = PWM_CH5; //设置INDEX值对应PWM5
PWMCON = TIE(0) | ZIE(0) | PIE(0) | MS(0) | CKS_IH ; //设置PWM时钟源为IRCH
INDEX = PWM_CH5;
PWMCON = TIE(0) | ZIE(0) | PIE(0) | MS(0) | MOD(0);
PWMCFG = TOG(0) | 0;
//设置PWMDIV、PWMDUT
PWMDIVH = (unsigned char)(PWMDIV_V>>8);
PWMDIVL = (unsigned char)(PWMDIV_V);
PWMDUTH = (unsigned char)(PWMDUT_V>>8);
PWMDUTL = (unsigned char)(PWMDUT_V);
P51F = P51_PWM5_SETTING; //设置P5.1为PWM1输出引脚
PWMUPD |= (1<<PWM_CH5); //PWMDIV、PWMDUT更新使能
while(PWMUPD);
}
void LED_Init(void)
{
uint8 a;
//初始化LCD驱动引脚
P31F = 2;//3&8
P30F = 2;P34F = 2;P17F = 2;P35F = 2;P13F = 2;P14F = 2;P15F = 2;P16F = 2;//other
P43F = 2;P43(0);P42F=2;P41F=2;P37F=2;//3LED
for(a = 0; a < 9; a++)
{
INDEX = a;
LXDAT = 0;
}
LXDIVH = 0; //设置LED时钟分频
LXDIVL = 112;
LXCFG = COMHV(COM_L) | SEGHV(SEG_H) | BLNK(0) | LDRV(LDRV_7); //设置LED COM、SEG有效电平,亮度级别
LXCON = LEN(LEN_IRCH) | LMOD(LMOD_led); //设置LED时钟源,选择LED模式
}
void Timer0_Init(void) //256us
{
TMOD = (TMOD&0xFC)|0x02; //模式选择: 定时器0,模式2。
TH0 = 0x00; //装载重载值
TL0 = 0x00; //装载计数初值
TR0 = 1; //定时器0使能
ET0 = 1; //定时器0中断使能
// PT0 = 1; //设置定时器0中断优先级为高优先级
}
void Timer1_Init(void) //10ms
{
TMOD = (TMOD&0xCF)|0x10; //模式选择: 定时器0,模式1。
TH1 = TH_VAL; //高8位装初值
TL1 = TL_VAL; //低8位装初值
//TR0 = 1; //定时器1使能
ET1 = 1; //定时器1中断使能
}
/*外部中断4控制例程****************************************************************************************************/
void Ext_Int4_Init(void)
{
P36F = INPUT; //INT4可选择除P0.0~ P0.3外的其它任意I/O引脚为中断触输入引脚
EP1CON |= EPPSEL_H(P36_INDEX); //EPPS2[5]放在EP1CON[0]
EP2CON = EPIE(1) | EPPL(1) | EPPSEL_L(P36_INDEX); //使能外部中断,并选择下降沿触发, 设置P36为INT4中断引脚
INT4EN = 1; //外部中断4中断使能
}
void irpros() //红外接收数据处理 ,区分是数据0还是1
{
uint8 i,j,value;
uint8 k=1; //引导码去掉,所以令k=1;
for(j=0;j<4;j++) //取出了一帧数据中的四个字节数据
{
for(i=0;i<8;i++) //取出了一个字节的数据
{
value>>=1;
if(irdata[k]>6)
{
value=value|0x80;
}
k++;
}
ircode[j]=value;
}
irprosok=1;
}
void irwork() //对接收到的四个字节的数据进行处理转化成十进制
{
dat1=ircode[2]/16;
dat2=ircode[2]%16;
keynum++;
if((dat1==8)&&(dat2==2)){keynum2++;}
}
void impros()
{
if((dat1==8)&&(dat2==0)&&(keynum)) //蜂鸣器
{
dat1=0;
dat2=0;
PWMEN |= (1<<PWM_CH5);Delay_ms(500);PWMEN &= (0<<PWM_CH5);
}
if((dat1==8)&&(dat2==1)&&(keynum)) //数码管显示加1
{
dat1=0;
dat2=0;
k++;
}
if((dat1==8)&&(dat2==2)&&(keynum2%2)) //数码管自动循环累加
{
dat1=0;
dat2=0;
TR1=1;
}
if((dat1==8)&&(dat2==2)&&(!(keynum2%2))) //再按一次数码管停止累加
{
dat1=0;
dat2=0;
TR1=0;
}
if((dat1==8)&&(dat2==3)&&(keynum)) //数码管显示减1
{
dat1=0;
dat2=0;
k--;
}
if((dat1==8)&&(dat2==4)&&(keynum)) //3个小灯循环点亮
{
dat1=0;
dat2=0;
j++;
}
}
void lighten (unsigned char a) //点灯
{
P37=a&(1<<0);P41(a&(1<<1));P42(a&(1<<2));
return ;
}
void lighten_duan (unsigned char a) //点亮数码管
{
P35=a&(1<<0);P30=a&(1<<1);P17=a&(1<<2);P13=a&(1<<3);P14=a&(1<<4);P34=a&(1<<5);P15=a&(1<<6);P16=a&(1<<7);
}
/*********************************************************************************************************************/
void System_Init(void)
{
LVDCON = 0xE2; //开启LVD,设置为低电压复位模式,检测电压为2.7V
LOAD_CLK_CFG();
#ifdef SYSCLK_12MHZ
CKDIV = 0; //系统时钟上电默认为IRCH的二分频(6MHz),运行12MHz,则CKDIV设置为0
#endif
#ifdef UART0_EN
Uart0_Initial(UART0_BAUTRATE);
#endif
#ifdef UART1_EN
Uart1_Initial(UART1_BAUTRATE);
#endif
#ifdef UART2_EN
Uart2_Initial(UART2_BAUTRATE);
#endif
}
void main(void)
{
System_Init();
CKCON |= IHCKE;
EA = 1; //开全局中断
Timer0_Init();
Timer1_Init();
Ext_Int4_Init();
PWM_Init();
LED_Init();
while(1)
{
//int4_flag = 0;
if(irreceok)
{
irreceok=0;
irpros();
if(irprosok)
{
irwork();
}
}
impros();
if(j>=3){j=0;}
lighten(g_code[j]);
if(k>=10){k=0;}
if(k<0){k=9;}
lighten_duan(gsmg_code[k]);
}
}
void TIMER0_ISR (void) interrupt 1
{
irtime++;;
}
void EXT_INT4_ISR(void) //函数在INT4_ISR中调用
{
if(EPIF & 0x04) //判断外部中断4中断标志
{
EPIF = 0x04; //中断标志写1清0
if(startflag)
{
if(irtime>32)//检测引导码,求法是用引导码时间除以一次计数时间,看看要多少次
{
bitnum=0;
}
irdata[bitnum]=irtime;
irtime=0;
bitnum++;
if(bitnum==33)
{
bitnum=0;
irreceok=1;//一帧红外数据接收完成标志
}
}
else
{
startflag=1;//将开始标志位置1,等到下次进入中断即可进入if语句
irtime=0;//将开始之前的计数器时间清零。等到下次进入中断的时候才是引导码真正的时间
}
}
}
void TIMER1_ISR (void) interrupt 3
{
static unsigned int i;
TH1 = TH_VAL;
TL1 = TL_VAL;
i++;
if(i==50){
k++; //500ms执行一次
i=0;
}
}
#endif
一开始选择红外接收的输出口P36作为外部中断4的输入引脚,设置下降沿触发。根据NEC协议规定,逻辑1时长2.25ms,经历两次电平翻转,逻辑0时长1.125ms,同样两次电平翻转,思路对每一次下降沿到来之前的时间进行记录,判断时长即可区分0或1.对引导码的处理想的是在中断函数中直接对P36判断低电平是否持续9ms,再判断高电平是否持续4.5ms,若否,直接return。但是不行,用keil调试试图分析原因,奈何菜鸡一个,没捣鼓出来。具体情况是到判断低电平这一步根本不进去if语句,有点懵逼,可惜代码当时没保存,大概如下
if(IRED==0)
{
time_cnt=1000;
while((!IRED)&&(time_cnt))//等待引导信号9ms低电平结束,若超过10ms强制退出
{
delay_10us(1);//延时约10us
time_cnt--;
if(time_cnt==0)return;
}
if(IRED)//引导信号9ms低电平已过,进入4.5ms高电平
{
time_cnt=500;
while(IRED&&time_cnt)//等待引导信号4.5ms高电平结束,若超过5ms强制退出
{
delay_10us(1);
time_cnt--;
if(time_cnt==0)return;
}
......
}
}
解决方案:
在马哥的指导下,改变中断4的触发方式为双边沿触发,包括引导码,对每一次电平翻转的时间进行记录,判断每一段电平的持续时间,这样也会方便引导码的处理,主要修改的地方如下
uint8 xdata irdata[67];
#define EPPLH(n) (n<<6)
void Ext_Int4_Init(void)
{
P36F = INPUT; //INT4可选择除P0.0~ P0.3外的其它任意I/O引脚为中断触输入引脚
EP1CON |= EPPSEL_H(P36_INDEX); //EPPS2[5]放在EP1CON[0]
EP2CON = EPIE(1) | EPPLH(1)|EPPL(1)| EPPSEL_L(P36_INDEX); //使能外部中断,并选择双边沿触发, 设置P36为INT4中断引脚
INT4EN = 1; //外部中断4中断使能
}
void irpros() //红外接收数据处理 ,区分是数据0还是1
{
uint8 i,j,value;
uint8 k=0; //
if((irdata[k]<34)||(irdata[k]>37)){
if((irdata[k+1]<16)||(irdata[k+1]>19)){ //不符合直接return
return;
}
}
k=k+2;
for(j=0;j<4;j++) //取出了一帧数据中的四个字节数据
{
for(i=0;i<8;i++) //取出了一个字节的数据
{
value>>=1;
if((irdata[k]<=3)&&(irdata[k+1]>=6)) //逻辑1
{
value=value|0x80;
}
k=k+2;
}
ircode[j]=value;
}
irprosok=1;
}
void EXT_INT4_ISR(void) //函数在INT4_ISR中调用
{
if(EPIF & 0x04) //判断外部中断4中断标志
{
EPIF = 0x04; //中断标志写1清0
if(startflag)
{
if(irtime>30){ //判断是不是引导码
bitnum=0;
}
irdata[bitnum]=irtime;
irtime=0;
bitnum++;
if(bitnum==67)
{
bitnum=0;
irreceok=1;//一帧红外数据接收完成标志
}
}
else
{
startflag=1;//将开始标志位置1,等到下次进入中断即可进入if语句
irtime=0;//将开始之前的计数器时间清零。等到下次进入中断的时候才是引导码真正的时间
}
}
}
需要重点注意的地方在于irdata这个数组,在简单版本中,irdata的数组大小为33,这无关紧要,但是修改之后要记录的电平数变多了,当数组大小到达64的时候,就会出现 ERROR L107: ADDRESS SPACE OVERFLOW
,解决方法只需要加上地址类型xdata
即可,将其存储到片外内存空间。