简介:一套基于51单片机的12864大液晶屏Proteus仿真设计资源,适合单片机初学者、课程设计与毕业设计学生使用。项目以12864点阵液晶屏为显示终端,完整覆盖51单片机基础、C语言编程、液晶屏驱动协议、Proteus电路搭建与调试等关键环节。资源共19个文件,压缩包约55KB,主要包含6个C源码文件、6个头文件、Keil工程文件(Uv2/Hex)以及Proteus工程文件(DSN/DBK/PWI)等,可直接在Proteus中加载仿真,也可结合Keil进行代码修改与重构。通过该设计可掌握并行/串行接口控制、字符与图形绘制、初始化流程等核心知识,体会从需求分析到软硬件联调的完整流程。已有279人学习下载,对入门嵌入式显示应用具备不错的参考价值。 做单片机项目做到显示这一环,很多人会卡在“用什么屏”上。1602能显示的内容太少,OLED要折腾I2C时序,而12864大液晶屏几乎是性价比最高、也最容易出效果的选择——4行中文、自带字库、还能画波形,做好了整个项目的交互质感立刻就上来了。但一上来就直接买屏焊板子,成本不低,调试麻烦,还容易在硬件问题上消耗掉大半热情。所以我更建议先走一遍基于51单片机的12864 Proteus仿真设计,把连线逻辑、驱动时序、常见翻车点全部跑通,之后再碰实物会顺手得多。这篇文章把我在仿真里完整的搭建过程、驱动思路和排错经验都写出来,适合刚学完C51基础、准备做显示类课程设计或DIY项目的朋友参考。
1. 为什么先用Proteus跑12864,而不是直接买屏
1.1 12864到底强在哪
12864这个名字已经剧透了核心参数:横向128个点,纵向64个点,一共是128x64的点阵。对比1602那种字符屏,12864的显示密度高了一大截,能显示4行汉字,每行8个汉字;如果显示英文字符,一行能塞下16个。更关键的是它内部带ST7920控制器,这个控制器直接内置了GB2312简体中文字库,你要显示“温”“度”“湿”这些汉字,不需要自己做字模,代码里直接写字符串就行。
而对于课程设计、电子竞赛、个人DIY这类场景,一块12864屏幕往往就能把整个系统的状态界面撑起来。做万年历,它可以一屏显示年月日、星期、农历;做温度采集,它可以把实时曲线画出来;做菜单交互,它甚至可以做到图形化图标。功能上限比1602高很多。
1.2 仿真设计解决的核心问题
很多人学单片机有一个误区,觉得Proteus仿真不如实物高端,能跳过就跳过。但实际上Proteus跑12864,能帮你把真正的难点提前暴露出来。12864的问题不在于单条指令多复杂,而在于并行时序、引脚模式、初始化顺序这些环节,任何一个出错,屏幕都是黑乎乎的没反应。如果直接上手实物,你要同时面对接线错误、电平不匹配、模块损坏风险,很难判断问题到底出在哪一步。
仿真环境的好处就是状态完全可控。你可以把单片机程序反复烧录,可以把某个引脚用电平探针盯着看,可以随时暂停运行观察时序波形。等仿真里跑出正确显示效果,说明你的驱动逻辑已经没问题了,剩下就是照搬到真实电路。所以我一般建议:先仿真验证方案可行性,再动手做实物。
1.3 适用人群和前置基础
这篇文章面向的是已经学过C51基础语法、大概知道GPIO怎么控制的人。不需要你会多少复杂算法,但至少要会用Keil写一个main函数、能编译生成HEX文件。Proteus这边,只要会用元件拾取和连线就行,深入操作我在后文会一步步讲。如果你之前只点过LED灯、做过数码管,那这篇文章的难度对你来说刚刚好。
2. 接线前必须搞懂的引脚功能与并串模式选择
2.1 ST7920控制器和两种接口模式
市面上几乎所有12864液晶模块,核心都是ST7920控制器。这块控制器支持两种访问方式:8位并行和串行。并行就是RS、RW、E加DB0到DB7一共11根线,一次传输一个字节;串行模式下RS变成片选CS,RW变成数据线SID,E变成时钟SCLK,总共只需要3根控制线。
我的建议是:仿真里优先用8位并行模式。第一,并行模式下代码逻辑最直观,写命令和写数据分别控制RS电平,你很容易理解屏幕的工作原理;第二,Proteus里的ST7920模型对并行模式的支持非常稳定,新手不容易被时序问题劝退。串行模式我后面会单独讲,等并行模式跑通了再去换,思路会清晰很多。
2.2 引脚功能一张表说清楚
12864模块上引脚的名称和功能,不同的屏会有细微差别,但绝大多数模块引脚定义如下表:
| 引脚 | 名称 | 功能说明 | 并行模式接法 |
|---|---|---|---|
| 1 | VSS | 电源地 | GND |
| 2 | VDD | 电源正极 | +5V |
| 3 | V0 | 液晶对比度调节 | 接10k电位器分压 |
| 4 | RS(CS) | 并行:命令/数据选择;串行:片选 | 接P2.0 |
| 5 | RW(SID) | 并行:读/写选择;串行:数据线 | 接P2.1 |
| 6 | E(SCLK) | 并行:使能信号;串行:时钟 | 接P2.2 |
| 7~14 | DB0~DB7 | 8位数据总线 | 接P0.0~P0.7 |
| 15 | PSB | 并/串模式选择 | 接VCC(并行) |
| 16 | RST | 复位引脚 | 接VCC |
| 17 | BLA | 背光源正极 | 串电阻接VCC或PWM |
| 18 | BLK | 背光源负极 | GND |
这张表里最容易忽略的是PSB和V0。PSB必须接高电平,芯片才工作在并行模式;如果悬空或者接地,后续所有并口指令全部无效,屏幕死活不亮。V0则负责调节屏幕的对比度,在仿真里如果不接电位器,或者电位器调在极限位置,屏幕会全黑或者完全看不到字。
2.3 推荐的并行接线方案与理由
控制线我建议接P2口的前三位,数据线接P0口。这样安排的好处是驱动代码简单,宏定义一看就懂。由于P0口是开漏输出,无法主动输出高电平,必须在P0口和VCC之间接一个10k排阻做上拉,不然高电平状态根本驱动不起来。这个上拉排阻在Proteus仿真里经常被漏掉,漏掉的典型症状就是屏幕出现随机乱码。
如果你手头没有排阻,用8个独立10k电阻分别接P0.0到P0.7也可以。仿真图中排阻元件名搜索RESPACK-8,拉出来后注意让公共端COM接VCC,电阻端分别接到P0口。
注意:仿真里PSB引脚如果找不到对应的引脚号,可以直接点击引脚连线后接VCC,不要悬空。悬空等同于不确定电平,是12864不显示的常见原因之一。
3. Proteus建图、烧程序、跑起来的三步实操
3.1 元件搜索与放置
Proteus版本不同,元件库名字略有差异,但搜索关键词基本通用。新建工程后,点击左侧工具栏的“P”进入元件拾取界面,依次搜索下面几个元件:
- AT89C51:单片机主体,用AT89C52也可以,驱动逻辑一样。
- 12864液晶屏:搜索关键词可以试“12864”“AMPIRE128X64”“LGM12641BS1R”。不同版本Proteus里12864模型名称不太一样,AMPIRE128X64是我在Proteus 8系列里用得比较多的一个模型,也可以搜“LM12864F”一类名称,找到像素点阵是128x64的即可。
- RESPACK-8:8位排阻,给P0口做上拉。
- CRYSTAL:晶体振荡器,默认12MHz。
- CAP:电容,两个30pF配合晶振使用。
- RES:电阻,10k用于复位电路。
- POT-HG:电位器,用来调节12864的V0对比度。
在Proteus 8里,12864的搜索框也可以直接输入“ST7920”试试,有些版本会直接关联到对应液晶模型。
3.2 从连线到加载HEX的完整操作
元件都放好后,按照第2章的接线表逐个连线。这里有个经验:虽然Proteus支持总线工具,但第一次做12864仿真时建议直接用普通导线一根根连,尤其是DB0到DB7这8根数据线,一根根连过去能强迫你检查每根线的顺序。如果用总线,一旦中间标号标错,排查起来非常痛苦。
复位电路采用经典方案:晶振两个引脚分别接30pF电容到地,1脚和2脚之间跨接晶振本体即可。单片机RST引脚接一个10uF电解电容到VCC,再接一个10k电阻到GND,这是最常用的上电复位电路。
双击AT89C51芯片,在弹出的属性对话框中,把Program File一栏选择你Keil生成的HEX文件路径,同时把Clock Frequency设置为12MHz,和晶振保持一致。然后点界面左下角的运行按钮,就可以观察屏幕状态了。
3.3 Keil配置:生成HEX的那一步别漏
Keil工程如果编译完没有生成HEX文件,是因为默认只生成AXF,需要手动开启。在Options for Target对话框里,选择Output选项卡,勾选Create HEX File,然后重新编译,工程目录下才会出现HEX文件。这一步是Proteus能烧录程序的前提,漏掉的话Proteus里等于跑了一个空单片机,屏幕上当然什么都没有。
如果你用的编译环境是Keil C51,记得在Device里选择Atmel AT89C51或AT89C52。Target选项卡里可以保持默认的12MHz晶振参数,但更重要的是延时函数的时间基准要和实际晶振一致。Proteus仿真的时钟是理想时钟,和代码里延时函数的匹配度直接影响显示时序,晚点会讲到。
4. 驱动代码逐段拆解:从初始化到中文显示
4.1 初始化顺序为什么是这样
ST7920上电以后需要一串初始化指令,这串指令的顺序是硬性的,不能乱。核心包括:
- 0x30:切换到8位并行基本指令集。前三次延时发送是为了让控制器内部状态机稳定,手写驱动时不要省略中间两个delay_ms。
- 0x0C:开显示,同时关闭光标。如果想显示光标可以用0x0E,但一般界面场景不需要。
- 0x01:清屏,同时把DDRAM地址指针归零,必须给几毫秒延时等清屏动作完成。
- 0x06:设置写入数据后地址指针自动加1、光标右移。这样连续写入字符串时就不用每写一个字符就重设一次地址。
很多网上找的代码在仿真里不显示,问题就出在初始化时只发了一遍0x30就急着后续操作。ST7920内部从复位状态切换到基本指令集需要时间,稳妥的做法是上电延时40毫秒以上,再重复发送三次0x30,中间各留5毫秒间隔。
4.2 写命令和写数据函数
并行模式的核心就是两个函数:写命令和写数据。写命令时RS置0,写数据时RS置1,RW在整个写操作里保持0。E引脚是使能信号,锁存时刻在E的下降沿,也就是E从高电平跳变到低电平的那一瞬间。
#include <reg52.h> #define uchar unsigned char #define uint unsigned int sbit RS = P2^0; sbit RW = P2^1; sbit E = P2^2; #define DATA P0 void delay_us(uint t) { while(t--); } void delay_ms(uint t) { uint i, j; for(i = 0; i < t; i++) for(j = 0; j < 120; j++); } void LCD_WriteCmd(uchar cmd) { RS = 0; RW = 0; DATA = cmd; E = 1; delay_us(50); E = 0; delay_us(50); } void LCD_WriteData(uchar dat) { RS = 1; RW = 0; DATA = dat; E = 1; delay_us(50); E = 0; delay_us(50); }这里有个细节:先给数据、再拉高E,等数据稳定后拉低E完成锁存。顺序反过来,就容易出现把上一条总线残留数据写进屏幕的情况,表现就是显示内容错位。
初始化函数和清屏函数如下:
void LCD_Init(void) { delay_ms(40); LCD_WriteCmd(0x30); delay_ms(5); LCD_WriteCmd(0x30); delay_ms(5); LCD_WriteCmd(0x30); delay_ms(5); LCD_WriteCmd(0x0C); LCD_WriteCmd(0x01); delay_ms(2); LCD_WriteCmd(0x06); }4.3 显示英文、数字与中文的底层逻辑
初始化完成后,写入命令0x80加行内偏移量,就可以定位第一行的起始地址。后面每次写一个字节,地址指针自动加1,所以连续写一串字符就能显示出来。
12864的DDRAM行地址映射和1602不一样,这是一个特别容易踩坑的点:
| 物理行 | 行起始命令 |
|---|---|
| 第1行 | 0x80 |
| 第2行 | 0x90 |
| 第3行 | 0x88 |
| 第4行 | 0x98 |
第三行和第四行的地址并不是很多人以为的0xA0、0xB0,而是0x88和0x98。这个地址跳跃是因为ST7920把横向64个字(每个字对应两个DDRAM地址单元)做了一个折行映射。如果按连续的0x80、0x90、0xA0、0xB0去写,第三行和第四行的字会跑乱。
12864每个汉字占两个字节长度,因为内部字库用GB2312编码匹配。英文和数字是单字节,所以一行16个英文字符宽度等同于8个汉字宽度。
4.4 直接用的一段Demo代码
下面的代码实现两行字符串显示,中英文混合也没问题:
void LCD_ShowString(uchar row, uchar col, uchar *str) { uchar addr; uchar i = 0; switch(row) { case 0: addr = 0x80 + col; break; case 1: addr = 0x90 + col; break; case 2: addr = 0x88 + col; break; case 3: addr = 0x98 + col; break; default: addr = 0x80 + col; } LCD_WriteCmd(addr); while(str[i] != '\0') { LCD_WriteData(str[i]); i++; if(i >= 16) break; } } void main() { LCD_Init(); LCD_ShowString(0, 0, "12864 LCD TEST"); LCD_ShowString(2, 1, "HELLO 12864"); while(1); }编译烧进Proteus,屏幕应该能显示两行内容。注意到第0行和第2行都用到了,第三、四行地址也能正常显示,因为代码里用的是映射后的0x88和0x98。
提示:在Keil里写中文字符串时,默认编码是GB2312,能正常显示。但如果你用VS Code或者某些现代编辑器写代码,再拿到Keil里编译,源码编码可能变成UTF-8,中文字符串在12864上就会显示成乱码。遇到这个情况,把源文件重新另存为ANSI或GB2312编码再编译即可。
5. 仿真中几个高概率翻车点与完整排查链路
5.1 全黑屏或无显示的排查过程
第一次上电,屏幕全黑或者完全没反应,是最常见的现象。遇到这种情况不要急着改代码,按下面的链路一步一步走,基本5分钟内能定位到问题。
第一步,先检查HEX文件到底加载进去没有。双击AT89C51,看Program File属性框里有没有文件路径。我见过不少学员在Proteus里放了个单片机,但根本没烧录程序,相当于让一个空芯片在跑,屏幕当然没反应。
第二步,检查PSB引脚是不是接了高电平。仿真和实物一样,PSB悬空或者接地都会让芯片进入串行模式,并口指令全是废操作,屏幕自然没有任何显示,也不会出现黑块。
第三步,检查V0对比度电压。V0直接接VCC或者直接接地,都会让屏幕黑死或者完全看不见字。正确做法是用POT-HG电位器,中间抽头接V0,两端分别接VCC和GND。仿真运行时双击电位器调整比例,让V0落在1V到3V之间,再观察屏幕。
第四步,如果屏幕上能看到整齐的黑色方块阵列,但自己的内容不显示,说明对比度没问题,问题在初始化或时序。把延时调大,例如所有delay_us(50)改成delay_us(500),看是否有改善。Proteus的ST7920模型在E脉冲过窄时偶尔会锁存不上数据,加大延时是最直接的测试手段。
5.2 乱码、花屏的排查过程
第二类高发问题是屏幕亮了,但显示的内容是乱码或者花屏。这个现象的根源往往是总线数据不干净。
先检查数据线顺序。DB0到DB7必须一一对应接P0.0到P0.7,如果中间交叉了一根,比如DB0接到P0.1而DB1接到P0.0,显示必然乱码。这种错误用肉眼核对一次就能发现,但在总线下容易隐藏,所以前面我强调第一次连线尽量用单根导线。
再检查P0口的上拉排阻。P0是开漏输出,没有上拉的话高电平会偏弱,Proteus里会表现为数据不稳定,某一位读到了错误的0或1。排阻公共端接VCC,另一端分别接P0口,接反了也会导致全部高电平失效。
还要注意显示位置。如果写入的字符串超过了16个英文字符宽度,地址会溢出到屏幕之外,视觉上表现为内容断掉或花屏。显示字符串前想清楚这一行能容纳几多个字符,不要让i超过15。
5.3 排查速查表
| 现象 | 可能原因 | 检查方法 |
|---|---|---|
| 全黑无显示 | HEX未加载/PSB悬空/V0电压不对 | 双击单片机查加载路径;PSB接VCC;电位器调V0 |
| 有黑块但无内容 | 初始化指令不完整/时序太快 | 补重复0x30初始化;加大E引脚延时 |
| 英文字符错位 | 行地址映射错误 | 确认第3、4行用0x88/0x98 |
| 乱码花屏 | DB0~DB7交叉/上拉缺失/编码错误 | 单线核对接线;补排阻;源文件存为ANSI |
| 汉字显示乱码 | Keil源文件编码不是GB2312 | 将源文件另存为ANSI/GB2312 |
| 仿真能显示实物不行 | 实物对比度负压/背光限流问题 | 实物V0接负压调节,串电阻限背光 |
注意:Proteus里12864模型对于E脉冲宽度要求比真实芯片更严格一些,网上有些老代码在实物上能用、在Proteus里反而黑屏,多半是初始化延时不够。解决办法就是照着4.2节的延时重写一遍,基本都能解决。
6. 让12864更有用:绘图模式、串行模式与实物移植
6.1 绘图模式:让屏幕变成示波器
ST7920除了文本模式,还内置了一个绘图模式。进入绘图模式要发扩展指令集命令0x34,然后操作GDRAM。GDRAM横向64字节、纵向64位,实际上被分成上下两个64x64区域,所以绘制点时计算坐标会绕一些。
绘图模式最典型的应用是波形显示。做温度采集或电压采集时,可以把采样值换算成Y坐标,每隔一段时间画一个点,再配合延时就能在屏幕上看到实时曲线滚动。很多“单片机波形显示”课程设计就是这么做的。
不过绘图模式对地址计算的要求高,建议先在文本模式跑通后再单独调试绘图部分。至少要让“画点函数”能正确显示一个像素点,再去做曲线。绘图模式下汉字显示和画点不能同时混用,退出绘图模式要再发0x30切回基本指令集,这一点很多人会忘记。
6.2 串行模式的省IO思路
如果你的项目还有矩阵键盘、传感器、电机驱动等外设,并行模式占用的11个IO口确实有点多。这时可以把屏幕切换到串行模式,只需要3根IO线。接线方法是PSB接GND,RS接CS、RW接SID、E接SCLK,数据从高位到低位逐位输出。串行模式的时序本质是模拟三线SPI,每次先发5个同步位“11111”,再发两位RS标志位(写命令是00,写数据是10),最后发8位数据。代码实现时要自己写移位循环,时序必须严格按照数据手册来,不能直接复用并行模式的代码。
在Proteus里串行模式的模型也同样支持,可以用来验证。但我认为调试串行模式之前,先确认并行模式已经完全理解并且能独立写出来,不然两个模式的问题叠在一起,会越调越乱。
6.3 从仿真走向实物的经验
最后说说从仿真到实物移植时的几个经验。仿真跑通之后,做实物时我遇到过三个和仿真不太一样的坑:
第一,V0电压。仿真里V0用电位器从VCC分压到地,直接可调;但很多实物12864模块的V0需要接一个负压,才能正常调节对比度,直接用滑动变阻器接法会发现屏幕对比度调到最大还是发暗。
第二,背光限流。实物模块的BLA引脚需要串联一个几十欧的限流电阻再接到5V,直接接5V虽然也能亮,但模块发热明显,长期运行有风险。PWM调背光时,注意背光电流不是逻辑电平,需要三极管或者MOS管驱动,不能直接接单片机IO。
第三,电平匹配。如果你用的是3.3V系统单片机,而12864是5V模块,逻辑高电平可能达不到芯片要求,需要在RS、RW、E、DB0到DB7上做电平转换,或者直接用带电平转换的模块。仿真里不存在这个问题,但实物设计必须考虑。
我个人在实际操作中的体会是:12864这块屏,最值得花时间的地方不是把某个字符串显示出来,而是把并行的读写时序彻底搞明白。一旦你对“RS决定命令还是数据、E下降沿锁存”这个机制形成肌肉记忆,后面再看其他带并行接口的屏幕,比如TFT彩屏、LCD2004,上手速度会快很多。如果只是把代码抄进去看到屏幕亮了就结束,那仿真和实物的差距迟早会再回来找你一次。
本文还有配套的精品资源,点击获取