西电微机原理课程设计之公交报站

本文介绍了一种基于128X64点阵LCD的公交报站系统设计方案,包括键盘控制、LCD显示控制等内容,实现了动态显示站名、上下行切换等功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

一、题目要求

课题五 点阵式 LCD 动态显示系统设计

课程设计目的

课程设计内容

系统功能与设计要求

1.基本功能要求

2.发挥部分

设计思路

二、功能实现

1. 键盘和8255模块

2.LCD点阵显示模块

3.功能设计

公交车报站器的控制键盘如下图所示:

LCD点阵显示内容分布:

滚动显示 RollNextSta(int i) 代码思路:(发挥)

DisWelcome() 中 “上下行状态” 的显示(额外设计)

设计公交车的行驶状态

关于变量的声明:

设计“出站”操作为:

“进站”操作为:

“进一站”操作:

“退一站”操作:

按键 5 播放广告的设计:(发挥)

按键 6 和 7 的重置设计,直接调用重置的函数:(额外设计)

我们还设计了一些“违规操作”的处理,如下:

按键 4:关于上下行切换的操作(发挥)

菜鸟自己发的,多多见谅哈~


这是广告界面:

一、题目要求

课题五 点阵式 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 的不同,以及站点的下标变化。 与此同理,对“进站”、“进一站”和“退一站”操作,还有以上的两个违规操作,修改对应 的条件和操作,即可实现切换上下行的操作。

总结:实现了课设要求中的基本功能和发挥功能,以及一些额外的设计部分。

菜鸟自己发的,多多见谅哈~

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值