信利 ILI9341 lcd适配,从0开始,spi访问接口封装,lcd cmd参数说明(参考lcd规格书或者数据手册),gpio控制屏幕的背光得亮灭,pwm控制屏幕的亮度包括spi访问,dma访问,给出完整的框架。给出vcc供电lcd,rst复位,gpio/pwm引脚复用功能,给出一个常见的信利ILI9341 lcd的代码框架。
一、硬件接口与引脚配置
| 功能 | 引脚 | 模式 | 说明 |
|---|---|---|---|
| VCC_EN | PC13 | GPIO_Output | 控制 LDO 给 LCD 供电(可选) |
| RST | PB1 | GPIO_Output | 硬件复位 |
| DC | PB0 | GPIO_Output | 命令/数据选择(0=命令,1=数据) |
| CS | PA4 | GPIO_Output | SPI 片选 |
| SCLK | PA5 | SPI_SCK | SPI 时钟 |
| MOSI | PA7 | SPI_MOSI | SPI 数据输出 |
| BL | PB5 | TIM3_CH1(PWM)或 GPIO_Output | 背光控制(PWM 调光或开关) |
在写代码之前,我们需要先理清屏幕需要哪些硬件引脚。通常信利LCD会使用SPI接口,并配合几个控制引脚:
- VCC(供电):给屏幕提供电源。一般在硬件电路上通过 LDO(稳压芯片)供电,或者由主控芯片的 GPIO 直接控制电源使能脚。
- RST(复位):用于硬件复位 LCD 屏幕的内部寄存器。
- BL/LEDA(背光控制):控制屏幕亮灭。可以通过普通 GPIO 控制开关,也可以通过 PWM 控制亮度。
- SPI 接口:SCLK(时钟)、MOSI(数据,有些数据手册叫 SDA)、CS(片选)。另外通常还有 DC(数据/命令选择脚,部分屏幕叫 RS),用来告诉屏幕当前发的是指令还是数据。
硬件初始化、SPI 接口封装、背光控制、LCD 逻辑控制、DMA 刷图五个部分。
1,引脚复用功能(GPIO/PWM)说明:
在主控芯片(比如 STM32、ESP32 等)中,引脚是可以多路复用的(AF,Alternate Function)。我们可以把 SCLK、MOSI 配置为 SPI 复用模式,把 RST、DC 配置为普通推挽输出 GPIO,把背光引脚配置为 PWM 复用输出模式。
// lcd_hw.h
#ifndef __LCD_HW_H
#define __LCD_HW_H
#include "stm32f4xx_hal.h"
/* 引脚宏定义 */
#define LCD_VCC_PORT GPIOC
#define LCD_VCC_PIN GPIO_PIN_13
#define LCD_RST_PORT GPIOB
#define LCD_RST_PIN GPIO_PIN_1
#define LCD_DC_PORT GPIOB
#define LCD_DC_PIN GPIO_PIN_0
#define LCD_CS_PORT GPIOA
#define LCD_CS_PIN GPIO_PIN_4
/* 背光 PWM 定时器与通道(若使用 PWM) */
#define LCD_BL_TIM &htim3
#define LCD_BL_CHANNEL TIM_CHANNEL_1
#define LCD_BL_PORT GPIOB
#define LCD_BL_PIN GPIO_PIN_5
/* 操作宏 */
#define LCD_CS_LOW() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET)
#define LCD_CS_HIGH() HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET)
#define LCD_DC_CMD() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET)
#define LCD_DC_DATA() HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET)
#define LCD_RST_HIGH() HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_SET)
#define LCD_RST_LOW() HAL_GPIO_WritePin(LCD_RST_PORT, LCD_RST_PIN, GPIO_PIN_RESET)
#define LCD_VCC_ON() HAL_GPIO_WritePin(LCD_VCC_PORT, LCD_VCC_PIN, GPIO_PIN_SET)
#define LCD_VCC_OFF() HAL_GPIO_WritePin(LCD_VCC_PORT, LCD_VCC_PIN, GPIO_PIN_RESET)
/* 背光控制宏(GPIO 模式) */
#define LCD_BL_ON() HAL_GPIO_WritePin(LCD_BL_PORT, LCD_BL_PIN, GPIO_PIN_SET)
#define LCD_BL_OFF() HAL_GPIO_WritePin(LCD_BL_PORT, LCD_BL_PIN, GPIO_PIN_RESET)
/* 外部 SPI 句柄(需在 main.c 或 lcd_driver.c 中定义) */
extern SPI_HandleTypeDef hspi1;
/* 函数声明 */
void LCD_GPIO_Init(void); // 初始化所有 GPIO 和 PWM
void LCD_SetBrightness(uint8_t duty); // PWM 设置亮度(0~100)
#endif
2. SPI 访问接口封装
将 SPI 发送命令和发送数据的过程封装起来,方便调用。
// lcd_spi.c
#include "lcd_spi.h"
extern SPI_HandleTypeDef hspi1; // 主程序中已初始化
/* 发送命令(DC=0) */
void LCD_WriteCommand(uint8_t cmd)
{
LCD_CS_LOW();
LCD_DC_CMD();
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
/* 发送一个字节数据(DC=1) */
void LCD_WriteData(uint8_t data)
{
LCD_CS_LOW();
LCD_DC_DATA();
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
/* 发送 16 位数据(高字节先发,按 MSB 顺序) */
void LCD_WriteData16(uint16_t data)
{
uint8_t bytes[2];
bytes[0] = (data >> 8) & 0xFF;
bytes[1] = data & 0xFF;
LCD_CS_LOW();
LCD_DC_DATA();
HAL_SPI_Transmit(&hspi1, bytes, 2, HAL_MAX_DELAY);
LCD_CS_HIGH();
}
spi访问接口:

举例lcd cmd参数说明,参考ILI9341数据手册

3. GPIO 控制背光亮灭 与 PWM 控制亮度
如果只用 GPIO 控制,只能开和关;用 PWM 则能做到无极调光。
// lcd_hw.c
#include "lcd_hw.h"
void LCD_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* 使能 GPIO 时钟 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/* CS (PA4) 推挽输出 */
GPIO_InitStruct.Pin = LCD_CS_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(LCD_CS_PORT, &GPIO_InitStruct);
LCD_CS_HIGH();
/* RST (PB1) 推挽输出 */
GPIO_InitStruct.Pin = LCD_RST_PIN;
HAL_GPIO_Init(LCD_RST_PORT, &GPIO_InitStruct);
LCD_RST_HIGH();
/* DC (PB0) 推挽输出 */
GPIO_InitStruct.Pin = LCD_DC_PIN;
HAL_GPIO_Init(LCD_DC_PORT, &GPIO_InitStruct);
LCD_DC_CMD();
/* VCC_EN (PC13) 推挽输出 */
GPIO_InitStruct.Pin = LCD_VCC_PIN;
HAL_GPIO_Init(LCD_VCC_PORT, &GPIO_InitStruct);
LCD_VCC_OFF(); // 默认关闭
/* 背光引脚(PB5)配置为 PWM 复用,若只想开关可配置为普通 GPIO */
GPIO_InitStruct.Pin = LCD_BL_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3; // 根据芯片手册选择 AF
HAL_GPIO_Init(LCD_BL_PORT, &GPIO_InitStruct);
/* 若不用 PWM,可配置为普通输出:
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
HAL_GPIO_Init(LCD_BL_PORT, &GPIO_InitStruct);
*/
/* 注意:PWM 定时器需单独初始化(例如 TIM3)并启动 */
// HAL_TIM_PWM_Start(LCD_BL_TIM, LCD_BL_CHANNEL);
}
/* PWM 设置亮度(占空比 0~100) */
void LCD_SetBrightness(uint8_t duty)
{
if (duty > 100) duty = 100;
uint32_t period = __HAL_TIM_GET_AUTORELOAD(LCD_BL_TIM);
uint32_t compare = (period * duty) / 100;
__HAL_TIM_SET_COMPARE(LCD_BL_TIM, LCD_BL_CHANNEL, compare);
}

4. LCD 初始化序列(参考数据手册)
// lcd_init.c
#include "lcd_spi.h"
#include "lcd_hw.h"
#include "lcd_init.h"
#include <stdint.h>
void LCD_Init(void)
{
// 1. 供电 & 硬件复位
LCD_VCC_ON(); // 开启 LCD 电源(若可控)
HAL_Delay(50); // 等待电源稳定
LCD_RST_LOW();
HAL_Delay(20); // 保持复位至少 10us
LCD_RST_HIGH();
HAL_Delay(120); // 等待退出复位,至少 5ms(数据手册建议 120ms)
// 2. 退出睡眠模式
LCD_WriteCommand(0x11);
HAL_Delay(120); // 需等待 ≥5ms,这里给 120ms
// 3. 设置像素格式为 RGB565(16位)
LCD_WriteCommand(0x3A);
LCD_WriteData(0x55); // 16-bit (5-6-5)
// 4. 内存访问控制(方向与 BGR)
// 0xC8 表示:BGR顺序,行列镜像关,竖屏(根据实际旋转调整)
LCD_WriteCommand(0x36);
LCD_WriteData(0xC8);
// 5. 其他可选设置(关反转、伽马等)
// 略...
// 6. 开启显示
LCD_WriteCommand(0x29);
HAL_Delay(20);
// 7. 开启背光(PWM 或 GPIO)
LCD_SetBrightness(80); // 设置 80% 亮度
}
5. 设置显示窗口与 DMA 高速刷屏
用普通 SPI 发送图像数据太慢了,屏幕会闪烁。我们需要用 DMA(直接内存访问)来搬运数据,让 CPU 解放出来。
// lcd_graphics.c
#include "lcd_graphics.h"
#include "lcd_spi.h"
#include "lcd_hw.h"
/* 设置显示窗口(行列地址) */
void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)
{
// 列地址 (CASET)
LCD_WriteCommand(0x2A);
LCD_WriteData(x1 >> 8);
LCD_WriteData(x1 & 0xFF);
LCD_WriteData(x2 >> 8);
LCD_WriteData(x2 & 0xFF);
// 行地址 (PASET)
LCD_WriteCommand(0x2B);
LCD_WriteData(y1 >> 8);
LCD_WriteData(y1 & 0xFF);
LCD_WriteData(y2 >> 8);
LCD_WriteData(y2 & 0xFF);
// 写存储准备 (RAMWR)
LCD_WriteCommand(0x2C);
}
/* DMA 传输完成回调(需在中断文件中调用) */
extern SPI_HandleTypeDef hspi1;
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
if (hspi->Instance == SPI1) {
LCD_CS_HIGH(); // 传输结束拉高片选
}
}
/* 使用 DMA 发送图像数据(RGB565) */
void LCD_DrawImage_DMA(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t *image)
{
LCD_SetWindow(x, y, x + w - 1, y + h - 1);
LCD_CS_LOW();
LCD_DC_DATA(); // 数据模式
uint32_t data_size = w * h * 2; // 每像素2字节
// 启动 SPI DMA 发送
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)image, data_size);
// 注意:传输完成后,CS 会在回调中拉高
}