STM32F103驱动LCD12864完整教程:并行/串行接口与避坑指南

📅 发布时间:2026/8/31 21:43:50
STM32F103驱动LCD12864完整教程:并行/串行接口与避坑指南
简介这是一份基于STM32F103VCT6的LCD12864液晶屏驱动实例工程面向嵌入式初学者与需要快速上手显示控制的开发者解决并行接口驱动LCD12864的初始化、时序控制与数据写入问题。资源包共138个文件约1.27MB包含32个H头文件、30个C源文件以及uvproj工程配置、hex固件、axf调试文件等覆盖标准外设库的RCC、GPIO、Flash、ADC等模块便于直接查阅与编译验证。目前已有857人学习下载。通过该工程可掌握STM32 GPIO推挽输出配置、LCD12864并行通信时序、显示指令如双行显示、光标移动、清屏的发送流程并了解文本与图形显示的内存映射及缓冲绘制思路实际项目中还可参考节能模式、动态刷新和抗干扰处理等策略是一份能同时提升底层驱动和工程调试能力的完整参考实现。 做嵌入式开发的朋友应该都有过这样的经历项目跑起来了传感器数据也有了怎么直观地展示出来成了头疼的问题。串口打印虽然方便但不能每次调试都抱着电脑看日志。所以一块小屏幕成了刚需而LCD12864ST7920控制器凭借内置中英文字库、接口简单、成本低这几点基本是STM32入门显示方案的默认选项。这篇博文基于我手头实际调通的实例工程完整梳理STM32F103VCT6驱动LCD12864的全部细节从硬件连接到驱动代码再到踩坑实录照着做就能跑起来。工程里我同时写了并行和串行两种接口的驱动能覆盖绝大多数使用场景。刚学完GPIO和定时器、想给项目加屏幕的初学者或者想快速评估方案、直接复用代码的工程师都可以直接参考。1. 项目整体思路与方案选型1.1 为什么是F103VCT6配LCD12864STM32F103VCT6这颗芯片很多人习惯叫它“F103大容量型号”Cortex-M3内核跑到72MHz256KB Flash48KB SRAMLQFP100封装引出了一堆GPIO算力跑一个简单的显示驱动绰绰有余资源上完全不紧张。选它来做LCD12864的驱动实例不是因为性能多强而是它足够“标准”市面上你能买到的开发板、核心板、最小系统板里F103VCT6的出现频率非常高而且引脚兼容性极好这个例子跑通之后换成F103ZET6或者F103RCT6代码只需要改引脚定义其他逻辑几乎不动。LCD12864这块屏说白了就是128x64分辨率的点阵液晶控制器以ST7920居多。它最大的吸引力在于内置了GB2312中文字库也就是说你想显示“温度”“湿度”“电压”这种汉字不需要自己取模生成点阵数据直接给编码就能显示开发效率高出一大截。分辨率不高但显示4行汉字、每行最多8个汉字对大部分仪表盘、菜单页面、传感器数值展示这类需求完全够用。它不像OLED那样需要反复刷新防烧屏也不像LCD1602那样只能显示ASCII字符价格还便宜几块钱到十几块钱一片非常合适入门。我选这个组合还有一个原因LCD12864的接口方式很典型既有并行又有串行可以同时把STM32的GPIO操作、时序控制、配置引脚复用这几块基本功都练到。后面你换其他型号的液晶或者换别的MCU这套驱动的思考方式是可以直接迁移的。1.2 并行接口与串行接口怎么选LCD12864的ST7920控制器支持三种接口模式8位并行、4位并行、串行由模块上的PSB引脚决定。PSB接高电平是并行接低电平或者悬空是串行。并行接口需要RS、RW、E三根控制线加8根数据线一共11个GPIO。它的优势是时序简单、写入速度快适合需要频繁刷新屏幕的场景比如做动态波形或者动画。缺点是占引脚多如果项目里还挂了传感器、按键、继电器GPIO可能不够用。串行接口只需要CS、SCLK、SID三根线省下的引脚去做别的事很香。速度上串行确实比并行慢但LCD12864本身刷新率不高正常显示汉字和数值的更新频率下串行完全不会感觉到卡顿。我用串行模式跑一个每秒刷新10次的温湿度页面肉眼看起来非常流畅。在这个工程里我两种模式都写了驱动代码默认用并行模式演示因为并行时序更直观便于理解ST7920的读写机制。你如果引脚紧张直接把宏定义的接口模式切换一下就能换成串行硬件接线上需要把CS、SID、SCLK三根线接到MCU的指定引脚。1.3 开发环境与调试器准备工程基于Keil MDK编写标准外设库和HAL库两种写法我都验过最终发布的是HAL库版本因为现在新项目基本都往HAL上靠CubeMX生成代码也方便。如果你还在用标准外设库核心逻辑完全一致把GPIO操作的API换成标准库的写法就行延时函数保留。调试器和下载器是很多人第一步就卡住的地方。板载ST-Link的板子插上USB后电脑不识别设备管理器里出现未知设备这种问题八成是驱动没装好ST-Link V2驱动装一下或者直接装STM32 ST-LINK Utility驱动会自动带上。用J-Link的话注意Windows 10/11下旧版本驱动容易有兼容性问题直接装新版的J-Link驱动包。如果板子上没有调试器只能通过串口下载那就要看板载USB转串口芯片是哪种CH340芯片装CH340驱动CP2102芯片装CP210x驱动FT232芯片装FTDI驱动这三种驱动不能混用认准芯片型号去装。我见过有人拿着CP2102的板子硬装CH340驱动折腾一晚上没解决最后发现芯片型号不匹配白白浪费时间。2. 硬件连接与电路设计要点2.1 引脚分配表LCD12864模块的引脚定义比较统一绝大多数模块都是20脚或者18脚排针。我用的这块是20脚标准接口引脚定义如表所示。LCD12864引脚接STM32F103VCT6功能说明VSSGND电源地VDD3.3V逻辑电源V010K电位器中点液晶对比度调节RSCSPD8并行数据/命令选择串行片选RWSIDPD9并行读/写选择串行数据线ESCLKPD10并行使能串行时钟DB0~DB7PD0~PD78位数据总线PSB3.3V并/串模式选择高电平为并行RST3.3V或MCU复位脚复位低电平有效LED_A3.3V串220R电阻背光正极LED_KGND背光负极我特意把8根数据线放在PD0~PD7控制线放在PD8~PD10这样写代码的时候可以利用GPIOD的ODR寄存器低8位一次输出完整数据效率高也很简洁。如果你用的板子PD口被其他外设占了换成PE口或者PB口都可以只要在代码里把端口和引脚宏改掉就行。2.2 对比度调节与背光电路对比度调节是LCD12864最容易忽略的环节。模块的V0引脚决定了液晶显示浓淡很多初学者上电后发现屏幕全黑或者完全看不清其实不是代码问题是V0悬空了。正确的做法是用一个10K电位器两端分别接VDD和GND中间抽头接V0通过旋钮调节对比度。有些模块板子上已经焊好了电位器这类屏买回来直接用就行如果买的模块没有就得自己外接一个否则驱动代码写得再对也看不到显示效果。背光LED_A不能直接接到3.3V电源上最好串一个100到220欧姆的限流电阻。我习惯用220欧姆亮度足够而且不会因为电流过大影响LED寿命。如果你把LED_A接到了5V上电阻用大一点330欧姆到470欧姆比较稳妥。LED_K接GND方向不要反了背光二极管反接是不会亮的。还需要注意VDD的电压范围。ST7920的工作电压规格是2.7V到5.5V所以STM32F103VCT6的3.3V供电是完全没问题的不用额外升压到5V。如果模块已经板载了5V转3.3V的稳压电路那就看一下模块背面有没有可跳线的电源选择没有就统一按3.3V接。2.3 电平匹配的坑这里要专门说说电平匹配的问题。STM32F103是3.3V逻辑LCD12864虽然是宽电压芯片但市面上很多模块在设计时是默认5V供电的模块上的逻辑电平转换电路不一定做了。如果你的模块VDD接的是5V而MCU这边是3.3V高电平ST7920的高电平输入阈值可能刚好处于临界区表现为时好时坏偶尔乱码或者写入数据丢失。这种问题很隐蔽示波器上看波形也没明显异常但就是不稳定。解决办法有三个层级最省事的办法是让模块也工作在3.3VST7920本身支持只是背光会稍微暗一点点不影响正常显示。第二是检查模块背面现在很多模块其实已经板载了电平转换电路只是丝印没写清楚看到有74HC245或者MOS管转换芯片就可以放心接5V。第三如果你的模块必须接5V又没有电平转换那就需要外接一块8路的电平转换模块或者用几个三极管/MOS管做双向转换不推荐用电阻分压因为LCD的RST、E等引脚对电平要求比较高分压容易出问题。3. 驱动代码核心实现3.1 初始化时序详解ST7920的初始化看着简单其实有固定的套路。上电后需要等待内部电源稳定一般延时40到50毫秒然后发送功能设定指令0x30告诉它使用8位并行接口、基本指令集。第一次上电时控制器可能还没完全稳定所以0x30要连续发两三次中间加几毫秒延时。之后再依次设置显示开关0x0C关闭光标和闪烁清屏0x01等10毫秒以上让清屏动作完成最后设置进入点模式0x06让DDRAM地址自动加1光标向右移动。初始化命令顺序不能乱。我刚开始写驱动的时候把0x0C放在0x30前面结果屏幕一直白屏后来对照数据手册才发现ST7920在基本指令集没有确定之前后面的显示开关指令是不会被正确解析的。void LCD12864_Init(void) { LCD12864_Delay(50); LCD12864_WriteCommand(0x30); // 8位并行基本指令集 LCD12864_Delay(5); LCD12864_WriteCommand(0x30); LCD12864_Delay(5); LCD12864_WriteCommand(0x0C); // 显示开光标关闪烁关 LCD12864_Delay(5); LCD12864_WriteCommand(0x01); // 清屏 LCD12864_Delay(20); LCD12864_WriteCommand(0x06); // 地址自动加1光标右移 LCD12864_Delay(5); }如果你的模块上有RST引脚初始化前建议先给一个低电平脉冲保持1毫秒拉高这样能确保LCD内部状态机复位到已知位置。直接把RST接3.3V也不是不能用但在上电瞬间可能出现一次花屏偶尔需要按一次复位键才能正常显示原因就是RST没有手动复位。3.2 并行接口读写代码并行模式下写入一个字节分三个步骤设置RS的电平确定当前是命令还是数据把要写的内容放到数据总线上给E引脚一个高脉冲ST7920在E的下降沿锁存总线上的数据。整个过程要注意时序上必须保证建立时间RS和RW的电平要先稳定E脉冲再拉高否则数据可能被错误锁存。我习惯把写入拆成两个函数LCD12864_WriteCommand负责发命令LCD12864_WriteData负责发数据内部共用一个字节写函数用isData标志区分。void LCD12864_WriteByte(uint8_t data, uint8_t isData) { // 控制线电平设置 HAL_GPIO_WritePin(LCD_RS_GPIO_Port, LCD_RS_Pin, isData ? GPIO_PIN_SET : GPIO_PIN_RESET); HAL_GPIO_WritePin(LCD_RW_GPIO_Port, LCD_RW_Pin, GPIO_PIN_RESET); // 数据输出只更新ODR低8位 LCD_DATA_GPIO_Port-ODR (LCD_DATA_GPIO_Port-ODR 0xFF00) | data; // E高脉冲下降沿锁存 __NOP(); __NOP(); HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_SET); __NOP(); __NOP(); HAL_GPIO_WritePin(LCD_E_GPIO_Port, LCD_E_Pin, GPIO_PIN_RESET); LCD12864_DelayUs(2); }有人问数据手册要求E脉冲宽度至少多长实际上ST7920的时序要求并不苛刻E高电平保持时间最小约450ns72MHz主频下两条NOP指令就已经有几ns到十几ns再加上HAL库API本身的调用开销完全能满足要求。但如果你用GPIO模拟函数、中间夹杂了太多其他操作适当加延时没有坏处。并行模式下我还实现了一个读状态的函数用来检测BF忙标志位。写命令前先读一下如果BF为1说明LCD还在处理内部操作暂时不能接收新数据。这个函数在显示批量数据时很重要不加的话偶发丢字加了就非常稳定。不过读操作需要把数据线改成输入模式代码复杂度会上升如果显示频率不是特别高等待延时的方式也够用——我在实际工程里两种方式都测过最终选择了延时方案因为更简单不占用额外的GPIO模式切换时间。3.3 串行接口实现串行模式是引脚紧张时的救星。ST7920的串行通信格式和SPI有点像但它不是标准SPI协议而是一种类SPI的三线方式需要自己用GPIO模拟。CS片选拉低SCLK提供时钟SID逐位发送数据数据在高电平期间被采样。串行每传输一个字节实际分成两个阶段先发送一个帧头字节0xF8表示后面跟的是命令0xFA表示后面跟的是数据然后数据本身要拆成高4位和低4位两次发送每次发送时先发4位有效数据再补4个0。这个格式我第一次看的时候有点懵后来对着逻辑分析仪才看明白ST7920串行模式下每个字节实际上要传输24个bit。void LCD12864_SendBit(uint8_t bit) { HAL_GPIO_WritePin(LCD_SCLK_GPIO_Port, LCD_SCLK_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(LCD_SID_GPIO_Port, LCD_SID_Pin, bit ? GPIO_PIN_SET : GPIO_PIN_RESET); LCD12864_DelayUs(1); HAL_GPIO_WritePin(LCD_SCLK_GPIO_Port, LCD_SCLK_Pin, GPIO_PIN_SET); LCD12864_DelayUs(1); } void LCD12864_WriteSerialByte(uint8_t data, uint8_t isData) { uint8_t i; uint8_t h data 0xF0; uint8_t l (data 4) 0xF0; if (isData) { LCD12864_SendBit(0xFA, 8); // 帧头11111 0 RW 1 RS } else { LCD12864_SendBit(0xF8, 8); // 帧头11111 0 RW 0 RS } for (i 0; i 8; i) { LCD12864_SendBit((h (7 - i)) 0x01); } for (i 0; i 8; i) { LCD12864_SendBit((l (7 - i)) 0x01); } }注意我这里的LCD12864_SendBit函数每次只发送一位实际操作时也可以把SendBit改成SendByte函数参数是8位数据内部循环8次发送逻辑更紧凑。串行模式不需要RW和E这两根线把它们固定接GND就行PSB必须接低电平否则模块仍然工作在并行模式串行数据根本进不去。3.4 字符显示与DDRAM坐标映射显示字符时ST7920的DDRAM地址映射是很多人写错的重点。屏幕分成4行每一行的起始地址并不是从上到下顺序排列的第一行是0x80第二行是0x90第三行跳回到0x88第四行是0x98。这个设计初看很奇怪实际是因为ST7920的DDRAM在物理上分为上下两个半屏地址行号和水平坐标组合而成的第三四行从0x88和0x98开始是为了配合内部字库的取模顺序。显示行起始DDRAM地址可显示内容第1行0x808个汉字或16个ASCII第2行0x908个汉字或16个ASCII第3行0x888个汉字或16个ASCII第4行0x988个汉字或16个ASCII写一个定位函数把行号和列号换算成DDRAM地址再调用写命令接口设置地址之后连续写数据即可。void LCD12864_SetCursor(uint8_t row, uint8_t col) { uint8_t addr; switch (row) { case 0: addr 0x80; break; case 1: addr 0x90; break; case 2: addr 0x88; break; case 3: addr 0x98; break; default: addr 0x80; break; } addr col; LCD12864_WriteCommand(addr); }有一个细节需要提醒LCD12864在汉字模式下DDRAM每个地址对应一个16x16点阵的汉字位置所以一个汉字占一个地址而ASCII字符是8x16点阵一个地址位置能放下两个ASCII字符但在写地址的时候仍然按地址加1的方式递增。实际工程中我遇到过一个很常见的坑用同一套DDRAM地址函数去显示字符串中英文混排时经常出现“英文往右串位、汉字对不齐”的情况这是因为没有单独处理ASCII的宽度。如果只显示纯中文地址逻辑很简单如果混排中英文需要自己定义一个“半角/全角”判断函数ASCII字符在写DDRAM前先写一个0x20空格占位这样光标移动才是正确的。void LCD12864_ShowString(uint8_t row, uint8_t col, const char *str) { LCD12864_SetCursor(row, col); while (*str) { if ((uint8_t)*str 0x80) { // 半角字符部分固件要求先补一个高位空格 LCD12864_WriteData(0x20); LCD12864_WriteData(*str); } else { // 全角汉字连续写入两个字节 LCD12864_WriteData(*str); LCD12864_WriteData(*str); } } }这个函数我实际测试下来配合我手头的LCD12864模块中英文混排显示基本正常。不同厂家模块使用的ST7920版本略有差异极少数模块直接写ASCII就能正常显示不需要补0x20如果你的模块显示出来有间隔或者串位把补空格那段去掉试试。3.5 绘图模式GDRAM扩展ST7920除了文本模式还内置了GDRAM也就是图形显示区域可以按像素点控制屏幕上的任意一个点。进入绘图模式需要先发送0x34切换到扩展指令集再发0x36使能绘图显示。GDRAM的地址规划和DDRAM一样反直觉它把屏幕分成上下两个区域每个区域水平方向8个字节、垂直方向32行。写入一个像素点一般需要先读回当前GDRAM地址的两个字节数据把对应位置1或清0再写回去。因为GDRAM不支持单独的位操作直接写整字节数据会覆盖掉原来已有的图案。绘图模式的一个典型用途是显示自定义的波形曲线或者简单的图标当然也可以自己定义汉字点阵绕过内置字库的限制。我在这块屏上跑过一个简易的心率波形每秒钟采样50个点用GDRAM把点画出来效果完全可以接受。不过要注意绘图模式显示期间文本模式显示的内容会受到影响两者共用一块显示区域使用前需要明确当前处于哪种模式切换模式时要重新发送功能设定指令。4. 常见问题与排查技巧4.1 屏幕显示异常排查清单LCD12864常见的问题很多不是代码逻辑错误而是硬件连接和初始化细节导致的。我整理了一个排查表按出现频率排列。现象可能原因解决办法屏幕全黑V0对比度电位器没调好旋转10K电位器让显示区域出现浅灰色底纹上电白屏按键复位才显示RST引脚悬空或上电时序不稳初始化前给RST一个低脉冲或把它接到MCU的复位输出显示乱码但能显示内容PSB引脚接错数据线接反确认PSB电平检查DB0~DB7的接线顺序第一行正常第三四行乱行地址写错确认第三、四行起始地址是0x88和0x98英文和中文混排错位没处理半角/全角宽度用ShowString函数中的宽度判断逻辑屏幕闪烁背光供电不稳或对比度电压抖动检查电源滤波V0电位器换成质量好一点的显示一段时间后丢字未等待忙标志写入过快加上读BF等待或增加写入延时我最常遇到的是V0没接电位器导致的“全黑假死”这个问题特别迷惑因为程序怎么查都查不出毛病最后发现是硬件问题。所以拿到新模块第一步先接好电位器调对比度再谈驱动代码。4.2 下载器与串口驱动相关问题调试LCD12864的过程里很大一部分时间其实花在让代码下载到芯片上。板载ST-Link的板子电脑不识别设备设备管理器里出现黄色感叹号基本都是驱动问题。解决方法是先装ST-Link V2驱动然后是STM32 ST-LINK Utility或者STM32CubeProgrammer一般来说装完这些开发软件驱动也就自动装好了。需要注意的是有些精简版系统会把驱动签名验证机制关闭ST-Link驱动在这种环境下会装不上开一下测试模式或者换一个官方驱动包。用J-Link调试的时候旧版驱动在Windows 10以上容易出现“无法连接”“无法识别”的报错直接到官网下载最新版J-Link驱动可以解决。串口下载方式的坑更多核心一点是板子上的USB转串口芯片型号决定了你要装哪个驱动CH340、CP2102、FT232三种芯片对应三种不同的驱动不能通用。我见过有人把CP2102的板子当成CH340来装折腾了一晚上最后用万能驱动软件扫描才装上但其实只要看一眼芯片丝印几分钟就能解决。没有调试器的话用串口ISP下载本文还有配套的精品资源点击获取