本帖最后由 chhyxf 于 2019-3-16 00:43 编辑
NEC 协议的数据格式包括了引导码、用户码、用户码(或者用户码反码)、按键键码和键码反码,最后一个停止位。停止位主要起隔离作用,一般不进行判断,编程时我们也不予理会。其中数据编码总共是 4 个字节 32 位,如图 16-7 所示。第一个字节是用户码,第二个字节可能也是用户码,或者是用户码的反码,具体由生产商决定,第三个字节就是当前按键的键数据码,而第四个字节是键数据码的反码,可用于对数据的纠错。
图 16-7 NEC 协议数据格式
这个 NEC 协议,表示数据的方式不像我们之前学过的比如 UART 那样直观,而是每一位数据本身也需要进行编码,编码后再进行载波调制。
- 引导码:9ms 的载波+4.5ms 的空闲。
- 比特值“0”:560us 的载波+560us 的空闲。
- 比特值“1”:560us 的载波+1.68ms 的空闲。
结合图 16-7 我们就能看明白了,最前面黑乎乎的一段,是引导码的 9ms 载波,紧接着是引导码的 4.5ms 的空闲,而后边的数据码,是众多载波和空闲交叉,它们的长短就由其要传递的具体数据来决定。HS0038B 这个红外一体化接收头,当收到有载波的信号的时候,会输出一个低电平,空闲的时候会输出高电平,我们用逻辑分析仪抓出来一个红外按键通过HS0038B 解码后的图形来了解一下,如图 16-8 所示。
图 16-8 红外遥控器按键编码
从图上可以看出,先是 9ms 载波加 4.5ms 空闲的起始码,数据码是低位在前,高位在后,数据码第一个字节是 8 组 560us 的载波加 560us 的空闲,也就是 0x00,第二个字节是 8 组 560us的载波加 1.68ms 的空闲,可以看出来是 0xFF,这两个字节就是用户码和用户码的反码。按键的键码二进制是 0x0C,反码就是 0xF3,最后跟了一个 560us 载波停止位。对于我们的遥控器来说,不同的按键,就是键码和键码反码的区分,用户码是一样的。这样我们就可以通过单片机的程序,把当前的按键的键码给解析出来。
以上内容摘自CSDN 猪怕肥 的**,如果不巧您看到了这帖子,你过来打我啊。
/*声明变量*/
extern UINT8 RecData[5];
extern UINT8 RecDataLongPress;
extern bit RecOK;
extern UINT16 AfterRecTimeCt;
extern bit AfterRecTimeStart;
extern UINT8 PulseCount; // 9ms -- 36 * 250
extern UINT8 RecStu;
extern UINT8 code RecBitTbl[];
extern UINT8 code RecSpareTbl[];
extern UINT8 code RecGuidanTbl[];
extern UINT8 gWKeyRec;
/*宏定义*/
#define RecPt P1_1 // 接收引脚IO上拉
#define D_RecStartFlag 0 // 从接收到0作为开始
#define D_StartFlagMin 30// (8500/250) // 由NEC协议知道引导码低电平需要持续9ms,由于进中断需要时间所以这边选择8500/250,250是定时器设定时间
#define D_GuidanFlagMin 16//(4200/250) 由NEC协议知道引导码高电平需要持续4.5ms,由于进中断需要时间所以这边选择4200/250,250是定时器设定时间
#define D_RecGuidanFlag 1
#define D_logic_0_MAX 1
#define D_logic_1_MIN 3 // 逻辑1的持续时间至少需要3个250us
/*变量定义*/
UINT8 RecData[5]; // 存放接收数据
UINT8 RecDataLongPress; // 存放长按编码
bit RecOK;
UINT8 PulseCount; // 电平持续时间计数器
bit AfterRecTimeStart; // 长按开始判断标志位
UINT16 AfterRecTimeCt; // 长按计数,由波形可以知大概100ms左右会发送一个长按波形,这边设定200ms左右
UINT8 RecStu; // 接收状态变量
UINT8 code RecBitTbl[]={7,7,7,7}; // 接收bit数,可固定
UINT8 code RecSpareTbl[] = {D_StartFlagMin,D_GuidanFlagMin};
UINT8 code RecGuidanTbl[]={D_RecStartFlag,D_RecGuidanFlag};
UINT8 gWKeyRec;
/*
代码说明:
1、第一二状态判断引导码是否按NEC协议
2、第三、四、五、六状态接收数据
判断逻辑1高电平持续时间是否大于3个250us,实际测得是1600us左右,因为进入中断需要250us时间所以选择3*250,
逻辑0的高电平不大于560us,不可能大于3个250,所以很好可以区分逻辑0和逻辑1.
3、状态切换时需要特别注意引脚电平的状态
4、注意这段代码也没做很多的验证只是简单的实现了功能,只是觉得从无到有的过程需要与人分享所以上传。
*/
/*函数主体*/
void RayReceive(void)
{
static UINT8 byRecBitCt = 0;
switch(RecStu)
{
case 0:
case 1:
if(RecPt == RecGuidanTbl[RecStu]) // 管脚引导码判断
{
// 开始计数
PulseCount++; //
}
else
{
if(PulseCount > RecSpareTbl[RecStu])
{
RecData[0] = 0;
RecData[1] = 0;
RecData[2] = 0;
RecData[3] = 0;
RecStu ++; // 状态变量自增
PulseCount = 0;
}
else if((PulseCount > 6)&&(AfterRecTimeCt < 500)&&(RecStu == 1)) // 长按信号判断 2.86ms 150ms
{
RecOK = 1;
RecData[2] = RecDataLongPress; // 从长按变量中取回按键值
AfterRecTimeCt = 0;
PulseCount = 0;
RecStu = 0;
RecData[4]++; // 长按次数统计,
}
else
{
// AfterRecTimeStart = 0;
PulseCount = 0;
RecStu = 0;
}
}
break;
case 2:
case 3:
case 4:
case 5:
if(RecPt == 1)
{
PulseCount++;
}
else if(PulseCount!=0) // 增加这个判断的原因是从第1状态进入2状态时电平可能还是低电平状态引起错误
{
if(PulseCount > D_logic_1_MIN)
{
RecData[RecStu-2] |= (1 << (byRecBitCt)); // 低位在前 // 因为接收数据是按位或的方式,所以进入接收数据时也就是接收状态要大于1一定要清接收buffer
}
PulseCount = 0;
byRecBitCt++;
}
if(byRecBitCt > RecBitTbl[RecStu - 2]) // 接收一个字节判断
{
byRecBitCt = 0;
PulseCount = 0;
RecStu++;
if(RecStu > 5) // 接收完毕
{
RecStu = 0;
byRecBitCt = 0;
PulseCount = 0;
if( (RecData[2] | RecData[3]) == 0xff) // 校验接收数据
{
RecOK = 1;
RecData[4] = 0;
RecDataLongPress = RecData[2]; // 保存长按按键值
AfterRecTimeStart = 1; // 开启长按超时计数
AfterRecTimeCt = 0;
}
}
}
break;
default:break;
}
}
/*定时中断--250us*/
void Timer3_Interrupt(void) interrupt 11
{
RayReceive();
if(AfterRecTimeStart) // 长按有效时间限定,超出200ms判定不是长按
AfterRecTimeCt++;
if(AfterRecTimeCt > 600) // 200ms
{
AfterRecTimeCt = 600;
AfterRecTimeStart = 0;
}
}
|