简介面向嵌入式开发者的 ST7920 驱动 12864 LCD 实战资料聚焦带字库屏幕显示自定义字符与图片覆盖控制器初始化、8×8 字符写入 CGRAM、显示反白、字体位图查找表、指定坐标输出字符串以及菜单图片绘制等常见需求适合制作仪表界面、参数菜单和小型人机交互界面的单片机开发者参考。压缩包共 14 个文件约 43KB包含 C 源码、Keil 工程与选项文件以及编译生成的 HEX、OBJ、LST、M51 等产物和 bak 备份文件便于直接打开工程、核对编译配置、复现实验现象并定位常见显示问题。已有 1123 人学习下载说明其在 12864 显示调试场景中具有一定参考价值。包内示例可帮助读者理解 ST7920 指令集与显存组织方式借鉴字符定位、图片取模、反白切换和菜单布局的实现思路并据此完成后续实际二次开发与问题排查。1. 从一块“带字库”的12864说起ST7920到底能省掉哪部分工作手里有一块带字库的12864屏主控是ST7920很多人第一反应是“字库都带了自定义字符和显示图片是不是不用折腾”真接上电就会发现文本显示只是最省事的那一层CGRAM里能塞几个自定义小图标、GDRAM怎么按128×64铺满、并口和串口初始化差在哪才是决定这块屏能不能进产品的细节。这个资源适合正在用ST7920做仪表面板、设备菜单、小型HMI的开发者也适合手上已有模块但被花屏、错位、自定义字符乱码卡住的A同学。带字库确实省掉了汉字点阵的存储和取模但它不等于图形显示也自动解决。下面按我拆模块的顺序把字库、CGRAM、GDRAM和初始化时序串起来。重点不是背指令表而是让一块来路不明的12864模块先变成可预期的显示设备再把自定义字符和图片稳稳写进去。2. ST7920的显存地图CGRAM、GDRAM和字库各自管什么ST7920这颗控制器的脾气和常见的纯图形LCD不太一样。它把文本显示和图形显示分成了几块区域CGROM负责内置字库CGRAM留给用户自定义小字符GDRAM负责128×64的点阵图形。很多“自定义字符显示不对”“图片只出来一半”的问题根子不在代码语法而在地址空间没分清。我一般先不急着写业务界面而是拿一张纸把这三块区域画出来哪些地址只能读、哪些能写、一次写多少字节、写完后怎么切回文本模式。把这几个边界钉死后面调试会省很多玄学时间。2.1 带字库不等于万能CGROM、CGRAM、GDRAM的分工带字库12864的“字库”通常指CGROM里预存了汉字和半宽ASCII字符。常见规格是8192个16×16汉字点阵加128个8×16半宽字符。写文本时只要往DDRAM对应位置送字符编码控制器自己从CGROM取模显示。这也是为什么菜单、参数名、单位符号可以先跑起来。但CGROM是只读的改不了。你想显示一个自定义电池图标、一个温度计、一个箭头就得用CGRAM。CGRAM的容量很小ST7920常见是64字节。按8×16点阵算一个字符16字节最多放4个按16×16点阵算一个字符32字节最多放2个。这个限制很硬不要指望用CGRAM塞一整排状态图标。GDRAM则是另一套空间按128×64点阵组织整屏1024字节。它适合放LOGO、波形、进度条、图标组合。和CGRAM相比GDRAM没有“只能放几个”的问题但写入地址和扫描顺序更讲究。文本DDRAM的地址也不是线性排列四行文本常见的起始地址是0x80、0x90、0x88、0x98具体以模块手册为准。区域容量/组织典型用途关键限制CGROM8192个16×16汉字 128个8×16半宽字符内置文本、菜单只读不能改字模CGRAM64字节自定义小图标8×16最多4个16×16最多2个GDRAM128×64点1024字节图片、波形、LOGO按行列地址写入行宽16字节DDRAM4行文本地址文本定位行地址不连续不能按线性坐标算这张表看着简单实际调试时最容易翻车的是CGRAM和GDRAM混用。比如你刚写完图片想直接写文本却忘了从扩展指令集切回基本指令集结果文本地址被当成图形地址解释屏幕就开始出现不可预期的块。后面避坑章会展开这里先记住CGRAM是“换字模”GDRAM是“画点阵”DDRAM是“摆文本”。2.2 并口还是串口PSB、RS、RW、E的接线取舍ST7920模块通常同时支持并口和串口选择脚是PSB。PSB接高电平是并口接低电平是串口。并口占线多但时序直观、刷屏快调试时也容易用逻辑分析仪抓串口省引脚适合引脚紧张的小板子但刷新128×64整屏会慢一些而且不同模块的串口脚位命名不完全一样。我一般建议先把并口跑通。并口至少需要DB0到DB7、RS、RW、E有的模块还有PSB和复位脚。RS为高是数据为低是命令RW为高是读为低是写E是使能脉冲通常高电平维持一段时间后拉低数据在下降沿被锁存。RW如果直接接地省了忙标志读取但所有命令后都要加足够延时否则初始化会时好时坏。串口模式则是PSB接地用CS、SID、SCLK传命令和数据。常见坑是照着并口代码只改PSB结果RS、RW、E还在按并口时序翻转屏幕自然没反应。串口下这些脚的角色变了必须按模块手册重新映射。还有一点串口模式下忙标志读取也不一样很多代码干脆用固定延时替代刷屏时要注意别把延时压得太短。提示手上模块没有原理图时先用万用表确认PSB、RS、RW、E和DB0-DB7的通断再上代码。不要凭排线颜色猜。2.3 初始化序列与忙标志先把黑匣子变成可预期ST7920上电后不会立刻进入你想要的8位并口基本指令集。常见做法是先给足复位延时再连续发两次0x30让控制器稳定到8位接口然后开显示、清屏、设置输入模式。下面这段是通用性比较好的并口初始化骨架引脚宏按你的MCU改。#include stdint.h #define LCD_DATA P0 sbit RS P2^0; sbit RW P2^1; sbit EN P2^2; sbit PSB P2^3; static void delay_us(uint16_t n) { /* 按主频实现微秒延时 */ } static void delay_ms(uint16_t n) { /* 按主频实现毫秒延时 */ } void lcd_wait_busy(void) { LCD_DATA 0xFF; // 数据口切输入准备读DB7 RS 0; // 命令 RW 1; // 读 EN 1; delay_us(2); while (LCD_DATA 0x80) { } // DB71表示忙 EN 0; } void lcd_write_cmd(uint8_t cmd) { lcd_wait_busy(); RS 0; RW 0; LCD_DATA cmd; EN 1; delay_us(2); EN 0; delay_us(2); } void lcd_write_data(uint8_t dat) { lcd_wait_busy(); RS 1; RW 0; LCD_DATA dat; EN 1; delay_us(2); EN 0; delay_us(2); } void lcd_init(void) { PSB 1; // 并口模式 delay_ms(50); // 上电复位余量 lcd_write_cmd(0x30); delay_ms(1); lcd_write_cmd(0x30); delay_ms(1); lcd_write_cmd(0x0C); // 开显示关光标 lcd_write_cmd(0x01); // 清屏 delay_ms(2); lcd_write_cmd(0x06); // 地址自增 }这段代码的关键不是“照抄能亮”而是三个参数上电延时、忙标志判断、E脉冲宽度。上电延时我一般给50ms以上尤其是冷启动或电源上升慢的板子忙标志判断能避免命令发太快被吞E脉冲宽度按模块手册通常不小于几百纳秒但实际用2us更稳。如果RW接地lcd_wait_busy要删掉改成每条命令后固定延时。常见做法是命令后延时1ms到2ms清屏后延时5ms以上。别在初始化阶段抠这几毫秒后面花屏排查会花更多时间。3. 自定义字符落地向CGRAM写8×16点阵的完整步骤CGRAM是ST7920里最容易被低估的一块。它容量小但做状态图标非常合适电池、信号格、锁、箭头、温度计都可以用8×16点阵塞进去。关键是地址算对、字节序排对、显示编码别写错。我见过最多的翻车不是字模画错而是把CGRAM地址当成DDRAM地址用。CGRAM有自己的地址范围0x00到0x3F写入前要先发设置CGRAM地址命令0x40加偏移写完再切回DDRAM。顺序错了字模会写到别的地方屏幕上自然只看到方块或乱码。3.1 自定义字符的地址与编码64字节到底能放几个ST7920的CGRAM按字节编址常见可用范围是0x00到0x3F共64字节。一个8×16点阵字符占16字节所以最多放4个一个16×16点阵字符占32字节最多放2个。这个容量是硬件决定的不要试图通过改编码突破。如果按8×16用自定义字符编码通常对应0x00、0x01、0x02、0x03。第0个字符的CGRAM起始地址是0x00第1个是0x10第2个是0x20第3个是0x30。写CGRAM时发0x40 | base其中base就是这些起始地址。显示时把编码当普通字符写到DDRAM位置即可。有些模块手册会写成“8个自定义字符”那通常是按8×8点阵算的。你手上是12864带字库屏先按8×16或16×16确认别直接套8×8的地址表。实在不确定就写一个全1和一个全0字模看屏幕上占多高很快能判断。3.2 取模与字节序8×16点阵怎么排成16字节8×16点阵按横向取模时每一行是一个字节共16字节。字节的最高位对应最左边的像素最低位对应最右边的像素。比如下面这个向上箭头前四行是箭头头部后面是箭杆最后一行留空。const unsigned char icon_arrow_up[16] { 0x18, // 00011000 0x3C, // 00111100 0x7E, // 01111110 0xFF, // 11111111 0x18, // 00011000 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x00 };这段数组里0x18表示中间两位亮0x3C表示中间四位亮0x7E表示中间六位亮0xFF是一整行亮。如果你的取模软件默认“阴码/阳码”相反显示出来会是反色把每个字节按位取反即可。如果选成纵向取模箭头会像被梳子梳过一行一行错位这时不是屏坏了是取模方向错了。注意CGRAM字模的字节序必须以模块实际显示为准。先写一个0xAA、0x55交替的测试字模能最快看出高位在前还是低位在前。3.3 写入CGRAM并显示可复现的C代码下面这段把自定义字符写入CGRAM再按四行文本地址显示。lcd_write_cmd和lcd_write_data沿用上一章的并口实现。code限制在0到3对应四个8×16自定义字符。void lcd_load_cgram(uint8_t code, const uint8_t *dots) { uint8_t i; uint8_t base; if (code 3) return; // 8x16最多4个 base code * 16; // 每个字符16字节 lcd_write_cmd(0x30); // 确保基本指令集 lcd_write_cmd(0x40 | base); // 设置CGRAM地址 for (i 0; i 16; i) { lcd_write_data(dots[i]); } lcd_write_cmd(0x80); // 回到DDRAM首地址 } void lcd_put_custom(uint8_t line, uint8_t col, uint8_t code) { const uint8_t line_addr[4] {0x80, 0x90, 0x88, 0x98}; if (line 3) return; lcd_write_cmd(line_addr[line] col); lcd_write_data(code); // code0~3 }lcd_load_cgram里先发0x30是为了回到基本指令集避免之前写过GDRAM还停在扩展模式。0x40 | base是设置CGRAM地址base为0x00、0x10、0x20、0x30时分别对应四个字符。循环写16字节后用0x80把地址指针拉回DDRAM否则后续文本会继续往CGRAM里写。lcd_put_custom里的四行地址不是线性的0x80、0x90、0x88、0x98是常见映射。col按字符列算自定义8×16字符占一个半宽列如果同一行混排汉字列偏移要按16×16字符宽度重新算。显示不出来时先确认code确实写到了0x00到0x03再确认DDRAM地址有没有越界。4. 显示图片把128×64位图铺进GDRAM的四个关键参数自定义字符解决小图标整屏图片就要进GDRAM。ST7920的GDRAM按128×64点组织一行128点等于16字节整屏64行等于1024字节。听上去很直接但实际写入时要同时管住四个参数垂直地址、水平地址、行宽16字节、字节高低位顺序。我一般先刷一张棋盘格不先刷LOGO。棋盘格能同时暴露行错位、列错位、高低字节交换和扫描方向反了的问题。LOGO太复杂错了也看不出是地址错还是取模错。4.1 GDRAM地址与扫描顺序先垂直后水平写GDRAM时常见流程是先切扩展指令集再开图形显示然后对每一行发垂直地址0x80 | y发水平地址0x80 | 0接着连续写16字节。连续写的过程中水平地址会自动递增所以一行写完再换下一行。void lcd_draw_full(const uint8_t *bmp) { uint8_t x, y; lcd_write_cmd(0x34); // 扩展指令集 lcd_write_cmd(0x36); // 开图形显示 for (y 0; y 64; y) { lcd_write_cmd(0x80 | y); // 垂直地址0~63 lcd_write_cmd(0x80 | 0); // 水平地址从0开始 for (x 0; x 16; x) { lcd_write_data(bmp[y * 16 x]); } } }这段代码里y * 16 x是图片数组的行优先索引。如果你的图片数组是按列优先生成的显示会变成斜的或只显示一半。0x80 | y的y范围是0到63不要写成0到31除非你的模块确实是半屏映射。水平地址从0开始连续写16字节覆盖128点。有些模块要求每个水平地址写两个字节先高字节后低字节。连续写16字节等价于写8个地址。如果显示出来每两个字节互换就在内层循环里交换相邻字节。这个现象很像“图片被切成一竖条一竖条”不是取模软件坏了。4.2 取模脚本PNG转C数组的16字节行格式手工取模容易在阈值和反色上翻车我一般用一个小脚本把PNG转成C数组。下面这段用Pillow输出1024字节按行优先每行16字节黑像素为1白像素为0。from PIL import Image def png_to_c_array(src, dst, invertFalse, threshold128): img Image.open(src).convert(L).resize((128, 64)) data [] for y in range(64): for xb in range(16): b 0 for bit in range(8): x xb * 8 bit v img.getpixel((x, y)) black v threshold if invert: black not black if black: b | 0x80 bit data.append(b) with open(dst, w, encodingutf-8) as f: f.write(const unsigned char bmp_logo[1024] {\n) for i, v in enumerate(data): f.write(f0x{v:02X},) if (i 1) % 16 0: f.write(\n) f.write(};\n) png_to_c_array(logo.png, logo.h, invertFalse, threshold128)resize((128, 64))把图片压到屏幕分辨率convert(L)转灰度threshold决定多暗算黑。invertFalse表示黑像素写成1如果你的屏是1灭0亮把invert改成True。b | 0x80 bit是高位在左的横向取模和上一节GDRAM代码对应。生成数组后不要直接整屏刷先用lcd_draw_full(bmp_logo)看方向。如果图片上下颠倒把行循环改成y 63; y 0如果左右颠倒把字节内位序反过来如果只显示左半屏检查是不是每行少写了8字节。4.3 局部刷新与清屏不要每次全屏重刷整屏刷1024字节不算大但在低主频MCU上频繁全刷会拖慢按键响应。常见做法是只刷新变化区域先发垂直起始地址和水平起始地址再按窗口宽度写字节。GDRAM没有硬件窗口命令所谓局部刷新就是自己控制行列循环不碰窗口外的数据。清屏也一样不要每次用文本清屏命令0x01去清图形区它主要清DDRAM。GDRAM要自己写0x00。我一般写一个lcd_fill_gdram(0x00)在切页或开机时调用一次后续只画变化部分。局部刷新时注意水平地址要按字节对齐窗口起点不是8的倍数时需要读改写ST7920读GDRAM的时序比写麻烦能对齐就对齐。5. 避坑排查ST7920花屏、乱码、只显示半屏的现场记录这块屏的坑大多有规律怕的是一上来就怀疑代码。下面五条是我在实验室和某公司小批试产时反复遇到的现场记录按“现象→原因→解决”写方便对着排查。5.1 上电花屏且清屏命令无效PSB和复位延时现象上电后满屏黑块或雪花发0x01清屏没反应偶尔复位几次又能正常。原因PSB电平不对模块实际在串口模式MCU却按并口发命令或者上电复位延时不够ST7920还没稳定就收到第一条0x30。电源上升慢的板子更容易出现。解决先量PSB确认并口模式接高把上电延时加到50ms以上初始化连续发两次0x30中间隔1msRW接地的方案把每条命令后的延时加到1ms以上。还不行就查E脉冲脉冲太窄会导致命令偶尔丢。5.2 自定义字符显示成方块或乱码CGRAM地址与字节序现象文本能显示自定义字符位置出现方块、乱码或者显示成上一个字符的残影。原因CGRAM地址没加0x40直接把code*16当DDRAM地址发或者显示时写的编码超出0x本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
语义检索系统优化:向量数据库实战指南 1. 语义检索与向量数据库的黄金组合第一次接触语义检索系统时,我被传统关键词匹配的局限性震惊了。用户搜索"苹果",系统无法区分水果和科技公司;查询"机器学习好书",结果中充斥着大量仅包含"机器学习&qu… · 2026/11/5 2:27:50
MySQL大数据量IN查询优化实战方案 1. 问题背景与核心挑战最近在优化一个订单查询系统时,遇到了一个经典难题:业务上必须使用IN子句查询大量ID(单次查询包含5万个ID),导致数据库响应缓慢,接口超时频发。这种场景在电商、物流等需要批量处理数… · 2026/11/5 2:27:50
pyTEP:Python化工仿真与TEP故障注入闭环实践 简介:pyTEP 是面向过程控制、仿真建模与故障诊断研究的开源 Python 工具包,封装田纳西伊士曼(Tennessee Eastman)流程的交互式仿真接口。该工具包让使用者无需理解模拟器底层实现,即可通过高级 API 完成仿真初始化、操… · 2026/11/5 2:27:50
ADS122U04与dsPIC33EP组合:工业与HVAC多路温度采集实战方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:37:45
ADS122U04+STM32L476RG:工业与HVAC精密温度采集系统设计 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:37:45
ADS122U04搭配STM32F407ZG:高精度多通道温度采集方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:37:45
工业测温实战:ADS122U04与PIC18F85J50的多路温度采集方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:37:45
ADS122U04+STM32F765ZI工业温度采集模块:PT100/热电偶精确测量方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:37:45
ADS122U04+PIC18F46K42:高精度PT1000测温模块设计与实现 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/5 7:36:44
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38
多智能体协作系统架构设计与实践优化 1. 多智能体协作系统的时代背景2017年Transformer架构的诞生彻底改变了人工智能的发展轨迹。当行业还沉浸在单一大模型带来的性能突破时,Anthropic的研究团队已经将目光投向了更前沿的领域——如何让多个智能体通过协作产生超越单体能力的系统智能。这就像交响乐团中… · 2026/11/5 0:00:39
大模型系统性入门实战指南:从原理到部署微调的全链路路线 提到大模型的系统性入门,我先说个结论:这事真不难,难的是资料太散、路线太多,今天这个框架明天那个工具,新人很容易在“收藏夹吃灰”和“教程看到一半放弃”之间反复横跳。市面上关于大模型的内容少说有几十万篇&#… · 2026/11/5 0:00:39
鸿蒙文件系统沙箱机制解析与开发实践 1. 鸿蒙文件系统沙箱机制深度解析作为一名从Android转向鸿蒙开发的工程师,我深刻体会到文件系统访问是迁移过程中最先遇到的"拦路虎"。在Android上运行良好的文件读写代码,在鸿蒙真机环境却频频出现文件写入后神秘消失或直接抛出权限拒绝错误的… · 2026/11/5 0:00:39
双极步进电机控制实战:DRV8818PWPR与PIC18F86K22驱动调试 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 3:16:49
STM32F207ZG + DRV8818PWPR 步进电机驱动实战解析 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 6:59:29
DRV8818PWPR+dsPIC30F4013工业级双极步进电机控制方案 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/11/4 7:02:38