第一次拿到ST7735S驱动的1.8寸TFT屏时,我盯着这块比硬币大不了多少的屏幕,完全不知道从哪下手。这种128x160分辨率的彩色屏在嵌入式领域很常见,但驱动它需要理解控制器的工作逻辑。ST7735S就像个固执的画家——你必须用特定顺序递上颜料(命令)和画布(数据),它才会开始作画。
数据手册里密密麻麻的命令列表看着头疼,但实际常用的核心命令不到10个。比如SLPOUT是唤醒屏幕的"起床铃",DISPON相当于掀开画布上的遮光布,而CASET/RASET则是告诉画家:"从左上角这个坐标开始画"。最有趣的是MADCTL命令,通过修改它的参数,可以让屏幕显示方向像手机自动旋转那样变化。
我用的是STM32F103C8T6最小系统板,接线时最容易踩的坑是电源问题。ST7735S需要3.3V供电,但有些模块背光LED需要单独接5V。实测发现,如果背光亮度不足,会误以为是驱动没成功。推荐接线方案:
记得在电源引脚加个100μF电容,我有次因为电源波动导致屏幕初始化失败,排查了半天才发现是这个问题。
新手可以直接使用HAL库,但追求性能的话建议用寄存器版SPI。这里分享一个初始化SPI的配置要点:
c复制void SPI_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_SPI2_CLK_ENABLE();
// MOSI和SCK配置
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
// SPI参数配置
hspi2.Instance = SPI2;
hspi2.Init.Mode = SPI_MODE_MASTER;
hspi2.Init.Direction = SPI_DIRECTION_1LINE;
hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
hspi2.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi2.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi2.Init.NSS = SPI_NSS_SOFT;
hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4;
hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB;
HAL_SPI_Init(&hspi2);
}
唤醒序列:就像叫醒装睡的人要有固定流程
c复制// 硬件复位
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET);
// 软件唤醒
ST7735_WriteCommand(0x11); // SLPOUT
HAL_Delay(120); // 必须等待120ms
显示设置:控制屏幕的"眼皮"
c复制ST7735_WriteCommand(0x29); // DISPON
内存访问控制:屏幕方向的魔法开关
c复制uint8_t madctl = 0xC0; // 竖屏镜像模式
ST7735_WriteCommand(0x36); // MADCTL
ST7735_WriteData(&madctl, 1);
完整的初始化有20多个步骤,但可以拆解为几个阶段:
我建议把这些命令整理成结构体数组,方便维护:
c复制typedef struct {
uint8_t cmd;
uint8_t data[16];
uint8_t len;
} ST7735_Command;
const ST7735_Command init_seq[] = {
{0x11, {0}, 0}, // SLPOUT
{0xB1, {0x01,0x2C,0x2D}, 3}, // FRMCTR1
{0xC0, {0xA2,0x02,0x84}, 3}, // PWCTR1
// ...其他命令
};
每次绘图前都要告诉屏幕绘制范围,这就像给画家划定画布区域:
c复制void ST7735_SetWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
ST7735_WriteCommand(0x2A); // CASET
uint8_t data[] = {0, x0, 0, x1};
ST7735_WriteData(data, 4);
ST7735_WriteCommand(0x2B); // RASET
data[1] = y0; data[3] = y1;
ST7735_WriteData(data, 4);
ST7735_WriteCommand(0x2C); // RAMWR
}
用SPI发送像素数据时,注意颜色格式。RGB565模式下每个像素占2字节:
c复制void ST7735_FillRect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
uint8_t buff[2];
buff[0] = color >> 8;
buff[1] = color & 0xFF;
ST7735_SetWindow(x, y, x+w-1, y+h-1);
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET);
for(uint32_t i=0; i<w*h; i++) {
HAL_SPI_Transmit(&hspi2, buff, 2, 100);
}
}
当需要刷新全屏时,普通SPI传输会有明显卡顿。使用DMA可以解放CPU:
c复制void ST7735_FlushFrame(uint16_t *frame_buffer) {
ST7735_SetWindow(0, 0, 127, 159);
HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET);
HAL_SPI_Transmit_DMA(&hspi2, (uint8_t*)frame_buffer, 128*160*2);
}
在内存中维护两个缓冲区,一个用于绘制,一个用于显示。切换时只需更新差异部分:
c复制// 在VSync中断中切换缓冲区
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if(GPIO_Pin == VSYNC_Pin) {
active_buffer = !active_buffer;
ST7735_FlushFrame(buffers[active_buffer]);
}
}
调试时我习惯用逻辑分析仪抓SPI波形,重点检查:
有个特别隐蔽的坑:某些模块的RESET引脚是低电平有效,而有些是高电平有效。我有次因为搞反了这个,屏幕死活不亮。