资讯动态

单色LCD驱动例程拆解:STM32接口适配与中文字库实现

发布时间:2026/9/11 1:23:41 来源:尧图企业网站定制
简介JLX19296G-918例程是一套针对192×96分辨率液晶屏的STM32驱动示例面向嵌入式开发者与LCD显示初学者解决在STM32平台上快速驱动并显示中文文本的实际问题。压缩包共9个文件由8个C源文件与1个中文编码头文件构成整体大小32KB代码结构紧凑适合直接移植或二次开发。例程按接口类型分为I2C、SPI和8080并行三种版本同时包含带中文字库与不带字库两种模式开发者可根据硬件连接和显示需求自由选择。其中中文编码头文件覆盖常用汉字点阵数据可解决无字库屏的中文显示难题。目前已有445人学习下载适合正在学习液晶屏底层驱动或需要快速集成显示模块的读者。通过研读例程能够掌握LCD初始化、寄存器配置、数据写入及屏幕刷新等关键操作并深入理解不同通信接口的时序特点与适用场景。此外代码注释清晰、函数划分明确既可作为基础教学案例也可作为项目开发中的参考模板对提升嵌入式图形界面开发效率有直接帮助。1. 一块 192×96 单色屏为什么值得抠它的驱动例程192×96 是个不算主流的尺寸比 128×64 宽了半列比 160×128 又矮了一截刚好卡在手持仪表、工控面板和电池供电设备最常用的那块信息密度区间。JLX19296G-918 例程包的价值不在于代码有多难而在于它把同一块 LCD 的三种接口、两种玻璃版本、带不带中文字库的情况全给齐了I2C、SPI、8080 并行各一份PL 和 PN 两种型号各一份还附带 CHINESE_CODE.H 字库头文件。对用 STM32 做产品原型的人来说这意味着不需要自己对着数据手册去推初始化序列也不需要为了换接口重写整个驱动——直接改引脚映射、选对文件就能在逻辑分析仪上看到预期的波形。这篇博文就把这份例程按「命名规则 → 底层时序 → 字库实现 → 移植排错」的顺序完整拆一遍。2. 解码例程包PL/PN 玻璃与 I2C、SPI、8080 三种接口怎么选2.1 文件名里藏着接口类型和玻璃版本解压后能看到七份主文件加一个字库头文件文件名后缀并不完全是很多人第一印象里的「P并行、S串行」。更接近实际的判断方法是看中间段-PL-和-PN-对应两种玻璃或显示模式-I2C结尾的是 I2C-S结尾的是 SPI带8080-前缀的是并行接口。以JLX19296G-918-PN-P.C和8080-JLX19296G-918-PL-P.C为例前者是 PN 玻璃加 SPI串行驱动后者是 PL 玻璃加 8080 并行驱动。单看最后的-P容易误判要结合中间段的接口标识一起读。PL 和 PN 的差异通常在玻璃的偏光模式、视角方向或工作温度范围上反映到代码里主要是初始化序列中扫描方向、偏压比这几个寄存器不一样。实际使用中选型依据是安装方式和对视角的要求设备横放竖放、人在哪个方向看屏决定了该选哪一版驱动。2.2 三种接口的引脚占用与适用场景下表把三类接口在 STM32 侧的典型接法、IO 占用和各自擅长的场景列出来方便做板子之前先定方案接口信号线最小占用 IOSTM32 外设匹配典型场景I2CSCL、SDA、RST可选2硬件 I2C1/I2C2 或 GPIO 模拟引脚紧张的手持设备、电池供电设备SPICS、DC(RS)、SCLK、SDA(SID)、RST4SPI1/SPI2 硬件片选或 GPIO 模拟大多数工控表头、需要一定刷新率8080 并行CS、RS(DC)、WR、RD、DB0~DB7、RST12~13FSMC NOR/SRAM 模式或 GPIO 模拟需要频繁刷新、显示动态数据的场合I2C 接口只有两根线但速率通常被限制在 400kHz 以内刷一整屏 192×96 单色图2304 字节需要约 50ms 级别动态刷新会明显看到闪烁。SPI 用 4 根线做到 18~36MHz 的时钟刷屏时间降到个位数毫秒是性价比最高的选择。8080 并行吞吐量最大配合 STM32F103 的 FSMC 可以把 LCD 当外部 SRAM 操作适合刷新率要求高或需要频繁局部更新的界面。2.3 从例程抄 SPI 驱动写命令和写数据是两个必须分开的函数例程里 SPI 驱动的核心是两个底层函数LCD_WriteCmd()和LCD_WriteData()。区分它们的唯一手段是 DC 引脚电平命令模式拉低数据模式拉高CS 在整个传输过程中保持低电平。下面这段是移植到 STM32 后最常见的实现#define LCD_CS_L() GPIO_ResetBits(GPIOB, GPIO_Pin_12) #define LCD_CS_H() GPIO_SetBits(GPIOB, GPIO_Pin_12) #define LCD_DC_L() GPIO_ResetBits(GPIOB, GPIO_Pin_13) #define LCD_DC_H() GPIO_SetBits(GPIOB, GPIO_Pin_13) #define LCD_SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_15) #define LCD_SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_15) #define LCD_SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_14) #define LCD_SDA_H() GPIO_SetBits(GPIOB, GPIO_Pin_14) static void LCD_SPI_WriteByte(uint8_t data) { for (uint8_t i 0; i 8; i) { LCD_SCL_L(); // 时钟拉低准备移出数据 if (data 0x80) LCD_SDA_H(); // 高位先出MSB first else LCD_SDA_L(); LCD_SCL_H(); // 上升沿锁存 data 1; } } void LCD_WriteCmd(uint8_t cmd) { LCD_DC_L(); // 命令模式 LCD_CS_L(); LCD_SPI_WriteByte(cmd); LCD_CS_H(); LCD_DC_H(); // 恢复数据模式 } void LCD_WriteData(uint8_t dat) { LCD_DC_H(); // 数据模式 LCD_CS_L(); LCD_SPI_WriteByte(dat); LCD_CS_H(); }这段代码的关键在于 DC 引脚必须在片选拉低之前就切换到正确状态否则第一个字节会被屏端误判成命令或数据。SPI 模式通常配置为模式 0CPOL0、CPHA0或模式 3CPOL1、CPHA1JLX19296G-918 这类 COG 屏两种都接受但例程默认的是模式 0。LCD_SPI_WriteByte()里故意不用硬件 SPI是因为模拟方式在初次移植时更容易排查逻辑问题确认时序后可以换成SPI_I2S_SendData()提高速率。2.4 8080 并行接口和 FSMC 的配合方式8080 版本的例程把 8 根数据线直接对到 STM32 的 FSMC_D0~D7再用一根地址线 A16 接 RS 引脚这样操作地址0x60000000写的是命令操作0x60020000写的是数据。FSMC 在 NOR/SRAM 模式下写时序和 8080 的 WR 低脉冲天然匹配初始化时把数据总线宽度设为 8bit读写时序参数按 LCD 数据手册的建立时间、保持时间配置即可。如果板子没用 FSMC用 GPIO 模拟 8080 也能跑但每写一个字节要手动拉低 WR、拉高 WR整体速率会比 FSMC 慢一个数量级只适合验证逻辑不适合做产品。3. 底层驱动拆解从复位时序到画点函数的完整链条3.1 上电复位与内部状态机JLX19296G-918 这类带控制器的 COG 屏上电后必须先复位才能接受初始化命令。复位信号分硬件和软件两种硬件复位是把 RST 引脚拉低再拉高低电平时间建议不少于 10ms软件复位是发送初始化命令表中的0xE2SWRESET。例程通常在LCD_Init()开头把两者都做一遍避免上电瞬间电源不稳导致复位不可靠。void LCD_Reset(void) { GPIO_ResetBits(GPIOB, GPIO_Pin_11); // RST 拉低 DelayMs(50); GPIO_SetBits(GPIOB, GPIO_Pin_11); // RST 拉高 DelayMs(50); LCD_WriteCmd(0xE2); // 软件复位 DelayMs(100); }这里DelayMs(50)不是随手写的COG 屏内部电荷泵需要时间建立稳定电压复位后立刻发初始化命令会丢失。实际调试时如果发现初始化后屏幕亮度异常或出现随机点先把复位低电平时间拉到 100ms 试试大概率是电源上电爬坡太慢导致的。3.2 初始化序列逐条拆解不只是一张命令表例程里的初始化函数是一串连续的命令写入初看全是魔法数字其实每条都有对应寄存器。下面是典型的初始化序列做了注释不同玻璃版本之间只差几个关键值void LCD_Init(void) { LCD_Reset(); LCD_WriteCmd(0xAE); // 关闭显示进入设置模式 LCD_WriteCmd(0xA8); // 设置占用比占空比 LCD_WriteData(0x3F); // 64? 这里按192x96屏实际使用0x5F95行 LCD_WriteCmd(0xAD); // 选择内部电源工作模式 LCD_WriteData(0x02); // 开启内部 DC-DC 升压 LCD_WriteCmd(0x33); // 设置电压倍压 LCD_WriteData(0x07); // 倍压系数决定对比度上限 LCD_WriteCmd(0x40); // 设置显示起始行 LCD_WriteData(0x00); // 从第 0 行开始扫描 LCD_WriteCmd(0xA0); // 列输出方向 LCD_WriteData(0x16); // 左右扫描方向选择 LCD_WriteCmd(0xC8); // 行扫描方向上下反转控制 LCD_WriteCmd(0x81); // 对比度设置双字节命令 LCD_WriteData(0x38); // 对比度值低亮度环境可调到 0x50 LCD_WriteCmd(0xAF); // 打开显示 }注意0x3F和0x5F这类占空比设置192×96 屏的 COM 数是 96占用比要设成 96 行对应的值。例程里0xA8后面跟的参数如果写错屏幕会只显示半幅画面。0xAD命令用于开启内部 DC-DC如果不设置屏的驱动电压不足整体发灰甚至白屏。0x81的双字节对比度是调试时最频繁改的参数环境温度越低需要的对比度值越高建议在产品里把对比度做成可调变量而不是常量。3.3 显存映射与区域设置为什么是 12 页192×96 的分辨率在驱动 IC 内部是按「页」组织的每 8 行算一页96 行分成 12 页每页 192 列。向屏写数据必须先用0xB0~0xBB选页再用0x10、0x00组合设置列地址高四位和低四位之后连续写的数据会按列递增排列。void LCD_SetRegion(uint8_t row, uint8_t col) { LCD_WriteCmd(0xB0 (row 0x0F)); // 页地址0xB0~0xBB LCD_WriteCmd(((col 4) 0x0F) | 0x10); // 列地址高四位 LCD_WriteCmd(col 0x0F); // 列地址低四位 }row是页号0~11对应屏幕纵向每 8 行一块col是列号0~191。因为列地址是通过两段命令写入的所以每次切换写区域都要先设页再设列顺序不能反。例程里的清屏函数就是循环 12 页、每页写 192 个0x00写完后光标自动移到下一列这是屏端控制器自带的行为。3.4 画点函数与局部刷新策略在页模式下画点不能直接只写一个 bit因为一页包含 8 行像素写入的是整列字节。最简单的处理是读改写但例程通常不带读回命令所以我一般维护一块 2304 字节的显存缓冲所有绘图操作都在这块 RAM 上做需要刷新时再整块写进屏幕uint8_t display_buf[12][192]; void LCD_DrawPixel(uint8_t x, uint8_t y, uint8_t color) { if (x 192 || y 96) return; uint8_t page y / 8; // 算出所在页 uint8_t bit y % 8; // 算出页内 bit 位 if (color) { display_buf[page][x] | (1 bit); } else { display_buf[page][x] ~(1 bit); } } void LCD_Flush(void) { for (uint8_t page 0; page 12; page) { LCD_SetRegion(page, 0); for (uint8_t col 0; col 192; col) { LCD_WriteData(display_buf[page][col]); } } }2304 字节的缓冲对 STM32F103 的 20KB RAM 来说完全可接受。这个方案的优点是画点、画线、反白显示都先在内存里处理避免频繁对屏发起写操作缺点是每次刷新都是全屏写在 8080 并行接口下没压力I2C 下则会明显慢。I2C 场景建议改成脏标记只刷新display_buf中变化过的页每次比较memcmp或维护一个dirty_page[12]标志数组能省掉大量无效耗时。4. 中文字库拆开看CHINESE_CODE.H 查表与外部字库扩展4.1 汉字显示的本质是查字模不是画曲线192×96 屏上显示汉字最常见的是 16×16 点阵也就是一个汉字占 16 列、16 行正好 2 页。CHINESE_CODE.H 这个头文件实际就是一段按 GB2312 区位码顺序排列的const uint8_t数组每个汉字 32 字节前 16 字节是左半部分或上半部分取决于取模方向后 16 字节是右半部分。例程里带中文字库版本和不带字库版本的区别就在这不带字库的需要外部通过CHINESE_CODE.H提供字模数据带字库的则是屏端控制器内置了编码到字模的映射直接发送汉字机内码就能显示。4.2 按 GB2312 区位码计算字模偏移GB2312 编码的汉字范围从0xB0A1到0xF7FE区号从 16 区到 87 区。计算字模在数组中的偏移量需要两步先算出区位码再乘上每个汉字占用的字节数。#define FONT_WIDTH 16 #define FONT_HEIGHT 16 #define BYTES_PER_CHAR ((FONT_WIDTH / 8) * FONT_HEIGHT) // 每字32字节 const uint8_t chinese_code[]; // CHINESE_CODE.H 中定义的字模数组 uint16_t GetGB2312Offset(uint16_t gbcode) { uint8_t high (gbcode 8) 0xFF; // 区码 uint8_t low gbcode 0xFF; // 位码 if (high 0xB0 high 0xF7 low 0xA1 low 0xFE) { uint8_t qu high - 0xB0; // 区号从 16 区开始 uint8_t wei low - 0xA1; // 位号从 1 位开始 return (uint16_t)((qu * 94 wei) * BYTES_PER_CHAR); } return 0; // 非 GB2312 汉字返回空格或 ? }94 是每个区实际容纳的字符数区位码从A1A1开始排到FEFE但汉字只从B0A1开始。数组如果在数据手册里确认过是从0xB0A1开始排列就用上面的算法如果数组包括 ASCII 和符号偏移量要把前面的字符数也算进去。例程的CHINESE_CODE.H通常只包含汉字部分所以直接用(qu * 94 wei)就能定位。4.3 取模方向决定显示是否颠倒或镜像这是移植时最隐蔽的坑。同一个 16×16 汉字用逐行取模和逐列取模得到的两组数组显示结果完全不一样。逐行取模是每行 16 bit 拆成两个字节先左后右逐列取模是先垂直取 16 bit 再拆字节。如果例程生成字模用的工具和你的不一样会出现汉字整体镜像或者上下颠倒。确认方法很简单在屏幕上显示一个「永」字看它与正常显示方向的差异。镜像则调换左右字节位置颠倒则需要对 bit 逐位反转。代码层面可以用一个宏处理#define REVERSE_BYTE(b) (uint8_t)(((b) * 0x0202020202ULL 0x010884422010ULL) % 1023)如果显示方向仍有问题优先检查初始化序列里的0xA0和0xC8两条方向命令它们控制列扫描和行扫描方向这两个寄存器改对了大部分镜像/颠倒问题能直接消失而不是去改字模数组。4.4 CHINESE_CODE.H 放不下时的外部字库方案全量 GB2312 汉字 6763 个每个 32 字节CHINESE_CODE.H 编译后体积约 216KB。对 STM32F103C8T6 这种只有 64KB Flash 的芯片来说根本放不下所以例程里出现 CHINESE_CODE.H 通常是给大容量型号用的或者只取了常用的几百字。更通用的做法是把字库文件烧录到外部 SPI Flash如 W25Q64运行时按偏移读取uint8_t font_buf[32]; void ReadFontFromSPIFlash(uint16_t gbcode) { uint32_t offset (uint32_t)GetGB2312Offset(gbcode); SPI_Flash_Read(font_buf, FONT_BASE_ADDR offset, 32); } void LCD_ShowChinese(uint8_t x, uint8_t y, uint16_t gbcode, uint8_t color) { ReadFontFromSPIFlash(gbcode); for (uint8_t row 0; row 16; row) { for (uint8_t col 0; col 2; col) { uint8_t byte font_buf[row * 2 col]; for (uint8_t bit 0; bit 8; bit) { if (byte (0x80 bit)) { LCD_DrawPixel(x col * 8 bit, y row, color); } } } } }这个方案把 Flash 占用转移到外部存储代码逻辑不变只是把chinese_code[offset]换成SPI_Flash_Read()。注意 SPI Flash 的读取速度通常在 10MB/s 以上比 I2C 刷屏快得多瓶颈仍然在 LCD 接口上。5. 移植到自己的工程三个最容易翻车的硬件细节移植 JLX19296G-918 例程到新板子很多人第一步就错在没确认 PSB接口选择引脚的电平。这类屏通常有一个或一组硬引脚决定当前使用的是 I2C、SPI 还是 8080 并行例程只是软件部分硬件上必须把 PSB 接对否则屏毫无反应。接法每个型号略有差异以例程提供的原理图为准。第一个高频问题是复位时序。屏的 RST 引脚如果直接接在 STM32 的 GPIO 上初始化代码里会控制如果接的是 RC 复位电路要确认电阻电容值给出的复位脉冲宽度大于数据手册要求的最小值。我遇到过接 1kΩ 0.1μF 的板子复位脉冲只有 0.1ms屏偶尔起来偶尔白屏最后改成 GPIO 控制才稳定。第二个问题是对比度值不能照抄例程。同一块屏在 25℃ 和 0℃ 环境下相同0x81参数下的显示效果差异极大产品化时建议把对比度值放在 EEPROM 里开机先读配置再初始化并留一个按键组合进入对比度调节模式。第三个问题是 8080 并行接口的时序参数。FSMC 的地址建立时间、数据建立时间需要按屏的规格书设置太快会丢失字节表现为屏幕出现规律的缺行或花点这时优先把 FSMC 的ADDSET和DATAST加大。验证例程是否移植成功最直接的方法是初始化完成后写一个全屏0xAA图案能看到横条纹说明扫描方向正常再写0xFF验证是否全亮最后画一个 1 像素边框检查边框是否闭合画线有断裂就回头看LCD_SetRegion的页地址递进逻辑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价