ST32CubeMX + HAL库驱动ST7735彩屏全流程实战解析

在嵌入式开发中,图形显示界面往往是提升用户体验的关键要素。ST7735作为一款性价比极高的彩色TFT液晶驱动芯片,被广泛应用于各种嵌入式场景。本文将基于STM32CubeMX和HAL库,从硬件配置到软件实现,完整呈现ST7735屏幕的驱动过程,特别针对开发中常见的SPI配置、颜色模式、屏幕旋转等痛点问题提供解决方案。

1. 硬件准备与CubeMX工程创建

在开始编码之前,我们需要确保硬件连接正确。ST7735通常通过SPI接口与MCU通信,除了基本的SPI引脚(SCK、MOSI)外,还需要三个控制引脚:

  • RST(复位信号)
  • DC(数据/命令选择)
  • CS(片选信号)

打开STM32CubeMX,按照以下步骤创建工程:

  1. 选择正确的STM32型号(任何带有SPI外设的型号均可)
  2. 在"Connectivity"选项卡中启用SPI1(或其他可用SPI接口)
  3. 配置SPI为"Transmit Only Master"模式
  4. 设置合适的Prescaler值,建议波特率在5-20MHz之间

提示:SPI时钟频率并非越高越好,过高的频率可能导致信号完整性问题。如果屏幕出现雪花噪点或显示不稳定,可尝试降低SPI速率。

对于GPIO引脚配置:

/* 在CubeMX中将三个GPIO配置为Output模式 */
/* 建议的引脚标签命名 */
#define SPI1_RST_GPIO_Port  GPIOA
#define SPI1_RST_Pin        GPIO_PIN_0
#define SPI1_DC_GPIO_Port   GPIOA  
#define SPI1_DC_Pin         GPIO_PIN_1
#define SPI1_CS_GPIO_Port   GPIOA
#define SPI1_CS_Pin         GPIO_PIN_4

2. ST7735驱动实现与HAL库整合

创建好基础工程后,我们需要添加ST7735的驱动程序。建议将驱动代码分为头文件和源文件,便于维护和移植。

2.1 驱动头文件设计

st7735.h应包含以下核心内容:

#ifndef ST7735_H
#define ST7735_H

#include "main.h"
#include "spi.h"

/* 屏幕物理参数 */
#define ST7735_WIDTH  128
#define ST7735_HEIGHT 160

/* 控制引脚定义 - 需与CubeMX配置一致 */
#define ST7735_RST_Pin        SPI1_RST_Pin
#define ST7735_RST_GPIO_Port  SPI1_RST_GPIO_Port
#define ST7735_DC_Pin         SPI1_DC_Pin
#define ST7735_DC_GPIO_Port   SPI1_DC_GPIO_Port
#define ST7735_CS_Pin         SPI1_CS_Pin
#define ST7735_CS_GPIO_Port   SPI1_CS_GPIO_Port

/* SPI实例选择 */
#define ST7735_SPI_INSTANCE hspi1

/* 颜色模式配置 */
#define ST7735_MADCTL_RGB 0x00
#define ST7735_MADCTL_BGR 0x08
#define ST7735_MADCTL_MODE ST7735_MADCTL_RGB

/* 函数声明 */
void ST7735_Init(void);
void ST7735_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
void ST7735_FillScreen(uint16_t color);

#endif

2.2 核心驱动函数实现

st7735.c中,我们需要实现几个关键函数:

SPI数据传输函数

void ST7735_WriteCommand(uint8_t cmd) {
    HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&ST7735_SPI_INSTANCE, &cmd, 1, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_SET);
}

void ST7735_WriteData(uint8_t *data, uint16_t length) {
    HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
    HAL_SPI_Transmit(&ST7735_SPI_INSTANCE, data, length, HAL_MAX_DELAY);
    HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_SET);
}

初始化序列

ST7735的初始化需要发送一系列配置命令,这些命令因屏幕型号而异。以下是一个典型的初始化流程:

void ST7735_Init(void) {
    // 硬件复位
    HAL_GPIO_WritePin(ST7735_RST_GPIO_Port, ST7735_RST_Pin, GPIO_PIN_RESET);
    HAL_Delay(100);
    HAL_GPIO_WritePin(ST7735_RST_GPIO_Port, ST7735_RST_Pin, GPIO_PIN_SET);
    HAL_Delay(100);
    
    // 发送初始化命令序列
    ST7735_WriteCommand(0x11); // Sleep out
    HAL_Delay(120);
    
    ST7735_WriteCommand(0xB1); // Frame rate control
    uint8_t data1[] = {0x01, 0x2C, 0x2D};
    ST7735_WriteData(data1, sizeof(data1));
    
    ST7735_WriteCommand(0xB2); // Frame rate control
    uint8_t data2[] = {0x01, 0x2C, 0x2D};
    ST7735_WriteData(data2, sizeof(data2));
    
    // ... 更多初始化命令
}

3. 高级功能实现

3.1 屏幕旋转与显示方向控制

ST7735支持通过MADCTL命令设置显示方向。我们可以通过以下函数实现屏幕旋转:

void ST7735_SetRotation(uint8_t rotation) {
    uint8_t madctl = 0;
    
    switch(rotation % 4) {
        case 0:
            madctl = ST7735_MADCTL_MX | ST7735_MADCTL_MY | ST7735_MADCTL_MODE;
            break;
        case 1:
            madctl = ST7735_MADCTL_MY | ST7735_MADCTL_MV | ST7735_MADCTL_MODE;
            break;
        case 2:
            madctl = ST7735_MADCTL_MODE;
            break;
        case 3:
            madctl = ST7735_MADCTL_MX | ST7735_MADCTL_MV | ST7735_MADCTL_MODE;
            break;
    }
    
    ST7735_WriteCommand(0x36); // MADCTL
    ST7735_WriteData(&madctl, 1);
}

