第一次拿到1.44寸TFT彩屏时,我完全被它小巧的体积和鲜艳的色彩吸引了。这种屏幕虽然尺寸不大,但128×128的分辨率在嵌入式设备上已经足够显示丰富的信息。它的核心是ST7735S驱动芯片,采用SPI接口通信,这让它比并口屏节省了大量IO口资源。
SPI接口的优势在于接线简单、速度快。我实测过,用硬件SPI驱动这块屏,刷新率能达到30fps以上,完全满足动态显示需求。不过很多初学者会困惑:为什么有些开发板上要用软件模拟SPI?其实这是为了兼容那些没有硬件SPI接口的单片机,比如某些51内核的芯片。
说到引脚连接,这块屏总共需要6根线:
这里有个小技巧:背光控制如果不接PWM,直接接3.3V也能常亮。我在做智能手表项目时,就通过PWM调光实现了自动亮度调节,效果很不错。
我用STM32F103C8T6核心板做测试时,引脚分配如下:
c复制#define LCD_CTRL GPIOB
#define LCD_LED GPIO_Pin_8 // PB8 背光
#define LCD_CS GPIO_Pin_7 // PB7 片选
#define LCD_RS GPIO_Pin_6 // PB6 数据/命令
#define LCD_RST GPIO_Pin_5 // PB5 复位
#define LCD_SCL GPIO_Pin_11 // PB11 时钟
#define LCD_SDA GPIO_Pin_10 // PB10 数据
这里有个坑要注意:STM32的硬件SPI1和SPI2引脚是固定的,不能随意分配。如果用硬件SPI,SCK必须接在PB3(SPI1)或PB13(SPI2),否则无法工作。我第一次调试时就因为接错引脚,折腾了半天。
虽然屏幕标称支持3.3V-5V供电,但我实测发现:
如果要做产品,建议加个电平转换芯片,比如TXS0108E,这样既能兼容3.3V主控,又能让屏幕工作在5V获得最佳显示效果。
ST7735S的初始化比较繁琐,但都是有规律的。我把它分成几个阶段:
这里有个优化技巧:初始化时的延时很关键。比如退出睡眠模式后至少要延时120ms,如果省掉这个延时,后续配置可能会失效。我在代码里专门封装了一个精准延时函数:
c复制void delay_syms(uint32_t ms) {
uint32_t i;
while(ms--) {
for(i=0; i<7200; i++);
}
}
硬件SPI的配置如下:
c复制void SPI_Init(void) {
SPI_InitTypeDef SPI_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SPI1, ENABLE);
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_2;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_Init(SPI1, &SPI_InitStructure);
SPI_Cmd(SPI1, ENABLE);
}
而软件SPI的实现更灵活,但速度会慢3-5倍。我做过对比测试:
如果项目对刷新率要求高,强烈建议用硬件SPI。有个小技巧:SPI时钟分频不要大于4,否则会影响显示流畅度。
用Img2Lcd取模时要注意这些参数设置:
我通常先用Photoshop把图片处理成128x128像素,保存为BMP格式,然后用Img2Lcd转换。转换后的数组可以直接用这个函数显示:
c复制void Show_Image(const uint8_t *img) {
LCD_SetWindows(0, 0, 127, 127);
for(uint32_t i=0; i<16384; i++) {
uint16_t color = (img[i*2]<<8) | img[i*2+1];
LCD_WR_DATA_16Bit(color);
}
}
要实现流畅的动画效果,可以采用这些方法:
比如实现一个进度条动画:
c复制void Draw_ProgressBar(uint8_t percent) {
static uint8_t last = 0;
uint16_t width = (uint16_t)(120 * percent / 100);
// 只刷新变化部分
if(width > last) {
LCD_SetWindows(4+last, 60, 4+width, 68);
for(uint16_t i=last; i<width; i++) {
for(uint8_t j=0; j<9; j++) {
LCD_WR_DATA_16Bit(BLUE);
}
}
}
last = width;
}
显示不同大小字体时,我总结出几个经验:
这是我常用的字体显示函数:
c复制void Show_Text(uint16_t x, uint16_t y, char *str, uint16_t color) {
while(*str) {
if(*str > 0x80) { // 中文
Draw_CN_Char(x, y, *str, color);
x += 16;
str += 2;
} else { // 英文
Draw_EN_Char(x, y, *str, color);
x += 8;
str += 1;
}
}
}
遇到花屏时,按这个顺序检查:
我遇到过最诡异的花屏问题是接地不良导致的,后来在屏幕和主板间加了个10Ω电阻就解决了。
ST7735S支持4种显示方向,通过0x36寄存器配置:
如果发现坐标不对,先检查这个寄存器的值。有个技巧:修改显示方向后,记得同步更新lcddev.width和lcddev.height的值。
颜色显示异常通常是这两种情况:
我封装了一个颜色转换宏,非常好用:
c复制#define RGB(r,g,b) (((r>>3)<<11) | ((g>>2)<<5) | (b>>3))
使用DMA可以大幅提升刷新速度。以STM32F4为例:
c复制void DMA_Config(void) {
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
DMA_InitStructure.DMA_Channel = DMA_Channel_3;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(SPI1->DR);
DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)image_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
DMA_InitStructure.DMA_BufferSize = 128*128;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;
DMA_Init(DMA2_Stream3, &DMA_InitStructure);
}
配合这个函数使用,刷新速度能提升5倍以上:
c复制void DMA_Refresh(void) {
LCD_SetWindows(0, 0, 127, 127);
LCD_WriteRAM_Prepare();
DMA_Cmd(DMA2_Stream3, ENABLE);
while(DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_TCIF3) == RESET);
DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_TCIF3);
}
对于复杂UI,建议使用帧缓冲技术:
这样能避免频繁操作SPI导致的闪烁问题。我的实现方案:
c复制uint16_t frame_buffer[128][128];
void Draw_Pixel(uint16_t x, uint16_t y, uint16_t color) {
if(x < 128 && y < 128) {
frame_buffer[y][x] = color;
}
}
void Refresh_Screen(void) {
DMA_SendData((uint8_t*)frame_buffer, sizeof(frame_buffer));
}
最后分享一个我用这块屏做的简易示波器项目。核心代码如下:
c复制// 初始化ADC和定时器
void Scope_Init(void) {
ADC_Init();
TIM_Init(1000); // 1kHz采样率
LCD_Init();
LCD_Clear(BLACK);
// 绘制坐标网格
for(uint8_t i=0; i<128; i+=16) {
Draw_Line(i, 0, i, 127, GRAY);
Draw_Line(0, i, 127, i, GRAY);
}
}
// 主循环
void Scope_Run(void) {
static uint16_t last_y = 64;
uint16_t adc_val = ADC_Read();
uint16_t new_y = 127 - (adc_val >> 5); // 12bit转7bit
Draw_Line(0, last_y, 127, new_y, GREEN);
last_y = new_y;
// 每128点清屏一次
static uint16_t count = 0;
if(++count >= 128) {
count = 0;
LCD_Clear(BLACK);
// 重绘网格...
}
}
这个项目充分利用了1.44寸屏的快速刷新特性,实现了1kHz的波形显示。通过这个实战,我发现ST7735S虽然是小屏,但性能完全能满足很多嵌入式GUI的需求。