ILI9341 lcd 驱动适配纪要

信利 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 会在回调中拉高
}