1. 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引脚 | 说明 |
|---|---|---|
| SCLK | PB13 (SPI2_SCK) | SPI时钟 |
| MOSI | PB15 (SPI2_MOSI) | 主机输出 |
| CS | PB12 | 片选,低有效 |
| DC | PB11 | 0=命令,1=数据 |
| RST | PB10 | 复位,低有效 |
| BLK | PB1 | 背光,高电平点亮 |
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初始化均正常。
关键说明
本案例的延时函数采用简单循环近似,仅适用于功能验证。若需精确定时,应配置SysTick或使用定时器。ILI9341初始化序列仅包含点亮屏幕所需的最小指令集,未涉及伽马校正等画质优化项,不影响全屏颜色显示的正确性。代码中SPI时钟约10.5MHz,在合理范围内,若LCD模块不支持该速率可降低分频系数。