1. GPIO模拟8080时序点亮TFT屏

GPIO模拟8080时序点亮TFT屏 学习卡片

任务说明

本案例目标是在MCU上完成LCD屏幕的首次点亮:初始化GPIO与基本延时,连接LCD模块,编写代码点亮背光并显示全屏颜色,以此验证硬件连接和基本通信是否正常。案例以STM32F407最小系统板搭配ILI9341驱动、SPI接口的2.4寸TFT LCD模块为例,实现最小可复现的“点亮”闭环。

硬件连接与通信约定

LCD模块使用4线SPI模式,MCU作为主机。除SPI时钟、主机输出从机输入(MOSI)、片选(CS)外,还需要数据/命令选择(DC)、复位(RST)和背光控制(BLK)三根GPIO线。具体引脚分配如下表:

LCD引脚MCU引脚说明
SCLKPB13 (SPI2_SCK)SPI时钟
MOSIPB15 (SPI2_MOSI)主机输出
CSPB12片选,低有效
DCPB110=命令,1=数据
RSTPB10复位,低有效
BLKPB1背光,高电平点亮

SPI2配置为主模式,时钟频率约10MHz,数据帧8位,MSB先行,SPI模式0(CPOL=0,CPHA=0)。

代码实现

以下代码使用STM32标准外设库,包含GPIO、SPI、延时初始化,以及ILI9341的初始化序列和全屏填充函数。代码中通过宏定义区分命令与数据写入,核心操作为:拉低CS选中设备,根据DC电平发送命令字节或数据字节,最后拉高CS释放设备。


#include "stm32f4xx.h"
#include "stm32f4xx_gpio.h"
#include "stm32f4xx_spi.h"
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_tim.h"

// 引脚宏定义
#define LCD_CS_PORT     GPIOB
#define LCD_CS_PIN      GPIO_Pin_12
#define LCD_DC_PORT     GPIOB
#define LCD_DC_PIN      GPIO_Pin_11
#define LCD_RST_PORT    GPIOB
#define LCD_RST_PIN     GPIO_Pin_10
#define LCD_BLK_PORT    GPIOB
#define LCD_BLK_PIN     GPIO_Pin_1

// 命令/数据选择
#define LCD_DC_CMD()    GPIO_ResetBits(LCD_DC_PORT, LCD_DC_PIN)
#define LCD_DC_DAT()    GPIO_SetBits(LCD_DC_PORT, LCD_DC_PIN)
#define LCD_CS_EN()     GPIO_ResetBits(LCD_CS_PORT, LCD_CS_PIN)
#define LCD_CS_DIS()    GPIO_SetBits(LCD_CS_PORT, LCD_CS_PIN)

// 简单延时(基于SysTick,初始化后使用)
static void delay_us(uint32_t us) {
    // 假设SysTick已配置为168MHz主频下的1us中断周期
    // 此处使用简单循环近似,实际工程建议使用定时器
    uint32_t i;
    for (i = 0; i < us * 20; i++) {
        __NOP();
    }
}

static void delay_ms(uint32_t ms) {
    uint32_t i;
    for (i = 0; i < ms; i++) {
        delay_us(1000);
    }
}

// SPI2发送一个字节
static void spi2_send_byte(uint8_t data) {
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
    SPI_I2S_SendData(SPI2, data);
    while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
    SPI_I2S_ReceiveData(SPI2);  // 清除RXNE标志
}

// 写命令
static void lcd_write_cmd(uint8_t cmd) {
    LCD_CS_EN();
    LCD_DC_CMD();
    spi2_send_byte(cmd);
    LCD_CS_DIS();
}

// 写数据
static void lcd_write_data(uint8_t data) {
    LCD_CS_EN();
    LCD_DC_DAT();
    spi2_send_byte(data);
    LCD_CS_DIS();
}

// 初始化GPIO和SPI2
static void lcd_gpio_spi_init(void) {
    GPIO_InitTypeDef gpio;
    SPI_InitTypeDef spi;

    // 使能GPIOB和SPI2时钟
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

    // 配置控制引脚:CS, DC, RST, BLK 为推挽输出
    gpio.GPIO_Pin = LCD_CS_PIN | LCD_DC_PIN | LCD_RST_PIN | LCD_BLK_PIN;
    gpio.GPIO_Mode = GPIO_Mode_OUT;
    gpio.GPIO_OType = GPIO_OType_PP;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB, &gpio);

    // 配置SCK和MOSI为复用功能
    gpio.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_15;
    gpio.GPIO_Mode = GPIO_Mode_AF;
    gpio.GPIO_OType = GPIO_OType_PP;
    gpio.GPIO_Speed = GPIO_Speed_50MHz;
    gpio.GPIO_PuPd = GPIO_PuPd_NOPULL;
    GPIO_Init(GPIOB, &gpio);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource13, GPIO_AF_SPI2);
    GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_SPI2);

    // SPI2主机模式配置
    SPI_StructInit(&spi);
    spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
    spi.SPI_Mode = SPI_Mode_Master;
    spi.SPI_DataSize = SPI_DataSize_8b;
    spi.SPI_CPOL = SPI_CPOL_Low;
    spi.SPI_CPHA = SPI_CPHA_1Edge;
    spi.SPI_NSS = SPI_NSS_Soft;
    spi.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;  // 84MHz/8≈10.5MHz
    spi.SPI_FirstBit = SPI_FirstBit_MSB;
    SPI_Init(SPI2, &spi);
    SPI_Cmd(SPI2, ENABLE);

    // 初始电平:CS高,DC低,RST高,BLK低
    GPIO_SetBits(GPIOB, LCD_CS_PIN);
    GPIO_ResetBits(GPIOB, LCD_DC_PIN);
    GPIO_SetBits(GPIOB, LCD_RST_PIN);
    GPIO_ResetBits(GPIOB, LCD_BLK_PIN);
}

