1. 项目背景与核心需求
在嵌入式开发领域,LCD液晶显示屏驱动是每个工程师都会遇到的基础课题。HS12864TG10B这款128x64点阵的液晶模块,搭配ST7567驱动芯片的组合,因其性价比高、接口简单,被广泛应用于工业控制、医疗设备和消费电子产品中。但很多新手在初次接触时会遇到显示乱码、刷新慢、功耗异常等问题。
我曾在一个智能家居中控项目中使用过这个组合,当时为了优化显示效果,前后调试了近两周时间。本文将分享经过实战检验的驱动方案,包含寄存器配置技巧、显存管理策略和实际工程中的性能优化手段。
2. 硬件接口设计与初始化
2.1 硬件连接方案
ST7567支持6800并行和SPI两种接口模式。在STM32项目中,我更推荐使用4线SPI模式(CS/SCL/SDA/RES),可以节省宝贵的IO资源。具体接线如下:
- CS: 接任意GPIO(如PA4)
- SCL: 接SPI时钟线(如PA5)
- SDA: 接SPI数据线(如PA7)
- RES: 接任意GPIO(如PB0)
- A0: 接任意GPIO(如PB1)用于命令/数据切换
关键提示:RES复位信号必须保持至少10μs的低电平,实测发现部分屏模组需要延长到100μs才能稳定初始化。
2.2 底层驱动实现
首先需要封装基本的SPI发送函数:
c复制void ST7567_SendByte(uint8_t byte) {
HAL_SPI_Transmit(&hspi1, &byte, 1, 100);
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
}
然后是命令/数据发送函数:
c复制void ST7567_WriteCmd(uint8_t cmd) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_RESET); // A0=0
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS=0
ST7567_SendByte(cmd);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS=1
}
void ST7567_WriteData(uint8_t data) {
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET); // A0=1
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // CS=0
ST7567_SendByte(data);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // CS=1
}
3. 显示核心算法实现
3.1 显存管理策略
ST7567的显存结构比较特殊:
- 共132列×65行(实际使用128×64)
- 每8行组成一个Page(共8个Page)
- 数据格式:垂直排列,LSB在上
推荐采用双缓冲机制:
c复制uint8_t disp_buffer[8][128]; // 显示缓冲
uint8_t draw_buffer[8][128]; // 绘图缓冲
void ST7567_Refresh() {
for(uint8_t page=0; page<8; page++) {
ST7567_SetPage(page);
ST7567_SetColumn(0);
for(uint8_t col=0; col<128; col++) {
ST7567_WriteData(disp_buffer[page][col]);
}
}
}
3.2 基础绘图函数实现
以画点函数为例,需要处理位操作:
c复制void DrawPixel(uint8_t x, uint8_t y, uint8_t color) {
if(x>=128 || y>=64) return;
uint8_t page = y / 8;
uint8_t bit = y % 8;
if(color) {
draw_buffer[page][x] |= (1<<bit);
} else {
draw_buffer[page][x] &= ~(1<<bit);
}
}
直线和圆算法建议使用Bresenham算法优化,避免浮点运算。实测在STM32F103上,绘制一个完整界面(含多个图形和文字)仅需2-3ms。
4. 高级功能实现技巧
4.1 灰度显示实现
虽然ST7567是单色屏,但可以通过PWM控制背光或使用帧调制实现16级灰度效果:
c复制void SetGrayLevel(uint8_t level) {
// 每帧显示时间 = level * 基础时间单位
TIM3->CCR1 = level * 5;
}
// 在主循环中
while(1) {
if(show_frame_count < gray_level) {
ST7567_Refresh();
}
show_frame_count = (show_frame_count+1)%16;
}
4.2 动画优化方案
对于需要流畅动画的场景,建议:
- 使用局部刷新(dirty rectangle)技术
- 将静态元素和动态元素分层处理
- 开启STM32的SPI DMA传输
实测DMA传输可使全屏刷新时间从12ms降低到3ms以下:
c复制void ST7567_DMA_Refresh() {
for(uint8_t page=0; page<8; page++) {
ST7567_SetPage(page);
ST7567_SetColumn(0);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_1, GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET);
HAL_SPI_Transmit_DMA(&hspi1, disp_buffer[page], 128);
while(HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY);
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
}
5. 常见问题与解决方案
5.1 显示模糊或对比度异常
这是新手最常见的问题,通常由以下原因导致:
- 初始化时未正确设置V0电压
- 电源纹波过大(建议在VDD对地加10μF电容)
- 复位时序不符合要求
正确的对比度设置流程:
c复制void ST7567_SetContrast(uint8_t val) {
ST7567_WriteCmd(0x81); // SET EV
ST7567_WriteCmd(val & 0x3F); // 建议初始值0x22
ST7567_WriteCmd(0xA6); // 正常显示模式
}
5.2 显示内容错位
通常是因为页地址或列地址设置错误。ST7567的地址命令比较特殊:
c复制void ST7567_SetPage(uint8_t page) {
ST7567_WriteCmd(0xB0 | (page & 0x0F));
}
void ST7567_SetColumn(uint8_t col) {
ST7567_WriteCmd(0x10 | ((col >> 4) & 0x0F)); // 高4位
ST7567_WriteCmd(0x00 | (col & 0x0F)); // 低4位
}
特别注意:部分兼容模块需要额外加4的列偏移,即col=实际列+4
6. 工程优化建议
-
功耗控制:在不需要刷新时,可以进入睡眠模式(命令0xAE),实测可降低85%功耗
-
抗干扰设计:
- SPI时钟建议不超过8MHz
- 长距离连接时,SCL线需要加100Ω电阻
- 避免与电机等干扰源共用电源
-
温度补偿:
c复制void ST7567_TempComp(int8_t temp) {
uint8_t comp = 0x20 + (temp/5); // 每5℃调整一次
ST7567_WriteCmd(0x81);
ST7567_WriteCmd(comp);
}
在实际项目中,我将这些驱动代码封装成了独立的图形库,支持图层混合、中文显示和触摸交互。经过三年多的产品验证,这个方案在-40℃到85℃环境下都能稳定工作。