拓冰建站拓冰建站
首页 / 资讯中心 / 正文

GD32F103串行驱动LCD12864:ST7920时序与实现详解

简介面向嵌入式初学者的LCD12864串行控制源码包提供基于SPI/I2C协议的字符与图片显示驱动可直接用于STM32、51等常见单片机项目解决128×64点阵液晶的文本输出和简单图形绘制问题。压缩包内共2个文件一个C源文件实现串行时序、光标设置、清屏、写字符及图片刷新等核心函数一个头文件则声明接口并给出引脚配置说明整个包仅3KB结构紧凑便于拷贝到工程中修改使用。已有183人学习下载。代码将底层时序与上层画图逻辑分离阅读时既能观察到位操作如何控制SCLK、SID等信号线也能看到ASCII字符点阵如何映射到显存实际应用中直接调用其中封装好的显示函数可省去反复调试波形和查手册的时间也更容易排查无显示、乱码等常见问题。适合电子竞赛备赛、课程设计及自学液晶驱动原理的开发者参考整体风格简洁便于直接移植或二次开发。1. 为什么串行LCD12864比并口更值得调拿到LCD12864多数人首先想到并口RS、RW、E再加8根数据线十个IO起步GD32F103引脚一下子就被占掉一大半。这套由 anybody392 维护的LCD12864.c/h反其道而行只用SCL、SDA、CS三根线就能驱动ST7920实测刷新一屏字符在几毫秒级别肉眼和并口没有差别。串行传输确实慢但12864只有128列64行瓶颈根本不在传输速度而在你每次改写DDRAM之后的间隔。对I/O紧张、又要保留文本和图片显示能力的项目串行驱动是更实际的选择这篇直接把协议、初始化、字符和图片显示函数一起拆开讲。2. LCD12864串行时序与ST7920初始化配置2.1 三线串行协议从控制字节到数据位ST7920的串行模式并不是普通SPI。每个有效字节发送前必须先发一个控制字节用来区分后续是命令还是数据。控制字节0xF8表示后续为命令0xFA表示后续为数据。这两个字节的高5位都是同步位串111110xF8的低3位是RW0、RS0、00xFA的低3位是RW0、RS1、0。正是这个RS位决定了命令和数据通道。实际传输时CS拉低在SCL上升沿逐位发送控制字节然后发送数据字节的高4位低4位补0再发送低4位左移4位。也就是说一个实际数据要占24个时钟周期。很多人直接用硬件SPI发送原始字节屏幕没有任何反应原因就出在这个强制格式上。我一般不用硬件SPI驱动ST7920而是用GPIO模拟因为标准SPI的片选时序很难卡在控制字节和数据字节之间GPIO模拟虽然代码长一些但可移植性好从GD32F103换到STM32或51系列只需改引脚宏。下面是LCD12864.c里最核心的底层时序片段#define LCD_CS_LOW() gpio_bit_reset(GPIOB, GPIO_PIN_8) #define LCD_CS_HIGH() gpio_bit_set(GPIOB, GPIO_PIN_8) #define LCD_SCL_LOW() gpio_bit_reset(GPIOB, GPIO_PIN_9) #define LCD_SCL_HIGH() gpio_bit_set(GPIOB, GPIO_PIN_9) #define LCD_SDA_LOW() gpio_bit_reset(GPIOB, GPIO_PIN_10) #define LCD_SDA_HIGH() gpio_bit_set(GPIOB, GPIO_PIN_10) static void lcd_delay_short(void) { for (volatile int i 0; i 2; i); } static void lcd_send_byte(uint8_t dat) { for (int i 0; i 8; i) { LCD_SCL_LOW(); lcd_delay_short(); if (dat 0x80) { LCD_SDA_HIGH(); } else { LCD_SDA_LOW(); } dat 1; lcd_delay_short(); LCD_SCL_HIGH(); lcd_delay_short(); } } void lcd_write_cmd(uint8_t cmd) { LCD_CS_LOW(); lcd_send_byte(0xF8); lcd_send_byte(cmd 0xF0); lcd_send_byte((cmd 4) 0xF0); LCD_CS_HIGH(); } void lcd_write_data(uint8_t dat) { LCD_CS_LOW(); lcd_send_byte(0xFA); lcd_send_byte(dat 0xF0); lcd_send_byte((dat 4) 0xF0); LCD_CS_HIGH(); }这段代码的逻辑是一次完整串行传输以CS拉低开始lcd_send_byte(0xF8)写入控制字节ST7920根据RS位决定后续字节是命令还是数据。命令和数据字节本身只用到高4位cmd 0xF0取出高4位(cmd 4) 0xF0把原低4位移到高4位。SCL低电平期间改变SDA高电平期间采样两次lcd_delay_short()保证SDA在SCL上升沿前稳定。如果你的MCU主频更高需要适当增加这个循环次数否则信号建立时间不足屏幕会收到随机数据。这里有个容易忽略的参数CS在每次传输完成后要重新拉高不能一直保持低。ST7920在CS高电平期间会完成内部命令锁存省略这一步会导致连续多条命令只执行最后一条。2.2 ST7920上电初始化序列ST7920上电后需要多次发送0x30完成8位接口自检。这个初始化过程比较机械但每一步延时都不能省尤其是首次上电时电源还没有稳定命令容易被吞掉。void lcd_init(void) { lcd_delay_ms(50); lcd_write_cmd(0x30); lcd_delay_ms(10); lcd_write_cmd(0x30); lcd_delay_ms(1); lcd_write_cmd(0x30); lcd_delay_ms(1); lcd_write_cmd(0x0C); lcd_write_cmd(0x01); lcd_delay_ms(3); lcd_write_cmd(0x06); }0x30是功能设置命令把控制器设为8位数据长度并进入基本指令集。即使屏幕最终跑在串行模式这条命令依然必须。之后的0x0C打开显示但不显示光标0x01清屏并把DDRAM地址归零0x06设置光标右移模式。如果产品需要光标可以把0x0C换成0x0E但一般设备上不会开光标。常用命令整理如下命令字功能备注0x308位模式、基本指令集初始化必发0x348位模式、扩展指令集、关图形写GDRAM前使用0x368位模式、扩展指令集、开图形显示图片后使用0x0C显示开无光标日常显示0x01清屏需要延时3ms0x06光标右移连续写入字符串0x02地址归零光标回左上角0x34和0x36的区别很容易搞混。0x34进入扩展指令集并关闭图形显示0x36进入扩展指令集并开启图形显示。写GDRAM时一般先发0x34写完图片再发0x36开启显示顺序反了就会出现残影或花屏。2.3 头文件如何划分接口LCD12864.h保留的接口很克制上层应用不关心底层时序只看到字符和图片函数#ifndef LCD12864_H #define LCD12864_H #include stdint.h void lcd_init(void); void lcd_write_cmd(uint8_t cmd); void lcd_write_data(uint8_t dat); void lcd_clear(void); void lcd_set_cursor(uint8_t row, uint8_t col); void lcd_putchar(char ch); void lcd_puts(const char *str); void lcd_show_image(const uint8_t *img); #endif这里的接口分了三层lcd_write_cmd和lcd_write_data是协议层直接面向ST7920lcd_clear、lcd_set_cursor、lcd_putchar、lcd_puts是字符层封装DDRAM操作lcd_show_image是图形层操作GDRAM。移植到其他平台时只需要重写底层函数和延时上层代码不需要改动这种分层方式在中小型嵌入式项目里维护成本最低。3. 字符显示函数DDRAM地址映射与ASCII字库3.1 行列到DDRAM的换算ST7920的DDRAM总容量是64字节每行16个字符最多4行。但地址并不是按0x80、0x90、0xA0、0xB0线性递增的第三行的起始地址回到了0x88第四行是0x98。很多驱动bug就出在这里用第一行基地址加上行号乘0x10结果第3行显示位置偏了8列。地址对应关系如下显示行DDRAM起始地址每行地址范围第1行0x800x80-0x8F第2行0x900x90-0x9F第3行0x880x88-0x97第4行0x980x98-0xA7因此行列定位不能按线性公式算直接查表最稳妥void lcd_set_cursor(uint8_t row, uint8_t col) { static const uint8_t base_addr[4] {0x80, 0x90, 0x88, 0x98}; uint8_t addr; if (row 3) row 3; if (col 15) col 15; addr base_addr[row] col; lcd_write_cmd(addr); }函数逻辑很直接按行号查表得到基地址再加上列偏移。ST7920的DDRAM地址设置命令本身要求最高位为1所以表里直接保存0x80开头的完整地址调用时不需要再叠加标志位。对row和col做钳位是为了防止错误参数把地址写到GDRAM或CGRAM区域。3.2 字符写入和字符串函数ST7920内置了完整的ASCII字库和常用中文字库写入数据时不需要外部点阵。ASCII字符是单字节编码直接发送数据口即可中文字符是两字节GB2312内码连续发送两个字节也能正确显示。初始化时设置了0x06所以每次写入数据后DDRAM地址自动加1连续输出字符串不用反复定位光标。void lcd_clear(void) { lcd_write_cmd(0x01); lcd_delay_ms(3); } void lcd_putchar(char ch) { lcd_write_data((uint8_t)ch); } void lcd_puts(const char *str) { while (*str ! \0) { lcd_putchar(*str); str; } }lcd_clear之后的3ms延时不能省略清屏需要把全部DDRAM写成0执行时间远长于普通命令。lcd_puts按字节发送字符串不管ASCII还是GB2312都适用。这里有一个容易被坑的地方工程源码文件如果是UTF-8编码中文字符串中的每个汉字会被编译器编码成3个字节而ST7920只认GB2312两字节内码显示出来就是乱码。带中文字符串的.c文件必须设置成GB2312或GBK编码。实际项目中经常需要拼装参数显示char buf[8]; snprintf(buf, sizeof(buf), T:%02d, temp); lcd_set_cursor(0, 0); lcd_puts(buf); lcd_set_cursor(1, 0); lcd_puts(Hum:); lcd_putchar(0 hum / 10); lcd_putchar(0 hum % 10);这段代码先拼接温度字符串再手动输出湿度两位数字。注意snprintf需要包含stdio.h在小型MCU工程中如果不喜欢引入stdio库可以直接用数字拆分的方式逐位输出能省不少flash空间。3.3 字符显示乱码和花屏的定位方法字符显示最典型的三种现场第一行正常第二行错乱、第2行第15列出现半个汉字、整屏都是方块。第一种大概率是DDRAM地址表写错把第三行基地址算成了0xA0而0xA0之后是CGRAM和GDRAM保留区间显示区域自然对不上。第二种是字符对齐问题GB2312汉字占两个字节如果光标停在奇数列控制器会把前后两个字节拼成一个汉字出现半个字写中文前要确认列地址为偶数。第三种整屏方块基本就是初始化没生效。验证方法很直接上电后只发三遍0x30再发0x01清屏如果屏幕上还有残留说明串行时序有问题。用逻辑分析仪抓SCL、SDA、CS三个信号重点看每一字节传输结束后CS是否回到高电平。实际调试中我发现CS拉高时机太早或太晚都会导致下一字节控制字被吞ST7920对CS保持时间比较敏感建议在最后一个数据位发送完后再延时一个周期再拉高CS。4. 图片显示函数GDRAM地址规则与写图流程4.1 图形模式与GDRAM地址构成ST7920的图形RAM和字符RAM是分开的。GDRAM垂直地址范围0到63水平地址范围0到15。每次设置地址后必须连续写入两个字节数据才组成一个16位像素组。这16位对应屏幕上一行中的16个像素。一幅128x64的图片需要循环64行每行执行16次地址设置和2次数据写入总共写入2048字节。注意ST7920的GDRAM实际上是左右两个64x64区域拼接的所以点阵数组通常按每行32字节组织而不是每行16字节。写GDRAM前必须进入扩展指令集并关闭图形显示否则写入数据的同时屏幕还在刷新画面会闪烁甚至残留上一帧。这个前置条件和字符模式差别很大我见过不少人在字符显示正常后直接调图片函数屏幕就是没有反应多半是忘了切换指令集。4.2 图片显示函数实现下面这段代码取自LCD12864.c的图片写入部分函数接收一个const uint8_t *指针指向按每行32字节组织的点阵数组。void lcd_show_image(const uint8_t *img) { uint8_t row, col; lcd_write_cmd(0x34); // 进入扩展指令集关闭图形显示 for (row 0; row 64; row) { for (col 0; col 16; col) { lcd_write_cmd(0x80 | (row 0x3F)); // 设置垂直地址 lcd_write_cmd(0x80 | (col 0x0F)); // 设置水平地址 lcd_write_data(img[row * 32 col * 2]); lcd_write_data(img[row * 32 col * 2 1]); } } lcd_write_cmd(0x36); // 开启图形显示 }核心逻辑是先发0x34让控制器进入扩展指令集并暂时不显示图形避免写入过程中的闪烁。然后行循环从0到63列循环从0到15。每次先设置垂直地址再设置水平地址最后写入两个数据字节。数组下标row * 32 col * 2表示每一行有16个水平地址每个地址对应两个字节所以一行占32字节。col * 2取当前水平地址的第一个字节col * 2 1取第二个字节。如果你的取模工具生成的是每行16字节的标准位图需要先把数据重排成左右半屏结构或者把下标改成img[row * 16 col]并把两次数据写入改成一次lcd_write_data加一次空白写入具体以实际点阵排列为准。取模参数建议这样设置图像分辨率128x64单色位图扫描方式选择与上面循环方向一致的模式。用PCtoLCD2002或Image2Lcd时勾选“逐行扫描”后生成数组。如果图片上下颠倒或左右翻转不用改C代码直接在取模工具里调整扫描方向即可。4.3 图片显示异常与修复现象原因处理图片上下颠倒取模扫描方向从下往上取模时选择正向扫描图片左右翻转字节位序相反勾选顺向或反向取模出现零星噪点写GDRAM时图形显示未关闭确保写图前发送0x34图片闪烁写入和刷新没有同步先在RAM中准备好整帧数据再写入退出图片后字符混乱没有返回基本指令集写图完成后发送0x30如果需要在图片上叠加字符必须先显示图片再切回基本指令集最后用lcd_set_cursor和lcd_puts输出文字。很多项目在图片正常后字符全乱就是因为lcd_show_image末尾只发了0x36没有发0x30回退到基本指令集。正确的顺序是写完GDRAM后发送0x30再继续字符操作这样字符和图片才能共存。5. GD32F103驱动步骤与性能优化技巧5.1 驱动整合步骤把LCD12864.c/h加入GD32F103工程后接入顺序分四步修改引脚宏把SCL、SDA、CS对应到实际GPIO口初始化GPIO时钟和模式调用lcd_init()调用lcd_puts或lcd_show_image。GPIO初始化必须放在lcd_init之前。void lcd_gpio_config(void) { rcu_periph_clock_enable(RCU_GPIOB); gpio_mode_set(GPIOB, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10); gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10); LCD_CS_HIGH(); }引脚全部配置为推挽输出输出速度50MHz初始状态下CS保持高电平。GD32F103的rcu_periph_clock_enable对应STM32的RCC_APB2PeriphClockCmd函数名不同功能一致。5.2 提速技巧位带操作与整行刷新图片刷新时库函数gpio_bit_set和gpio_bit_reset的开销不能忽略。每帧图片要翻转过千次引脚状态函数调用和参数校验会拖慢速度。改成直接操作GPIO的BOP和BC寄存器能明显提升时序#define LCD_SDA_HIGH() (GPIOB-BOP GPIO_PIN_10) #define LCD_SDA_LOW() (GPIOB-BOP (GPIO_PIN_10 16)) #define LCD_SCL_HIGH() (GPIOB-BOP GPIO_PIN_9) #define LCD_SCL_LOW() (GPIOB-BOP (GPIO_PIN_9 16))BOP寄存器低16位写1输出高高16位写1输出低一次赋值就能完成不再经过中间层。字符刷新也有一个小技巧先lcd_set_cursor一次然后连续写满16个字符最后光标自动停在行尾不需要每写一个字符都定位一次。这样刷新一屏文字只需要两次地址命令其余全是数据写入。5.3 用逻辑分析仪验证时序调试时序问题时把SCL、SDA、CS接到逻辑分析仪采样率至少10MHz抓取lcd_write_cmd(0x01)的波形。正确波形应该看到三个完整字节第一个是0xF8第二个是0x00第三个是0x10最后CS拉高。如果看到0xFA出现在命令位置说明RS位被写错。进一步检查每个字节内SDA是否在SCL上升沿前稳定ST7920对数据建立时间要求并不苛刻但物理连线过长时会出现边沿抖动。可以在SDA和SCL之间并一个100pF电容能有效抑制高频噪声。验证通过后再把SCL频率调高直到画面出现花屏就能找到当前板子的最大稳定频率。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门