目录
滚动显示 RollNextSta(int i) 代码思路:(发挥)
DisWelcome() 中 “上下行状态” 的显示(额外设计)
按键 6 和 7 的重置设计,直接调用重置的函数:(额外设计)
这是广告界面:
一、题目要求
课题五 点阵式 LCD 动态显示系统设计
课程设计目的
1. 了解点阵式液晶显示器的硬件接口电路、控制原理和方法。
2. 掌握点阵英文、汉字和图形的字模提取和显示方法。
3. 掌握点阵式 LCD 动态显示程序的设计思路和实现方法。
课程设计内容
根据 128X64 点阵液晶显示控制器说明书,认真阅读和理解 LCD 的硬件接口电路、 控制原理,并根据 ASCII 码、汉字码以及图形的提取和显示方法,设计连接硬件线路,然后 编写公交车报站监控程序,使汽车运行过程中,液晶显示屏在司机的控制下,按照行车路线 用汉字动态显示下一站的站名。
系统功能与设计要求
1.基本功能要求
公交车报站器的控制键盘参考下图:
设某路公交车共有 8 站(站名自定),车从起点站开出后,按【出站】”键,液晶屏 幕显示下一站的站名,如“下一站 钟楼”,当行驶到站时,按【进站】键,液晶屏幕显示该 站到达,如:“钟楼 到了”,再次按【出站】”键,液晶屏幕显示钟楼下一站的站名,如此循 环,直到终点站结束。每按一次键,显示一个整屏,显示字体、字号及格式自定,要求美观 清晰。 在运行过程中,可以重复按【进一站】或【退一站】键,随时调整当前站的站名,调 整后,当再次按【出站】或【进站】键,则从调整后的站名开始继续向下循环显示。
2.发挥部分
(1) 增加【上/下行】按键功能,按一次【上/下行】键,系统反方向(下行)显示 站名,再按一次后,系统正方向(上行)显示站名。
(2) 增加插播广告功能,车在运行中,按【广告】键,屏幕显示广告信息,信息中 含有自行设计的点阵图案。
(3)当按【出站】键后,从右向左滚动显示下一站的内容,直到按下【进站】键。
设计思路
公交车报站控制器的显示部分使用 128X64 点阵 LCD 液晶显示,按键采用 4X4 矩 阵键盘模拟。LCD 的数据线可以直接与 ISA 数据总线连接,控制信号 RS/CS2/CS1/RW/E 可以 与 ISA 系统总线的地址线 A0/A1/A2/A3/A6 连接,并连接 IORD/IOWR 信号,微机系统通过 8255 可编程并行接口的 PA 口和 PC 口控制 4X4 矩阵键盘,进行动态扫描,测得某键按下后即可 执行相关功能,完成显示。
LCD 液晶显示器原理图及连接图如下:
ASCII 字符的字模可选 8x16,每个 ASCII 字符占用 16 字节,汉字字模可选 16x16, 每个汉字占 32 字节,图形点阵根据图案自己设计。汉字字模可以采用如 Win-TC 等软件取得, 需要注意的是:字模是按行横向存放的,LCD 显示是按列纵向存放的,显示时需要进行转换。
二、功能实现
1. 键盘和8255模块
↑↑↑ 这是试验箱上的键盘模块↑↑↑ ↑↑↑ 这是试验箱上的8255模块↑↑↑
在C语言版源代码中,找到名为kd_8255.c的代码文件,这个就是键盘显示的实验例程。
我们需要获取键值,就需要这个例程代码里的一些函数来获取,因此我们必须弄懂这个例程的实现过程。我们应该明白最主要的是要获取按下的键值,所以只需要知道哪些函数能完成这些功能就可以了。
于是我们在例程文件中可提取的有用代码如下:
//这几行是8255的端口地址,必须要有
#define PA_Addr 0x270
#define PB_Addr 0x271
#define PC_Addr 0x272
#define CON_Addr 0x273//写入控制字,使8255调整到我们需要的串口模式
outportb(CON_Addr, 0x89); //PA、PB输出,PC输入
以下分析获取键值的代码:
u8 AllKey()
{
u8 i;
outportb(PB_Addr, 0x0);
i = (~inportb(PC_Addr) & 0x3);
return i;
}
这个函数是判断是否有按键按下,对应的硬件原理,是在8255模块的PC口的最低两位,用来扫描4*4键盘的行:第0位是扫描0行和2行,第1位是扫描1行和3行;并将扫描出来的结果取反输入到PC口,若PC的值不为0,则表示有按键按下。
u8 key()
{
u8 i, j, keyResult;
u8 bNoKey = 1;
while(bNoKey)
{if (AllKey() == 0) //调用判有无闭合键函数
continue;
i = 0xfe;
keyResult = 0;
do
{
outportb(PB_Addr, i);
j = ~inportb(PC_Addr);
if (j & 3)
{
bNoKey = 0;
if (j & 2) //1行有键闭合
keyResult += 8;
}
else //没有键按下
{
keyResult++; //列计数器加1
i = ((i << 1) | 1);
}
}while(bNoKey && (i != 0xff));
}
if (!bNoKey)
{
while(AllKey()) //判断释放否
{}
}
return keyResult;
}
很显然,这个函数就是获取按下的键值并返回,对比键盘显示的实验例程,我们不需要在数码管显示。所以可以将与数码管有关的代码全部抛弃掉(注意,因为不需要数码管,所以在硬件连线上我们也不需要B和C的串口连接)。
这里需要注意的是,怎么扫描获取列值到最后的输出,需要根据代码和硬件原理去理解,老师会问一些相关的问题,所以要理解代码和硬件。
根据键盘模块的原理图,简要说明一下键盘的列扫描:
在以上的行扫描不为0之后,即有按键按下,则从8255的PB口输送0xfe(11111110B)到键盘的A串口,对应的是键盘的列;如果没有感应到按键,则将0xfe左移一位得到0xfd(11111101B),一直到0x7f,直到感应到按键为止,然后根据行列计算出键值,最后返回。
2.LCD点阵显示模块
硬件图:
在C语言版源代码中,找到名为12864J.C的代码文件,这个就是LCD点阵显示的实验例程。
实验例程中的函数定义和调用很多,学习代码可以得到两种显示文字的函数:
//字节显示
void ByteDisR(unsigned char x, unsigned char y,unsigned char * pt)
void ByteDisL(unsigned char x, unsigned char y,unsigned char * pt)
//字显示
void WordDisR(unsigned char x, unsigned char y,unsigned char * pt)
void WordDisL(unsigned char x, unsigned char y,unsigned char * pt)
个人推荐使用字显示的函数,这个实验中需要文字的字模数据,我推荐使用实验室文件夹里自带的字模提取软件,并且设置输入文字的格式为宋体12,此格式提取出来的字模数据刚好是16*16,一个字显示的函数刚好能将整个字完整显示,不用考虑其他的麻烦。
然后所有的代码就在实验例程的基础上进行删改就OK了。
3.功能设计
公交车报站器的控制键盘如下图所示:
按键功能说明:
按键 0:出站,在除了终点站以外的站点内按下,显示状态变为行驶中。终点站按下显示无 变化。
按键 1:进站,行驶中按下,显示对应的站点到了。
按键 2:进一站,调整当前的站点。静止则显示当前站的下一站,行驶中则显示的下一站调 整到下下站;进到终点站则显示终点站的显示界面。
按键 3:退一站,和进一站相反,但没有终点站的显示。
按键 4:切换上下行,调整公交的行驶方向,改变显示屏最后一行的箭头方向。若在初始站 切换则显示为终点站的显示界面;若在其他站点则调整下一站的显示。
按键 5:广告键:第一次按下播放广告,第二次按下则停止播放。播放中按键 0-4 按下无效。
按键 6:重置键:按下,重置为上行初始界面。
按键 7:重置键:按下,重置为下行初始界面。
LCD点阵显示内容分布:
说明:
第 0 行:行驶中显示“行驶中”,按下“进站”则显示 “XX 站到了”,在站内则显示“当前站:XX 站”,显示 优先级从前往后递减;
第 1 行:行驶中从右向左滚动显示“下一站:XX 站”, 到达终点站则显示“终点站到了”;
第 2 行:显示当天日期和星期(预设为验收当天);
第 3 行:显示“→→欢迎乘车→→”或“←←欢迎乘车 ←←”,“→”表示上行,“←”表示下行。
根据不同的显示要求,设计了一共 12 个显示函数。
void DisCurrentSta(int i) //第一行显示当前站
void DisArrive(int i) //第二行显示“XX 站到了”
void RollNextSta(int i) //第二行滚动显示“XX 站到了”
void DisRunning() //第一行显示“行驶中”
void DisNextSta(int i) //第二行显示 “下一站:XX 站”
void DisFinalSta() //第二行显示“终点站到了”
void DisDate() //第三行显示日期和星期
void DisWelcome() //第四行显示“欢迎乘车”
void DisOhter() //显示第三第四行,方便调用
void DisAdvertise() //显示广告
void INIT_Menu() //初始上行界面
void INIT_Menu_R() //初始下行界面
滚动显示 RollNextSta(int i) 代码思路:(发挥)
声明一个临时数组,用来存放要滚动显示的文字数据,再声明一个下标,使其一直自增, 并且对数据长度取模,按顺序显示下标,使下标从右向左进行转移,达到滚动的效果。
循环滚动实现如下:
unsigned char space[32] = { //空白字符
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
};
int index = 0;
unsigned char Roll[8][32];
void RollNextSta(int i){
int k = 0;
for(k = 0; k < 32; k++){ // 下
Roll[0][k] = XiaYiZhan[0][k];
}
for(k = 0; k < 32; k++){ // 一
Roll[1][k] = XiaYiZhan[1][k];
}
for(k = 0; k < 32; k++){ // 站
Roll[2][k] = XiaYiZhan[2][k];
}
for(k = 0; k < 32; k++){ // :
Roll[3][k] = XiaYiZhan[3][k];
}
for(k = 0; k < 32; k++){ // X
Roll[4][k] = Stations[i][0][k];
}
for(k = 0; k < 32; k++){ // X
Roll[5][k] = Stations[i][1][k];
}
for(k = 0; k < 32; k++){ // 站
Roll[6][k] = Stations[i][2][k];
}
for(k = 0; k < 32; k++){ // 空格
Roll[7][k] = space[k];
}index = 0;
while(AllKey() == 0){ //没有按键按下则持续循环
WordDisL(2, 0, Roll[ index % 8]);
WordDisL(2, 16, Roll[(index + 1) % 8]);
WordDisL(2, 32, Roll[(index + 2) % 8]);
WordDisL(2, 48, Roll[(index + 3) % 8]);
WordDisR(2, 0, Roll[(index + 4) % 8]);
WordDisR(2, 16, Roll[(index + 5) % 8]);
WordDisR(2, 32, Roll[(index + 6) % 8]);
WordDisR(2, 48, Roll[(index + 7) % 8]);
delay(800); //停留
index++;
}//while
}//rollnextsta
DisWelcome() 中 “上下行状态” 的显示(额外设计)
对于上下行状态,声明一个变量来标志,对于显示的图案数据,根据变量对应的值来取。
上下行状态变量:
int status = 0; //0/1 上下行,初始默认上行
图案数据:
unsigned char Arrow[2][32] = {
{ // →
0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x90,0xA0,0xE0,0xC0,0xC0,0x80,0x80,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x02,0x03,0x01,0x01,0x00,0x00,0x00
},
{ // ←
0x80,0x80,0xC0,0xC0,0xE0,0xA0,0x90,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,
0x00,0x00,0x01,0x01,0x03,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
}
};
显示函数:
void DisWelcome(){ //第四行显示“欢迎乘车”
WordDisL(6, 0, Arrow[status]);
WordDisL(6, 16, Arrow[status]);
WordDisL(6, 32, Welcome[0]);
WordDisL(6, 48, Welcome[1]);
WordDisR(6, 32, Arrow[status]);
WordDisR(6, 48, Arrow[status]);
WordDisR(6, 0, Welcome[2]);
WordDisR(6, 16, Welcome[3]);
}
设计公交车的行驶状态
先分析默认上行的状态变化:
在上行中,公交车的状态有三个操作可以进行改变:“出站”“进站”和“进一站” 所以需要一个变量来记录公交车的行驶状态,我们使用 isRunning 来表示公交车是否在行 驶中。
在存储车站的站名数据时,我们为了调用方便,选择使用三维数组:
unsigned char Stations[8][3][32];
(8 个站点,站名最多 3 个字,每个字 16*16) 并且默认 0 到 7 为上行顺序。
我们设计为在 while 循环中一直读取 key 值,根据读取的 key 值进行对应的操作,选择使用 if-else 语句来进行不同操作的分支设计:
关于变量的声明:
int keyValue = 0; //读取键盘值
int location = 0; //默认站点为初始站点
int isRunning = 0; //表示行驶状态,1为行驶中
int isadv = 0; // 1/0 :有/无广告
int status = 0; //0/1 上下行,初始默认上行
设计“出站”操作为:
if(keyValue == 0 && isRunning == 0 && location < 7 && status == 0 && isadv == 0){ //未在行驶,则出站--上行
isRunning = 1;
LCDClear();
DisRunning();
DisOhter();
RollNextSta(location + 1);
}
条件 location < 7 是为了忽略掉在终点站进行出站操作,我们认为是“违规 操作”
isadv == 0 则判断是否正在播放广告
“进站”操作为:
else if(keyValue == 1 && isRunning == 1 && status == 0){ //在行驶中,可进站--上行
if(location < 7) //最多不能超过第7站
location++;
isRunning = 0;
LCDClear();
DisOhter();
DisArrive(location);
if(location < 7){ //未到终点站
DisNextSta(location + 1); //滚动
}
else if(location == 7){ //达到终点站
DisFinalSta();
}
}
“进一站”操作:
else if(keyValue == 2 && status == 0 && isadv == 0){ //进一站--上行
LCDClear();
if(location < 7) //最多不能超过第7站
location++;
if(location == 7)
isRunning = 0;
DisOhter();
if(isRunning == 0 || location == 7) //是否行驶中
DisCurrentSta(location);
else if(location < 7 && isRunning == 1)
DisRunning();
if(location < 7 && isRunning == 1){ //未到终点站
RollNextSta(location + 1);
}
else if(location < 7 && isRunning == 0){
DisNextSta(location + 1);
}
else if(location == 7){ //达到终点站
DisFinalSta();
}
}
“退一站”操作:
else if(keyValue == 3 && status == 0 && isadv == 0){ //退一站--上行
LCDClear();
if(location > 0) //最多退到第0站
location--;
if(location == 0)
isRunning = 0;
DisOhter();
if(isRunning == 0 || location == 0) {//初始站点或未行驶
DisCurrentSta(location);
DisNextSta(location + 1);
}
else if(location > 0 && isRunning == 1) {//未到初始且行驶中
DisRunning();
RollNextSta(location + 1);
}
}
按键 5 播放广告的设计:(发挥)
与上面操作的同一个 if-else 语句:
else if(keyValue == 5){ //播放广告
isadv = isadv > 0 ? 0 : 1;
}
以上是切换广告的状态,在 while 循环最后添加另一个 if 语句,判断是否需要播放广告:
if(isadv == 1 && keyValue == 5){ // display advertise
LCDClear();
DisAdvertise();
}
else if(isadv == 0 && keyValue == 5){ // remove advtertise
LCDClear();
if(isRunning == 1)
DisRunning();
else
DisCurrentSta(location);
DisOhter();
if(isRunning == 1 && location < 7)
RollNextSta(location + 1);
else if(isRunning == 0 && location < 7)
DisNextSta(location + 1);
else if(location == 7)
DisFinalSta();
}
按键 6 和 7 的重置设计,直接调用重置的函数:(额外设计)
else if(keyValue == 6){ //重置上行
INIT_Menu();
}
else if(keyValue == 7){ //重置下行
INIT_Menu_R();
}
我们还设计了一些“违规操作”的处理,如下:
在终点站进站我们认为是违规操作,则显示终点站显示界面:
if(keyValue == 1 && location == 7 && status == 0 && isadv == 0){ //违规:终点进站--上行
LCDClear();
DisOhter();
DisCurrentSta(location);
DisFinalSta();
}
以上代码要在判断键值的if语句之前。
在行驶中出站的违规处理:显示行驶中状态界面
if(keyValue == 0 && isRunning == 1 && location < 7 && status == 0 && isadv == 0){ //违规:行驶中出站--上行
LCDClear();
DisRunning();
DisOhter();
RollNextSta(location + 1);
}
以上代码在判断键值的if语句之后,广告的if语句之前。
按键 4:关于上下行切换的操作(发挥)
我们选择一个 status 变量来记录上下行的状态,按下按键 4 则切换变量的值,并且根 据切换后的状态显示对应的界面,比如:
中间站点的静止和行驶中,切换后则更新下一站的名字;
到达终点站后切换上下行,则从终点站界面更新为初始站界面;
若在初始站时切换上下行,则更新为终点站界面。
在此之后,我们要对“出站”、“进站”、“进一站”和“退一站”操作进行上下行的切换, 因为有 status 作为上下行状态的记录,我们选择直接将上行的操作和判定进行反向取值, 这样即可实现上下行的操作切换,例如“出站”的下行操作和上行对比:
上行出站代码:
if(keyValue == 0 && isRunning == 0 && location < 7 && status == 0 && isadv == 0){ //未在行驶,则出站--上行
isRunning = 1;
LCDClear();
DisRunning();
DisOhter();
RollNextSta(location + 1);
}
下行出站代码:
else if(keyValue == 0 && isRunning == 0 && location > 0 && status == 1 && isadv == 0){ //未在行驶,则出站--下行
isRunning = 1;
LCDClear();
DisRunning();
DisOhter();
RollNextSta(location - 1);
}
变化就是在 if 判断语句里 status 和 location 的不同,以及站点的下标变化。 与此同理,对“进站”、“进一站”和“退一站”操作,还有以上的两个违规操作,修改对应 的条件和操作,即可实现切换上下行的操作。
总结:实现了课设要求中的基本功能和发挥功能,以及一些额外的设计部分。