STM32F4驱动ST7735S小屏幕:从SPI配置到图片显示实战指南
手里这块1.44寸的ST7735S屏幕已经吃灰三个月了?跟着我做这个温湿度监测项目,保证让你的开发板和小屏幕都活起来。不需要死记硬背那些SPI参数,咱们直接动手做出能显示动态数据的实用界面。
1. 硬件连接与SPI配置
先来看看硬件接线。ST7735S通常有7个关键引脚需要连接:
| 屏幕引脚 | STM32F4对应引脚 | 备注 |
|---|---|---|
| VCC | 3.3V | 注意电压匹配 |
| GND | GND | 共地是关键 |
| SCL | PB3 (SPI1_SCK) | 时钟信号线 |
| SDA | PB5 (SPI1_MOSI) | 主设备输出从设备输入 |
| RES | PA1 | 自定义复位引脚 |
| DC | PA2 | 数据/命令选择 |
| CS | PA3 | 片选信号 |
硬件SPI初始化代码要特别注意时钟相位和极性的配置:
void SPI1_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; SPI_InitTypeDef SPI_InitStruct; // 启用GPIOB和SPI1时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE); // 配置PB3(SCK), PB5(MOSI)为复用功能 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_5; GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStruct); // 引脚复用映射 GPIO_PinAFConfig(GPIOB, GPIO_PinSource3, GPIO_AF_SPI1); GPIO_PinAFConfig(GPIOB, GPIO_PinSource5, GPIO_AF_SPI1); // SPI参数配置 SPI_InitStruct.SPI_Direction = SPI_Direction_1Line_Tx; // 单线发送模式 SPI_InitStruct.SPI_Mode = SPI_Mode_Master; SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b; SPI_InitStruct.SPI_CPOL = SPI_CPOL_High; // 关键配置 SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // 关键配置 SPI_InitStruct.SPI_NSS = SPI_NSS_Soft; SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4; // 21MHz/4=5.25MHz SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB; SPI_Init(SPI1, &SPI_InitStruct); SPI_Cmd(SPI1, ENABLE); }提示:ST7735S的SPI时序要求CPOL=1, CPHA=1,这个配置不对屏幕会完全没有反应。如果遇到白屏问题,首先检查这两项参数。
2. 屏幕初始化与基础绘图
ST7735S的初始化需要发送一系列命令和参数。这里有个技巧:把厂商提供的初始化代码封装成数组,直接批量发送:
const uint8_t init_cmds[] = { // 命令 参数长度 参数 0x11, 0, // 睡眠退出 0x36, 1, 0x08, // 内存访问控制 0x3A, 1, 0x05, // 颜色格式设置 RGB565 0xB1, 2, 0x01, 0x2C, // 帧率控制 0xB2, 2, 0x01, 0x2C, // 帧率控制 0xB3, 2, 0x01, 0x2C, 0x01, 0x2C, // 帧率控制 0xB4, 1, 0x07, // 显示反转控制 0xC0, 2, 0xA2, 0x02, // 电源控制1 0xC1, 1, 0x05, // 电源控制2 0xC2, 2, 0x0A, 0x00, // 电源控制3 0xC3, 2, 0x8A, 0x2A, // 电源控制4 0xC4, 2, 0x8A, 0xEE, // 电源控制5 0xC5, 1, 0x0E, // VCOM控制 0x20, 0, // 关闭反显 0x29, 0 // 开启显示 }; void ST7735_Init(void) { uint8_t *p = (uint8_t *)init_cmds; for(int i=0; i<sizeof(init_cmds);) { uint8_t cmd = p[i++]; uint8_t len = p[i++]; ST7735_WriteCommand(cmd); while(len--) { ST7735_WriteData(p[i++]); } if(cmd == 0x11 || cmd == 0x29) { delay_ms(120); // 重要命令需要延时 } } }实现基础绘图函数时,注意设置正确的窗口地址:
void ST7735_SetWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) { ST7735_WriteCommand(0x2A); // 列地址设置 ST7735_WriteData(0); ST7735_WriteData(x0); ST7735_WriteData(0); ST7735_WriteData(x1); ST7735_WriteCommand(0x2B); // 行地址设置 ST7735_WriteData(0); ST7735_WriteData(y0); ST7735_WriteData(0); ST7735_WriteData(y1); ST7735_WriteCommand(0x2C); // 开始写入GRAM } void ST7735_DrawPixel(uint8_t x, uint8_t y, uint16_t color) { if(x >= 128 || y >= 160) return; ST7735_SetWindow(x, y, x, y); ST7735_WriteData(color >> 8); ST7735_WriteData(color & 0xFF); }3. 图片显示与字库实现
显示图片前需要将图片转换为RGB565格式的数组。推荐使用Img2Lcd工具,设置如下:
- 输出格式选择"C语言数组"
- 扫描模式选择"水平扫描"
- 颜色位数选择"16位真彩色"
- 勾选"高位在前"选项
生成的数组可以直接用DMA传输:
// 图片数据示例 const uint16_t gImage_test[128*160] = { 0xFFFF, 0xFFFF, 0xFFFF, // ... }; void ST7735_ShowImage(uint16_t x, uint16_t y, uint16_t width, uint16_t height, const uint16_t *img) { ST7735_SetWindow(x, y, x+width-1, y+height-1); ST7735_WriteData_DMA((uint8_t *)img, width*height*2); }字库实现需要先制作字模。使用PCtoLCD2002软件生成8x16 ASCII字库:
- 选择"字符模式"
- 设置字体为宋体,大小8x16
- 取模方式设置为"逐行式","高位在前"
- 生成所有可见ASCII字符(0x20-0x7E)
// 8x16 ASCII字模示例 const uint8_t font8x16[95][16] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, // 空格 {0x00,0x00,0x18,0x3C,0x3C,0x3C,0x18,0x18,0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00}, // ! // ...其他字符 }; void ST7735_DrawChar(uint8_t x, uint8_t y, char c, uint16_t color, uint16_t bgcolor) { if(c < 0x20 || c > 0x7E) return; const uint8_t *p = font8x16[c - 0x20]; for(uint8_t i=0; i<16; i++) { uint8_t line = p[i]; for(uint8_t j=0; j<8; j++) { if(line & (0x80 >> j)) { ST7735_DrawPixel(x+j, y+i, color); } else if(bgcolor != TRANSPARENT) { ST7735_DrawPixel(x+j, y+i, bgcolor); } } } }4. 温湿度监测界面实现
结合DHT11传感器,我们可以实现一个完整的温湿度监测界面。首先设计界面布局:
typedef struct { uint16_t temp; uint16_t humidity; uint8_t update_flag; } EnvData; void UI_DrawFrame(void) { // 绘制边框 ST7735_DrawRect(0, 0, 127, 159, RGB(0,255,0)); // 绘制标题 ST7735_DrawString(10, 5, "Env Monitor", RGB(255,255,255), RGB(0,0,0)); // 温度区域 ST7735_FillRect(10, 30, 108, 50, RGB(50,50,50)); ST7735_DrawString(15, 35, "Temperature:", RGB(255,255,0), RGB(50,50,50)); // 湿度区域 ST7735_FillRect(10, 90, 108, 50, RGB(50,50,50)); ST7735_DrawString(15, 95, "Humidity:", RGB(0,255,255), RGB(50,50,50)); } void UI_UpdateData(EnvData *data) { if(data->update_flag) { char buf[16]; // 更新温度 sprintf(buf, "%2d C",>int main(void) { SystemInit(); ST7735_Init(); DHT11_Init(); UI_DrawFrame(); EnvData env = {0}; uint32_t last_update = 0; while(1) { if(HAL_GetTick() - last_update > 2000) { // 每2秒更新一次 if(DHT11_ReadData(&env.temp, &env.humidity) == SUCCESS) { env.update_flag = 1; UI_UpdateData(&env); } last_update = HAL_GetTick(); } } }注意:实际项目中建议使用双缓冲机制,先在内存中绘制完整帧再一次性刷新到屏幕,可以避免闪烁现象。对于STM32F4,可以利用其充足的RAM开辟一个128x160x2=40KB的显存缓冲区。