1. 项目概述
在嵌入式系统开发中,显示外设作为人机交互的重要接口,其驱动开发是每个嵌入式工程师必须掌握的核心技能。本章将深入探讨STM32平台下I2C协议OLED屏和SPI协议LCD屏的驱动开发,从协议原理到实际应用,全面解析字符、图片和中文显示的完整实现方案。
2. 核心知识点解析
2.1 I2C与SPI协议对比
I2C和SPI是嵌入式系统中最常用的两种串行通信协议,它们在显示驱动中各有优势:
I2C协议特点:
- 两线制(SCL时钟线+SDA数据线)
- 半双工通信
- 多主多从架构
- 标准模式100Kbps,快速模式400Kbps
- 通过地址寻址从设备
SPI协议特点:
- 四线制(SCK时钟线+MOSI主出从入+MISO主入从出+CS片选)
- 全双工通信
- 单主多从架构
- 通信速率可达数十MHz
- 通过硬件片选选择从设备
在显示驱动应用中,I2C更适合小尺寸、低分辨率的OLED屏,而SPI则更适合大尺寸、高分辨率的LCD屏。
2.2 SSD1306 OLED驱动原理
SSD1306是常见的OLED驱动芯片,支持128×64分辨率显示。其驱动核心包括:
-
显示内存结构:
- 分为8页(Page0-Page7),每页8行
- 每页包含128列
- 每个字节数据对应一列中的8个垂直像素
-
通信协议:
- 命令模式:控制字节0x00
- 数据模式:控制字节0x40
- 支持连续写入和单字节写入
-
初始化流程:
c复制void OLED_Init(void) { HAL_Delay(100); OLED_Write_Cmd(0xAE); // 关闭显示 OLED_Write_Cmd(0xD5); // 设置时钟分频 OLED_Write_Cmd(0x80); // 建议值 // ...更多初始化命令 OLED_Write_Cmd(0xAF); // 开启显示 }
2.3 ILI9341 LCD驱动原理
ILI9341是常见的TFT LCD驱动芯片,支持240×320分辨率、16位色显示。其驱动特点包括:
-
控制信号:
- RESET:硬件复位
- DC:数据/命令选择
- CS:片选信号
-
显存操作:
- 采用GRAM(图形内存)结构
- 通过设置窗口地址实现局部刷新
- 支持16位RGB565格式
-
典型初始化序列:
c复制void LCD_Init(void) { LCD_RES_LOW(); HAL_Delay(100); LCD_RES_HIGH(); HAL_Delay(100); LCD_Write_Cmd(0xCF); LCD_Write_Data16(0x00); // ...更多初始化命令 LCD_Write_Cmd(0x29); // 开启显示 }
3. 硬件设计与配置
3.1 硬件连接方案
OLED屏连接:
| OLED引脚 | STM32引脚 | 备注 |
|---|---|---|
| VCC | 3.3V | 严禁接5V |
| GND | GND | 共地 |
| SCL | PB6 | I2C1_SCL |
| SDA | PB7 | I2C1_SDA |
LCD屏连接:
| LCD引脚 | STM32引脚 | 备注 |
|---|---|---|
| VCC | 5V | 电源 |
| GND | GND | 共地 |
| SCK | PA5 | SPI1_SCK |
| MOSI | PA7 | SPI1_MOSI |
| CS | PA4 | 软件片选 |
| DC | PA2 | 数据/命令选择 |
| RES | PA3 | 复位信号 |
| BL | 3.3V | 背光控制 |
3.2 STM32CubeMX配置
-
I2C配置:
- 模式:I2C
- 速度:Fast Mode (400kHz)
- 引脚:PB6(I2C1_SCL), PB7(I2C1_SDA)
-
SPI配置:
- 模式:Full-Duplex Master
- 分频:2分频(36MHz @72MHz系统时钟)
- 数据大小:8位
- 时钟极性:Low
- 时钟相位:1 Edge
- 引脚:PA5(SCK), PA7(MOSI)
-
GPIO配置:
- PA2:LCD_DC,输出
- PA3:LCD_RES,输出
- PA4:LCD_CS,输出
-
DMA配置:
- SPI1_TX通道:DMA1 Channel3
- 方向:Memory to Peripheral
- 优先级:High
- 数据宽度:Byte
4. 软件驱动实现
4.1 OLED驱动实现
核心函数实现:
c复制// OLED写命令
void OLED_Write_Cmd(uint8_t cmd) {
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x00, I2C_MEMADD_SIZE_8BIT, &cmd, 1, 100);
}
// OLED写数据
void OLED_Write_Data(uint8_t *data, uint16_t len) {
HAL_I2C_Mem_Write(&hi2c1, 0x78, 0x40, I2C_MEMADD_SIZE_8BIT, data, len, 100);
}
// 设置显示位置
void OLED_Set_Pos(uint8_t x, uint8_t y) {
OLED_Write_Cmd(0xB0 + y);
OLED_Write_Cmd(((x & 0xF0) >> 4) | 0x10);
OLED_Write_Cmd((x & 0x0F) | 0x00);
}
// 显示ASCII字符
void OLED_Show_Char(uint8_t x, uint8_t y, uint8_t ch) {
ch = ch - ' ';
OLED_Set_Pos(x, y);
OLED_Write_Data(&asc2_1608[ch*16], 8);
OLED_Set_Pos(x, y+1);
OLED_Write_Data(&asc2_1608[ch*16+8], 8);
}
4.2 LCD驱动实现
核心函数实现:
c复制// LCD写命令
void LCD_Write_Cmd(uint8_t cmd) {
LCD_CS_LOW();
LCD_DC_LOW();
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
LCD_CS_HIGH();
}
// LCD写数据
void LCD_Write_Data(uint8_t *data, uint16_t len) {
LCD_CS_LOW();
LCD_DC_HIGH();
HAL_SPI_Transmit(&hspi1, data, len, 1000);
LCD_CS_HIGH();
}
// 设置显示窗口
void LCD_Set_Window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
LCD_Write_Cmd(0x2A); // 列地址设置
LCD_Write_Data16(x0);
LCD_Write_Data16(x1);
LCD_Write_Cmd(0x2B); // 行地址设置
LCD_Write_Data16(y0);
LCD_Write_Data16(y1);
LCD_Write_Cmd(0x2C); // 写入GRAM
}
// 显示图片(DMA方式)
void LCD_Show_Pic(uint16_t *pic_buf) {
LCD_Set_Window(0, 0, 239, 319);
LCD_CS_LOW();
LCD_DC_HIGH();
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)pic_buf, 240*320*2);
}
5. 显示内容实现
5.1 字符显示
字符显示的核心是将ASCII码转换为点阵数据。通常使用8×16或6×12点阵字模,每个字符对应16或12字节的数据。
取模注意事项:
- 取模方向必须与驱动代码匹配
- 字节顺序(高位在前/低位在前)
- 扫描方式(行扫描/列扫描)
5.2 中文显示
中文显示基于GB2312编码标准,常用16×16点阵,每个汉字需要32字节的点阵数据。
实现要点:
- 建立完整的中文字库
- 实现汉字编码到点阵数据的映射
- 优化存储空间(使用const存储在Flash中)
5.3 图片显示
图片显示需要将图片转换为适合屏幕的格式:
-
OLED图片:
- 单色位图
- 128×64分辨率
- 转换为uint8_t数组
-
LCD图片:
- RGB565格式
- 240×320分辨率
- 转换为uint16_t数组
图片取模工具推荐:
- PCtoLCD2002(单色)
- Image2Lcd(彩色)
6. 常见问题与解决方案
6.1 OLED屏不显示
可能原因及解决方案:
- 供电错误:确保使用3.3V供电,严禁接5V
- I2C地址不匹配:尝试0x3C或0x3D地址
- 初始化序列错误:严格遵循数据手册的初始化流程
- 硬件连接问题:检查SCL/SDA是否接反
6.2 LCD显示花屏
排查步骤:
- 检查SPI时钟速率是否过高
- 验证RGB565数据格式是否正确
- 确认显存窗口设置是否合理
- 检查DMA传输长度是否匹配
6.3 中文显示乱码
解决方案:
- 确认字库编码与显示代码一致
- 检查字模取模参数
- 验证汉字索引计算是否正确
- 确保字库存储在Flash中(const修饰)
7. 性能优化技巧
- 局部刷新:只更新屏幕变化的部分,减少数据传输量
- DMA传输:利用DMA实现零CPU占用的数据传输
- 双缓冲:在内存中准备下一帧数据,减少屏幕闪烁
- 数据压缩:对重复数据进行压缩处理
8. 实际应用案例
8.1 实时数据监测显示
c复制void Show_Sensor_Data(float temp, float humi) {
char buf[32];
// OLED显示
sprintf(buf, "Temp:%.1fC", temp);
OLED_Show_String(0, 2, buf);
sprintf(buf, "Humi:%.1f%%", humi);
OLED_Show_String(0, 4, buf);
// LCD显示
LCD_Set_Window(50, 100, 200, 120);
sprintf(buf, "Temperature: %.1fC", temp);
LCD_Show_String(50, 100, buf, 0xFFFF, 0x0000);
sprintf(buf, "Humidity: %.1f%%", humi);
LCD_Show_String(50, 120, buf, 0xFFFF, 0x0000);
}
8.2 多级菜单系统
c复制typedef struct {
char *title;
void (*func)(void);
} MenuItem;
MenuItem main_menu[] = {
{"System Info", Show_System_Info},
{"Sensor Data", Show_Sensor_Data},
{"Settings", Enter_Settings},
{"About", Show_About}
};
void Show_Menu(uint8_t selected) {
OLED_Clear();
for(int i=0; i<4; i++) {
if(i == selected) {
OLED_Show_String(0, i*2, ">");
}
OLED_Show_String(8, i*2, main_menu[i].title);
}
}
9. 进阶开发建议
- 使用RTOS管理显示任务:在FreeRTOS等实时操作系统中创建专门的显示任务
- 实现GUI框架:构建简单的GUI框架支持按钮、列表等控件
- 添加触摸支持:为LCD屏增加触摸控制功能
- 优化电源管理:合理控制背光亮度,降低系统功耗
10. 开发注意事项
- 静电防护:OLED屏对静电敏感,操作时需做好防护
- 屏幕保护:长时间显示静态内容可能导致烧屏,需定期刷新或设置屏保
- 内存管理:大尺寸图片会占用大量存储空间,需合理规划内存使用
- 跨平台兼容:考虑代码的可移植性,抽象硬件相关部分
通过本章的学习,开发者可以全面掌握STM32平台下I2C OLED和SPI LCD的驱动开发技术,实现各种复杂的显示需求,为嵌入式产品开发提供强大的人机交互支持。
