1. 项目概述
N096-1608TBBIG11-H13是一款0.96英寸的TFT液晶显示模块,采用ST7735S驱动芯片,分辨率为80×160像素。这款显示屏在嵌入式系统中应用广泛,尤其适合需要小型彩色显示界面的场景。我在最近的一个智能家居控制器项目中就采用了这款显示屏,用来显示温湿度数据和简单的控制界面。
这款显示屏的主要特点包括:
- 采用SPI接口通信,占用MCU引脚少
- 支持65K色显示
- 内置DC-DC转换器,简化电源设计
- 工作电压范围宽(2.8V-3.3V)
- 模块尺寸小巧,适合空间受限的应用
2. 硬件连接与初始化
2.1 引脚定义与连接
N096-1608TBBIG11-H13模块通常提供8个引脚,与STM32的连接方式如下:
| 显示屏引脚 | STM32引脚 | 功能说明 |
|---|---|---|
| VCC | 3.3V | 电源正极 |
| GND | GND | 电源负极 |
| SCL | PA5 | SPI时钟线 |
| SDA | PA7 | SPI数据线 |
| RES | PA1 | 复位信号 |
| DC | PA2 | 数据/命令选择 |
| CS | PA3 | 片选信号 |
| BLK | PA4 | 背光控制 |
注意:不同型号的STM32芯片SPI引脚可能不同,需要根据具体型号调整。我在使用STM32F103C8T6时采用了上述连接方式。
2.2 SPI接口配置
在STM32CubeMX中配置SPI1接口:
- 选择Full-Duplex Master模式
- 设置波特率预分频为8(在72MHz系统时钟下得到9MHz SPI时钟)
- 设置数据大小为8位
- 选择CPOL=Low,CPHA=1Edge
- 启用硬件NSS信号(可选)
c复制/* SPI1 init function */
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
if (HAL_SPI_Init(&hspi1) != HAL_OK)
{
Error_Handler();
}
}
3. ST7735S驱动程序设计
3.1 基本驱动函数
首先需要实现几个基础的硬件抽象函数:
c复制#define LCD_RES_PIN GPIO_PIN_1
#define LCD_RES_PORT GPIOA
#define LCD_DC_PIN GPIO_PIN_2
#define LCD_DC_PORT GPIOA
#define LCD_CS_PIN GPIO_PIN_3
#define LCD_CS_PORT GPIOA
// 写命令函数
void LCD_WriteCommand(uint8_t cmd)
{
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET); // DC=0表示命令
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET); // CS=0选中设备
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET); // CS=1取消选中
}
// 写数据函数
void LCD_WriteData(uint8_t data)
{
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET); // DC=1表示数据
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}
// 复位函数
void LCD_Reset(void)
{
HAL_GPIO_WritePin(LCD_RES_PORT, LCD_RES_PIN, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(LCD_RES_PORT, LCD_RES_PIN, GPIO_PIN_SET);
HAL_Delay(100);
}
3.2 显示屏初始化序列
ST7735S需要按照特定顺序发送一系列初始化命令:
c复制void LCD_Init(void)
{
LCD_Reset();
// 软件复位
LCD_WriteCommand(0x01);
HAL_Delay(150);
// 退出睡眠模式
LCD_WriteCommand(0x11);
HAL_Delay(255);
// 设置颜色模式为16位RGB565
LCD_WriteCommand(0x3A);
LCD_WriteData(0x05);
// 设置显示方向
LCD_WriteCommand(0x36);
LCD_WriteData(0xA0); // 0xA0为竖屏模式,0xC0为横屏模式
// 设置列地址范围
LCD_WriteCommand(0x2A);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x7F); // 0-127列
// 设置行地址范围
LCD_WriteCommand(0x2B);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x00);
LCD_WriteData(0x9F); // 0-159行
// 开启显示
LCD_WriteCommand(0x29);
// 开启背光
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET);
}
提示:不同批次的ST7735S芯片可能需要不同的初始化序列。如果遇到显示异常,可以尝试调整初始化命令的顺序或参数。
4. 图形绘制功能实现
4.1 设置显示区域
在绘制图形前,需要先设置显示区域:
c复制void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1)
{
LCD_WriteCommand(0x2A); // 列地址设置
LCD_WriteData(x0 >> 8);
LCD_WriteData(x0 & 0xFF);
LCD_WriteData(x1 >> 8);
LCD_WriteData(x1 & 0xFF);
LCD_WriteCommand(0x2B); // 行地址设置
LCD_WriteData(y0 >> 8);
LCD_WriteData(y0 & 0xFF);
LCD_WriteData(y1 >> 8);
LCD_WriteData(y1 & 0xFF);
LCD_WriteCommand(0x2C); // 开始写入GRAM
}
4.2 像素点绘制
最基本的绘图函数是绘制单个像素点:
c复制void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color)
{
if(x >= 80 || y >= 160) return; // 超出显示范围
LCD_SetWindow(x, y, x, y);
LCD_WriteData(color >> 8);
LCD_WriteData(color & 0xFF);
}
4.3 矩形填充
矩形填充函数可以用于清屏或绘制色块:
c复制void LCD_FillRect(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
{
uint32_t i, j;
uint32_t size = (x1 - x0 + 1) * (y1 - y0 + 1);
LCD_SetWindow(x0, y0, x1, y1);
for(i = 0; i < size; i++) {
LCD_WriteData(color >> 8);
LCD_WriteData(color & 0xFF);
}
}
4.4 字符显示
实现字符显示需要先定义字模数据,这里以8x16点阵ASCII字符为例:
c复制void LCD_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bgcolor)
{
uint8_t i, j;
uint8_t line;
if(x >= 80 - 8 || y >= 160 - 16) return;
for(i = 0; i < 16; i++) {
line = font8x16[c * 16 + i];
for(j = 0; j < 8; j++) {
if(line & (1 << (7 - j))) {
LCD_DrawPixel(x + j, y + i, color);
} else {
LCD_DrawPixel(x + j, y + i, bgcolor);
}
}
}
}
5. 性能优化技巧
5.1 DMA传输优化
使用DMA可以显著提高图形绘制速度:
c复制void LCD_FillRect_DMA(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint16_t color)
{
uint32_t size = (x1 - x0 + 1) * (y1 - y0 + 1);
uint8_t color_hi = color >> 8;
uint8_t color_lo = color & 0xFF;
uint8_t *buffer = malloc(size * 2);
// 准备数据缓冲区
for(uint32_t i = 0; i < size; i++) {
buffer[i*2] = color_hi;
buffer[i*2+1] = color_lo;
}
LCD_SetWindow(x0, y0, x1, y1);
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit_DMA(&hspi1, buffer, size * 2);
// 注意:需要在SPI传输完成回调中释放buffer
}
5.2 双缓冲技术
对于动画显示,可以采用双缓冲技术减少闪烁:
c复制uint16_t frame_buffer[80][160]; // 主缓冲区
uint16_t draw_buffer[80][160]; // 绘制缓冲区
void LCD_Refresh(void)
{
LCD_SetWindow(0, 0, 79, 159);
HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET);
HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)frame_buffer, 80*160*2);
// 交换缓冲区
memcpy(frame_buffer, draw_buffer, sizeof(frame_buffer));
}
6. 常见问题与解决方案
6.1 显示屏无反应
可能原因及解决方法:
- 电��问题:检查VCC和GND连接,确保电压在2.8V-3.3V之间
- 复位信号问题:确保复位引脚在上电时有正确的低电平脉冲
- SPI通信问题:用逻辑分析仪检查SCL和SDA信号
- 背光问题:检查BLK引脚是否接高电平
6.2 显示颜色异常
可能原因:
- 颜色模式设置错误:确保设置为RGB565模式(命令0x3A参数0x05)
- 数据位顺序错误:检查SPI的MSB/LSB设置
- 初始化序列不完整:确保发送了所有必要的初始化命令
6.3 显示内容错位
解决方法:
- 检查显示方向设置(命令0x36)
- 确认列和行地址范围设置正确(命令0x2A和0x2B)
- 确保像素坐标不超过显示屏分辨率
7. 实际应用示例
7.1 简单UI框架
实现一个简单的状态栏和内容区域:
c复制typedef struct {
uint16_t width;
uint16_t height;
uint16_t bg_color;
uint16_t text_color;
} LCD_Area;
void LCD_DrawStatusBar(LCD_Area *area, const char *title)
{
LCD_FillRect(0, 0, area->width-1, 15, 0x001F); // 蓝色状态栏
LCD_DrawString(2, 2, title, 0xFFFF, 0x001F); // 白色文字
}
void LCD_DrawContentArea(LCD_Area *area)
{
LCD_FillRect(0, 16, area->width-1, area->height-1, area->bg_color);
}
void LCD_InitUI(LCD_Area *area, uint16_t width, uint16_t height)
{
area->width = width;
area->height = height;
area->bg_color = 0xFFFF; // 白色背景
area->text_color = 0x0000; // 黑色文字
LCD_DrawStatusBar(area, "STM32 LCD Demo");
LCD_DrawContentArea(area);
}
7.2 实时数据显示
在内容区域显示实时数据:
c复制void LCD_UpdateTemperature(float temp)
{
char buffer[16];
snprintf(buffer, sizeof(buffer), "Temp: %.1fC", temp);
// 清除之前的内容
LCD_FillRect(0, 20, 79, 35, 0xFFFF);
// 显示新数据
LCD_DrawString(2, 20, buffer, 0x0000, 0xFFFF);
}
8. 进阶功能实现
8.1 触摸屏支持
如果显示屏带有触摸功能,可以通过以下方式读取触摸坐标:
c复制#define TOUCH_XP_PIN GPIO_PIN_0
#define TOUCH_XP_PORT GPIOB
#define TOUCH_YP_PIN GPIO_PIN_1
#define TOUCH_YP_PORT GPIOB
uint16_t TOUCH_ReadX(void)
{
// 配置XP为输出高,YP为输入
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = TOUCH_XP_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(TOUCH_XP_PORT, &GPIO_InitStruct);
GPIO_InitStruct.Pin = TOUCH_YP_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(TOUCH_YP_PORT, &GPIO_InitStruct);
HAL_GPIO_WritePin(TOUCH_XP_PORT, TOUCH_XP_PIN, GPIO_PIN_SET);
// 读取ADC值
// 这里需要根据具体硬件连接ADC通道
return HAL_ADC_GetValue(&hadc1);
}
// 类似地实现TOUCH_ReadY函数
8.2 图形库集成
可以集成更强大的图形库如u8g2或LVGL:
c复制// u8g2初始化示例
#include "u8g2.h"
u8g2_t u8g2;
void U8G2_Init(void)
{
u8g2_Setup_st7735_ips_160x80_f(&u8g2, U8G2_R0,
u8x8_byte_4wire_sw_spi,
u8x8_gpio_and_delay_stm32);
u8g2_InitDisplay(&u8g2);
u8g2_SetPowerSave(&u8g2, 0);
u8g2_ClearBuffer(&u8g2);
u8g2_SetFont(&u8g2, u8g2_font_6x10_tf);
u8g2_DrawStr(&u8g2, 0, 10, "Hello STM32!");
u8g2_SendBuffer(&u8g2);
}
9. 电源管理与低功耗
9.1 背光控制
通过PWM控制背光亮度:
c复制TIM_HandleTypeDef htim2;
void LCD_Backlight_Init(void)
{
TIM_OC_InitTypeDef sConfigOC = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 71;
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 999;
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&htim2);
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 50%亮度
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
}
void LCD_SetBacklight(uint8_t brightness)
{
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, brightness * 10);
}
9.2 睡眠模式
当系统空闲时,可以关闭显示屏以节省功耗:
c复制void LCD_EnterSleep(void)
{
LCD_WriteCommand(0x10); // 进入睡眠模式
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); // 关闭背光
}
void LCD_WakeUp(void)
{
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); // 开启背光
LCD_WriteCommand(0x11); // 退出睡眠模式
HAL_Delay(120);
}
10. 项目经验分享
在实际项目中,我发现以下几点特别值得注意:
-
SPI时钟速度:ST7735S的最大SPI时钟频率为15MHz,但实际使用中发现超过10MHz时容易出现数据错误。建议开始时使用较低频率(如4MHz),稳定后再逐步提高。
-
初始化时序:不同批次的ST7735S芯片可能需要不同的初始化序列。建议保留调试接口,可以动态调整初始化参数。
-
内存占用:全屏缓冲需要80x160x2=25.6KB内存,对于资源有限的STM32型号可能过大。可以考虑只缓冲当前显示内容或使用部分刷新技术。
-
抗干扰设计:长距离连接时,SPI信号容易受到干扰。可以在信号线上串联33Ω电阻,并靠近显示屏端加0.1μF去耦电容。
-
温度影响:在低温环境下,显示屏响应可能变慢。必要时可以增加初始化后的延时,或重复发送关键命令。
