STM32F103C6T6与1.3寸ST7789V屏幕实战:从硬件对接到字符显示全解析
第一次拿到这块小巧的1.3寸屏幕时,看着密密麻麻的引脚和陌生的专业术语,我也曾一头雾水。但经过几个项目的实战积累,我发现只要掌握几个关键要点,让STM32驱动ST7789V屏幕其实并不复杂。本文将用最直白的语言,带你完整走通从硬件连接到显示字符的全流程。
1. 硬件准备与连接
1.1 认识我们的硬件伙伴
这块1.3寸TFT屏幕的核心是ST7789V驱动芯片,虽然标称支持240×320分辨率,但实际物理分辨率是240×240。它采用SPI接口简化了连线,只需要7个引脚就能工作:
- GND/VCC:电源引脚(3.3V供电)
- SCL/SDA:时钟线和数据线(SPI通信基础)
- RES:硬件复位引脚(低电平有效)
- DC:数据/命令选择引脚(关键控制线)
- BLK:背光控制(高电平点亮)
我使用的STM32F103C6T6是Cortex-M3内核的经典款,64KB Flash和20KB RAM足够应付大多数显示需求。它的GPIO口丰富,我们选择PB5-PB9这组引脚连接屏幕:
// 引脚映射配置 #define LCD_BLK_PIN PB5 // 背光控制 #define LCD_DC_PIN PB6 // 数据/命令选择 #define LCD_RES_PIN PB7 // 复位信号 #define LCD_SDA_PIN PB8 // 数据线(MOSI) #define LCD_SCL_PIN PB9 // 时钟线(SCK)1.2 硬件连接细节
实际焊接时要注意几个关键点:
- 电源滤波:在VCC和GND之间并联一个0.1μF的陶瓷电容,能有效滤除电源噪声
- 上拉电阻:虽然STM32内部有上拉,但建议在SCL和SDA线上各加一个4.7KΩ外部上拉
- 背光限流:直接连接BLK可能电流过大,建议串联一个100Ω电阻
连线完成后,建议先用万用表检查所有连接点的通断,特别是GND一定要确保共地。我曾因为GND虚焊导致屏幕闪烁的问题排查了半天。
2. 底层通信实现
2.1 GPIO模拟SPI时序
由于STM32F103C6T6的硬件SPI可能被其他外设占用,我们采用GPIO模拟的方式。ST7789V的SPI模式0(CPOL=0, CPHA=0)要求:
- 时钟空闲时为低电平
- 数据在时钟上升沿采样
- 数据传输高位在前
void SPI_WriteByte(uint8_t data) { for(uint8_t i=0; i<8; i++) { LCD_SCL_LOW(); // 时钟拉低 if(data & 0x80) // 取最高位 LCD_SDA_HIGH(); else LCD_SDA_LOW(); delay_us(1); // 保持时间 LCD_SCL_HIGH(); // 产生上升沿 delay_us(1); data <<= 1; // 移位准备下一位 } }提示:delay_us的延时时间需要根据主频调整,我的72MHz系统用1us延时稳定可靠
2.2 命令与数据传输协议
ST7789V的每个操作都遵循"先命令后数据"的原则,DC引脚就是切换这两种状态的钥匙:
| DC引脚状态 | 传输内容 | 说明 |
|---|---|---|
| 低电平 | 命令 | 寄存器地址或操作码 |
| 高电平 | 数据 | 对应命令的参数或像素值 |
对应的基础函数实现:
void LCD_WriteCmd(uint8_t cmd) { LCD_DC_LOW(); // 命令模式 SPI_WriteByte(cmd); } void LCD_WriteData(uint8_t data) { LCD_DC_HIGH(); // 数据模式 SPI_WriteByte(data); }3. 屏幕初始化与配置
3.1 上电初始化流程
完整的初始化就像启动一台精密仪器,需要严格按照步骤进行:
- 硬件复位:拉低RES引脚至少10ms
- 背光开启:建议初始化完成后再开启
- 发送初始化序列:配置驱动IC的工作参数
- 设置显示方向:根据硬件安装方式调整
- 退出睡眠模式:最后唤醒显示屏
void LCD_Init() { // 硬件复位 LCD_RES_LOW(); delay_ms(20); LCD_RES_HIGH(); delay_ms(50); // 发送初始化命令序列 LCD_WriteCmd(0x36); // 设置扫描方向 LCD_WriteData(0x00); LCD_WriteCmd(0x3A); // 颜色格式 LCD_WriteData(0x05); // 16位RGB565 // 更多配置命令... LCD_WriteCmd(0x11); // 退出睡眠 delay_ms(120); LCD_WriteCmd(0x29); // 开启显示 // 最后开启背光 LCD_BLK_HIGH(); }3.2 关键参数解析
初始化过程中有几个重要参数需要特别注意:
颜色格式(0x3A命令):
- 0x03: 12-bit/pixel (RGB444)
- 0x05: 16-bit/pixel (RGB565)
- 0x06: 18-bit/pixel (RGB666)
扫描方向(0x36命令):
BIT0: 行地址顺序 BIT1: 列地址顺序 BIT4: 行/列交换 BIT5: 垂直刷新顺序 BIT6: RGB顺序电源控制(0xB0系列命令): 这些参数直接影响屏幕的对比度和功耗,建议参考数据手册的推荐值。
4. 图形显示实现
4.1 基础绘图功能
所有图形显示的基础是画点函数,而画点前需要设置操作窗口:
void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteCmd(0x2A); // 列地址设置 LCD_WriteData(x1 >> 8); LCD_WriteData(x1); LCD_WriteData(x2 >> 8); LCD_WriteData(x2); LCD_WriteCmd(0x2B); // 行地址设置 LCD_WriteData(y1 >> 8); LCD_WriteData(y1); LCD_WriteData(y2 >> 8); LCD_WriteData(y2); LCD_WriteCmd(0x2C); // 准备写入GRAM }基于这个基础,我们可以实现清屏函数:
void LCD_Clear(uint16_t color) { uint32_t total_pixels = 240 * 240; LCD_SetWindow(0, 0, 239, 239); for(uint32_t i=0; i<total_pixels; i++) { LCD_WriteData(color >> 8); // 先发高字节 LCD_WriteData(color & 0xFF); // 再发低字节 } }4.2 字符显示原理
字符显示本质上是将字形点阵渲染到屏幕上。我们以8×16点阵的ASCII字符为例:
- 字模提取:每个字符对应16字节数据
- 逐行渲染:每个bit代表一个像素是否点亮
- 颜色控制:前景色和背景色自由搭配
// 8x16 ASCII字模示例 const uint8_t font8x16[] = { // 空格(0x20) 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, // 数字'0'(0x30) 0x00,0x00,0x3E,0x63,0x63,0x6B,0x6B,0x63, 0x63,0x63,0x63,0x3E,0x00,0x00,0x00,0x00, // 更多字符... }; void LCD_DrawChar(uint16_t x, uint16_t y, char c, uint16_t fg, uint16_t bg) { uint8_t i,j; uint8_t line; LCD_SetWindow(x, y, x+7, y+15); for(i=0; i<16; i++) { line = font8x16[(c-32)*16 + i]; for(j=0; j<8; j++) { if(line & 0x80) LCD_WriteData16(fg); else LCD_WriteData16(bg); line <<= 1; } } }注意:实际项目中建议将字库存放在外部Flash或SD卡中,以节省宝贵的RAM空间
5. 性能优化技巧
5.1 提升刷新速度
当发现屏幕刷新缓慢时,可以尝试以下优化:
- 使用DMA传输:如果使用硬件SPI,配置DMA能大幅降低CPU占用
- 批量写入数据:设置窗口后连续发送像素数据,减少命令开销
- 局部刷新:只更新变化区域而非全屏刷新
5.2 内存优化策略
对于资源有限的STM32F103C6T6,内存管理很关键:
- 使用const存储字库:确保只读数据存放在Flash而非RAM
- 双缓冲技术:在内存中完成绘制后再整体刷新到屏幕
- 动态分配策略:根据显示内容动态加载资源
// 示例:使用内存缓冲 uint16_t frame_buffer[240][240]; // 在外部SRAM中分配 void LCD_Refresh() { LCD_SetWindow(0, 0, 239, 239); for(uint16_t y=0; y<240; y++) { for(uint16_t x=0; x<240; x++) { LCD_WriteData16(frame_buffer[y][x]); } } }6. 常见问题排查
在调试过程中,我遇到过各种奇怪现象,这里分享几个典型问题的解决方法:
屏幕全白或全黑:
- 检查背光控制信号
- 确认RESET时序正确
- 测量电源电压是否稳定
显示颜色异常:
- 确认颜色格式配置(RGB565/RGB444)
- 检查数据传输的字节顺序
- 验证DC引脚时序
显示内容错位:
- 重新校准扫描方向参数
- 检查窗口设置函数
- 确认字符显示坐标计算正确
记得第一次调试时,我的屏幕显示总是上下颠倒,后来发现是扫描方向寄存器(0x36)配置错误。经过反复试验,最终确定0xA0参数最适合我的安装方式。