// ILI9341初始化序列(最小必要指令)
static void lcd_ili9341_init(void) {
    // 硬件复位
    GPIO_ResetBits(GPIOB, LCD_RST_PIN);
    delay_ms(20);
    GPIO_SetBits(GPIOB, LCD_RST_PIN);
    delay_ms(120);

    // 软件初始化命令
    lcd_write_cmd(0x01);   // 软件复位
    delay_ms(5);
    lcd_write_cmd(0x11);   // 退出睡眠
    delay_ms(120);
    lcd_write_cmd(0x36);   // 内存访问控制
    lcd_write_data(0x48);  // BGR顺序,扫描方向
    lcd_write_cmd(0x3A);   // 像素格式
    lcd_write_data(0x55);  // 16位色
    lcd_write_cmd(0x29);   // 开启显示
}

// 设置显示窗口为全屏
static void lcd_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
    lcd_write_cmd(0x2A);  // 列地址
    lcd_write_data(x0 >> 8);
    lcd_write_data(x0 & 0xFF);
    lcd_write_data(x1 >> 8);
    lcd_write_data(x1 & 0xFF);
    lcd_write_cmd(0x2B);  // 行地址
    lcd_write_data(y0 >> 8);
    lcd_write_data(y0 & 0xFF);
    lcd_write_data(y1 >> 8);
    lcd_write_data(y1 & 0xFF);
    lcd_write_cmd(0x2C);  // 写内存
}

// 全屏填充指定颜色(RGB565格式)
static void lcd_fill_screen(uint16_t color) {
    uint32_t i;
    uint32_t pixels = 320 * 240;  // 2.4寸分辨率

    lcd_set_window(0, 0, 319, 239);

    LCD_CS_EN();
    LCD_DC_DAT();
    for (i = 0; i < pixels; i++) {
        spi2_send_byte(color >> 8);   // 高字节
        spi2_send_byte(color & 0xFF);  // 低字节
    }
    LCD_CS_DIS();
}

// 主函数
int main(void) {
    // 初始化SysTick用于延时(此处简化为空,实际需配置)
    // SystemInit() 已在启动文件中调用

    lcd_gpio_spi_init();
    lcd_ili9341_init();

    // 点亮背光
    GPIO_SetBits(GPIOB, LCD_BLK_PIN);

    // 依次显示红、绿、蓝三色,每色停留1秒
    lcd_fill_screen(0xF800);  // 红色
    delay_ms(1000);
    lcd_fill_screen(0x07E0);  // 绿色
    delay_ms(1000);
    lcd_fill_screen(0x001F);  // 蓝色
    delay_ms(1000);

    while (1) {
        // 保持最后一色
    }
}

编译与烧录

使用Keil MDK或STM32CubeIDE创建工程,选择STM32F407VG芯片。将上述代码放入main.c,并确保标准外设库的头文件路径正确。编译生成HEX文件后,通过ST-Link或J-Link烧录至开发板。

验证方法与判定标准

烧录后观察LCD屏幕:若硬件连接正确且初始化时序无误,屏幕背光应点亮,并依次显示红、绿、蓝三种全屏颜色,每种颜色持续约1秒,最后停留在蓝色画面。若屏幕无显示或颜色异常,按以下顺序排查:

  • 检查背光引脚电平是否为高,若屏幕亮但无内容,问题在SPI通信或初始化时序;
  • 用逻辑分析仪或示波器测量SPI2的SCLK和MOSI引脚,确认有时钟和数据输出;
  • 检查DC引脚在命令阶段为低、数据阶段为高;
  • 确认RST引脚先低后高的复位时序满足至少10ms低电平要求。

判定通过条件:屏幕依次显示纯红、纯绿、纯蓝画面,且颜色均匀无条纹,说明GPIO控制、SPI通信和LCD初始化均正常。

STM32F407 SPI2主机 PB13 PB15 PB12 PB11 PB10 ILI9341 LCD 2.4" TFT SCLK MOSI CS DC RST SCLK MOSI CS DC RST PB1 BLK 背光控制

关键说明

本案例的延时函数采用简单循环近似,仅适用于功能验证。若需精确定时,应配置SysTick或使用定时器。ILI9341初始化序列仅包含点亮屏幕所需的最小指令集,未涉及伽马校正等画质优化项,不影响全屏颜色显示的正确性。代码中SPI时钟约10.5MHz,在合理范围内,若LCD模块不支持该速率可降低分频系数。