
简介这是一份面向C51单片机初学者的LCD汉字滚动显示项目资源重点解决如何利用点阵型LCD如12864显示汉字并实现左右滚动效果。压缩包共18个文件、57KB包含Keil工程源码.c/.uv2/.hex、编译中间文件.obj/.lst及备份文件同时提供Proteus仿真电路.dsn/.dbk可直接打开查看电路连接和程序运行效果。已有2283人学习适合正在做单片机课程设计、毕业设计或想深入掌握LCD驱动开发的读者。通过该资源可学习汉字字模的提取与存储、LCD初始化与坐标设置、以及通过缓冲区偏移和延时控制实现滚动显示的具体写法项目中还涉及Keil uVision与Proteus联调的操作路径有助于理解软硬件协同验证流程并为后续嵌入式显示项目提供可直接复用的代码框架。整体代码结构清晰变量命名规范对初学者友好能够显著缩短从阅读原理到上手调试的周期。1. 项目整体设计思路与方案选型1.1 为什么拿单片机做汉字滚动显示做过电子设计、课程设计或者纯粹玩单片机的人几乎都会碰到一个需求让LCD屏幕显示一段汉字信息。静态显示倒还好办但一旦要求“滚动显示”——尤其是让汉字平滑地横向移动起来——很多新手就卡住了。这个项目看起来很基础实际上它把单片机开发的几个核心硬骨头全串起来了LCD驱动时序、字模数据的存储与读取、DDRAM显示数据RAM的地址操作、延时与刷新节奏的平衡以及代码结构怎么组织才不容易乱。我做这个项目时用的是51内核的单片机STC89C52RC这也是国内最普及、教材里出现频率最高的一款芯片。显示模块选了LCD12864带中文字库的那款。为什么不选LCD1602因为1602要显示汉字只能通过自定义CGRAM字符生成RAM临时拼字最多存8个5x8点阵的自定义字符做滚动显示非常局促而12864带字库版本直接内置了GB2312汉字库通过写两个字节的区位码就能显示一个16x16点阵汉字滚动起来几乎是为这种需求定制的。如果你手头只有1602也不是不行后面我会单独讲怎么适配但主力方案以12864为准。这个项目适合谁正学单片机、卡在“LCD只亮屏但不出字”阶段的人想搞清楚字模和取模软件原理的人以及课程设计、毕业设计需要做一个“看起来有点技术含量”的显示功能的人。做完这一个小项目你对LCD的掌控力会明显上一个台阶以后再碰OLED、TFT屏很多原理都是相通的。1.2 整体方案硬件、软件、字模三线并行先说清楚整个项目的构成它其实是三件事同时推进硬件上要有能跑起来的最小系统加LCD接口电路软件上要有初始化、写命令、写数据、延时这几层基础函数还有一个容易被忽略的环节——字模数据也就是你要显示的那些汉字到底是哪来的、怎么转换成单片机认识的十六进制数组。这里我想先强调一个认知滚动显示的难点不在“滚动”本身而在“显示”的底子。底子就是你能不能稳定地把一屏汉字显示出来并且搞清楚每一个汉字在屏幕上的坐标和地址关系。只要静态显示没问题滚动就是一个地址偏移的逻辑问题。所以整个方案我按“先静态、后动态”的顺序推进先把单屏显示跑通再实现左右滚动不要一上来就写滚动代码那是大多数调试半天调不出来的原因。2. LCD工作原理与汉字显示机制2.1 12864到底在显示什么LCD12864这个屏名字里的128和64指的是分辨率——横向128个像素点纵向64个像素点。带字库版本内部有三个关键区域DDRAM显示数据RAM、CGROM字符生成ROM和CGRAM自定义字符RAM。你写入DDRAM的字节并不是“像素”而是一个“编码”屏幕控制器会根据这个编码去CGROM里找到对应的字形点阵再映射到屏幕上。这和你在电脑上敲字是类似的你按下键盘系统存的是一个字符编码渲染的时候字体引擎根据编码去字体文件里查字形。12864内部就是这个流程的硬件简化版。它支持两种内容一种是字符ASCII码范围每个字符占8x16点阵显示在字符模式下另一种是汉字用的是GB2312编码每个汉字占16x16点阵。注意正因为汉字占16x16的格子而屏幕纵向64像素正好是4个16像素的倍数所以12864能完整显示4行汉字每行最多8个汉字128除以16正好等于8。这个“4行8列”就是整个滚动显示的坐标系基础。2.2 汉字编码区位码与点阵数据的对应关系带字库版的12864内部已经固化了汉字字形你要做的只是告诉它“显示哪个字”不用自己提供点阵。这个“告诉”的过程是先查这个汉字的GB2312编码再转换成区位码最后通过两个字节写入。以“单”字为例它的GB2312编码是0xB5A5区位码就是编码值分别减去0xA0得到区码0x15十进制的21、位码0x05十进制的5。控制器看到区域码后就在CGROM的对应位置找到“单”的字形数据。这整个过程完全不需要你操心字形点阵长什么样这也是带字库屏最大的省心之处。但这里必须提醒一个关键细节当写入的数据小于0xA0也就是在ASCII范围内时12864把它当字符处理当写入的数据大于等于0xA0时它才按汉字去查找。所以在写汉字时两字节都必须大于0xA0否则显示就会乱掉。如果你用的是不带字库的裸屏比如通过ST7920控制器外的其他驱动方案那就得自己准备16x16点阵的字模数据一字节一字节地喂给屏幕那又是另一套玩法了。2.3 不带字库的情况裸屏字模原理既然提到了字模就不得不展开讲一下。如果你用不带字库的通用LCD模块或者是用OLED屏、TFT屏那么显示汉字的核心就是“取模”。一个16x16的汉字点阵按行扫描每行2个字节一共32个字节。每个字节的每一位对应一个像素点1表示点亮0表示不亮。取模软件的作用就是把汉字图形转换成这32个字节的十六进制数组。常用的取模工具有很多比如PCtoLCD2002、字模软件等。参数设置比较关键阴码1亮0灭、逐行式横向取模、16x16点阵。用这种参数生成的数组就是从左上角开始第一行左右两个字节第二行左右两个字节……一直到第16行。单片机拿到这32个字节后通过写控制器的GDRAM图形显示RAM接口直接填入指定坐标区域就能在屏幕上画出一个汉字。这个知识点在这个项目里属于“备选路线”但建议你理解。因为无论你以后用哪款屏汉字显示的本质都是字形数据加坐标映射只是接口和驱动芯片不同。理解了裸屏字模原理再回来看带字库屏你反而会更明白它在内部替你做了什么。3. 硬件设计与电路连接3.1 最小系统与LCD接口电路我做这个项目时硬件部分特别简单简单到只要一个单片机最小系统和一块12864屏就能跑。最小系统就是单片机加晶振电路、复位电路、电源去耦电容这三个必备元素。晶振用11.0592MHz为什么用这个频率而不是12MHz因为11.0592MHz得到的机器周期是1.085微秒配合定时器能得到精确的波特率虽然这个项目不涉及串口通信但这个习惯我一直保留着方便以后扩展功能时直接加串口。LCD12864的接口有两种模式并行和串行。我强烈建议新手用并行模式因为并行模式时序直观通过观察八位数据线上的电平变化就能判断问题出在哪里。接口连接如下LCD引脚功能连接目标RS或CS数据/命令选择P2.0RW或SID读/写选择P2.1E或SCLK使能信号P2.2DB0-DB7数据总线P0.0-P0.7PSB并/串行选择VCC并行模式V0对比度调节接10K电位器到GNDLED_A背光正极串220Ω电阻到VCCLED_K背光负极GNDP0口要接上拉电阻很多读者在这个地方翻车P0口内部没有上拉必须外接4.7K或10K排阻否则高电平拉不上去显示全是乱的。这个坑我踩过当时折腾了一个晚上最后发现是上拉电阻没接。3.2 对比度调节与背光的细节对比度调节是12864最容易让人怀疑人生的地方之一。V0引脚需要通过一个10K电位器接到GND电位器中间抽头接V0。旋转电位器时屏幕的显示对比度会变化。很多人以为屏幕没接好其实只是对比度没调到合适的电压导致屏幕虽然是蓝色但一个字都看不见。正确的调试方法是先初始化LCD至少完成功能设置然后旋转电位器观察屏幕上是否出现第二行的黑块这是LCD固件自检后默认显示的暗色方格出现黑块后再微调让前两行隐约可见这时再写显示内容。这个顺序很重要因为有些人是在程序还没加载的时候就去调对比度那当然全屏无反应然后误以为硬件坏了。背光限流电阻也比较讲究。LED背光典型工作电流在20-40mA之间我习惯串一个220Ω的电阻实测亮度合适。如果电阻太小背光发烫而且寿命受影响太大了屏幕太暗。另外背光电源一定要和单片机共地我当时把背光正极接5V负极接GND如果共地不良会出现屏幕亮但数据写不进去的怪异现象。3.3 关于IO口保护的一点心得MCU的IO口直接驱动LCD模块是没问题的因为LCD的输入阻抗很高功耗极低。但如果你的系统里还有其他外设比如按键、传感器、电机驱动等建议在LCD的数据线上串33Ω或100Ω的限流电阻好处是防静电、防误接短路不至于一接错就烧IO口。另外还要注意5V和3.3V电平问题。STC89C52RC是5V单片机如果选用的LCD模块是5V版本直接连接没问题。但如果你的模块是3.3V版本那就得用电平转换芯片比如TXS0108E或者用开漏加外部上拉到3.3V的方案强行直连会让LCD工作在额定电压之外长期使用会出现显示漂移。4. 核心软件实现初始化、写命令、写数据4.1 LCD12864初始化流程详解初始化是LCD正常工作的“入场券”12864的初始化主要做这几件事延时等待、功能设置8位数据接口、基本指令集、开显示、清屏、光标设置。我用的初始化序列如下void LCD12864_Init(void) { DelayMs(100); // 等待LCD内部稳定 WriteCommand(0x30); // 基本指令集8位数据接口 DelayMs(5); WriteCommand(0x30); // 重复设置一次以确保稳定 DelayMs(5); WriteCommand(0x0C); // 开显示关光标不闪烁 DelayMs(5); WriteCommand(0x01); // 清屏 DelayMs(20); // 清屏需要较长时间 WriteCommand(0x06); // 地址指针自动加一光标右移 DelayMs(5); }这个序列里有几个细节值得展开。第一0x30这个命令要写两遍因为ST7920控制器的唤醒时间较长写一遍可能没生效写两遍才能保证稳定这是从官方数据手册和实际调试中总结出来的不是玄学。第二清屏命令0x01需要的等待时间比较长要在20毫秒左右如果等得不够紧接着写内容可能被丢帧。第三0x06决定的是写入数据后地址指针自动加一方向是从左到右如果你想让文字从右到左排列可以改成0x04。4.2 写命令与写数据的时序细节写命令和写数据的本质区别在于RS引脚的电平RS0表示总线上的内容是命令RS1表示总线上的内容是数据。整个时序是先拉低RS和RW表示写操作把数据送到P0口然后给E引脚一个下降沿LCD在下降沿时刻锁存数据。我写代码的时候习惯把读忙标志也实现出来void CheckBusy(void) { unsigned char tmp; P0 0xFF; // 释放数据线 RS 0; // 命令状态 RW 1; // 读模式 do { E 1; DelayUs(1); tmp P0; E 0; } while (tmp 0x80); // 最高位为1表示忙 }有人会问初始化序列里的DelayMs不可以替代读忙吗从工程实践看延时方案在开发板上跑是没问题的因为单片机主频固定延时时间完全可以覆盖LCD的响应时间。但从可靠性角度看读忙是正经的通信协议的一部分因为LCD在不同指令下的响应时间并不完全相同特别是清屏和地址设置这类操作耗时长用固定延时要么浪费CPU时间要么可能偶发性丢数据。我建议两个方案都实现调试时开延时模式稳定后换读忙模式。4.3 汉字显示坐标与addr的映射关系12864显示汉字的坐标系统是关键中的关键。在基本指令集下写汉字时用的是“行地址”加“列地址”的二维方式。第一行的起始地址是0x80第二行是0x90第三行是0x88第四行是0x98。列地址从0到7对应8个汉字位置也就是每写一个汉字列地址自动加一。这里有个非常容易搞混的点行地址不是连续的。第一行0x80第二行跳到0x90第三行是0x88第四行是0x98。很多人用0x80、0x81、0x82去遍历第一行没问题但到了第二行用0x91、0x92这种地址去访问那是错的第二行应该从0x90开始。我见过太多人卡在这里显示出来的汉字位置东一个西一个就是因为没有搞清楚这个地址跳跃的规律。映射到屏幕坐标上是这样的第1行显示在屏幕顶部第2行显示在屏幕中部偏上那块区域的左半部分第3行在左半部分的下方第4行在屏幕底部。如果你用的是分屏模式左右各64列那地址映射还会更复杂但基本指令集下统一按这个简易映射处理就好。5. 滚动显示的实现从静态到动态的进阶5.1 两种滚动方案的取舍滚动显示的方案有两条路一条是“整屏重绘”通过不断刷新整屏内容来实现滚动效果另一条是“地址偏移”利用DDRAM的读出地址偏移来实现滚动。对12864这种带字库的屏我更推荐前者也就是整屏重绘方案。为什么这么选核心原因是12864的显存结构和TFT屏不一样它不支持硬件层面的水平滚动有些TFT屏驱动芯片自带水平滚动寄存器但ST7920没有。所以要么你老老实实重绘要么就是用地址偏移模拟。整屏重绘的方案是把要显示的字符串按窗口截取每帧把窗口内的内容全部写到屏幕上窗口位置从左到右平移就产生了滚动效果。这个方案的瓶颈在于刷新速度。12864的写入速度有限而且汉字显示需要连续写两字节16个字节才能显示一个“屏幕汉字”两列DDRAM数据对应一个汉字位置。实测下来一帧完整显示8个汉字大约需要30到40毫秒也就是每秒最多25到30帧对于文字滚动来说完全够用了。如果刷新间隔小于这个极限屏幕会闪烁或者出现半边字这是很多人调试滚动时遇到的最典型问题。5.2 字模滑动窗口算法整个滚动程序的核心是一个“滑动窗口”。假设要显示的内容是“ 单片机LCD滚动显示汉字技术分享 ”一共18个字符屏幕一屏只能显示8个汉字那滚动过程就是窗口初始位置在第0到第7个字符然后整个窗口向右移一位变成第1到第8个字符……一直到窗口滑出内容末尾。写成代码就是void DisplayRollingText(unsigned char *str, unsigned char totalLen, unsigned int offset) { unsigned char i; unsigned char startPos offset % (totalLen 8); // 8是屏幕宽度 for (i 0; i 8; i) { ClearLine(0, i); // 清除该位置 if ((startPos i) totalLen) { DisplayChinese(0, i, str, startPos i); } } }主循环里每隔一定时间我一般取200毫秒让offset加一再重新调用DisplayRollingText就能看到文字逐字左移的效果。这个“屏幕宽度加内容长度”取模的逻辑很重要它让内容滚动到末尾后无缝衔接回开头形成循环滚动的效果这也是很多滚动显示功能要求的表现形式。5.3 平滑滚动的进阶优化逐字滚动虽然逻辑简单但视觉效果略显生硬。如果你追求更流畅的“平滑滚动”——也就是文字像流水一样连续向左移动而不是一格一格地跳——那就需要把16x16点阵的汉字按像素级移位来刷新。这个复杂度会明显上升但原理并不神秘把整个要显示的内容按16x16点阵拼接成一个大位图然后每次刷新时从这张大位图的某个像素偏移位置取一个128x16的窗口去更新屏幕的GDRAM区域。这种方式需要用到12864的图形模式扩展指令集中的0x34以及GDRAM写入功能。我把大位图放到单片机的RAM里STC89C52有512字节内部RAM足够放一个128像素宽、16像素高的位图每次刷新时按列读取写入GDRAM。实测下来一次刷新大约要50到60毫秒帧率在15帧左右文字滚动已经比较顺滑了。不过这里有个现实问题51单片机的性能上限摆在那里如果你要同时滚两行或者一行汉字一行图形CPU占用率会非常高这时候就要考虑用STM32了。我在项目里留着这个优化空间因为很常见的情况是你刚做完51版导师或领导就要求改成STM32版本作为扩展功能。6. 常见问题与排查技巧实录6.1 屏幕亮了但不出字从电源到对比度逐级排查这个现象是12864项目里最常见的问题没有之一。我的排查顺序是固定的先看背光是否亮再看屏幕是否有黑块最后检查对比度电位器。背光亮说明电源到位黑块说明LCD已经响应初始化并进入工作状态黑块隐约可见但字显示不出来那基本是对比度没调好。如果背光亮但连黑块都没有就要检查RS、RW、E这三个控制引脚是否接对以及初始化程序是否真的执行了。我建议在初始化代码里加一个P1口LED翻转的测试点程序跑到初始化末尾LED亮灭一次这样能确认单片机程序确实在执行。很多时候问题出在Keil里的下载配置上程序根本没烧进去或者烧的是旧版本这种低级错误影响了整个排查方向。6.2 显示乱码、出现半个字乱码的原因集中在两个地方一是数据线连接顺序错了比如DB0接成DB7那显示结果当然是一团糟二是汉字区位码的两字节都在0xA0以上但写入顺序反了。12864要求先写区码再写位码也就是先高字节后低字节如果顺序搞反显示出来的是另一个不相关的汉字。半个字的现象很典型每行的第一个汉字只能显示左半部分。这是因为12864的DDRAM在“基本指令集”下按字节寻址一个汉字占两个字节即两个“列”如果只写了一个字节就中断那个字节的右半部分就没有数据可画。解决方法是确保写汉字时两个字节连续写完中间不要插入其他命令或延时过久。我在写DisplayChinese函数时特意把两个字节的写操作放在一个函数里中间不加延时就解决这个问题。6.3 滚动时闪烁严重闪烁的本质是刷新率低于视觉残留阈值或者说屏幕内容在逐帧改变时存在明显的“全灭—更新—点亮”过程。我踩过一个坑滚动更新时每次都先清屏再写内容清屏和写入之间有几十毫秒的空白期眼睛就捕捉到了闪烁。解决方法有两种。第一把清屏范围缩小只清除需要更新的那几列而不是整个屏幕。第二利用“双缓冲”思路先在内存里把要显示的整行内容拼好然后一次性写进GDRAM避免在屏幕上“边擦边写”。对于51这种资源有限的单片机建议用第一种就足够了。另外把滚动间隔调大到150到200毫秒视觉上也不会觉得卡顿反而更符合阅读节奏。6.4 代码里出现奇怪字符字模数组的字节序如果你选择了不带字库的方案一定会遇到字模数组的字节序问题。取模软件默认生成的数组是按“逐行式”排列的也就是第1行左右、第2行左右……但有些屏的驱动器要求“按列式”排列也就是第1列上下、第2列上下。忽略这个问题就会出现汉字左右颠倒或者上下颠倒的怪相。我习惯在取模前先设置参数阴码、逐行式、顺向高位在前对应DB7。如果显示出来是反的就勾选“逆向”。这个问题没有统一答案因为不同的LCD控制芯片指令集不同只能通过试错来确定。不过一旦确定记住这个屏的参数组合以后同型号屏就直接照搬省去很多无用功。7. 实操过程与可复现的完整代码逻辑7.1 可复现的主程序骨架我把完整逻辑整理成一个可复现的骨架尽量剥离平台依赖便于移植到任何51内核单片机#include reg52.h sbit RS P2^0; sbit RW P2^1; sbit E P2^2; #define LCD_DATA P0 static const unsigned char code text[] 单片机LCD滚动显示汉字技术分享 ; // 对于带字库屏text是字符串每个汉字占两个字节 // 对于不带字库屏text是字模数组每个汉字占32字节 void DelayMs(unsigned int n); void LCD_WriteCmd(unsigned char cmd); void LCD_WriteData(unsigned char dat); void LCD_Init(void); void LCD_ClearLine(unsigned char line); void LCD_DisplayWindow(const unsigned char *str, unsigned int windowStart); void main(void) { unsigned int offset 0; LCD_Init(); while (1) { LCD_DisplayWindow(text, offset % 16); // 16为窗口总宽 DelayMs(200); offset; } }代码里我特意用了code关键字把字符串放到程序存储区因为51单片机的RAM只有256字节字符串如果放在RAM里会非常吃紧。这个细节在编写较大程序时尤其重要。另外窗口总宽的计算要根据你的实际字符串长度和屏幕宽度调整我这里是8个屏幕汉字位置加8个内容汉字长度所以范围是16。7.2 从1602移植到12864的调整思路如果你用的是LCD1602移植思路并不复杂。1602每行能显示16个字符但字符只有5x8点阵汉字要用CGRAM自定义。具体做法是把要显示的汉字拆成5x8像素的字符画将字形数据写入CGRAM的0到7号位置然后通过向DDRAM写入0x00到0x07来显示。但CGRAM只有8个槽位也就是说同一屏最多显示8个自定义字符这意味着滚动内容不能超过8个汉字。对于1602的滚动显示我建议换一种策略与其滚动汉字不如滚动字符加数字的组合比如系统状态信息“TEMP:25C VOL:3.3V ”这种每个字符都是ASCII码滚动逻辑跟12864完全一致只是窗口宽度变成16个字符。如果你一定要在1602上滚动显示汉字那就只能一次滚半个汉字8个点阵而且滚动过程中会看到明显的字形撕裂体验很差。所以我个人建议一心想做汉字滚动显示12864或OLED屏的性价比更高。7.3 OLED屏的扩展玩法做完12864之后扩展方向很自然就会转向OLED。以常见的0.96寸OLEDSSD1306驱动为例它在显示汉字时的做法和裸屏12864类似需要自己准备16x16字模但优势是它支持硬件层面的一些显示优化参数比如显示起始列地址可以灵活设置。如果你把整句汉字拼接成一个大位图通过修改显示起始列地址寄存器就能实现非常平滑的像素级滚动而且不需要重新写GDRAMCPU占用率极低。SSD1306的I2C接口版本连线只有两根SCL和SDA比12864的并口布线简单得多。但I2C通信速率比并行慢高速刷新时反而不如并口。这也是为什么很多产品设计在OLED上显示静态信息为主而12864并口模式常用于需要快速刷新的场景。这个项目的经验可以无缝迁移到OLED上因为字模原理、编码原理、主循环框架都是通用的。7.4 从51到STM32的移植要点玩过51之后很多人会想着把项目移植到STM32上。移植的重点不是重写LCD驱动而是换一套GPIO操作方式和延时机制。在51里你直接用sbit定义引脚然后用延时函数控制时序在STM32里你需要用HAL库的HAL_GPIO_WritePin或者寄存器操作来实现同样的时序同时用HAL_Delay或SysTick定时器来替代延时函数。更关键的变化是STM32的RAM远大于51你可以把整个显示内容缓存放在RAM里用DMA的方式把数据一次性刷给LCD实现高帧率的滚动效果。我做过一版STM32F103C8T6驱动12864并口的方案把8个汉字的显示数据放在一个数组里用定时器中断触发刷新主循环只负责滚动逻辑整个系统流畅度比51版高一大截而且还能同时处理按键、传感器等其他任务。这个方向尤其适合觉得51版“性能到头了”的读者去尝试。记住一个原则代码的架构是通用的换平台只是换头发核心逻辑不变。8. 经验总结与扩展思路做完这个项目我最深的一个体会是LCD显示类项目最大的“坑”从来不在代码逻辑而在时序细节和硬件匹配。你在调试过程中遇到的所有灵异现象——屏幕闪、字错位、乱码、白屏——几乎都能归结为某个时序没满足、某个引脚接错、或者某个初始化命令没执行到位。所以排查问题的时候先确认硬件连接再确认初始化是否真正执行最后才去看显示数据的逻辑这个顺序能帮你省下大量时间。另一个体会是字模的概念值得花时间彻底搞懂。它不仅是LCD显示的核心后面接触TFT屏、触屏界面、图像显示都会用到类似的思路。你了解了“文字到点阵、点阵到数据、数据到屏幕”这条链路就等于打通了嵌入式显示领域的任督二脉以后无论换什么屏心里都有底。最后再分享一个实际调试中的小技巧手边常备一个逻辑分析仪哪怕是几十块钱的简易版本在调试LCD时序时比示波器还方便。我当时用逻辑分析仪抓出了ST7920在初始化时对0x30命令的响应确实需要重复两次的时序特征也定位到了清屏后必须延时的问题。如果你刚开始做这个项目别急着相信自己的眼睛观察到的现象用数据说话排查效率完全不一样。本文还有配套的精品资源点击获取