LCD1602
液晶判忙
LCD 1602的响应速度相对于单片机的速度来说是偏慢的。
举个简单的例子,把一桶油通过漏斗向一个瓶子里倒,倒油的速度,即流量必须维持在一定范围之内,倒得太快油会从漏斗顶部溢出来,这样就浪费掉了。我们通过眼睛可以判断并使油面保持在顶面以下,以漏斗的额定流量来倒油,这样效率最高。
而对于单片机来说,1602好比那个瓶子漏斗,写入1602中要显示的数据好比油,如果以单片机的高运行速度向1602写数据就很可能造成上面所说的溢出,比如连续写入abc,结果只显示出了a,这是因为1602的显示芯片每次都要花时间来处理输入的ascii码数据,并把它显示出来。而我们却不容易主动地去控制写入数据的速度,所以1602使用忙信号就有必要了,每次单片机只有检测到忙信号为0,即不忙时,才向1602发数据。比如要显示abc,则这样操作,写a---判忙---写b---判忙---写c---判忙。这样就不会出错了。
/*等待液晶准备*/
void Lcdready()
{
unsigned char sta;
P0 = 0xFF; //P0在使用时要规定置1
RS = 0; //数据/指令选择位 1为数据,0为指令
RW = 1; //读/写位 1为读,0为写
do
{
EN = 1; //使能位
sta = P0;//读取状态字,即把P0口的数据赋值给sta
EN = 0;
}while(sta & 0x80);//当sta最高位为0时则跳出循环;若为1时则继续循环,相当于单片机停止让液晶先工作
}
液晶初始化,写入指令,数据
/* 初始化 1602 液晶 */
void init1602()
{
WriteCmd(0x38);
WriteCmd(0x0C);
WriteCmd(0x06);
WriteCmd(0x01);
}
/* 向 LCD1602 液晶写入命令,cmd-待写入命令值 */
void WriteCmd(unsigned char cmd)
{
Lcdready();
RS = 0;
RW = 0;
P0 = cmd;
EN = 1;
EN = 0;
}
/* 向 LCD1602 液晶写入数据,dat-待写入数据 */
void WriteData(unsigned char dat)
{
Lcdready();
RS = 1;
RW = 0;
P0 = dat;
EN = 1;
EN = 0;
}
液晶显示行,列的位置
/* 设置显示 RAM 起始地址(x,y)-对应屏幕上的字符坐标 */
void Lcdaddr(unsigned char x,unsigned char y)
{
unsigned char m;
if(y==0)
m = 0x00+x; //第一行字符地址从 0x00 起始
else
m = 0x40+x; //第二行字符地址从 0x40 起始
WriteCmd(m | 0x80);//设置 RAM 地址
}
液晶数据转换成字符串,以及显示
/* 整型数转换为字符串,str-字符串指针,dat-待转换数,返回值-字符串长度 */
unsigned char IntToString(unsigned char *str, int dat)
{
unsigned char i = 0;
unsigned char len = 0;
unsigned char buf[6];
if (dat < 0) //如果为负数,首先取绝对值,并在指针上添加负号
{
dat = -dat;
*str++ = '-';
len++;
}
do //先转换为低位在前的十进制数组
{
buf[i++] = dat % 10; //取最低位
dat /= 10;
} while (dat > 0);
len += i; //i最后的值就是有效字符的个数
while (i-- > 0) //将数组值转换为ASCII码反向拷贝到接收指针上
{
*str++ = buf[i] + '0'; //加0其实是加ASCII码中的0x30
}
*str = '\0'; //添加字符串结束符
return len; //返回字符串长度
}
/* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */
void Lcdshow(unsigned char x,unsigned char y,unsigned char *str)
{
Lcdaddr(x,y);
while(*str != '\0') //表示一直循环到字符串结尾
{
WriteData(*str++); //表示从高到低依次写入str数组的值
}
}
注:ASCII码中,数字0地址为0x30,1为0x31,2为0x32......因此,在指针*str=buf[i]中,为了表示数字0,1,2.......要加上0x30的地址('0')。
控制键按钮
选手按钮:1号,2号,3号,4号
主持人按钮:开始抢答,抢答成功,作答正确,作答错误,查询积分
/*控制键扫描函数,开始结束*/
void Keycontrol()
{
if(K5==0) //开始抢答键
{
if(K5==0)
{
while(!K5);//摁下按键的一瞬间会一直循环,直到松手才完成这一个过程
WriteCmd(0x01);
sec=20; //重新计时
TR0=~TR0; //TRO=1,定时器开始计时
s_flag = 1;//抢答开始键,直到抢答成功键摁下才会置0
e_flag=0; //抢答结束键,直到抢答成功键摁下才会置1
flag=1; //抢答标志,直到有人抢答才会置0
}
}
if(K6==0) //抢答成功键
{
if(K6==0 && flag==0) //有人抢答时flag会置0
{
while(!K6);
e_flag =1;//表示结束
LED1=1; //熄灭LED灯
LED2=1;
LED3=1;
LED4=1;
Buzz = 0; //蜂鸣器响应
Lcdshow(9,0,"Successed");
}
}
if(K9==0) //查分键
{
unsigned char ch;
if(K9==0 && e_flag==1) //按键按下且此时已结束
{
while(!K9);
WriteCmd(0x01); //清屏
Lcdshow(0,0,"P1:"); //当lcdshow显示"P1:"时,光标会在":"号后面
ch=(char) (score[1]+'0');
WriteData(ch); //此时写入数据ch即可继续在lcd上显示
Lcdshow(10,0,"P2:");
ch=(char) (score[2]+'0');
WriteData(ch);
Lcdshow(0,1,"P3:");
ch=(char) (score[3]+'0');
WriteData(ch);
Lcdshow(10,1,"P4:");
ch=(char) (score[4]+'0');
WriteData(ch);
}
}
}
//四路抢答器按键扫描
void KeyScan()
{
if( K1==0 && flag != 0 && s_flag==1) //有按键摁下,抢答标志键为1及存在,抢答结束键为1及还没确认
{
if(K1==0)
{
while(!K1);
num=1; //LCD显示1号"1"
TR0=0; //关闭定时器0,计时停止
TR1=1; //打开定时器1,蜂鸣器响应
LED1=0; //按下抢答器,LED亮
flag=0; //关闭开始键标志位,使再按其他三个键不会响应
}
}
if( K2==0 && flag != 0 && s_flag==1)
{
if(K2==0)
{
while(!K1);
num=2;
TR0=0;
TR1=1;
LED2=0;
flag=0;
}
}
if( K3==0 && flag != 0 && s_flag==1)
{
if(K3==0)
{
while(!K1);
num=3;
TR0=0;
TR1=1;
LED3=0;
flag=0;
}
}
if( K4==0 && flag != 0 && s_flag==1)
{
if(K4==0)
{
while(!K1);
num=4;
TR0=0;
TR1=1;
LED4=0;
flag=0;
}
}
}
void Judge()
{
if( K7==0 )//抢答正确
{
if(K7==0)
{
while(!K7);
WriteCmd(0x01); //清屏
Lcdshow(0, 0, "Your answer is:");
Lcdshow(0, 1, "Right");
score[num]++; //为选手加分
}
}
if( K8==0 ) //抢答错误
{
if(K8==0)
{
while(!K8);
WriteCmd(0x01);
Lcdshow(0, 0, "Your answer is:");
Lcdshow(0, 1, "Worry");
}
}
}
主函数
void main()
{
unsigned char str1[12];
unsigned char str2[12];
unsigned char len,len1;
EA = 1; //开启总中断
TMOD = 0x11; //打开定时器T0,T1
ET0 = 1; //开启定时器0中断
TR0 = 0; //定时器0关闭计时
TH0 = 0xFC; //定时1ms
TL0 = 0x67;
ET1 = 1; //开启定时器1中断
TR1 = 0 ;//定时器1关闭计时
TH1 = 0xFC; //定时1ms
TL1 = 0x67;
TCON=0x05; //外部中断0设置为边沿触发
EX0=1; //开外部中断0
//初始化
init1602();
Lcdshow(0,0,str1);
LED1=1;
LED2=1;
LED3=1;
LED4=1;
while(1)
{
Keycontrol();//扫描主持人按键
KeyScan();//扫描选手按键
len = IntToString(str1,sec);//倒计时转化为字符串
len1= IntToString(str2,num);//选手代号转化为字符串
if(e_flag==0)//抢答未结束
{
Lcdshow(0,0,"Time:");
Lcdshow(6,0,str1);//显示倒计时
}
if(num!=0&&e_flag==0&&flag==0)//没有结束且有人按下按键 有人抢答时flag会置0
{
Lcdshow(0, 1, "answerer:");
Lcdshow(9, 1, str2);//显示抢答的选手
}
if(e_flag==1)//抢答结束进行评分
{
Judge();
}
}
}
unsigned int cnt1= 0; //记录中断次数
void Time1 () interrupt 3
{
TH1=(65536-2000)/256;
TL1=(65536-2000)%256;
Buzz = ~Buzz;
cnt1++;
if(cnt1==1000) //定时1s
{
cnt1=0;
TR1=0;
}
}
unsigned int cnt0 = 0; //记录中断次数
void Time0 () interrupt 1
{
TH0 = 0xFC;
TL0 = 0x67;
cnt0++;
if(cnt0>=1000) //定时1s
{
cnt0 = 0;
sec--;
if(sec==0)
TR0 = 0;
}
}
proteus仿真
注:蜂鸣器的驱动电流较大,IO口无法直接驱动,所以需要经过三极管放大电流驱动。