3.2 颜色格式处理

ST7735使用RGB565颜色格式,即每个像素用16位表示(5位红色,6位绿色,5位蓝色)。我们可以提供方便的宏来生成颜色值:

#define ST7735_COLOR565(r, g, b) (((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3))

// 常用颜色定义
#define ST7735_BLACK   0x0000
#define ST7735_BLUE    0x001F
#define ST7735_RED     0xF800
#define ST7735_GREEN   0x07E0
#define ST7735_WHITE   0xFFFF

3.3 图像显示优化

显示图像时,直接使用SPI传输每个像素效率较低。我们可以通过设置窗口地址然后批量传输来提高效率:

void ST7735_SetWindow(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) {
    // 设置列地址
    ST7735_WriteCommand(0x2A);
    uint8_t data[] = {0x00, x0, 0x00, x1};
    ST7735_WriteData(data, sizeof(data));
    
    // 设置行地址
    ST7735_WriteCommand(0x2B);
    data[1] = y0;
    data[3] = y1;
    ST7735_WriteData(data, sizeof(data));
    
    // 准备写入GRAM
    ST7735_WriteCommand(0x2C);
}

void ST7735_DrawImage(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *img) {
    ST7735_SetWindow(x, y, x + w - 1, y + h - 1);
    ST7735_WriteData((uint8_t *)img, w * h * 2);
}

4. 常见问题排查与优化

4.1 显示异常排查指南

现象 可能原因 解决方案
屏幕全白 背光正常但无显示 检查RST引脚时序,确保复位信号正确
显示错位 窗口设置不正确 调整ST7735_XSTART和ST7735_YSTART偏移量
颜色异常 颜色模式不匹配 切换ST7735_MADCTL_MODE为RGB或BGR
显示反色 反色模式被启用 设置ST7735_INVERSE为0或1
雪花噪点 SPI时钟速率过高 降低SPI波特率,检查PCB布线

4.2 性能优化技巧

  1. 双缓冲技术:在内存中维护两个帧缓冲区,交替更新和显示
  2. 局部刷新:只更新屏幕上发生变化的部分区域
  3. DMA传输:使用DMA代替CPU来搬运显示数据
  4. 指令预存:将常用显示命令预先存储在数组中,减少函数调用开销
// 使用DMA传输示例
void ST7735_WriteData_DMA(uint8_t *data, uint16_t length) {
    HAL_GPIO_WritePin(ST7735_DC_GPIO_Port, ST7735_DC_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(ST7735_CS_GPIO_Port, ST7735_CS_Pin, GPIO_PIN_RESET);
    HAL_SPI_Transmit_DMA(&ST7735_SPI_INSTANCE, data, length);
    // 注意:需要等待DMA传输完成才能拉高CS
}

4.3 字体显示实现

为了在屏幕上显示文字,我们需要实现字体系统。以下是基本字体结构的定义:

typedef struct {
    uint8_t width;     // 字符宽度
    uint8_t height;    // 字符高度
    const uint32_t *data; // 字体数据指针
} FontDef;

// 7x10字体示例
const uint32_t Font7x10 [] = {
    // 空格
    0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000,
    // 其他字符数据...
};

const FontDef Font_7x10 = {7, 10, Font7x10};

字符显示函数实现:

void ST7735_DrawChar(uint16_t x, uint16_t y, char c, uint16_t color, uint16_t bg, const FontDef *font) {
    uint32_t charData = font->data[(c - 32) * font->height];
    
    for(uint8_t i = 0; i < font->height; i++) {
        for(uint8_t j = 0; j < font->width; j++) {
            if(charData & (1 << (font->width - 1 - j))) {
                ST7735_DrawPixel(x + j, y + i, color);
            } else {
                ST7735_DrawPixel(x + j, y + i, bg);
            }
        }
        charData = font->data[(c - 32) * font->height + i];
    }
}

5. 项目实战:构建UI框架

基于以上基础功能,我们可以构建简单的UI框架。以下是一个状态机式的UI管理系统示例:

typedef enum {
    UI_HOME,
    UI_MENU,
    UI_SETTINGS,
    UI_ABOUT
} UI_State;

typedef struct {
    UI_State current_state;
    void (*draw_func)(void);
    void (*input_handler)(uint8_t key);
} UI_Manager;

void UI_DrawHome(void) {
    ST7735_FillScreen(ST7735_BLACK);
    ST7735_DrawString(10, 10, "Home Screen", ST7735_WHITE, ST7735_BLACK, &Font_11x18);
    // 绘制更多UI元素...
}

void UI_HandleHomeInput(uint8_t key) {
    if(key == KEY_OK) {
        ui_manager.current_state = UI_MENU;
        ui_manager.draw_func = UI_DrawMenu;
        ui_manager.input_handler = UI_HandleMenuInput;
    }
}

UI_Manager ui_manager = {
    .current_state = UI_HOME,
    .draw_func = UI_DrawHome,
    .input_handler = UI_HandleHomeInput
};

void UI_Update(void) {
    ui_manager.draw_func();
}

void UI_HandleInput(uint8_t key) {
    ui_manager.input_handler(key);
}

在实际项目中,这种架构可以方便地扩展各种界面和交互逻辑。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