STM32驱动ST7565 12864液晶:从初始化到排错全流程解析

发布时间:2026/10/15 18:23:46
STM32驱动ST7565 12864液晶:从初始化到排错全流程解析 简介针对ST7565 12864点阵液晶在STM32平台上的驱动需求这份资源提供了基于硬件SPI接口的完整Keil工程方案。压缩包共111个文件主要包含C源文件与头文件、STM32标准外设库驱动、工程配置文件如uvprojx/uvoptx以及编译生成的hex/axf文件整体大小仅1.55MB可直接用MDK打开、查看或烧录验证。源文件涵盖初始化、命令写入、显示数据传输等核心模块并附有工程辅助脚本和编译输出文件便于对比学习与二次移植同时涉及SPI通信时序、无字库液晶逐像素控制思路适用于需要自绘字符或图形的嵌入式人机界面。该资源已有676人浏览学习适合刚接触ST7565驱动、或想快速在STM32上复用该液晶屏的开发者作为代码级参考资料。1. ST7565_12864液晶STM32驱动不是SSD1306的换皮是另一套时序逻辑接手过一个攒了很久的项目主控是STM32F103原来跑的是SSD1306的OLED屏后来因为户外强光下看不清换了一块ST7565_12864液晶。我以为驱动逻辑差不多都是单色点阵结果照搬过去之后——黑屏。查了半天不是供电问题不是接线问题是ST7565和SSD1306的显存映射、命令格式、电源开关时序完全不同甚至SPI模式都分两种。这块屏在工控仪表、手持设备里很常见带内部升压3.3V供电能驱动比ST7920那种带字库的屏快比SSD1306的OLED抗造但它属于“老实但不好惹”的类型命令不多可组合出来的坑不少。这篇文章不写手册按我实际调通一块ST7565_12864液晶STM32驱动的全过程来写从硬件接口到初始化命令再到显存寻址和滚动技巧最后把常见的黑屏、镜像、花屏问题一次说透。适不适合你的项目看完前两章基本就有数了。2. 硬件连接与接口选型SPI还是8080并口先看这几根引脚2.1 ST7565、ST7920、SSD1306的区别决定了你接线不能照搬ST7565是一个LCD驱动控制器它本身不带字库也不带GRAM以外的任何“智能”功能。市面上常见的12864液晶模块用的是三种控制器ST7920、SSD1306、ST7565。ST7920带中文字库串口模式下时钟很慢刷全屏能明显看到拖影适合只显示固定文本的场合。SSD1306是OLED驱动它有一个很出名的电荷泵命令0x8D但它是为OLED面板设计的电压和扫描时序跟LCD不一样。ST7565是纯LCD段驱动内部集成了升压电路和温度补偿适合用3.3V单电源推动偏压较高的LCD面板而且它的GRAM是按“页×列”组织的写一列就是竖着的8个点这一点和SSD1306写显存的方式形似而神不似。接口方面ST7565模块一般引出两种接口一种是SPI串行SCL、SDA、A0、CS、RST另一种是8080并口DB0DB7、A0、RD、WR、CS、RST。很多模块做了PS引脚来切换接口模式有的模块直接把并口和SPI引脚都引出来接错了就是白屏。选型时我的建议是如果STM32引脚够用但总线不紧张SPI更省IO如果要刷视频级别的动画8080并口吞吐更高但占用8个数据脚F103C8这种48脚的片子会捉襟见肘。多数项目用SPI就够本文以SPI四线模式为主线最后提一下并口的改动点。2.2 实际接线与电平匹配别让5V MCU直接怼3.3V模块我常用的接法如下表针对STM32F103系列PA5PA7做SPI1再补两个GPIO做A0和CSST7565模块引脚接STM32引脚说明VDD3.3V模块逻辑电源多数模块标3.3VGNDGND共地SCLPA5 (SPI1_SCK)时钟SDAPA7 (SPI1_MOSI)数据ST7565不用MISOA0PA4 (GPIO)低电平写命令高电平写数据CSPA3 (GPIO)片选低有效RSTPA2 (GPIO)复位低有效VOUT悬空或接电容内部升压输出模块一般已处理LEDA背光正极经限流电阻接3.3V或5V电平匹配是最容易被忽略的坑。如果STM32是5V供电GPIO输出高电平5V直接进3.3V模块的SDA/SCL长期用会损伤模块的输入缓冲。我在一个量产板上吃过亏屏幕用了半年开始随机花屏。后来统一改成MCU供3.3V或者加电平转换芯片。如果只是做实验可以在所有信号线上串330Ω电阻能压住大部分过冲但不建议量产这么干。RST的复位时序也有讲究上电后拉低至少10ms再拉高然后等10ms再初始化太短会偶发初始化失败。2.3 硬件SPI配置模式0和模式3都有可能先跑起来再说我用的是STM32的硬件SPI1配置关键参数如下SPI_HandleTypeDef hspi1; void MX_SPI1_Init(void) { hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; // 全双工但只发不收 hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; // CPOL0 hspi1.Init.CLKPhase SPI_PHASE_1EDGE; // CPHA0即SPI Mode 0 hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_4; // 72MHz/418MHz hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; HAL_SPI_Init(hspi1); }注意BaudRatePrescaler我最后选了4但实际跑的时候如果发现花屏改成8或16再试。ST7565模块的SPI时钟上限一般标8MHz或10MHz18MHz理论超了部分模块能跑但余量小。稳妥做法量产选分频8即9MHz调试阶段为了排除时序问题可以先选16也就是4.5MHz调通后再提速度。CLKPolarity和CLKPhase两个参数是另一个坑。我调过两种模块一种必须Mode 0CPOL0, CPHA0另一种必须Mode 3CPOL1, CPHA1都是ST7565控制器只是模块厂商在设计输入缓冲时选择了不同的采样沿。所以手头有屏但不知道SPI模式时先用Mode 0试不行就切Mode 3再不行检查时钟相位是否和数据建立时间冲突——这块我会在第5章展开。3. 初始化序列与寄存器配置点亮屏幕的13条命令和对比度调整3.1 关键寄存器先立起来偏压比、扫描方向、电源控制ST7565的初始化命令不多但每一条都有讲究。我先列一张表按写入顺序排列后面代码里会原样复现命令值作用为什么这么设偏压比0xA21/9偏压多数3.3V模块配1/9屏幕均匀性好ADC选择0xA0列方向正常0xA1会左右镜像SHL选择0xC8行扫描反向0xC0会上下镜像电源控制0x2F开升压、稳压、跟随不开升压就是黑屏电阻比0x24粗调对比度值越小对比度越高范围0x200x27对比度寄存器0x81后跟0x1F微调对比度0x00最淡0x3F最深显示起始行0x40从GRAM第0行显示改这个值可以做滚动显示开0xAF打开显示最后一步电源控制0x2F是最关键的一条。ST7565的电源系统分成三个部分升压电路VC、稳压电路VR、跟随电路VF0x2F是全部打开让内部产生LCD驱动需要的VOUT电压。有些代码只写0x28或者0x2C结果屏幕有显示但极淡或者完全没显示就是升压没起来。偏压比0xA2和0xA3的选择影响液晶的偏压电压选错了最典型的现象是屏幕对比度不均匀——上半部分深、下半部分浅。3.3V供电的12864模块绝大多数用1/9偏压也就是0xA2。如果你用的模块是5V供电的可能就需要0xA3也就是1/7偏压。实在不确定就切换试这个现象视觉上非常明显。ADC和SHL这两条决定显示方向。0xA0是正常列方向0xC8是反向行扫描这两个组合起来在大部分模块上正立显示。但模块PCB布局不同有的需要0xA1配合0xC0才是正的。我的习惯是先把画面点亮再根据实际显示的“上下/左右颠倒”来调整这两个值不要一开始就纠结“标准值”第5章会给出四组试错组合。3.2 完整初始化函数直接可抄但看注释再改下面是我在STM32上用的初始化代码基于HAL库A0和CS用两个GPIO控制SPI发送一个字节#define ST7565_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_RESET) #define ST7565_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_3, GPIO_PIN_SET) #define ST7565_A0_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define ST7565_A0_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET) #define ST7565_RST_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET) #define ST7565_RST_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET) static void ST7565_WriteCmd(uint8_t cmd) { ST7565_A0_LOW(); // A00命令 ST7565_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); ST7565_CS_HIGH(); } static void ST7565_WriteData(uint8_t data) { ST7565_A0_HIGH(); // A01数据 ST7565_CS_LOW(); HAL_SPI_Transmit(hspi1, data, 1, 100); ST7565_CS_HIGH(); } void ST7565_Init(void) { // 复位时序低电平至少10ms再拉高 ST7565_RST_LOW(); HAL_Delay(20); ST7565_RST_HIGH(); HAL_Delay(20); ST7565_WriteCmd(0xA2); // 1/9偏压 ST7565_WriteCmd(0xA0); // 列方向正常如左右镜像改0xA1 ST7565_WriteCmd(0xC8); // 行扫描反向如上下颠倒改0xC0 ST7565_WriteCmd(0x2F); // 打开所有电源电路升压起来 HAL_Delay(10); // 等升压稳定这里不能省 ST7565_WriteCmd(0x24); // 内部电阻比粗调对比度 ST7565_WriteCmd(0x81); // 对比度寄存器设置 ST7565_WriteCmd(0x1F); // EV31中间值后面再细调 ST7565_WriteCmd(0x40); // 显示起始行0 ST7565_WriteCmd(0xAF); // 显示开 ST7565_Clear(0x00); // 清屏 }逻辑说明复位后立刻写偏压比因为电源电路是按偏压比来生成参考电压的0x2F之后必须给一个延时内部升压电路需要时间建立电压我见过有人把延时去掉结果10次里有3次黑屏。对比度先写粗调0x24再写微调0x810x1F顺序颠倒会出现对比度不随EV值变化的假象。清屏函数单独说一句void ST7565_Clear(uint8_t fill) { for (uint8_t page 0; page 8; page) { ST7565_WriteCmd(0xB0 page); // 选择第page页 ST7565_WriteCmd(0x10); // 列地址高4位0 ST7565_WriteCmd(0x00); // 列地址低4位0 for (uint8_t col 0; col 128; col) { ST7565_WriteData(fill); } } }这段代码看起来像SSD1306的清屏但注意一个区别SSD1306在同一页里列地址会自动递增ST7565也递增但页地址不会自动切换必须手动写0xB0page。3.3 对比度调整EV、电阻比、偏压比三者的配合是玄学对比度是ST7565项目里最玄学的部分。很多开发者只调0x81后面的EV值发现调满还是淡就以为屏坏了。实际上ST7565的对比度由三个寄存器共同决定偏压比0xA2/0xA3、电阻比0x200x27、EV值0x81。偏压比负责总体电压档位电阻比负责LCD驱动电压的粗调EV做细调。常见组合现象偏压比电阻比EV值整体太淡保持0xA20x24调到0x220x1F调到0x2E整体太深甚至出现残影保持0xA20x24调到0x260x1F调到0x10上半深下半浅0xA2换0xA3试保持0x24保持0x1F我的实测经验EV加到0x30以上容易出现“糊”笔画粗、拖影电阻比调到0x22以下低温环境下会明显发黑。如果项目要在-20℃环境用对比度要往淡了调留出温度补偿的余量。4. 显存结构与画点画字列扫描顺序反直觉坐标变换是第一个坑4.1 GRAM映射一列对齐一字节页和列的配合方式ST7565的GRAM有132列64行12864模块实际用128列。它把显示区分成8页每页8行页地址由0xB00xB7选择。写入一个数据字节时这个字节的8个bit对应同一列里上下相邻的8行像素。也就是说你往列地址0写一个0x01屏幕左上角那一列的顶部第1行出现一个点写0x80则是同一列的第8行出现一个点。这与我们平时理解的行扫描完全不同反而和SSD1306类似。但坐标变换时有一个关键坑SSD1306的列地址从0开始连续写128字节后一页完成ST7565也是连续写但如果你要从屏幕左上角行0列0开始画一个点必须把坐标拆成“页行/8”和“列行%8对应的bit位”这个拆法我在第一次写画点函数时搞反了导致画出来的点竖直方向上每隔8行多一个。ST7565页排序的方向也受SHL影响。0xC8的行扫描反转会让GRAM的页0显示在屏幕底部而不是顶部所以你在framebuffer里越界写页0屏幕上显示的是底下那部分画面。这就是为什么很多人清屏后写字符字符上下颠倒或者割裂——坐标在逻辑上是反的。4.2 画点函数framebuffer是必须的ST7565不能读RAMST7565不支持读GRAMSPI模式下更是没有数据回传通道所以想改一个点必须先保存完整的帧数据。我做得最顺手的方式是开一个1KB的数组做显存镜像static uint8_t framebuffer[8][128]; void ST7565_SetPixel(uint8_t x, uint8_t y) { if (x 128 || y 64) return; // 坐标变换页行/8字节内bit位行%8 uint8_t page y / 8; uint8_t bit y % 8; framebuffer[page][x] | (0x01 bit); } void ST7565_ClearPixel(uint8_t x, uint8_t y) { if (x 128 || y 64) return; uint8_t page y / 8; uint8_t bit y % 8; framebuffer[page][x] ~(0x01 bit); } void ST7565_RefreshAll(void) { for (uint8_t page 0; page 8; page) { ST7565_WriteCmd(0xB0 page); ST7565_WriteCmd(0x10); ST7565_WriteCmd(0x00); for (uint8_t col 0; col 128; col) { ST7565_WriteData(framebuffer[page][col]); } } }逻辑说明framebuffer定义为[8][128]第一维是页第二维是列初始化时全清零。画点先算页再算列最后用位或的方式写进对应字节的对应bit。这里要注意x和y的范围判断我在调试时有一次坐标越界直接把数组写穿了画面出现随机噪点——不是驱动问题是内存越界。把整个framebuffer刷到屏幕按页循环每次先设置页地址再设置列地址为0然后连续写128字节。列地址在写满128字节后会自动停在127所以下一页必须重新设置页地址和列地址。如果你只写了部分列就换页剩下没写的列会保留上一次的内容这样画面上会出现“拉丝”或者“残影”。4.3 字模和图片纵向取模优先横向取模要去转换显示中文或者图片时取模方向决定你写显存的顺序。ST7565硬件是一字节对应8行1列所以取模必须使用纵向取模也就是“每字节8个bit沿垂直方向排列”。在取模软件里通常选择“纵向取模逐列扫描字节倒序”或者“字节不倒序”取决于你画点时bit位的方向。以显示一个8x16字符为例字符分上下两半上半部8行占页0下半部8行占页1。每一半都是8列数据每列一字节。我把代码写成这样void ST7565_ShowChar(uint8_t x, uint8_t y, uint8_t ch, const uint8_t* font8x16) { uint8_t i; const uint8_t* addr font8x16 ch * 16; // 上半部分写到y所在页 for (i 0; i 8; i) { framebuffer[y / 8][x i] addr[i]; } // 下半部分写到下一页需要检查页边界 if (y / 8 1 8) { for (i 0; i 8; i) { framebuffer[y / 8 1][x i] addr[i 8]; } } }参数说明font8x16是12×16的等宽ASCII字模表每个字符占16字节前8字节是上半行后8字节是下半行。x表示字符左上角的列坐标范围01198像素宽y是行坐标。下半部分写入页的时候要判断页不能超过7字符显示在屏幕最底部时下半部分会失去内容——这是很多字符库共有的边界问题我通常会在UI设计上保证字符不贴底。图片显示的道理一样取模选择“纵向8bit逐列扫描”生成的数组可以直接按下标顺序写入framebuffer。横向取模的图也不是不能用但必须在代码里做bit旋转我一般不这么干因为既浪费MCU时间又容易出bug。5. 实战排错黑屏、镜像、花屏翻车的5个排查点5.1 黑屏电荷泵没开和对比度为0最容易混淆现象初始化代码全部执行完屏幕什么都看不见用手电筒贴着屏幕能隐约看到有笔画说明液晶已经响应但驱动电压不够。原因两种可能。第一种是0x2F没写升压电路没工作LCD偏压不足第二种是写了0x2F但电阻比写成了0x27或者EV写成了0x00对比度降到了最低。解决先写0x2F等10ms然后把电阻比固定到0x24EV值设0x2E。如果能看到画面再往回调EV。还有一种特殊情况模块的VOUT引脚如果悬空且没有外接电容升压会不稳定表现为闪烁或暗。检查模块原理图确认VOUT有10uF电容。5.2 屏幕左右或上下镜像ADC与SHL的四组组合现象文字能显示但像是照镜子或者屏幕上下颠倒又或者旋转180度才对。原因ST7565的列扫描由0xA0/0xA1控制行扫描由0xC0/0xC8控制。模块厂的PCB走线不同默认方向会不一样。解决直接试四组组合看哪组方向全对我一般按下面的表五分钟定位组合列方向命令行方向命令效果组合10xA00xC0常规方向组合20xA00xC8上下翻转组合30xA10xC0左右翻转组合40xA10xC8180度翻转注意0xA0/0xA1和0xC0/0xC8修改后清屏也要重新执行一次否则残留数据会让判断产生错觉。5.3 A0接反或者时序不对显示乱码但屏幕是亮的现象屏幕点亮背光正常但显示的是无规律的点阵或者写一个字符出来六个错位的点。原因A0信号在命令和数据之间切换的时间点不对。HAL_SPI_Transmit是阻塞函数A0切换发生在CS拉低之前理论上没问题。但如果你用了软件模拟SPI在翻转SCL过程中切换A0数据就会被采样到错误的总线上。另一种情况是A0和CS接反了CS变成高低切换屏幕每次写入都像是重新片选。解决用逻辑分析仪抓SCL、SDA、A0、CS四根线写命令时A0应保持低电平至少一个SCL周期写数据时A0保持高。如果用的是模拟SPI在A0_set之后加2个空指令的延时再拉低CS给引脚电平建立留时间。5.4 SPI速率太快导致花屏余量要留不是越快越好现象低速SPI一切正常提高到18MHz之后随机花屏低概率出现偶尔一整行错位。原因模块上的长走线和连接排线引入的容性负载导致SCL上升沿变缓数据建立时间不够。ST7565手册标称最大SPI时钟约10MHz但这是理想负载下的值。我遇到过8MHz也偶发错位的模块换线材之后又正常。解决把BaudRatePrescaler从4改到8也就是9MHz再不行改164.5MHz。量产品我直接固定9MHz为了省电加长了SCL低电平时间。另外检查SDA和SCL两路有没有串电阻或电容有些模块板上自带RC滤波会明显限制可用速率。如果一定要跑满速做DMA发送同时把SCK极性从Mode 0换Mode 3试一次——有一些模块在Mode 3下反而更稳。5.5 初始化代码从SSD1306移植过来不能用命令体系完全不同现象之前调好的SSD1306驱动把初始化命令里0x8D那一段替换掉还是不行屏幕完全无反应。原因SSD1306和ST7565的命令集有几个关键区别。SSD1306用0x8D开电荷泵ST7565没有这个命令它是用0x2F直接控制电源组合SSD1306的显存页地址和列地址在写满后自动回卷ST7565必须手动设页设列SSD1306通常用I2C或者SPI带控制字节0x00命令/0x40数据,ST7565是A0引脚区分没有控制字节的概念。解决不要试图在旧代码上打补丁直接按ST7565写一套底层。命令长度和格式完全不同硬套必然出问题。我那次踩坑的血泪经验是把SSD1306的显存缓冲数组复用过来没问题但发送显存的函数必须重写ST7565每一页要先写0xB0page再写列地址顺序错了满屏乱点。6. 进阶用显示起始行寄存器做平滑滚动和局部刷新ST7565有个SSD1306也有的隐藏技能显示起始行寄存器命令0x400x7F。它控制的是从GRAM哪一行开始显示改这个值等于把整个画面上下平移而不需要搬动一字节显存。实现垂直滚动的代码量极少void ST7565_Scroll(uint8_t offset) { // offset范围063超过则回卷 ST7565_WriteCmd(0x40 | (offset 0x3F)); }调用时用一个定时器中断比如每10ms将offset加1就能看到整屏平滑向上滚动。这个方法的开销几乎为零不像刷新framebuffer那样要搬运1KB数据。注意滚动是整个屏幕一起滚没办法只滚某个窗口——如果只滚动部分内容还是得走framebuffer。局部刷新是我在显示波形和菜单时养成的习惯。ST7565的页地址是单独设置的所以能给“脏页”打标记只刷新有变化的页uint8_t dirty_pages[8]; void ST7565_RefreshDirty(void) { for (uint8_t page 0; page 8; page) { if (!dirty_pages[page]) continue; ST7565_WriteCmd(0xB0 page); ST7565_WriteCmd(0x10); ST7565_WriteCmd(0x00); for (uint8_t col 0; col 128; col) { ST7565_WriteData(framebuffer[page][col]); } dirty_pages[page] 0; } }画点或者画字符时同时标记涉及的页为dirty刷新时只重写这些页。对于实时性要求高的仪表界面比如CPU占用率曲线只刷新下半部分的3页比全屏刷新省掉一多半的SPI传输时间而且能减少LCD的拖影感。我现在做ST7565的项目习惯把初始化里四个常调的值——SHL方向、ADC方向、电阻比、EV值——做成四个宏定义放在文件头部换屏时只改头文件不碰驱动函数。这和一开始逐条硬调相比省了大量时间也希望帮到你让你在一块ST7565_12864液晶STM32驱动上少走两轮“黑屏—查手册—换命令”的弯路。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询