简介:这是一份面向单片机课程设计的完整PDF报告,围绕LCD12864显示屏实现十字路口交通信号灯的倒计时显示与控制。报告源自湖南大学电气与信息工程学院本科生课程设计,适合电气、自动化及相关专业学生作为课程设计参考,也可帮助入门者学习ADuC848单片机、LCD12864接口编程、定时器与中断应用。内容先从开发板硬件资源分布讲起,给出板上资源分布图与ADuC848简要介绍,再详细讲解LCD显示模块的信号连接图与关键引脚说明,之后给出交通信号灯系统的原理分析、程序流程框图、具体程序实现、操作步骤和参考文献,覆盖信号灯倒计时、三色灯切换、紧急事件处理等需求。资源为单个PDF文档,大小约1.5MB,图文并茂,目录结构清晰。目前已有190人浏览学习,对于正在准备单片机课程设计或想快速上手LCD12864显示与交通灯控制逻辑的读者,是一份可直接对照参考的实用资料。
1. 基于LCD12864的交通信号灯课程设计,先想清楚做成什么
如果只看课程设计报告的标题,容易把“基于LCD12864的交通信号灯”理解成流水灯加一个液晶屏。真正拉开差距的是状态切换与LCD12864的显示节奏是否同步:红灯倒计时不能跳秒,绿灯切黄灯时不能先灭一下,南北方向禁行时屏幕上要给出和灯面一致的汉字提示。这个题目最稳的做法,是用51单片机做四状态循环,用定时器产生1秒时基,再用ST7920主控的LCD12864以并行方式显示状态文字和剩余秒数。下文按这个骨架展开,覆盖Proteus仿真、C51代码、配时调节和课程设计报告的收尾,适合准备做51单片机交通灯课程设计的读者参考。
2. 交通信号灯控制器系统结构与LCD12864接口选型
2.1 交通信号灯四状态循环:绿、黄、红怎么排
交通信号灯的核心是一个有限状态机。课程设计里最常要求的是十字路口,东西方向为主干道,南北方向为支路,不能出现两个方向同时绿灯。按照国标习惯,放行顺序是东西绿灯、东西黄灯、南北绿灯、南北黄灯四个状态循环。南北红灯和东西红灯只是对侧状态的镜像,不需要额外定义。
| 状态 | 东西方向灯面 | 南北方向灯面 | 默认配时 | 典型调节范围 |
|---|---|---|---|---|
| S1 | 绿灯 | 红灯 | 60秒 | 30~99秒 |
| S2 | 黄灯 | 红灯 | 5秒 | 3~10秒 |
| S3 | 红灯 | 绿灯 | 30秒 | 20~60秒 |
| S4 | 红灯 | 黄灯 | 5秒 | 3~10秒 |
表格里的默认配时不是拍脑袋写的。主干道车流大,东西绿灯要比南北绿灯长;黄灯时间由车速和路口宽度决定,一般不低于3秒。答辩时如果老师问“为什么南北红灯也是60秒”,回答是:南北红灯时长等于东西绿灯加东西黄灯,也就是对侧放行和缓冲的总和。
在这个状态下,单片机要输出6路信号,正好占用一个8位IO口。常见做法是把6个LED接到P1口的低6位,用低电平点亮的方式驱动。51单片机IO口的高电平驱动能力弱,P1口灌电流能力比拉电流好,所以LED阴极接IO口、阳极经限流电阻接电源,这是51单片机硬件设计里最基本的一条。
sbit EW_GREEN = P1^0; // 东西绿灯 sbit EW_YELLOW = P1^1; // 东西黄灯 sbit EW_RED = P1^2; // 东西红灯 sbit NS_GREEN = P1^3; // 南北绿灯 sbit NS_YELLOW = P1^4; // 南北黄灯 sbit NS_RED = P1^5; // 南北红灯这段定义把灯面和IO位一一对应。后面状态机切换时,只需要给P1口写入一个字节,就能同时改变6个灯。例如S1状态对应二进制1110 1011,也就是P1 = 0xEB,但不能把低电平信号理解成“熄灭”,上面已经说过是灌电流点亮,所以在实际板子上还要用万用表确认VCC到LED限流电阻这一路是否接通。
2.2 LCD12864选型:带字库ST7920与并行/串行两种接法
LCD12864的模块型号很多,课程设计里要优先选带中文字库的ST7920主控模块。ST7920内部有GB2312中文汉字库,写汉字时直接发送两个字节的编码,不需要自己准备12864取模表。另一类以KS0108为控制器的LCD12864没有字库,要想显示状态文字就得取模,课堂演示时既要查编码又要调试对齐,没必要给自己加难度。
ST7920同时支持并行和串行接口。并行又分8位和4位,串行是SPI式三线接口。如果是在Proteus仿真51单片机,建议直接用8位并行;如果是想把板子做得简洁、少占用IO口,再用串行。
| LCD12864引脚 | 功能 | 8位并行接法 |
|---|---|---|
| 1 | VSS | 接GND |
| 2 | VCC | 接+5V |
| 3 | V0 | 接10k电位器中点,调整对比度 |
| 4 | RS | 接P2.5 |
| 5 | RW | 接P2.6 |
| 6 | E | 接P2.7 |
| 7~14 | DB0~DB7 | 接P0口 |
| 15 | LEDA | 接+5V |
| 16 | LEDK | 接GND |
这里有个容易踩的坑:DB0~DB7接到P0口后,必须给P0口加上拉排阻,否则LCD12864在仿真和实物上都可能出现花屏或白屏。P0口是开漏输出,内部没有上拉,不接10k排阻时高电平不稳定。Proteus仿真时很多人忽略这一点,屏幕能亮但字乱跳,原因就在这里。
串行接法则简单很多:把模块背面的PSB跳线接到低电平或直接接地,SCLK接P2.5,SID接P2.6,CS接P2.7,数据线和控制线共用,最多占3个IO口。串行的缺点是初始化代码和时序比较复杂,调试时先看SCLK的波形是否符合要求,比较费时间。如果报告篇幅有限,并行接法写起来更直观;如果板子引脚不够用,串行接法更合适。
2.3 Proteus最小系统:用AT89C52模型代替STC89C52RC常见做法
在Proteus仿真51单片机课程设计时,STC89C52RC往往没有完全对应的元件模型,最常见的做法是用AT89C52代替,引脚完全兼容,晶振、复位电路和程序下载方式略有差别,但仿真阶段不影响逻辑验证。放置元件时,晶振选11.0592MHz,两个30pF电容接晶振两端到地,复位电路用10uF电容加10k电阻,P0口放一个RESPACK-8排阻拉到VCC。
LCD12864在Proteus中可以用元件库里的LGM12864,它的控制器模型兼容ST7920,接线和实物一致。注意不要在仿真里改动其内部Data Format,否则汉字显示会变成乱码。启动仿真后如果屏幕背光亮但无内容,先检查V0电位器是否拧到了中间位置,再检查PSB是否接高电平,最后确认清屏命令后面是否给了足够延时。
3. 用C51写LCD12864交通信号灯状态机与显示驱动
3.1 定时器0产生1秒时基:中断里只做累加
交通信号灯最忌讳用空循环Delay来凑时间。LED是发光器件,人眼对时序抖动很敏感;LCD12864刷新又需要时间,如果延时函数占住CPU,按键会失灵,屏幕更新也会卡顿。正确的做法是用定时器0产生固定时基,在主循环里只做状态判断和显示刷新。
以11.0592MHz晶振、12T模式为例,定时器0用方式1,16位计数,每50ms产生一次中断,20次中断累计1秒。初始化代码和中断服务函数如下:
#include <reg52.h> #include <intrins.h> typedef unsigned char uchar; typedef unsigned int uint; sbit LCD_RS = P2^5; sbit LCD_RW = P2^6; sbit LCD_EN = P2^7; #define LCD_DATA P0 bit sec_flag = 0; uint t50ms = 0; uchar countdown = 60; void Timer0_Init(void) { TMOD &= 0xF0; TMOD |= 0x01; // 定时器0,方式1,16位定时 TH0 = 0x4C; // 11.0592MHz下50ms初值 TL0 = 0x00; EA = 1; ET0 = 1; TR0 = 1; } void Timer0_ISR(void) interrupt 1 { TH0 = 0x4C; TL0 = 0x00; if (++t50ms >= 20) { // 50ms * 20 = 1s t50ms = 0; sec_flag = 1; } }代码中TMOD &= 0xF0是为了保留定时器1的配置,不覆盖高4位。TH0 = 0x4C、TL0 = 0x00是11.0592MHz下50ms的初值,换用12MHz晶振时要重新计算。STC新固件里如果选择6T或1T模式,这个初值也会变,所以不要直接抄一份代码就认为一定准。另一种更省事的方法是打开STC-ISP工具里的定时器计算器,选择定时器0、方式1、50ms间隔,它会自动生成初始化代码和中断函数。
3.2 交通灯状态切换:倒计时归零才换状态
有了秒脉冲后,主循环里通过sec_flag判断是不是该走一次状态机。状态切换不能放在中断里做,否则LCD刷新会被打断,显示会闪烁。下面这段代码是把状态机单独抽成一个函数:
typedef enum { EW_GREEN_NS_RED = 0, EW_YELLOW_NS_RED, EW_RED_NS_GREEN, EW_RED_NS_YELLOW } TrafficState; TrafficState state = EW_GREEN_NS_RED; uchar time_ew_green = 60; uchar time_ew_yellow = 5; uchar time_ns_green = 30; uchar time_ns_yellow = 5; uchar countdown = 60; void Traffic_Loop(void) { if (!sec_flag) return; sec_flag = 0; countdown--; switch (state) { case EW_GREEN_NS_RED: if (countdown == 0) { state = EW_YELLOW_NS_RED; countdown = time_ew_yellow; } break; case EW_YELLOW_NS_RED: if (countdown == 0) { state = EW_RED_NS_GREEN; countdown = time_ns_green; } break; case EW_RED_NS_GREEN: if (countdown == 0) { state = EW_RED_NS_YELLOW; countdown = time_ns_yellow; } break; case EW_RED_NS_YELLOW: if (countdown == 0) { state = EW_GREEN_NS_RED; countdown = time_ew_green; } break; } OutPut_LED(state); LCD_ShowTraffic(state, countdown); }这段代码的关键是“倒计时归零才换状态”。如果写成countdown == 1,显示上会感觉少了1秒,因为秒脉冲先减1再判断换状态。这里统一把countdown解释成“当前状态剩余秒数”,当前状态剩余秒数减到0再切换,逻辑最顺。每切换一次状态,就把下一状态的秒数赋给countdown,整个循环不需要额外变量,也不容易出现两个方向配时串位。
OutPut_LED和LCD_ShowTraffic两个函数需要再定义。LED输出就是给P1口写一个状态字节,LCD显示则是把状态文字和剩余秒数写进12864。如果嫌枚举类型在C51里占用RAM资源,可以直接用uchar state,效果一样,但代码可读性会差一些。
3.3 LCD12864初始化和汉字显示:写指令、写数据、清屏顺序
ST7920的LCD12864初始化有几条固定指令,顺序不能乱。先发0x30把模块设置成8位数据接口和基本指令集,再发0x0C打开显示但关闭光标,接着清屏,最后设置地址自动加1。清屏命令0x01执行时间较长,指令后面最好加一点延时,否则后续数据可能被吞掉。
void LCD_Delay(void) { uint i = 100; while (i--); } void LCD_WriteCmd(uchar cmd) { LCD_RS = 0; LCD_RW = 0; LCD_DATA = cmd; LCD_EN = 1; _nop_(); _nop_(); LCD_EN = 0; } void LCD_WriteData(uchar dat) { LCD_RS = 1; LCD_RW = 0; LCD_DATA = dat; LCD_EN = 1; _nop_(); _nop_(); LCD_EN = 0; } void LCD12864_Init(void) { LCD_WriteCmd(0x30); LCD_Delay(); LCD_WriteCmd(0x0C); LCD_Delay(); LCD_WriteCmd(0x01); LCD_Delay(); LCD_WriteCmd(0x06); LCD_Delay(); }这里LCD_RS=0表示写指令,LCD_RS=1表示写数据。ST7920的数据口是P0,所以必须配合P0口上拉排阻,否则LCD_DATA = cmd写出去的电平可能不够高,模块识别到的状态就错了。_nop_()来自intrins.h,目的是给E引脚维持一点高电平时间,实际板上如果速度不够稳定,可以把这个延时加大。
汉字显示时,ST7920的字库地址按行分布,12864屏幕横向分成4行,每行显示8个汉字或16个字符数字。LCD12864的行址分别是0x80、0x90、0x88、0x98,row * 0x10 + col是简化的地址计算公式。写汉字时连续写两个字节,模块会从内置字库里找到对应汉字:
void LCD_ShowChinese(uchar row, uchar col, const uchar *str) { LCD_WriteCmd(0x80 + row * 0x10 + col); while (*str) { LCD_WriteData(*str++); LCD_WriteData(*str++); } } void LCD_ShowNum(uchar row, uchar col, uchar num) { uchar tens = num / 10; uchar ones = num % 10; LCD_ShowChinese(row, col, "倒计时"); LCD_WriteCmd(0x80 + row * 0x10 + col + 4); LCD_WriteData('0' + tens); LCD_WriteData('0' + ones); }使用示例如下:LCD_ShowChinese(0, 0, "东西绿灯")会把“东西绿灯”显示在第一行前四个汉字位置。注意字符串里不能出现ASCII和汉字混排,例如“东E绿”会显示乱码,需要把状态提示字符串统一写成汉字。倒计时数字用ASCII字符显示,占8个半角字符位置,因此数字部分在col+4之后,正好排在“倒计时”三个字后面。
4. 交通信号灯配时参数、按键调节与LCD12864调试排错
4.1 配时参数集中到配置区
状态机里的60、30、5是魔法数字。课程设计报告里如果只贴程序,老师看不出参数依据,修改时也得一个一个找。更好的做法是把配时提取成配置参数,放在程序头部,运行时用变量保存:
#define T_EW_GREEN 60 // 东西绿灯默认秒数 #define T_EW_YELLOW 5 // 东西黄灯默认秒数 #define T_NS_GREEN 30 // 南北绿灯默认秒数 #define T_NS_YELLOW 5 // 南北黄灯默认秒数这几个参数直接影响交通信号灯控制器的运行节奏。黄灯秒数不建议小于3,否则车辆很难刹停;南北绿灯也不宜过短,否则行人过马路时间不足。按下复位键后,程序从头开始执行,所有状态回到初始值,倒计时也回到60秒,这样调试时最容易观察。
4.2 按键调节配时与消抖
课程设计要求里常有一条“可调配时”。实现方法很多,最稳的是在P3口接两个独立按键,一个切换要调的状态,一个增加秒数。按键扫描放在主循环里,每隔10ms扫描一次,连续读到低电平才认为按下,避免机械抖动造成一次按击触发多次调整。
sbit key_mode = P3^2; sbit key_add = P3^3; uchar adjust_mode = 0; // 0:无调整,1:调东西绿灯,2:调南北绿灯 void Key_Scan(void) { if (key_mode == 0) { adjust_mode++; if (adjust_mode > 2) adjust_mode = 0; } if (key_add == 0) { if (adjust_mode == 1 && time_ew_green < 99) time_ew_green++; if (adjust_mode == 2 && time_ns_green < 99) time_ns_green++; } }这里的key_mode和key_add是外部IO口读到的电平。按键没有按下时IO口被上拉电阻拉高,按下后接地为低,所以判断条件写成== 0。按键消抖不能只靠一次读取,还要在两次读取之间加延时,或者在循环里数连续低电平的周期。更严格的做法是把按键扫描放到定时器中断里,10ms调用一次,不过把显示和按键都塞进中断会让代码变复杂,主循环扫描已经能满足课程设计的演示需求。
4.3 LCD12864白屏、花屏、缺字的三个典型原因
LCD12864的调试问题集中在屏幕显示上。第一类是白屏无显示,先调节V0对比度电位器,把V0电压调在1V左右,屏幕才会出现黑色像素点。如果调了对比度仍然白屏,检查PSB引脚有没有接错,并行模式需要接高电平。
第二类是花屏,通常是P0口没有上拉电阻,或者上下拉排阻虚焊。花屏还会出现在程序运行中,原因是LCD_WriteCmd后没有留足稳定时间,可以适当加大LCD_Delay,但不要为了赶速度把_nop_()全删掉。
第三类是缺字,多半是写入地址不对。ST7920在汉字模式下一行只能放8个汉字,如果写入位置超过第8列,后面的字就到了第二行;如果字符串长度超过两行,也会出现乱码。检查0x80 + row * 0x10 + col是否写错,特别是第二行和第四行地址不是连续递增,第二行是0x90,第四行是0x98。
4.4 Proteus仿真与Multisim仿真怎么选
很多课程设计题目后面带“仿真”两个字,交通信号灯multisim仿真也是经常被搜索的组合。Multisim适合纯数字电路,例如用555定时器加计数器搭交通灯,但要让LCD12864显示汉字,Multisim的8051模型和液晶库远不如Proteus顺手。我的建议是:代码和LCD12864相关的仿真用Proteus,Multisim只用来做替代方案里的数字逻辑图。Proteus中仿真速度如果太慢,可以把定时器初值改小,让1秒变成0.2秒,肉眼就能看到倒计时快速下降,但报告里要说明这是预演模式,实际参数以代码配置区为准。
下载到实物时,STC系列单片机可以用STC-ISP软件通过串口下载。下载线接P3.0和P3.1,注意下载时单片机上电顺序是先点下载再上电。如果板子上有LCD12864,下载时它的数据线接在P0口,不影响串口下载,因为P0口没有直接连接下载信号。
5. 课程设计报告收尾:流程图、仿真截图与答辩演示技巧
5.1 报告里必须有的三张图和一张表
第一张是系统总体框图,画出单片机、电源、晶振复位、按键、LED灯组、LCD12864的箭头关系。第二张是状态转移图,图里要标出每个状态之间的转移条件,也就是“倒计时归零”这个触发词。第三张是LCD12864显示界面示意图,把“东西绿灯 南北红灯 倒计时60”标出来。表格则放IO分配表,交代哪个引脚接LED、哪个引脚接LCD,审阅老师通常先看这张表。
5.2 答辩演示前按顺序检查
演示容易翻车的地方不是代码逻辑,而是硬件和仿真软件的小问题。先按复位键,确认LCD12864清屏后再显示文字;把V0调到字迹清晰,不要在屏幕上留一整行花屏状态;再演示一次按钮配时调整,让老师看到倒计时从调整后的秒数重新开始。如果演示时用的是Proteus仿真,记得把仿真速度从Faster调成合适比例,否则状态切换快得看不清。
最后留一个我常用的收尾习惯:演示前把配时参数表贴在代码配置区,老师问“为什么是60秒”时,打开LCD12864显示界面,直接指状态转移图,比临时从原理开始推导更稳。
本文还有配套的精品资源,点击获取