简介:51单片机12864大液晶屏Proteus仿真设计资源,适合电子类专业课程设计、毕业设计,以及正在学习单片机显示驱动的初学者。工程以Proteus仿真替代实物焊接,演示了如何用C语言控制12864点阵液晶屏完成初始化、字符与图形显示,可在无开发板条件下反复验证程序逻辑,便于理解单片机和液晶模块之间的并行数据通信过程。资源包共19个文件:6个C源文件与6个头文件,按按键扫描、液晶显示映射、游戏图形等模块拆分,便于复用到其他界面设计;同时附带Keil的uv2工程、编译生成的hex与调试辅助文件(m51、plg),以及Proteus的dsn、dbk、pwi仿真工程文件,包体仅55KB,小巧但环境配置完整。目前已有281人学习下载。通过该工程可掌握12864液晶屏的驱动时序、字模取模与显示坐标控制,理解按键扫描与显示刷新的配合方式,并学会Keil和Proteus的联合仿真调试。在此基础上可扩展菜单界面或简单游戏,为课程设计和毕业设计的实物演示打下良好基础,同时也能提升阅读原理图与编写模块化代码的能力。
1. 一块12864大屏卡住整个项目?Proteus仿真帮你把时序问题挡在焊接之前
做单片机课程设计或者毕业设计,很多人第一次接触12864这种大屏。和1602相比,它能显示的内容多,还能直接显示汉字,看起来高一个档次。但问题也随之而来:引脚多、接口模式复杂、初始化时序容易翻车。我帮某学弟调一个“基于51单片机的12864大液晶屏proteus仿真设计”题目时发现,仿真阶段把逻辑理顺,后面画板、焊接、调试能省一半时间。这篇文章把从选屏、接线、驱动、调试的完整链路拆开讲,包含可以直接抄的代码,以及五条血泪踩坑记录。适合正在做课程设计、毕业设计,或者第一次接触ST7920系列控制器的人。
2. 12864的接口与选型:先搞清楚你手里的屏是ST7920还是KS0108
2.1 ST7920与KS0108:带字库和不带字库的屏怎么选
12864液晶屏,顾名思义是128×64像素的显示区域。但同样叫12864,内部控制芯片却可能完全不同。Proteus里能搜到的LCD12864模型,绝大多数基于两种芯片:ST7920和KS0108(也兼容HD61202)。
ST7920最大的特点是内置中文字库。你只要知道某个汉字的GB2312内码,直接把这个内码写进DDRAM地址,屏幕就会自动把这个汉字画出来。这对只要求显示常规文本的项目来说太省事了,不需要自己准备任何点阵数据。KS0108不带字库,整个显示过程全部要自己控制。你需要自己准备字模,还要按页、按列去管理,逻辑复杂度上升一个台阶。
| 对比项 | ST7920 | KS0108 |
|---|---|---|
| 中文字库 | 内置 | 无 |
| 显示汉字 | 直接写内码 | 自己取模写显存 |
| 接口方式 | 8位并行/4位并行/串行 | 通常只有并行 |
| 引脚数量 | 相对少 | 较多 |
| Proteus仿真难度 | 低 | 高 |
我一般建议课程设计优先选ST7920。理由很直接:省掉取模的整套流程后,核心精力可以放在逻辑功能上。Proteus里的DEM12864A模型就是典型的ST7920方案,引脚上有PSB、SID、SCLK这几个标识,一眼能认出来。如果你看到模型引脚是CS1、CS2、E、R/W这种组合,那基本就是KS0108,直接换模型。
2.2 接口模式选择:串行和并行的取舍
ST7920支持三种外部接口方式:8位并行、4位并行和串行。选择哪一个,取决于PSB引脚的电平。PSB接高电平,选择并行接口;PSB接低电平或悬空,选择串行接口。
在Proteus仿真环境里,我强烈建议用串行模式。原因有两点。第一,51单片机的IO资源本来就不宽裕,12864并行模式要占8个数据引脚还有RS、RW、E三个控制脚,整整11个IO,整个P0和P2基本都搭进去了,后面想加按键、传感器都得重新分配引脚。第二,串行模式下只需要SID、SCLK、CS三根线,走线清爽,排查问题的时候逻辑更清楚。
还有一个容易忽略的点:某些课程设计题目要求实物板上必须用并行接口,因为并行速度快、接口是标准工业接口。这种题目的初衷是让学生把并行时序搞明白。但如果是仿真为主,用并行只是自找麻烦,串行模式下ST7920的内部指令集完全一样,屏幕效果也完全一样,唯一的差别就是传输速度。
2.3 时序参数基准:先记住这几个关键数字
无论选哪种接口模式,ST7920的时序基准是一样的。这里有几个数字很重要:ST7920内部工作时钟最高大约2MHz,串行模式下SCLK高电平和低电平的时间都不小于微秒级,命令与命令之间的延时最稳妥的做法是给到几十到几百微秒,清屏命令执行时间比较长,需要给到10ms以上。
在Proteus仿真里,模型的时序容差通常比实物芯片大。也就是说,仿真能跑通的代码,烧到实物上不一定能跑通,反过来也一样。我在一个项目里遇到过代码里延时写得特别小,仿真完美,实物完全不动的场景。所以写驱动代码的时候,延时宁多勿少,这是贯穿全文的一个原则。
3. 搭建Proteus仿真环境:从最小系统到12864接线
3.1 Keil工程与Proteus元件:先搭一个最小系统
做仿真之前先把编译环境准备好。用Keil C51新建工程,选择AT89C52作为目标芯片。Proteus这边放上AT89C52、晶振、两个30pF电容、复位电路以及12864 LCD模型,先不连12864,先跑一个LED闪烁程序确认工程通了。
#include <reg52.h> sbit LED = P1^0; void delay_ms(unsigned int t) { unsigned int i, j; for (i = 0; i < t; i++) { for (j = 0; j < 120; j++); } } void main(void) { while (1) { LED = 0; delay_ms(500); LED = 1; delay_ms(500); } }逻辑说明:这个程序的目的很简单,就是验证单片机最小系统能跑。如果LED在Proteus里正常闪烁,说明晶振、复位、程序下载链路都没有问题。如果LED不闪,优先检查晶振频率设置和复位电路参数,不要急着接LCD。
参数说明:delay_ms函数里的内层循环次数120是根据12MHz晶振预估的,大约延时1ms。这个值在该方案下够用,换晶振频率需要重新校准。Proteus里挖到12MHz晶振后双击确认频率,同时检查单片机的Clock Frequency属性是否为12MHz,这两个地方不一致会导致延时严重不准。
3.2 串行模式接线:PSB接地,CS、SID、SCLK三条线的讲究
最小系统跑通后,开始接12864。以ST7920串行模式为例,接线表如下:
| 12864引脚 | 接法 |
|---|---|
| VCC | VCC |
| GND | GND |
| PSB | GND(选择串行模式) |
| CS | P2.2(片选,高电平有效) |
| SID | P2.0(串行数据) |
| SCLK | P2.1(串行时钟) |
| V0 | 接可调电阻或空接 |
这里面有一个很重要的细节:PSB接地是切换到串行模式的必要条件。很多人用仿真器测了半天,屏幕怎么都点不亮,最后发现PSB默认接到VCC上,芯片一直在并行模式下等工作,串行数据自然不会被响应。
CS引脚的接法也有讲究。在ST7920串行模式下,CS相当于片选使能,发送数据前必须拉高,发完一个完整字节之后可以拉低等待。如果你用的是并行模式,CS引脚通常直接接VCC或者悬空处理。两种模式下的CS功能不完全一样,踩坑的点就在这。
3.3 电源和复位的隐藏细节:P0口上拉和晶振匹配
Proteus里AT89C52的VCC和GND引脚通常是隐藏的,默认连好,不需要额外操作。但是这里有一个特别容易被忽略的坑:如果某个模型库里带的是P0口作为数据口,一旦用到P0口,就必须在外部接上拉电阻,因为P0口内部是开漏输出,直接输出高电平会出现电平值上不去的情况。
并行模式或者用P0口驱动LED、读取按键的场景,都需要在P0口上挂排阻或者单个10k电阻到VCC。Proteus仿真里不加上拉,会导致电平识别混乱,可能会出现程序逻辑对但硬件行为诡异的情况。串行模式下SID和SCLK接在P2口,P2口内部有上拉,这个坑才没有踩到。
复位电路方面,用10uF电容串联10k电阻接GND,复位引脚接在电容与电阻的节点上,这是标准的RC上电复位。电容太小会导致复位时间不足,程序可能在奇怪的状态下启动。Proteus对此要求不严,但实物上按这个参数来做是稳妥的。
3.4 辅助调试工具:虚拟终端和示波器的搭配
Proteus自带Virtual Terminal虚拟终端,这个工具在驱动12864之前特别有用。做法是临时把单片机串口的TX引脚和12864的SID引脚短接,让单片机能同时把数据输出到虚拟终端。这样你在代码里发送一个字节,屏幕如果有显示,虚拟终端里也能看到对应的数据波形。如果虚拟终端显示的数据内容和预期一致,就能排除单片机的IO口配置问题,把疑问限定在LCD模型本身。
虚拟示波器同样重要。把探针分别接SCLK和SID,运行仿真后能看到一串方波脉冲。正常通信时,SCLK上应该有均匀的脉冲,SID则在SCLK上升沿前后稳定。如果SCLK波形高电平时间特别短,看起来像一根根细针,说明延时不匹配,需要调整延时函数。波形确认好之后再开始写显示逻辑,排错效率会高很多。
4. 51单片机驱动代码:从底层时序到汉字显示
4.1 串行底层通信:同步字节、拆分发送的逻辑
ST7920串行模式的通信协议有固定格式。片选拉高后,主机发送的第一个字节是同步字节:高5位固定为11111,第6位是R/W读写方向(写操作固定为0),第7位是RS位(0表示命令,1表示数据),第8位固定为0。也就是说,写命令的同步字节是0xF8,写数据的同步字节是0xFA。
同步字节之后,每条命令或数据被拆成两个字节发送:先发高4位,再发低4位,低4位补齐为0。比如要发送命令0x30,先发0x30的高4位,也就是0x30 & 0xF0 = 0x30,再发低4位左移四位的结果,也就是0x00。这里有初学者经常搞混的地方:发送的不是原数据本身,而是经过移位后的结果。
sbit SID = P2^0; // 串行数据 sbit SCLK = P2^1; // 串行时钟 sbit LCD_CS = P2^2; // 片选,高电平有效 void LCD_DelayUs(unsigned int t) { while (t--); } void LCD_SendByte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { SID = (dat & 0x80) ? 1 : 0; // 高位先出 dat <<= 1; SCLK = 0; LCD_DelayUs(10); SCLK = 1; LCD_DelayUs(10); } }逻辑说明:LCD_SendByte把传入的字节按位送出,每送出一个bit就切换一次SCLK。ST7920在SCLK上升沿采样数据,所以SID的电平必须先于SCLK上升沿稳定。代码里先把SID赋值,再让SCLK从低变高,顺序不能反过来。
参数说明:LCD_DelayUs用空循环实现,在12MHz晶振下,这个函数大约能产生10微秒级别的延时。SCLK的高电平和低电平时间大致相等,符合ST7920时序要求。如果晶振不是12MHz,需要重新标定循环次数,否则时序过紧可能导致实物不工作。
4.2 命令发送与初始化序列:顺序和时间间隔都不能省
有了底层的字节发送函数后,写命令和写数据都变得很直接。区别只在于同步字节的值不同。
void LCD_WriteCmd(unsigned char cmd) { LCD_CS = 1; LCD_DelayUs(50); LCD_SendByte(0xF8); // 11111 + RW=0 + RS=0 + 0 LCD_SendByte(cmd & 0xF0); // 高四位 LCD_SendByte((cmd << 4) & 0xF0); // 低四位 LCD_CS = 0; } void LCD_WriteData(unsigned char dat) { LCD_CS = 1; LCD_DelayUs(50); LCD_SendByte(0xFA); // 11111 + RW=0 + RS=1 + 0 LCD_SendByte(dat & 0xF0); // 高四位 LCD_SendByte((dat << 4) & 0xF0); // 低四位 LCD_CS = 0; }逻辑说明:写命令和写数据的流程完全一样,唯一区别是同步字节里的RS位。0xF8的命令同步字节和0xFA的数据同步字节必须写对,否则屏幕会正确接收但是不执行对应的操作,表现出来就是命令无效。
初始化序列是重点。ST7920的上电初始化要严格按照顺序来:
void LCD_Init(void) { LCD_DelayMs(80); // 上电等待,手册要求至少40ms LCD_WriteCmd(0x30); // 8位数据接口,基本指令集 LCD_DelayMs(5); LCD_WriteCmd(0x30); LCD_DelayUs(200); LCD_WriteCmd(0x30); LCD_DelayUs(200); LCD_WriteCmd(0x0C); // 显示开,光标关,反白关 LCD_DelayUs(200); LCD_WriteCmd(0x01); // 清屏 LCD_DelayMs(15); LCD_WriteCmd(0x06); // 地址自动加一,光标右移 }逻辑说明:0x30被连续发送三次。原因是ST7920上电后内部的接口模式不确定,连续发送三次0x30可以逐步将芯片锁定到所需状态。这个序列是手册里明确要求的,少一次或者间隔太短,都有可能让芯片处于不可预测的模式,导致显示异常。
参数说明:0x0C控制显示开关和光标,具体含义是显示开、光标关、反白关。如果想让光标闪烁,可以改成0x0F。0x01清屏命令执行时间长,代码里给到15ms是因为ST7920清整屏显存确实需要较长时间,给太短会出现在清屏过程中收到下一条命令而被忽略的情况。
4.3 坐标系统与字符串显示:四行八列的地址规律
ST7920的DDRAM地址映射很有规律。整个屏幕横向分成8列,每列宽16像素,正好放一个16×16的汉字;纵向分成4行,每行高16像素。所以一屏最多能显示4行8列的汉字。
四行的基地址分别是0x80、0x90、0x88、0x98。注意这里不是连续的:第二行和第四行的基地址比第一行和第三行低0x08,这是因为ST7920在硬件上把显存分成上下两部分,第二行和第四行位于下半部分。弄清楚这个之后,写坐标函数就非常直接:
void LCD_SetPos(unsigned char row, unsigned char col) { unsigned char addr; unsigned char code rowBase[4] = {0x80, 0x90, 0x88, 0x98}; if (row > 3) row = 3; if (col > 7) col = 7; addr = rowBase[row] + col; LCD_WriteCmd(addr); }逻辑说明:LCD_SetPos把行列坐标换算成DDRAM地址,然后作为命令发送出去。后续的写数据操作都从该地址开始自动递增。列地址最大到7,因为一行最多8个汉字位置,再往后就超出了显示范围。
void LCD_ShowStr(unsigned char row, unsigned char col, unsigned char *str) { LCD_SetPos(row, col); while (*str != '\0') { LCD_WriteData(*str); str++; } }逻辑说明:字符串内容直接写入DDRAM。这里有一个容易踩的坑:ST7920字库里的英文和数字是半角字符,只占8×16点阵,而汉字占16×16点阵。如果字符串混合了英文和汉字,DDRAM地址递增会导致半角字符排在奇数位置时出现错位。解决办法要么是英文和汉字分开放在不同行列,要么把所有字符都做成16×16的字模。
4.4 点阵取模与绘图模式:超越字库的自定义显示
ST7920的字库覆盖常用汉字,但生僻字、特殊图标、实时波形都显示不了。这时候需要自己准备点阵数据。PCtoLCD2002这类取模软件可以直接生成C语言数组。
unsigned char code logo[32] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x7F, 0xFF, 0xFF, 0xFE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };取模时要特别注意方向设置。ST7920显存是分页结构,16×16点阵占两页,每页8行16列。我建议选择“纵向取模”方式,即每8个点取一个字节,第一字节是高8行的第一列,第二字节是低8行的第一列,以此类推。这样生成的数组顺序和ST7920的显存写入顺序完全匹配。
绘图模式下,可以把任意图形显示在指定位置。这在显示温度曲线或者心跳波形的时候非常有用,后面进阶部分会专门讲它的刷新策略。
5. 常见问题与避坑:Proteus仿真12864的五个典型翻车场景
5.1 上电后屏幕完全没反应,连光标都没有
现象:程序在Proteus里跑起来,12864屏幕全白或者全黑,没有任何字符。
原因:最常见的是CS片选没有拉高。ST7920在串行模式下,CS必须为高电平才会响应通信。很多人把CS当成像1602使能脚那种一次性触发信号,结果CS一直保持低电平,所有命令都被忽略了。其次是PSB引脚电平不对,模型属性里明明选了串行模式,外部又接了高电平到PSB,芯片实际工作在并行模式,串行数据对不上。
解决:确认代码里LCD_CS = 1,并且保持高电平直到一帧数据发送完成。检查PSB是否接地。如果用的是某个模型库的默认属性,到模型的Edit Properties里查看Interface Mode是否为Serial。
5.2 初始化后满屏黑色方块,看起来像随机噪点
现象:屏幕能亮,但是显示区域布满黑点,完全看不出内容。
原因:初始化时序不严格。0x30命令的发送次数不够,或者每次发送间隔太短,芯片内部模式识别出错。ST7920上电时可能处于未知状态,连续三次0x30且中间间隔充足的延时,才能确保它进入基本指令集模式。
解决:严格按照手册的上电初始化序列来写。上电后等80ms,连续发送三次0x30,每次间隔至少100us,然后再发显示控制、清屏等命令。我自己的习惯是初始化代码里全用毫秒级延时,仿真环境不在乎这点时间,但能换来稳定的可靠性。
5.3 显示文字镜像或上下颠倒
现象:汉字本身能看清,但像是照镜子一样左右反了,或者整体倒过来。
原因:串行发送时位序反了。ST7920要求先发高4位再发低4位,如果代码里先发低4位,数据字节在DDRAM里就是反的,显示结果就是镜像。另一个原因是取模方向选错,横向取模和纵向取模得到的数据顺序完全不同,按错的方向送进显存,图像自然错乱。
解决:检查LCD_SendByte,确认数据位是从最高位开始送,也就是dat & 0x80这一位先输出。再检查取模软件的设置,16×16汉字推荐用“纵向取模、从上到下、从右到左”,这和ST7920的显存结构是对齐的。
5.4 英文数字正常,中文显示成乱码
现象:写“ABC”正常,写“你好”显示出来是乱码或者空白。
原因:有两个层面。第一,某个Proteus模型的ST7920字库覆盖不全,GB2312里很多汉字在模型里没有对应点阵。第二,Keil C51编辑器保存源码文件时用了UTF-8编码,字符串里的汉字内码变成了UTF-8的字节序列,ST7920当然无法识别。
解决:先测试一个最基础的汉字“啊”,内码是B0A1。如果“啊”也显示不对,说明模型字库有问题,改用自取模方案。如果“啊”正常而其他字乱码,检查Keil的编辑器配置,把源码文件保存为ANSI编码,再重新编译。工程级排除顺序应该是先查编码再查字库。
5.5 仿真正常但CPU占用高,整体卡顿
现象:程序能跑,屏幕显示也对,但整个仿真运行很慢,拖动窗格都卡。
原因:最常见的是每帧都刷新整屏。128×64的显存要写8192字节,在标准模式下整屏刷新要好几十毫秒,再加上Proteus的波形渲染,CPU就跑满了。另一个原因是延时函数里空循环次数过大,串行模式下每个字节都要发8个bit,整个数据链路被拖得很慢。
解决:程序只刷新变化区域。比如某个数值变化,只更新对应坐标的DDRAM地址,不要全屏重写。延时函数里的空循环次数合理设置,ST7920的SCLK时序是微秒级,循环次数写到几百就够了,用不着上千次。
6. 进阶:局部刷新与波形验证:把仿真做出动画效果
很多12864的课程设计做到能显示汉字就停了,但其实稍微动一下刷新策略,就能做出温度曲线、心跳波形甚至简单动画菜单,答辩现场效果完全不同。
先算一笔账。一屏128×64像素,整个显存8192字节。如果每帧全屏刷新,在12MHz晶振下至少要上百毫秒,帧率连10fps都不到,动画会非常卡。所以动态效果必须靠局部更新。温度曲线这类应用,历史数据基本不变,只有最新一个点需要画,再往前往后各擦一个点即可。一次局部更新只需要写入几个字节,刷新成本比全屏刷新低两个数量级。
实现局部曲线的常见做法是维护一个环形缓冲区,把所有采样点保存在数组里,每次采样后把旧位置擦除、新位置画点,再更新缓冲区下标。显示区域选择屏幕上的某一列,列号作为时间轴,该列上的点的高低就是采样值。整个过程只操作一块很小的显存区域,刷新速度肉眼观感很流畅。
Proteus仿真还有一个很好的验证手段:用虚拟示波器抓SCLK和SID引脚的波形。初始化翻车的时候,这两路波形会明显异常,可能是SCLK高低电平时间不均衡,或者是SID在SCLK上升沿前电平未稳定。把抓到的波形截图存下来,插入答辩PPT里讲解时序,会非常加分。
我自己有个习惯一直保持到现在:所有用到12864的项目,不管初始化还是普通显示函数,第一步都在入口设断点,先确认CS电平状态,再查第一个同步字节的波形。这个习惯是从一次实物调试踩坑后养成的,当时仿真一切正常,实物怎么都不亮,后来抓波形才发现CS在发送完成后被代码提前拉低了,导致最后一字节丢失。从那以后每次拿到新的屏幕模型,我都强制走一遍这个流程,先接CS、发0x30、看波形,再写显示函数,没有再把时间浪费在猜问题上。希望这套排错思路也能帮到你。
本文还有配套的精品资源,点击获取