ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

51单片机LCD滚动显示汉字:字模、显存与算法的完整实现

51单片机LCD滚动显示汉字:字模、显存与算法的完整实现 简介基于单片机的LCD滚动显示汉字项目工程包面向电子工程、嵌入式系统初学者以及单片机课程设计/毕业设计人群帮助理解汉字点阵显示原理与滚动刷新实现方法。压缩包共18个文件内含Keil工程源文件uv2、c、a51等、编译生成的hex文件、Proteus原理图dsn及调试过程生成的目标文件与备份文件可配合Keil uVision和Proteus进行联合仿真与硬件烧录。资源已吸引2283人学习下载适合希望快速上手单片机LCD汉字动态显示项目的读者参考。工程展示了汉字字模在存储器中的组织方式、LCD初始化与I/O时序控制、通过缓冲区与显示起始位置变化实现平滑滚动的算法以及延时调速、防闪烁等实用处理。借助该工程可系统掌握C51编程、点阵LCD驱动和显示算法设计并了解从源码编写、编译到仿真验证的完整开发流程是嵌入式显示方向实践入门的高价值参考。 做单片机项目的人十有八九会遇到这么个需求把一串汉字放到LCD上滚动显示做广告屏、信息屏那样跑起来。这次我做的就是一个基于51单片机的LCD滚动显示汉字小项目代码量不大但背后涉及LCD显示原理、字模取模、显存管理、定时器调度、滚动算法这些最常用也最容易被新手卡住的东西。不管你是刚学完流水灯想进阶还是准备用LCD做毕业设计或产品界面这篇文章应该都能给你一套完整可抄作业的方案。我先说结论如果只是快速验证LCD1602加CGRAM也能显示几个自定义字符但要做“一串汉字平滑滚动”更合适的是12864点阵LCD配上自己取的字模。整套流程下来你会发现所谓滚动其实就是一个不断变化的显示偏移量加上一整套“先画显存、再更新屏幕”的驱动逻辑。下面按我做这个项目时的拆解顺序来讲。1. 项目整体设计与方案选型1.1 核心需求拆解“滚动显示汉字”这句话拆开其实是三个叠加需求第一LCD上能正确显示出汉字第二显示内容要能按固定节奏动态变化第三变化方式是从右向左连续滚动。很多人一开始只盯着第三点结果前两步没做好滚动的画面全是乱码排查半天才发现是字模格式错了。再往下拆你会发现还有几个隐藏问题需要提前想清楚屏幕上一次能容纳多少字滚动到末尾之后怎么处理是跳回开头还是循环无缝衔接滚动速度怎么控制用什么定时器这几个问题的答案直接决定了方案选型所以别急着写代码先把方案定下来。1.2 三种主流LCD方案的对比市面上常见的能显示中文的LCD方案我按控制器类型分成三类LCD1602HD44780、带中文字库的LCD12864ST7920、图形点阵LCDST7565、SSD1306等。这三类我都实际用过放在一起对比会看得很清楚方案显示汉字方式滚动平滑度开发难度典型场景LCD1602 CGRAM仅8个自定义5x8字符槽拼字受限字符级跳动简单但能力弱英文数字仪表、简单状态提示LCD12864 ST7920带字库内置GB2312中文库直接发编码只能是整格跳或被屏幕行限制简单适合快速出中文信息展示屏、菜单LCD12864 ST7565/SSD1306图形模式自己取字模写入显存像素级平滑自由控制要理解取模与显存稍复杂平滑滚动字幕、图形界面从表格能看出来ST7920带字库做静态中文最省事但它把汉字当作16x16点阵的“半图形”块来处理想做到逐列平移很别扭很多时候只能整字整字蹦。而ST7565这类图形控制器把整屏当成一块可任意操作的画布显存里每一列像素都能直接改滚动自然就能做到非常平滑。1.3 我的选型结论我最终选了ST7565控制的128x64点阵LCD自己做字模。理由有三一是能实现真正的逐列平滑滚动效果接近户外广告屏二是ST7565的驱动思路和SSD1306 OLED几乎一致做完这个项目以后换OLED、换其他图形屏成本很低三是这个过程能把“显存”这个概念彻底搞清楚对后续做菜单、画图、动画都有帮助。如果你手头只有ST7920带字库屏也不是不能做我会在第4章补充说明它的折中做法但主线我会以ST7565图形模式讲透。2. LCD显示汉字的工作原理与字模基础2.1 LCD是怎么把汉字“画”出来的要理解整个项目先得知道LCD内部到底发生了什么。你可以把LCD屏幕想象成一面贴满微型百叶窗的玻璃墙每个小百叶窗就是一个像素。它自己不发光靠背光从后面照进来哪个“叶片”打开光就从那里透出去人眼看到的就是一个亮点哪个“叶片”合上那个位置就是暗的。显示控制器比如ST7565负责维护这128x64个“叶片”的开关状态MCU的工作就是告诉控制器哪个坐标的叶片打开哪个关掉。汉字本质上就是一堆点组成的图案。比如一个16x16的汉字拆成16行乘16列一共256个点每个点有亮和灭两种状态。把这些状态按顺序排列成二进制数就形成了“字模”。字模里的1代表这个点要亮0代表不亮这就是汉字能显示出来的底层逻辑。2.2 取模工具与字模数据格式手动把256个点写成0和1太不现实一般用PCtoLCD2002或Img2Lcd这类工具生成。这里我要反复强调一个新手最容易踩的坑取模设置必须和你用的LCD控制器的显存组织方式匹配否则数据完全对不上。ST7565显示RAM的组织方式是“页列”整屏128x64被分成8页每页高8像素每列有1个字节这个字节的8个bit正好对应这一列从上往下的8个像素。所以取模时你要选“纵向取模”“列行式”“8点一行”这类模式。这样生成的字模一个字节正好对应显存里的一个字节写进去时不需要再手工做bit重排。以16x16汉字为例在“列行式”取模下会生成32个字节前16字节是汉字上半部分每一列的纵向8位数据后16字节是下半部分每一列的纵向8位数据。写进显存时前半段对应第0页屏幕上方8行后半段对应第1页屏幕下方8行。如果你误选了“横向取模”一行一行取字模是按16个bit一行的顺序排列的直接写到按列寻址的显存里出来的就是一团乱麻或者左右颠倒的残影。2.3 显存设计思路51单片机的RAM很小但128x64的显存只需要8页x128列1024个字节。放在广义上也就是1KB传统8052的idata是256字节不够用所以通常用全局数组放在xdata或code里。用Keil C51的话可以声明为unsigned char xdata display_buf[8][128];如果你用的是STC的增强型51很多型号内置1KB以上的RAM直接放普通数组也行。display_buf的第一维是页号0到7第二维是列号0到127。平时画点、画汉字都是往这个数组里写数据只有需要更新屏幕时才把整个数组一次性发给LCD控制器。这就是所谓的“显存缓冲”好处是画面不会因为画到一半被刷新出去而闪烁。3. 硬件设计与初始化配置3.1 硬件接线与电路思路硬件部分我用的是一块常见的STC89C52最小系统板加一块ST7565驱动的12864液晶。ST7565支持多种接口方式我选的是SPI四线接线少调试也方便。引脚对应关系我整理成了表格单片机引脚LCD模块引脚说明P1.0SCLSPI时钟P1.1SDASPI数据P1.2CS片选低有效P1.3DC数据/命令选择P1.4RST复位低有效5VVCC模块供电GNDGND公共地电位器中点VL对比度调节这里面有两点要注意。第一STC单片机有些IO口可以配置成推挽输出但驱动模块前先确认模块逻辑电平是5V还是3.3V电平不匹配轻则显示异常重则烧坏IO口。第二背光LED要串一个限流电阻很多人直接接5V电流很大LED发光倒是猛但寿命和发热都受不了。P0口如果被占用必须加上拉电阻因为传统8051的P0是开漏结构不加外部上拉高电平输出不了。3.2 LCD初始化序列LCD控制器上电后不是立刻就能显示的要先走一遍初始化流程告诉它偏压比、扫描方向、显示开关等参数。ST7565的典型初始化序列大致是这样void lcd_init(void) { lcd_cmd(0xA2); // 偏压比1/9 lcd_cmd(0xA1); // SEG方向反转 lcd_cmd(0xC0); // COM方向正常 lcd_cmd(0x40); // 显示起始行为第0行 lcd_cmd(0x2F); // 打开内部升压电路 lcd_cmd(0xF8); lcd_cmd(0x00); // 倍压比设置 lcd_cmd(0x27); // 对比度粗调 lcd_cmd(0x81); lcd_cmd(0x16); // 对比度微调 lcd_cmd(0xA6); // 正常显示白底黑字 lcd_cmd(0xAF); // 显示开 lcd_clear(); }初始化顺序听起来很“死板”但它背后是有逻辑的先确定扫描方向和地址映射关系再配置电压和对比度最后打开显示。如果还没设置完就提前开显示屏幕会闪出一堆乱码或者直接花屏。另外如果你用的是ST7920带字库屏初始化又是另一套流程功能设定、清屏、进入点设定等所以到手第一件事永远是查对应控制器的数据手册不要照搬别人的代码。3.3 为什么要先调对比度我会建议第一次上电先不跑滚动程序就写一个最简单的点亮一列像素的测试代码然后慢慢拧VL电位器。对比度调得太低字迹很淡像没吃饭调得太高背景会变黑甚至出现残影。这一步属于硬件层面的校准软件怎么写都弥补不了。很多“屏坏了”的假象其实就是对比度电位器没调到位置。4. 滚动显示的核心算法与代码实现4.1 从静态显示到滚动显示的思路转变静态显示其实很简单就是把字模画到固定坐标。滚动就不一样了它要求文字的位置随时间变化。最直观的思路是引入一个滚动偏移量scroll_offset画面里每个汉字画到屏幕上的横坐标都减去这个偏移量。我用的算法并不复杂假设要显示的文本有N个汉字整个文本占用的像素宽度就是16乘以N。屏幕宽度是128列所以scroll_offset的合法范围是0到(16*N - 128)。如果从左滑到右边末尾后直接跳回0虽然简单但视觉上会有一个“嗖”的跳跃不太舒服。想要无缝循环可以在文本后面补一串空白列或者让整个文本循环重复让offset取模回到开头。在每次刷新时先根据offset算出当前第一个可见汉字是第几个int hz_index scroll_offset / 16; // 第几个汉字 int pixel_shift scroll_offset % 16; // 向左偏移的像素数然后从hz_index开始把可见范围内的汉字按顺序画到屏幕上每个汉字的x坐标等于“它在文本中的序号乘16再减去pixel_shift”。这个公式很朴素但效果就是标准的广告屏滚动效果。4.2 定时器与滚动速度计算滚动要匀速不能一会儿快一会儿慢我用定时器0产生固定间隔的中断来驱动。假设晶振12MHz常用方式1是16位定时器机器周期是1us要定时50ms就需要50000个计数周期。定时器初值计算如下65536 - 50000 15536换成十六进制就是0x3CB0。所以初始化代码是TMOD 0x01; // 定时器0方式1 TH0 0x3C; // 初值高8位 TL0 0xB0; // 初值低8位 ET0 1; // 开定时器0中断 EA 1; // 开总中断 TR0 1; // 启动定时器0这里有个细节如果用12MHz晶振最大定时范围也就65.536ms50ms完全够用。但如果你用的是11.0592MHz晶振给串口通信用的机器周期不是整数1us计算初值时要先把晶振频率除以12得到机器周期频率再算需要多少计数最后用65536减掉。很多人串口通信正常但定时器时间不对基本都是把晶振频率用错了。速度的计算很简单每隔50ms让scroll_offset加1相当于每秒钟移动20个像素一屏128列大概6.4秒滚动完一遍。想要更快可以在每次中断里让offset加2或加3或者把定时时间改短想让滚动更细腻可以保持每次加1把定时改成30ms左右。4.3 核心代码讲解显示一个汉字的函数关键是理解字模数据怎么落进显存。我的字模是“列行式纵向”32字节前16字节对应上半页后16字节对应下半页void draw_hz(unsigned char x, unsigned char y, unsigned char code_hz) { unsigned char i; // 上半页前16字节 for (i 0; i 16; i) { unsigned char dat hz_font[code_hz][i]; for (bitpos 0; bitpos 8; bitpos) { if (dat (0x80 bitpos)) { set_pixel(x i, y bitpos); } } } // 下半页后16字节 for (i 0; i 16; i) { unsigned char dat hz_font[code_hz][16 i]; for (bitpos 0; bitpos 8; bitpos) { if (dat (0x80 bitpos)) { set_pixel(x i, y 8 bitpos); } } } }set_pixel就是往display_buf对应的页和列写1。因为我在2.2节讲的显存组织方式是“页列”所以把一个16x16汉字拆成上下两个8行半区来写正好和页面对齐这也是我当时选“列行式”字模的真正原因。滚动主逻辑我放在主循环里定时器中断只负责置一个标志位void main() { lcd_init(); timer0_init(); while (1) { if (scroll_flag) { // 每50ms置1一次 scroll_flag 0; scroll_offset; if (scroll_offset MAX_OFFSET) scroll_offset 0; render_scroll_text(); // 按当前offset重画到显存 lcd_update(); // 整屏刷新 } } }注意这里的顺序先画显存再整屏刷新中间不要穿插其他耗时的操作。如果画显存到一半就刷新屏幕会出现撕裂感尤其滚动速度快的时候特别明显。4.4 ST7920带字库屏的折中方案如果你手头是ST7920这种带中文字库的屏又不想换硬件也不是完全做不了滚动。可以把要显示的整段文字分成若干个16x16的汉字块每次根据滚动步数重新计算每个汉字应该停在哪个“半字”位置然后调用显示字符的命令把它写到对应的行和列地址上。但受限于它的内部地址是半格的最多做到两个汉字之间的跳变想逐像素平滑移动很难。实际体验就是“字一顿一顿地蹦”。如果场景能接受这种效果那就用ST7920代码简单很多如果接受不了还是建议回到图形模式。5. 常见问题与排查技巧实录5.1 常见问题速查表做这个项目过程中我踩过不少坑也帮别人查过不少问题把高频问题整理成表建议收藏现象可能原因解决方法屏幕全黑/全白无内容初始化没执行、显示未开、对比度没调检查AF命令、拧VL电位器显示乱码或汉字左右颠倒取模方式不对、SEG方向相反重新按列行式取模调A1/C0命令汉字上下两半错位字模前16字节和后16字节没对应页面检查draw_hz的页面映射逻辑滚动时画面闪烁显存绘制和刷新没有分离先写display_buf再lcd_update画完再刷滚动速度太快/太慢定时器初值算错或offset步长不对重新计算初值调整步长屏幕有残影刷新率太低或偏压太高提高帧率微调对比度文字滚到末尾突然跳回MAX_OFFSET设置不合理在文本后补空白或设计循环文本SPI时序不对导致花屏SCL/SDA接反、时序极性不对逻辑分析仪看波形对照模块手册5.2 调试LCD的几个独家技巧排查的时候别只盯着代码看我有几个实际用下来很有效的手法。第一备一个逻辑分析仪看SPI的SCL和SDA波形CS、DC的时序对不对一目了然。Keil调试的System View窗口很多时候是空白因为它需要仿真器支持不如逻辑分析仪直接抓引脚来得直观。第二写一个“测试图案”函数把显存全部填成0xAA或0x55刷新后如果屏幕出现整齐的竖条纹说明底层写入通路是通的问题就锁定在字模或绘图逻辑层。第三把取模数据用串口打印到电脑上和PCtoLCD2002里的预览比对能立刻发现数据是反的还是没有取对。5.3 关于51单片机IO口的一点提醒STC单片机配置成推挽输出时驱动能力确实强但不要因此就省略限流。LCD模块的背光、数据线都加一点保护总没错。有些模块是5V供电但IO是3.3V电平这种情况下STC的5V脚直接驱动3.3V模块逻辑高电平可能不够需要加电平转换或分压。我见过好几个人因为偷懒没做电平匹配模块要么不工作要么偶尔抽风最后查了半天才发现是高电平阈值问题。6. 扩展与个人体会6.1 这套方案还能怎么玩写完这个滚动项目后你会发现手里的“显存偏移”思路是个万能工具。把scroll_offset从横向改成纵向就能实现上下滚动把每次偏移量从固定值改成曲线或随机值就能做出弹跳或者缓动效果把文本替换成小型图标或动画帧序列就是一套简单的动画系统。后面我又把同一套代码迁移到了SSD1306的OLED上除了初始化函数和写数据接口绘图与滚动逻辑几乎原封不动这说明底层架构合理的话换硬件只是换驱动层而已。6.2 踩坑后的个人体会我个人的体会是这类项目最容易翻车的地方往往不是算法而是“数据格式和硬件寻址方式不匹配”这种看起来很低级的问题。所以做之前一定花十分钟看清楚LCD控制器的数据手册确认它的显示RAM是页加列还是地址加偏移再回到PCtoLCD2002里调整取模设置。顺序永远是先点亮屏幕再显示静态汉字最后才做滚动。跳过步骤赶进度只会把排查时间加倍赔回去。最后再分享一个小技巧调滚动速度时不要直接改定时器初值反复烧录你可以用两个按键控制一个速度变量以这个变量决定每次中断里offset加几。这样现场就能比对不同速度的视觉效果找到最舒服的滚动节奏后再固定参数效率会高很多。本文还有配套的精品资源点击获取
返回列表