
简介这是一套基于STM32F03RBT6微控制器的19264点阵LCD驱动工程面向嵌入式开发者和电子爱好者完整演示了KS0108兼容KS0107控制芯片的8位并行接口驱动方案。工程包共151个文件、2.36MB其中包含36个.h头文件与36个.c源代码文件覆盖LCD驱动的主要逻辑同时附有Keil MDK工程文件uvproj/uvopt、链接脚本sct、启动文件s以及编译过程生成的crf、o、axf、hex、map等文件从源码到可执行文件一应俱全便于逐步分析和验证。目前已有745人浏览学习对希望深入理解MCU底层驱动和LCD工作原理的读者是很好的案例。代码实现了LCD初始化、显示开关控制、清屏、像素点绘、字符与字符串显示等基础功能并设计了显示缓冲区批量刷新机制减少扫描通信次数还预留了画直线、矩形等绘图函数的扩展空间。整个工程注释清晰GPIO配置和时序逻辑均给出较完整说明可直接编译烧录到STM32F0系列也能迁移到其他MCU平台是学习并口液晶驱动不可多得的参考项目。 192×64点阵液晶屏在嵌入式圈子里是个老面孔了但直到现在我还能在各个工业仪表、设备面板、车载终端上看到它。STM32驱动19264 LCD这活儿听起来不复杂——不就是写命令、写数据、刷个屏嘛可真要把它做稳定能显示汉字、能画图形、能长时间跑不死机还是会踩到不少坑。这篇文章是我实际调试19264驱动工程的完整记录从屏的接口类型、时序原理、硬件接线到驱动代码编写、汉字取模显示再到高频故障排查一步步拆开讲清楚适合刚用STM32做显示类项目的同学也适合想把手头屏幕驱动写得更规范的人参考。1. 19264LCD到底是什么屏项目拆解先行1.1 19264的硬件特性与常用接口19264这个名字其实已经把核心参数写在脸上了192列64行也就是一共有192×64个点属于单色点阵屏。相比常见的12864它横向点数更多一行能排下的字符和汉字更多所以在需要显示较长信息、曲线、表格的场景里很吃香。屏幕本身不带显存管理必须依赖外部的LCD控制器芯片常见的控制器有ST7565、ST7567、NT7534、ST7529等。ST7567和NT7534基本可以按ST7565的指令集兼容处理但具体到某块屏初始化序列多多少少会有差异这一点后面我会单独说。接口方面19264屏的常见接法分两类一类是并口以8080时序为主也有少部分6800时序另一类是串口SPI。并口屏引脚多D0到D7八根数据线加上CS、RS、WR、RD、RST、PSB等控制脚占用的MCU引脚比较多。SPI屏就省脚得多MOSI、SCK、CS、DC、RST五根线搞定。做工程时选哪种接口取决于你的MCU引脚余量和对刷新速度的要求。如果只做简单显示SPI够用如果要做曲线、图片这类大数据量刷新并口优先。1.2 为什么选择STM32来驱动它现在很多人一上来就推荐用带GUI硬件加速的高端MCU或者直接上串口屏但STM32驱动19264仍然是非常经典的组合原因很朴素第一STM32引脚多且电平是3.3V跟常见19264模组的逻辑电平天然匹配不需要额外电平转换第二芯片性价比高哪怕用一片STM32F103C8T6这种几块钱的片子也能一边刷屏一边跑ADC采样、按键扫描、串口协议栈互不干扰第三开发资料极其丰富标准库、HAL库、寄存器版本都有人用过遇到问题容易检索到答案。在我实际做的这个项目里主控用的就是STM32F103系列标准库开发屏幕走的是8080并口。之所以没用FSMC硬件总线是因为当时这款屏的引脚排列比较特殊FSMC映射起来不顺手而GPIO模拟并口逻辑简单、实现直观调试起来也方便。如果你对速度没有极端要求GPIO模拟并口完全够用这也是这套驱动工程最通用的做法。2. 驱动方案选型与底层原理2.1 三种主流的驱动方式怎么选先说结论STM32驱动并口19264的方式主要有三种各有利弊。第一种是GPIO模拟并口也就是用普通IO口逐位去操作数据线和控制线。它的优势是引脚任意分配代码逻辑完全可控出问题好排查劣势是刷屏速度受限于GPIO翻转频率全屏刷一帧大约要几毫秒到十几毫秒。对于菜单、仪表盘这类界面完全够用。第二种是FSMC硬件总线。STM32的FSMC可以把外部设备映射到内存地址你往某个地址写数据硬件自己就把时序生成了。它最大的优势是刷屏速度快得多CPU只需要做数据搬运剩下的时序交给硬件。劣势是引脚基本固定D0到D15、NOE、NWE、NE1、A0这些都要接到特定位置PCB布线时比较别扭而且不是所有型号都有FSMC外设。第三种是SPI接口。如果你买到的19264本身就是SPI版本或者模组上的PSB引脚可以切到串口模式那么用STM32的硬件SPI驱动最省引脚速率也不低。需要留意的是SPI模式下时钟极性和相位必须跟屏幕控制器的要求对上常见的配置是CPOL0、CPHA0但最好以手册为准。我最终选了GPIO模拟并口主要考虑到项目里屏幕数据读取读状态、读显存也要用GPIO方式实现起来最直接不依赖任何总线的怪异映射。2.2 8080时序到底在讲什么很多初学者卡在驱动写不出来其实不是不会写GPIO而是没看懂时序图。8080并口的写时序简单说就是“选设备、选通道、放数据、打拍子”。具体操作顺序是先把CS拉低选中这片LCD再设置RS也叫A0的电平表示接下来传输的是指令还是数据然后把要发送的字节放到D0到D7上最后给WR一个低脉冲。数据是在WR上升沿被锁存进去的。写数据和写指令唯一的区别就是RS电平不同——RS低是写命令RS高是写数据。读时序类似把WR改成RD数据从屏里读出来但读操作比写操作慢中间要加一点延时否则数据不稳定。这里有个特别容易犯的错有人先拉WR再放数据或者RS和数据同时变化结果是数据线还没稳定控制器已经采样了屏幕上就出现乱码。正确的做法永远是先准备好RS和数据线电平再给WR脉冲。时序上虽然数据建立时间tDS只有几十纳秒但对于低速的软件模拟来说你只要保证“先设置后打拍子”这个顺序就永远不会错。2.3 硬件接线设计注意事项硬件接线看着简单实际坑不少。我习惯把所有引脚选在同一个IO口组上比如数据线D0到D7放在PC0到PC7控制线放在PC8到PC12这样操作时可以按位操作端口寄存器代码效率更高也方便以后换FSMC方案时调整。选引脚时第一禁忌是占用JTAG/SWD调试口。STM32上PA13、PA14、PA15、PB3、PB4默认是调试接口如果你把LCD数据线挂在这些脚上程序一运行下载器直接断开连程序都烧不进去。别问我是怎么知道的这个错我早期至少踩过两回。背光引脚不要直接怼MCU的3.3V更不要直接接5V。19264的背光一般要串一个限流电阻阻值根据屏的背光电流算通常几十欧到一两百欧。如果要做背光开关控制用三极管或者MOS管做开关不要让背光电流流过MCU引脚。对比度调节引脚V0是另一个关键点。V0电压决定了屏幕上像素的深浅调低了花屏调高了鬼影。最省事的做法是V0接一个10K电位器一端接VEE一端接地中间抽头接V0上电后肉眼调到显示最清晰即可。3. 驱动代码核心实现标准库GPIO模拟并口3.1 引脚初始化与基础读写函数我用的标准库版本引脚初始化没有特别之处就是把控制引脚和数据引脚统一配置成推挽输出速率设为50MHz。如果不需要读屏RD可以留空但强烈建议把读函数也写了后面调试和读显存都方便。// GPIO初始化以PC口为例 void LCD_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOC, GPIO_InitStructure); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_9 | GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12; GPIO_Init(GPIOC, GPIO_InitStructure); }写一个字节的函数是整个驱动的心脏。为了效率我直接用寄存器操作不调用库函数。#define LCD_CS(x) GPIOC-BSRR x ? GPIO_Pin_8 : (uint32_t)GPIO_Pin_8 16 #define LCD_RS(x) GPIOC-BSRR x ? GPIO_Pin_9 : (uint32_t)GPIO_Pin_9 16 #define LCD_WR(x) GPIOC-BSRR x ? GPIO_Pin_10 : (uint32_t)GPIO_Pin_10 16 #define LCD_RD(x) GPIOC-BSRR x ? GPIO_Pin_11 : (uint32_t)GPIO_Pin_11 16 #define LCD_RST(x) GPIOC-BSRR x ? GPIO_Pin_12 : (uint32_t)GPIO_Pin_12 16 // 8080写字节isData为1写数据为0写命令 void LCD_WriteByte(uint8_t data, uint8_t isData) { LCD_CS(0); LCD_RS(isData); // 数据放到D0~D7 GPIOC-ODR (GPIOC-ODR 0xFF00) | data; LCD_WR(0); LCD_WR(1); // WR上升沿锁存数据 LCD_CS(1); }这段代码里有个细节先设置RS和数据再操作WR。这就是我前面强调的“先准备后打拍子”。对于初学标准库的人来说可能会疑惑为什么用BSRR寄存器而不是GPIO_SetBits这里直接操作寄存器一是减少函数调用开销二是在刷屏时可以明显提升速度。3.2 初始化序列与清屏、坐标定位19264初始化看起来代码不长但每条命令都有讲究。下面是一段适用于ST7565系列控制器的初始化序列基本覆盖了偏压设置、ADC方向、扫描方向、对比度等关键项。void LCD_Init(void) { LCD_GPIO_Init(); LCD_RST(0); delay_ms(10); LCD_RST(1); delay_ms(10); LCD_WriteByte(0xA2, 0); // 1/9偏压 LCD_WriteByte(0xA0, 0); // 列地址从左到右 LCD_WriteByte(0xC8, 0); // 行扫描方向从上到下 LCD_WriteByte(0xA6, 0); // 正常显示 LCD_WriteByte(0x2F, 0); // 开启电源 LCD_WriteByte(0x27, 0); // 内部电阻调整 LCD_WriteByte(0x81, 0); // 对比度设置第一字节 LCD_WriteByte(0x1A, 0); // 对比度值可调整 LCD_WriteByte(0xAF, 0); // 显示开 LCD_Clear(); }这里特别提醒0x81对比度设置是分两字节的第一字节是命令第二字节才是值。如果只写了0x81没写值对比度会处在一个随机状态屏幕要么全黑要么白屏。另外0x27这个内部电阻调整值在不同的屏上表现不一样屏幕底色偏深就把值往小调偏浅就往大调。19264的显示RAM是按页划分的。纵向上64行被分成8页每页8行横向上192列就是192个字节列。也就是说一个字节在屏幕上对应的是“某一列从上往下8个点”而不是横向的8个点。这一点和很多人的直觉不一样画点、画字符前一定要先搞清楚。// 设置列地址和页地址 void LCD_SetPos(uint8_t col, uint8_t row) { LCD_WriteByte(0xB0 | (row 3), 0); // 页地址row是0~63 LCD_WriteByte(0x10 | ((col 4) 0x0F), 0); // 列高四位 LCD_WriteByte(0x00 | (col 0x0F), 0); // 列低四位 }清屏就是把DDRAM全部写0。按照页0到7、列0到191的顺序循环写数据即可注意写完一页要重新设置下一页地址计数器不会自动跨页。3.3 画点、画线、画矩形、画圆的实现画点是所有图形功能的基础。19264的显存是按列页组织所以要画一个点必须先把目标字节读出来把对应位改成1或0再写回去。如果不读回直接整体赋值就会把同一页同一列其他行已有的点全部抹掉。void LCD_DrawPoint(uint8_t x, uint8_t y, uint8_t color) { uint8_t page y 3; uint8_t bitPos y 0x07; uint8_t readData; if (x 192 || y 64) return; LCD_SetPos(x, y); LCD_ReadByte(readData); // 读回原字节 if (color) { readData | (uint8_t)(0x01 bitPos); } else { readData (uint8_t)~(0x01 bitPos); } LCD_SetPos(x, y); LCD_WriteByte(readData, 1); }有了画点函数画线用Bresenham算法画矩形、填充矩形、画圆就都水到渠成了。工程里我把这些函数放在了graphic.c里和底层驱动lcd.c分开上层的菜单、状态机只调用graphic接口。这种分层写法后期想换SPI屏或者FSMC屏只需要改lcd.c上层代码一行不用动。4. 汉字与字符显示字模与取模实践4.1 字符与汉字显示的原理19264本身不认识汉字它只认显存里的点阵数据。所谓显示汉字本质就是把一个汉字按16×16点阵拆成32个字节逐字节写入DDRAM。字符更简单比如8×16的ASCII码一个字符16个字节占用一页里的两列。这里要理解“页”和“字高”的关系。16×16的汉字高度是16行跨越两页上半8行写到第n页下半8行写到第n1页。所以显示汉字时不能像显示字符一样一路顺写而是要分别定位到两个页地址再写入各自的两列数据。4.2 取模软件与参数设置取模软件我用的是PCtoLCD2002网上到处有免费好用。重点在参数设置设置不对显示屏上就会出现“躺倒”的汉字或者镜像文字。我的配置是取模方式选“横向取模”也就是每行从左到右取8个点组成一个字节字节内的位序选“高位在前”这样字节最高位对应最左边的点。汉字是16×16所以上半页是16个字节下半页也是16个字节总共32个字节。生成出来的数据大概是这样的格式// “嵌”字 16×16 点阵横向取模高位在前 const uint8_t hanzi_qian[32] { 0x08, 0x08, 0x08, 0x0B, 0x7C, 0x8A, 0x08, 0x88, 0x08, 0x88, 0x08, 0x0A, 0x28, 0x08, 0x08, 0x00, 0x00, 0x00, 0x00, 0xFE, 0x02, 0x22, 0x22, 0x20, 0x20, 0x20, 0xE0, 0x20, 0x20, 0x20, 0x20, 0x00 };特别强调一点不同控制器支持的显示方向不同取模方向必须和屏幕驱动方向一致。如果你设置的列地址是从左往右、扫描从上往下那就用上面的配置如果你的屏因为走线原因是从右往左扫取模方向就要反过来。否则字会左右颠倒很多人一上来就怀疑是硬件问题其实是取模参数不对。4.3 把汉字写进显存的思路显示一个16×16汉字的流程我封装成一个函数void LCD_ShowHz(uint8_t x, uint8_t y, const uint8_t *hzData) { uint8_t i, pageBase y 3; // 上半页 LCD_SetPos(x, pageBase); for (i 0; i 16; i) { LCD_WriteByte(hzData[i], 1); } // 下半页 LCD_SetPos(x, pageBase 1); for (i 16; i 32; i) { LCD_WriteByte(hzData[i], 1); } }下一行画字符时x坐标加16。如果是混排英文按8×16点阵处理x就加8。为了让字库不占太多RAM工程里我把字模数组放在const段编译后直接进Flash。如果项目里要显示的汉字特别多比如几百个建议提前用工具把常用汉字提取成自己的字库文件放到外部Flash或者SD卡里按索引查询这个项目规模不大直接数组就够了。5. 常见问题与排查技巧实录这部分是干货中的干货。我把做这个项目时遇到以及帮别人排查过的高频问题整理成一张速查表每一项都是真实踩过的坑。故障现象可能原因排查与解决办法屏幕全白或者全黑对比度V0没调对调电位器如果没有电位器就改初始化里的0x81后面的参数屏幕无任何反应复位引脚悬空或初始化没复位确认RST有上电拉低再拉高的动作至少10ms延时显示的字符颠倒取模方向与扫描方向不一致检查ACD命令设定的列地址方向跟取模方式对齐显示乱码、点错位页地址和列地址计算错误先用画点函数测试四个角再显示简单字符字体重叠写入前未关闭显示或没读回字节直接整字节覆盖写字符前做整列清除或者使用读改写模式程序下载不了占用了PA13/14/15或PB3/PB4调试口换引脚或者用ST-Link Utility清除读保护刷新很慢GPIO模拟并口频繁调用库函数关键函数直接用寄存器操作减少函数调用背光不亮限流电阻太大或三极管接错测背光两端电压正常应为2.8~3.3V有两个排查方法非常管用。第一个是“点灯法”上电后先调用一次清屏和画点函数在屏幕四角各画一个点如果四个点位置正确说明坐标体系没问题之后再显示字符、图形才可信。第二个是逻辑分析仪抓时序如果屏完全没反应用逻辑分析仪看CS、RS、WR和数据线的电平变化是否符合8080写时序。很多“屏坏了”的结论最后都是时序参数差了一口气。还有一个经验想专门说一下调试带汉字显示的工程不要最开始就把所有字库塞进去。先用单个汉字数组测试确认取模方向正确了再填充整个字库。因为字库错了的话看起来全是乱码很难定位是取模问题还是坐标问题分割变量才能快速定位。写在最后的建议如果这个驱动工程要复用我强烈建议把底层封装做干净。当时我就是图省事直接把画点函数写在lcd.c里后来要换SPI串口屏底层重写了一遍。后面我重新整理时把底层接口统一成了“LCD_SetPos、LCD_ReadByte、LCD_WriteByte”三个函数上层图形库只依赖这些接口。这样无论片内控制器换成ST7565还是ST7920底层替换都不痛苦。另外如果你做的产品要长期跑记得在初始化完成后做一次全屏填充0xAA的测试也就是画棋盘格肉眼检查有没有列短路或虚焊。很多焊接问题不是上电就能发现的棋盘格一眼就能扫出来。这个工程扩展起来也很有意思现在这个驱动基础上我做了一个简单的菜单框架一个结构体数组存菜单项一个光标位置变量控制高亮按键中断改状态屏幕只显示当前页。后面甚至加了一页实时数据曲线就是画点函数加一个环形缓冲区而已。所以说19264虽然老但把它玩透你收获的是一整套显存管理、坐标计算、字模处理的底层能力这些东西到了任何一块LCD屏上都能复用。本文还有配套的精品资源点击获取