文章节选自:《ARM Cortex-M0从这里开始》 作者:zhaojun_xf https://bbs.eeworld.com.cn/thread-324656-1-1.html
按键与红外遥控接收 数字时钟设置采用按键和红外遥控两种方式,极大的方便了时钟是设置。 1 遥控概述 红外遥控是目前使用最为广泛的一种通信方式。由于红外遥控装置具有体积小、功耗低、功能强、成本低等特点。 1. 红外遥控系统 通用红外遥控系统由发射和接收两大部分组成,应用编/解码专用集成电路芯片来进行控制操作,如图8-9所示。
2. 红外遥控发射编码 一般红外电视遥控器的输出都是用编码后串行数据对38~40kHz的方波进行脉冲幅度调制而产生的。当发射器按键按下后,即有遥控码发出,所按的键不同遥控编码也不同。这种遥控码具有以下特征: 遥控发射的一帧码含有一个引导码,16位的用户编码和8位的键数据码、键数据码的反码也同时被传送。码型结构如下:
引导码由一个9ms的载波波形和4.5ms的关断时间构成,随后跟随4字节编码。这4字节的编码由低8位用户编码、高8位用户编码、8位键数据码和8位键数据码的反码。编码采用脉冲位置调制方式(PPM)。利用脉冲之间的时间间隔来区分“0”和“1”。每次8位的码被传送之后,它们的反码也被传送,减少了系统的误码率。 3. 位定义 用户码或数据码中的每一个位可以是位“1”,也可以是位“0”。不同厂家的脉冲编码、码型和码流是不一样的。本书使用的遥控码型如图8-11所示。
2 硬件电路的设计 时钟的设置通过按键和遥控两种方式同时进行,图8-9所示为按键电路图,图8-10所示为遥控接收电路图。 LPC1100系列微处理器虽然内部有上下拉电阻,但是电阻阻值为40KΩ左右,属于弱上拉,为了保证电路的可靠性和通用性,在这里还是加上了10KΩ的上拉电阻。
红外遥控接收的连接非常简单,只要分别连接好电源和数据输出端口即可。
3 程序设计 为了能够及时接收到按键和红外遥控值,这里的按键输入和红外遥控输入均采用中断方式接收。具体代码下面将详细介绍。 1. 按键接收 (1)初始化函数 本函数主要是完成按键端口的设置及中断的初始化工作,使按键产生时,处理器能够正常的识别按键。所以先设置端口均为输入端口,采用低电平触发中断,之后使能每个端口的中断。 /****************************** 程序清单8-9 **********************************/ /******************************************************************************** * FunctionName : KeyInit() * Description : 初始化按键 * EntryParameter : None * ReturnValue : None ********************************************************************************/ void KeyInit(void) { // 设置为输入端口 GPIOSetDir(KEY_PORT, KEY1, 0); GPIOSetDir(KEY_PORT, KEY2, 0); GPIOSetDir(KEY_PORT, KEY3, 0); GPIOSetDir(KEY_PORT, KEY4, 0); GPIOSetDir(KEY_PORT, KEY5, 0); GPIOSetDir(KEY_PORT, KEY6, 0);
// 设置为低电平中断 GPIOSetInterrupt(KEY_PORT, KEY1, 1, 0, 0); GPIOSetInterrupt(KEY_PORT, KEY2, 1, 0, 0); GPIOSetInterrupt(KEY_PORT, KEY3, 1, 0, 0); GPIOSetInterrupt(KEY_PORT, KEY4, 1, 0, 0); GPIOSetInterrupt(KEY_PORT, KEY5, 1, 0, 0); GPIOSetInterrupt(KEY_PORT, KEY6, 1, 0, 0);
// 使能中断 GPIOIntEnable(KEY_PORT, KEY1); GPIOIntEnable(KEY_PORT, KEY2); GPIOIntEnable(KEY_PORT, KEY3); GPIOIntEnable(KEY_PORT, KEY4); GPIOIntEnable(KEY_PORT, KEY5); GPIOIntEnable(KEY_PORT, KEY6);
NVIC_EnableIRQ(KEY_EINT); // 使能PORT中断 } (2)中断服务函数 产生中断后,我们并不知道是哪个端口产生的,所以需要分别读取中断管脚的状态。如果读取是值为1,表示有键按下。此时,对按键赋值,清除相应管脚中断并退出,否则表示无键按下。 /****************************** 程序清单8-10 **********************************/ /******************************************************************************** * FunctionName : PIOINT1_IRQHandler() * Description : 中断服务函数 * EntryParameter : None * ReturnValue : None ********************************************************************************/ void PIOINT1_IRQHandler(void) { uint32 key;
key = GPIOIntStatus(KEY_PORT, KEY1); if (key == 1) { KeyValue = KEY_OK; GPIOIntClear(KEY_PORT, KEY1); return ; } key = GPIOIntStatus(KEY_PORT, KEY2); if (key == 1) { KeyValue = KEY_VOLM; GPIOIntClear(KEY_PORT, KEY2); return ; } key = GPIOIntStatus(KEY_PORT, KEY3); if (key == 1) { KeyValue = KEY_VOLP; GPIOIntClear(KEY_PORT, KEY3); return ; } key = GPIOIntStatus(KEY_PORT, KEY4); if (key == 1) { KeyValue = KEY_DOWN; GPIOIntClear(KEY_PORT, KEY4); return ; } key = GPIOIntStatus(KEY_PORT, KEY5); if (key == 1) { KeyValue = KEY_UP; GPIOIntClear(KEY_PORT, KEY5); return ; } key = GPIOIntStatus(KEY_PORT, KEY6); if (key == 1) { KeyValue = KEY_EXIT; GPIOIntClear(KEY_PORT, KEY6); return ; } } 2. 红外遥控 (1)红外解码函数 通过上面的介绍知道,遥控在被按下时,会按顺序发送4个字节的数据。对于应用来说,只需要获取第3字节数据就可以了。但是,接收时必须把4个字节数据都接收完成,特别是第4字节数据,它是按键的键值反码,接收这一字节数据并与第3字节进行比较,可以在很大程度上减少误码的产生。 对于红外解码来说关键在于延时时间的处理。读者在处理延时函数时,如果用for循环来实现,最好用示波器来观察延时的长度,以保证解码的顺利进行。如果没有条件也可以采用MDK的软件模拟来模拟延时函数的运行时间。 /****************************** 程序清单8-11 **********************************/ /******************************************************************************** * FunctionName : IrDecode() * Description : 红外解码函数,利用普通端口解码,也可以用外中断解码 * EntryParameter : None * ReturnValue : 返回按键编码 ********************************************************************************/ uint8 IrDecode(void) { uint8 i,j,num = 0; uint8 tmp = 0; uint8 irCom[4] = {0x00,0x00,0x00,0x00}; // irCom[0]和IRCOM[1]存放用户编码;irCom[2]键值码暂存;irCom[3]键值反码存放。
do // 读引导码 { for (i=0; i<4; i++) { if (IR_READ() == 0) { break; }
if (i == 3) { return IR_NO; } }
Ir_Delay(20); }while (IR_READ() != 0); while(IR_READ() == 0) // 等IR变为高电平 { Ir_Delay(1); } // 读4字节按键编码 = 16位的用户码+8位键值码+8位键值反码 for (i=0; i<4; i++) { for (j=0; j<8; j++) // 8位一接收 { while (IR_READ() != 0) // 等 IR 变为低电平 { Ir_Delay(1); } while (IR_READ() == 0) // 等 IR 变为高电平 { Ir_Delay(1); } while (IR_READ() != 0) // 计算IR高电平时长 { Ir_Delay(1); num++; if (num >= 30) { return IR_NO; } }
irCom >>= 1; // 接收数据右移一位 if (num >= 8) { irCom |= 0x80; // 电平长度大于等于8,则写入高电平 } num = 0; } // 读完一字节 } // 按键4字节读完 // 接收数据检测 tmp = ~irCom[3]; // 反码取反,如果反码不暂存而是直接使用,会错误 if (irCom[2] != tmp) // 8位键码!=8位键反码:按键错误 { return IR_NO; }
return irCom[2]; // 返回编码 } (2)获取按键值 在通过接收头得到红外信号并通过上面的函数解码后,我们需要对按键功能进行定义才能使用。不同遥控就算遥控上的标号是一样的,但是解码获取的码值不一定相同。所以,在接收到解码值后需要定义其含义才能使用。 程序清单8-12所示为本设计使用的遥控键值的定义,采用两个按键表示同一的功能,具体按键位置,读者可以参见表8-1。case后面的值就算遥控解码获取的,value为此值对应的含义。读者在使用其他遥控时,需通过解码函数获取按键的键值,放在对应的case语句后面即可。 /****************************** 程序清单8-12 **********************************/ /******************************************************************************** * FunctionName : IrGetValue() * Description : 获取按键值 * EntryParameter : None * ReturnValue : 返回按键值 ********************************************************************************/ uint8 IrGetValue(void) { uint8 value = IR_NO;
value = IrDecode(); // 获取按键编码 switch (value) { case 0x04: case 0x1E: value = IR_OK; break; // 确定键 case 0x15: case 0x00: value = IR_VOLP; break; // 声音加 case 0x4D: case 0x4C: value = IR_VOLM; break; // 声音减 case 0x0C: case 0x16: value = IR_UP; break; // 上翻 case 0x0F: case 0x0E: value = IR_DOWN; break; // 下翻 case 0x10: case 0x0A: value = IR_EXIT; break; // 退出 default : value = IR_NO; break; // 没有按键 }
if (KeyValue != IR_NO) // 这样判断是为了保证中断按键的有效操作 { return KeyValue; // 有中断按键,保存按键值不变 } else { return (value); // 返回红外按键值 } }
|