生活在大学的同学们在经过一天的学习和忙碌之后,到晚上是否想看一场电影。有时累了一天了,都不愿自己动手去操作播放电影了,这时是否想过用一个遥控器来代替键盘去操作电脑呢?下面的这个例子,将带着大家去实现具备这样功能的电脑遥控器。
首先你需要一个遥控器,需要知道他的按键码,按键码可以用程序测试出来。将这个按键码和你要设置的键盘的案件的扫描码建立一个对应的关系。接下来你需要一个单片机开发板,也可以自己做。自己做需要话PCB,选择好芯片并设计电路。最后需要做的就是了解PS/2通信协会并根据这个协议来编写程序。如果是要设置像ALT+F4这种的快捷键的朋友,这种键我们可以想象按下的顺序,首先按下ALT键,是发送他的通码,这时并没有释放,再按下F4的通码,接着松开F4,这时发送的是F4的断码,接着松开ALT,发送的是ALT的断码,这个数据是这样的。下面给出程序,此例直接在
电子人单片机开发板 上实现。
/***********************这是main.c文件中的代码。**********************************************************************************
** 文 件 名: 无线通信主文件
********************************************************************************************************/
#include "config.h"
#define MAXSIZE 33
UCHAR receive_data;
UCHAR sp; //奇偶校验
UCHAR start;
bit changbit=0; //改变位
UCHAR code InfraredData[MAXSIZE] = {
//0-14----------------------------------------------------------------------------------------
0x00/*0 */, 0x01/*1 */, 0x02/*2 */, 0x03/*3 */, 0x04/*4 */,
0x05/*5 */, 0x06/*6 */, 0x07/*7 */, 0x08/*8 */, 0x09/*9 */,
0x0A/*F5 */, 0x1C/*TAB */, 0x4F/*空格 */, 0x56/*回车 */,
0x5C/*BackSpace*/,
//15-26----------------------------------------------------------------------------------------
0x10/*静音 */, 0x12/*WINDOWS*/,0x1A/*音加 */, 0x1B/*上一曲 */,
0x1E/*音减 */, 0x1F/*下一曲 */, 0x4E/*delete */,0x53/*鼠标右*/, 0x5A/*上 */,
0x5B/*右 */, 0x5E/*下 */, 0x5F/*左 */,
//27-32----------------------------------------------------------------------------------------
0x16/*ctrl+v */, 0x51/*alt+esc */, 0x54/*ctrl+c */,0x57/*ALT+F4*/,
0x0F/*win+E */, 0x0B/*wins+D */,
};
/* 0x17 切换 */
/*********************************************************************************************************************/
/* 键盘扫描码 */
/*********************************************************************************************************************/
uchar code Table1[15][3]= { 0x70,0xf0,0x70,/*0 */0x69,0xf0,0x69,/*1 */0x72,0xf0,0x72,/*2 */
0x7a,0xf0,0x7a,/*3 */0x6b,0xf0,0x6b,/*4 */0x73,0xf0,0x73,/*5 */
0x74,0xf0,0x74,/*6 */0x6c,0xf0,0x6c,/*7 */0x75,0xf0,0x75,/*8 */
0x7d,0xf0,0x7d,/*9 */0x03,0xf0,0x03,/*F5 *//*0x76,0xf0,0x76,CAPSLK 58*/
0x0d,0xf0,0x0d,/*TAB */0x29,0xf0,0x29,/*空格 */0x5a,0xf0,0x5a,/*回车 */
0x66,0xf0,0x66,/*BackS */
};
uchar code Table2[12][5]= {
0xe0,0x23,0xe0,0xf0,0x23,/*【静音】 */
0xe0,0x27,0xe0,0xf0,0x27,/*【wins】 */
0xe0,0x32,0xe0,0xf0,0x32,/*【音加】 */
0xe0,0x15,0xe0,0xf0,0x15,/*【上一曲】 */
0xe0,0x21,0xe0,0xf0,0x21,/*【音减】 */
0xe0,0x4d,0xe0,0xf0,0x4d,/*【下一曲】 */
0xe0,0x71,0xe0,0xf0,0x07,/*【delete】 */
0xe0,0x2f,0xe0,0xf0,0x2f,/*【鼠标右键】 */
0xe0,0x75,0xe0,0xf0,0x75,/*【上】 */
0xe0,0x74,0xe0,0xf0,0x74,/*【右】 */
0xe0,0x72,0xe0,0xf0,0x72,/*【下】 */
0xe0,0x6b,0xe0,0xf0,0x6b,/*【左】 */
};
uchar code Table3[4][6]= { 0x14,0x2a,0xf0,0x2a,0xf0,0x14,/*ctrl +v */
0x11,0x76,0xf0,0x76,0xf0,0x11,/*alt +ESC */
0x14,0x21,0xf0,0x21,0xf0,0x14,/*ctrl +c */
0x11,0x0c,0xf0,0x0c,0xf0,0x11,/*ALT+F4 */
};
uchar code Table4[2][8]={ 0xe0,0x27,0x24,0xf0,0x24,0xe0,0xf0,0x27/*【windows】+E */,
0xe0,0x27,0x23,0xf0,0x23,0xe0,0xf0,0x27/*【windows】+D */};
UCHAR code Table5[10][3]={
0x34,0xf0,0x34,/*G*/0x33,0xf0,0x33,/*H*/0x24,0xf0,0x24,/*E*/0x4b,0xf0,0x4b,/*L*/
0x44,0xf0,0x44,/*O*/0x3b,0xf0,0x3b,/*J*/0x43,0xf0,0x43,/*I*/0x3c,0xf0,0x3c,/*U*/
0x1c,0xf0,0x1c,/*A*/0x31,0xf0,0x31,/*N*/
};
UCHAR Select(UCHAR Infrared);
void SendPS2(UCHAR Infrared);
void send_byte(uchar dat); //PS/2 协议 由键盘发送给主机
void init_keyboard(); //PS/2开机初始化
UCHAR Select(UCHAR Infrared) //查找按键序号,从MAXSIZE里比较
{
UCHAR i;
for(i=0;i<MAXSIZE;i++)
{
if(Infrared == InfraredData
)
break;
}
return i;
}
void SendPS2(UCHAR Infrared)//输入红外按键码值
{
UCHAR num;
num = Select(Infrared); //查找对应的PS/2 扫描码值
if(num<15)
{
if(changbit&&(num<10))
{
send_byte(Table5[num][0]); //发送字母
send_byte(Table5[num][1]);
send_byte(Table5[num][2]);
}
else
{
send_byte(Table1[num][0]); //发送数字
send_byte(Table1[num][1]);
send_byte(Table1[num][2]);
}
}
else if(num<27)
{
send_byte(Table2[num-15][0]);
send_byte(Table2[num-15][1]); //发送媒体键
send_byte(Table2[num-15][2]);
send_byte(Table2[num-15][3]);
send_byte(Table2[num-15][4]);
}
else if(num<31)
{
send_byte(Table3[num-27][0]);
send_byte(Table3[num-27][1]); //发送普通的快捷键
send_byte(Table3[num-27][2]);
send_byte(Table3[num-27][3]);
send_byte(Table3[num-27][4]);
send_byte(Table3[num-27][5]);
}
else
{
send_byte(Table4[num-31][0]);
send_byte(Table4[num-31][1]); //带wins的快捷键
send_byte(Table4[num-31][2]);
send_byte(Table4[num-31][3]);
send_byte(Table4[num-31][4]);
send_byte(Table4[num-31][5]);
send_byte(Table4[num-31][6]);
send_byte(Table4[num-31][7]);
}
send_byte(0xAA); //键盘控制器自检
}
/*********************************************************************************************************************/
/*********************************************************************************************************
** 函数名称: void Delay(unsigned int s)
** 功能描述: 延时程序
********************************************************************************************************/
void delay(uint z) //延时 毫秒级
{
uint x,y;
for(x=z;x>0;x--)
for(y=124;y>0;y--);
}
void micsec_delay(uchar z)//延时 10微秒级
{
uchar i,j;
for(i=z;i>0;i--)
for(j=3;j>0;j--);
}
void send_byte(uchar dat) //PS/2 协议 由键盘发送给主机 ,接收的数据
{
uchar i;
EX0 = 0; //从设备总是在时钟线为高时改变数据线状态,主设备在时钟下降沿读入数据线状态
while(!clk); //从设备向主设备发送数据时,首先检查时钟线,以确认时钟线是否为高电平
micsec_delay(5); //如果是高电平,从设备就可以开始传输数据
while(!clk);
if(da==1) // DA==1 表示从设备到主设备的通信有11位数据,为0表示主设备到从设备的通信有12位数据
{
da=0;clk=0;
micsec_delay(4);
for(i=0;i<8;i++) //数据位:8位
{
LED=~LED;
clk=1;
micsec_delay(2);
da=dat&0x01;
if(da==1)
sp++;
dat>>=1; //获取数据
micsec_delay(1);
clk=0;
micsec_delay(4);
}
clk=1;
micsec_delay(2);
switch(sp%2) //奇校验位
{
case 0 : {da=1;sp=0;} // 如果数据位中1的个数为偶数,校验位就为1;如果数据位中1的个数为奇数,
break;
case 1 : {da=0;sp=0;} //校验位就为0;总之,数据位中1的个数加上校验位中1的个数总为奇数,因此总进行奇校验。
break;
}
micsec_delay(1);
clk=0;
micsec_delay(4);
clk=1;
micsec_delay(2);
da=1;
micsec_delay(1);
clk=0;
micsec_delay(4);
clk=1;
EX0 = 1;
}
}
void init_keyboard() //PS/2开机初始化
{
delay(5);
switch(receive_data)
{
case 0xFF :{send_byte(0xFA);send_byte(0xAA);} //引起键盘进入Reset模式
break;
case 0xF2 :{send_byte(0xFA);send_byte(0xAB);send_byte(0x83);} //键盘回应2个字节设备的ID
break;
case 0xFE : send_byte(0xFE); //用于只是在接收中出现的错误,从发送最后的扫描码或者命令回应给主机
break;
case 0xCC : send_byte(0xFE);
break;
case 0x60 : send_byte(0xFE); //读输入缓冲区的内容就是读0x60端口的数据
break;
case 0xEE : send_byte(0xEE); //键盘用"Echo"(0xEE)回应
break;
case 0xF1 : send_byte(0xFE);
break;
default : send_byte(0xFA); //缺省设置,所有键的通码、断码都使能
break;
}
}
void PS2_receive() interrupt 0 //PS/2中断接收主机数据
{
uchar i; //主设备首先将时钟线和数据线设置为"请求发送"状态
EX0 = 0; //具体方式为:首先下拉时钟线至少100us抑制通信,然后下拉数据线"请求发送",最后释
if(clk==0) //放时钟线.在此过程中,从设备在不超过10us的间隔内必须检查这个状态,当设备检测到
{ //这个状态时,设备将开始产生时钟信号.此时数据传输的每一帧由12位构成
micsec_delay(11);
if(clk==0)
{
while(!clk);
if(da==0)
{
micsec_delay(1);
for(i=0;i<8;i++) //接收八位的数据
{
LED=~LED;
clk=0; //主设备总是在时钟线为低电平时改变数据线的状态,从设备在时钟上升沿读人数据线状态
receive_data>>=1;
micsec_delay(3);
clk=1;
micsec_delay(2);
if(da==1)
receive_data|=0x80;
micsec_delay(1);
}
clk=0;
micsec_delay(3);
clk=1;
micsec_delay(3);
clk=0;
micsec_delay(3);
clk=1;
micsec_delay(2);
while(!da);
da=0;
micsec_delay(1);
clk=0;
micsec_delay(3);
clk=1;
micsec_delay(1);
da=1;
init_keyboard();
}
}
}
EX0 = 1;
}
uchar chang(uchar cent) //把一个0-9,10-15转ASCII码‘0’-‘9’,‘A’-'F'
{
if( cent <= 9 )
cent = cent + 0x30; //显示数字0-9
else
cent = cent + 0x37; //显示字符A-F
return( cent );
}
/*----------------------------------------*/
void xianshi(uchar time11,uchar time22,uchar j,uchar k)
{ //解码显示两个16进制数据
uchar a,b,c,d;
a=time11/16;
b=time11%16;
c=time22/16;
d=time22%16;
LCD1602_write_char(j,k,chang(a));
LCD1602_write_char(j,k+1,chang(b));
LCD1602_write_char(j,k+2,' ');
LCD1602_write_char(j,k+3,chang(c));
LCD1602_write_char(j,k+4,chang(d));
}
/*********************************************************************************************************
** 函数名称: void main(void)
** 功能描述: 主函数
********************************************************************************************************/
void main(void)
{
UCHAR count = 0;
LCD_initial();
LCD_cls();
LCD1602_write_string(1,3,"KeyBoard...");
Init_main();
init_keyboard();
delay(1000);
start=0;
send_byte(0xAA);
while(1)
{
if(succes)
{
xianshi(infrared_R_DATA[0],infrared_R_DATA[1],2,2); //第2行,3列开始显示
xianshi(infrared_R_DATA[2],infrared_R_DATA[3],2,8); //第2行,9列
if(infrared_R_DATA[0] == InfraredUserCode)
{
if(infrared_R_DATA[2]==0x17) //切换键
changbit= !changbit; //changbit为0,发送数字;为1,发送字母
else
SendPS2(infrared_R_DATA[2]);
}
succes = 0;
}
}
}
#ifndef __HS0038_h__
#define __HS0038_h__
/*---------------------------------------------------------
HS0038控制口定义
---------------------------------------------------------*/
#define Inte_Int 1 //外部中断0/1
#define Inte_Time 1 //定时器0/1
#define Infrared_LED P13 //led灯引脚定义
bit succes = 0; //接收成功位
UCHAR infrared_Count = 0; //接收成功值,其值代表连按的次数值。
UCHAR infrared_R_DATA[4]; //接收后数据存入的数组
/*用户码:0x40 0xBF*/
#define InfraredUserCode 0x40//用户码位
/*---------------------------------------------------------
HS0038控制口及中断控制位定义
---------------------------------------------------------*/
#if Inte_Int==0 //中断0
#define Receive P32 //中断0引脚
#define Inte_I_Num 0 //中断0中断号
#define IT IT0
#define EX EX0
#define PX PX0
#endif
#if Inte_Int==1 //中断1
#define Receive P33 //中断1引脚
#define Inte_I_Num 2 //中断1中断号
#define IT IT1
#define EX EX1
#define PX PX1
#endif
#if Inte_Time==0 //定时器0
#define ET ET0 //允许T/C0中断
#define TR TR0 //关定时器中断
#define TH TH0
#define TL TL0
#endif
#if Inte_Time==1 //定时器1
#define ET ET1 //允许T/C1中断
#define TR TR1 //关定时器中断
#define TH TH1
#define TL TL1
#endif
/*---------------------------------------------------------
红外线的0、1电平区分的最低与最高值定义
---------------------------------------------------------*/
/*--晶振频率:12MHZ---------------------------------------
03 05 07 09 2B 34 7B FF
|_______|---|__|---|_______|--------|____|----------------|
| 0 1 码头 第一个下降延
---------------------------------------------------------*/
/*红外参数:*/
#define TH_0L 0x03 //0的低位定时判断值
#define TH_0H 0x05 //0的高位定时判断值
#define TH_1L 0x07 //1的低位定时判断值
#define TH_1H 0x09 //1的高位定时判断值
#define CodeStartL 0x30 //码头低位定时判断值
#define CodeStartH 0x40 //码头高位定时判断值
#define CodeOverL 0x7B //码结束低位定时判断值
/*---------------------------------------------------------
函数初始化Init_main();
---------------------------------------------------------*/
void Init_main(void)
{
IP = 0x01; //外部中断为最大级
IT1 = 1; //INT下降沿中断
EX1 = 1; //允许INT中断
TMOD=0x10; //定时器1的方式为一
TH1 = 0x00;
TL1 = 0x00;
ET1 = 0; //关闭T/C0中断
TR1 = 0; //关闭定时器
ET0 =1;
IT0 =1;
EA = 1; //开总中断
}
/*---------------------------------------------------------
利用中断接收数据程序
---------------------------------------------------------*/
void Int(void) interrupt 2
{
static UCHAR cont_R = 0xFF; //定义红外接收中断个数的计数器
UCHAR num; //定义用来存数组位置的变量
UCHAR list;
EX1 = 0;
TR = 0;
cont_R++; //中断计数开始
num = (cont_R) / 8; //计算保存到哪一个数组
list = (cont_R) % 8; //计算保存到数组的哪一位置
//-------------------------------------------------------------------------------------------
if( TH >=TH_1L && TH <= TH_1H ) //读取数据,如果为1
{
infrared_R_DATA[ num ] |= 0x01<<list; //在计算出的数组向右移动一位
}
else if( TH >=TH_0L && TH <= TH_0H ) //读取数据,如果为0
{
infrared_R_DATA[ num ] &= ~(0x01<<list); //在计算出的数组向右移动一位
}
else if(TH >= CodeStartL && CodeStartH) //红外码头检测
{
infrared_Count++; //红外重复码加
if(cont_R>=31&&infrared_R_DATA[0]==0x40)succes = 1; //只接收0X40的头码,大于32,接收成功
cont_R = 0xff; //计数初始值
}
else if(TH >= CodeOverL) //红外码头的结束标志
{
infrared_Count = 0;
cont_R = 0xff;
}
else
succes = 0; //接收失败
//-------------------------------------------------------------------------------------------
Infrared_LED = !Infrared_LED; //led灯取反闪烁
TH = 0x00; //开定时器部分
TL = 0x00;
TR = 1;
EX1 = 1;
}
/*----------------------------------------------------------------------*/
#endif
*---------------------配置文件-------------------------------------------------*/
#ifndef _CONFIG_H_
#define _CONFIG_H_
#include <reg52.h>
#include ".\\Liquid Crystal LCD1602\\LCD1602_8.h"
#include "HS0038.h"
#define LED P13
sbit clk = P3^2; //定义键盘的时钟脚(接中断口0)
sbit da = P3^1; //定义键盘的数据脚
#define KEY P24
#endif