基于STM32 HAL库与SPI的ILI9488液晶显示驱动实现

2026-03-30

[!INFO]
文章最后修改时间为2026年03月30日,请注意部分内容可能已改变。如有问题可在评论区提出哦~~~

ILI9488介绍

ILI9488文档

在这个文档中说明了ILI9488的寄存器应该如何配置。使用SPI通信。其中,当D/CX(DC/RS)为低电平为写命令;当D/CX(DC/RS)为高电平时为写数据。

硬件准备

这里使用的是3.5寸的TFT 480x320屏幕(无触摸)

screen

屏幕与mcu的连接

屏幕 stm32
SDO/MISO MISO
LED 3.3v(可以使用PWM来控制显示屏亮度)
SCK SCK
SDI/MOSI MOSI
DC/RS GPIO(使用没有用到的GPIO引脚)
RESET GPIO(使用没有用到的GPIO引脚)
CS GPIO(使用没有用到的GPIO引脚)
GND GND
VCC 5v

STM32CubeMX配置

选择你使用的芯片创建工程

clock

我是用的是f407系列开发板,所以填入168,其他系列按照下面显示的最高频率填入

clock_config SPI_set

这里选择开启DMA

SPI_DMA

这里将PE11连接CS,PE12连接DC,PE13连接RST

GPIO project project2

这样项目就生成好了

ILI9488驱动代码

ILI9488.h

#ifndef ILI9488_H
#define ILI9488_H

#include "main.h"
#include "spi.h"
#include "gpio.h"
#include <stdint.h>

#define CS_Pin GPIO_PIN_11 //这里更换为你选用的CS引脚
#define CS_GPIO_Port GPIOE //这里更换为你选用的CS引脚
#define DC_Pin GPIO_PIN_12 //这里更换为你选用的DC引脚
#define DC_GPIO_Port GPIOE //这里更换为你选用的DC引脚
#define RST_Pin GPIO_PIN_13 //这里更换为你选用的RST引脚
#define RST_GPIO_Port GPIOE //这里更换为你选用的RST引脚
  
#define LCD_WIDTH 320  //这里改为你屏幕的宽高
#define LCD_HEIGHT 480

#define CS_LOW()    HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET)
#define CS_HIGH()   HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET)
#define DC_LOW()    HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_RESET)
#define DC_HIGH()   HAL_GPIO_WritePin(DC_GPIO_Port, DC_Pin, GPIO_PIN_SET)
#define RST_LOW()   HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_RESET)
#define RST_HIGH()  HAL_GPIO_WritePin(RST_GPIO_Port, RST_Pin, GPIO_PIN_SET)

#define LCD_WHITE       0xFFFF
#define LCD_BLACK       0x0000    
#define LCD_BLUE        0x001F  
#define LCD_BRED        0XF81F
#define LCD_GRED        0XFFE0
#define LCD_GBLUE       0X07FF
#define LCD_RED         0xF800
#define LCD_MAGENTA     0xF81F
#define LCD_GREEN       0x07E0
#define LCD_CYAN        0x7FFF
#define LCD_YELLOW      0xFFE0
#define LCD_BROWN       0XBC40
#define LCD_BRRED       0XFC07
#define LCD_GRAY        0X8430

typedef enum {
    ILI9488_DMA_IDLE = 0,
    ILI9488_DMA_BUSY,
    ILI9488_DMA_ERROR
} ILI9488_DMA_State;

typedef enum {
    ILI9488_ROTATION_0   = 0,    // 正常方向
    ILI9488_ROTATION_90  = 1,    // 顺时针90度
    ILI9488_ROTATION_180 = 2,    // 180度
    ILI9488_ROTATION_270 = 3     // 顺时针270度
} ILI9488_Rotation;

typedef void (*ILI9488_DMATxCpltCallback)(void);
void LCD_WriteCommand(uint8_t command);
void LCD_WriteData(uint8_t data);
void LCD_WriteData16(uint16_t data);
void LCD_WriteDataBuffer16(const uint16_t* buffer, uint32_t len);
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi);
void LCD_WaitDMAComplete(void);
ILI9488_DMA_State ILI9488_GetDMAState(void);
void ILI9488_SetDMAState(ILI9488_DMA_State state);
void ILI9488_SetDMACallback(ILI9488_DMATxCpltCallback callback);
void LCD_Reset(void);
void LCD_Init(void);
void LCD_SetRotation(ILI9488_Rotation rotation);
void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color);
void LCD_FillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void LCD_DrawImageRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, const uint16_t *colors);
void LCD_FillColor(uint16_t color);
void LCD_InitDMA(void);
uint16_t LCD_ReadID(void);
#endif

ILI9488.c

#include "ILI9488.h"
#include <string.h>
#include <stdio.h>

static ILI9488_DMA_State dma_state = ILI9488_DMA_IDLE;
static ILI9488_DMATxCpltCallback dma_callback = NULL;
static volatile uint8_t dma_complete_flag = 0;
static ILI9488_Rotation lcd_rotation = ILI9488_ROTATION_0;

/**
 * @brief 将16位565颜色格式转换为18位666格式(用于ILI9488的SPI传输)
 * @param color 16位565颜色值
 * @param rgb   输出缓冲区,存储3字节的666颜色数据
 */
static void LCD_Color565To666(uint16_t color, uint8_t *rgb){
	rgb[0] = ((color >> 11) & 0x1F) << 3;
	rgb[1] = ((color >> 5) & 0x3F) << 2;
	rgb[2] = (color & 0x1F) << 3;
}

/**
 * @brief 根据当前旋转方向获取显示宽度
 * @return 当前显示宽度(像素)
 */
static uint16_t LCD_GetWidth(void){
    return (lcd_rotation == ILI9488_ROTATION_90 || lcd_rotation == ILI9488_ROTATION_270) ? LCD_HEIGHT : LCD_WIDTH;
}

/**
 * @brief 根据当前旋转方向获取显示高度
 * @return 当前显示高度(像素)
 */
static uint16_t LCD_GetHeight(void){
    return (lcd_rotation == ILI9488_ROTATION_90 || lcd_rotation == ILI9488_ROTATION_270) ? LCD_WIDTH : LCD_HEIGHT;
}

/**
 * @brief 向ILI9488写入命令字节
 * @param command 要写入的命令字节
 */
void LCD_WriteCommand(uint8_t command){
	DC_LOW();
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, &command, 1, HAL_MAX_DELAY);
    CS_HIGH();
}

/**
 * @brief 向ILI9488写入数据字节
 * @param data 要写入的数据字节
 */
void LCD_WriteData(uint8_t data){
	DC_HIGH();
	CS_LOW();
	HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
	CS_HIGH();
}

/**
 * @brief 向ILI9488写入16位颜色数据(自动转换为18位)
 * @param data 16位565颜色值
 */
void LCD_WriteData16(uint16_t data){
	uint8_t rgb[3];
	LCD_Color565To666(data, rgb);
	DC_HIGH();
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, rgb, 3, HAL_MAX_DELAY);
    CS_HIGH();
}

/**
 * @brief 批量写入16位颜色数据缓冲区(阻塞方式,非DMA)
 * @param buffer 颜色数据缓冲区指针
 * @param len    缓冲区中的颜色数量
 */
void LCD_WriteDataBuffer16(const uint16_t* buffer, uint32_t len){
	if (len == 0) return;

    DC_HIGH();
    CS_LOW();
    
    for (uint32_t i = 0; i < len; i++) {
    		uint8_t rgb[3];
    		LCD_Color565To666(buffer[i], rgb);
    		HAL_SPI_Transmit(&hspi1, rgb, 3, HAL_MAX_DELAY);
    }
  
    CS_HIGH();
}

/**
 * @brief SPI1 DMA传输完成回调函数
 * @param hspi SPI句柄指针
 */
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi){
	if (hspi->Instance == SPI1) {
		dma_complete_flag = 1;
		dma_state = ILI9488_DMA_IDLE;

		if (dma_callback != NULL) {
				dma_callback();
		}
  }
}

/**
 * @brief 等待DMA传输完成,带超时机制
 */
void LCD_WaitDMAComplete(void){
	uint32_t start_time = HAL_GetTick();

	while(dma_complete_flag == 0){
		if(HAL_GetTick() - start_time > 2000){
			dma_state = ILI9488_DMA_ERROR;
			break;
		}
		for(volatile int i = 0; i < 100; i++);
	}
}

/**
 * @brief 获取当前DMA状态
 * @return DMA状态枚举值
 */
ILI9488_DMA_State ILI9488_GetDMAState(void) {
    return dma_state;
}

/**
 * @brief 设置DMA状态
 * @param state 要设置的状态
 */
void ILI9488_SetDMAState(ILI9488_DMA_State state) {
    dma_state = state;
}

/**
 * @brief 设置DMA传输完成回调函数
 * @param callback 回调函数指针
 */
void ILI9488_SetDMACallback(ILI9488_DMATxCpltCallback callback) {
    dma_callback = callback;
}

/**
 * @brief 设置显示旋转角度
 * @param rotation 旋转角度枚举值
 */
void LCD_SetRotation(ILI9488_Rotation rotation) {
    uint8_t madctl;

    switch (rotation) {
        case ILI9488_ROTATION_0:
            madctl = 0x48;
            break;
        case ILI9488_ROTATION_90:
            madctl = 0x28;
            break;
        case ILI9488_ROTATION_180:
            madctl = 0x88;
            break;
        case ILI9488_ROTATION_270:
            madctl = 0xE8;
            break;
        default:
            madctl = 0x48;
            rotation = ILI9488_ROTATION_0;
            break;
    }

    LCD_WriteCommand(0x36);
    LCD_WriteData(madctl);
    lcd_rotation = rotation;
}

/**
 * @brief 硬件复位ILI9488
 */
void LCD_Reset(void) {
    RST_HIGH();
    HAL_Delay(100);
    RST_LOW();
    HAL_Delay(200);
    RST_HIGH();
    HAL_Delay(200);
}

/**
 * @brief 初始化ILI9488,配置寄存器并设置初始显示
 */
void LCD_Init(void){
	LCD_Reset();

	LCD_WriteCommand(0x01);
	HAL_Delay(10);

	LCD_WriteCommand(0xE0);
	uint8_t gammaP[] = {0x00, 0x03, 0x09, 0x08, 0x16, 0x0A, 0x3F, 0x78, 0x4C, 0x09, 0x0A, 0x08, 0x16, 0x1A, 0x0F};
	for(uint8_t i = 0; i < 15; i++) LCD_WriteData(gammaP[i]);

	LCD_WriteCommand(0xE1);
	uint8_t gammaN[] = {0x00, 0x16, 0x19, 0x03, 0x0F, 0x05, 0x32, 0x45, 0x46, 0x04, 0x0E, 0x0D, 0x35, 0x37, 0x0F};
	for(uint8_t i = 0; i < 15; i++) LCD_WriteData(gammaN[i]);

	LCD_WriteCommand(0xC0);
	LCD_WriteData(0x17);
	LCD_WriteData(0x15);

	LCD_WriteCommand(0xC1);
	LCD_WriteData(0x41);

	LCD_WriteCommand(0xC5);
	LCD_WriteData(0x00);
	LCD_WriteData(0x12);
	LCD_WriteData(0x80);

	LCD_SetRotation(ILI9488_ROTATION_0);

	LCD_WriteCommand(0x3A);
	LCD_WriteData(0x66);

	LCD_WriteCommand(0xB0);
	LCD_WriteData(0x00);

	LCD_WriteCommand(0xB1);
	LCD_WriteData(0xA0);

	LCD_WriteCommand(0xB4);
	LCD_WriteData(0x02);

	LCD_WriteCommand(0xB6);
	LCD_WriteData(0x02);
	LCD_WriteData(0x02);

	LCD_WriteCommand(0xE9);
	LCD_WriteData(0x00);

	LCD_WriteCommand(0xF7);
	LCD_WriteData(0xA9);
	LCD_WriteData(0x51);
	LCD_WriteData(0x2C);
	LCD_WriteData(0x82);

	LCD_WriteCommand(0x11);
	HAL_Delay(120);

	LCD_WriteCommand(0x29);
	HAL_Delay(20);
	LCD_FillColor(LCD_BLACK);
}

/**
 * @brief 读取ILI9488的ID寄存器
 * @return 芯片ID(3字节中的后2字节组成)
 */
uint16_t LCD_ReadID(void) {
    uint8_t rx[3] = {0};
    uint8_t cmd = 0x04;
    DC_LOW();
    CS_LOW();
    HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
    DC_HIGH();
    HAL_SPI_Receive(&hspi1, rx, 3, HAL_MAX_DELAY);
    CS_HIGH();
    return (rx[1] << 8) | rx[2];
}

/**
 * @brief 设置显示窗口(列地址和行地址范围)
 * @param x0 起始列坐标
 * @param y0 起始行坐标
 * @param x1 结束列坐标
 * @param y1 结束行坐标
 */
void LCD_SetWindow(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
	LCD_WriteCommand(0x2A);
	LCD_WriteData(x0 >> 8);
	LCD_WriteData(x0 & 0xFF);
	LCD_WriteData(x1 >> 8);
	LCD_WriteData(x1 & 0xFF);
    
	LCD_WriteCommand(0x2B);
	LCD_WriteData(y0 >> 8);
	LCD_WriteData(y0 & 0xFF);
	LCD_WriteData(y1 >> 8);
	LCD_WriteData(y1 & 0xFF);
}

/**
 * @brief 在指定位置绘制一个像素点
 * @param x      X坐标
 * @param y      Y坐标
 * @param color  16位565颜色值
 */
void LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color) {
    uint16_t width = LCD_GetWidth();
    uint16_t height = LCD_GetHeight();
    if (x >= width || y >= height) return;
    LCD_SetWindow(x, y, x, y);
    LCD_WriteCommand(0x2C);
    LCD_WriteData16(color);
}

/**
 * @brief 用指定颜色填充矩形区域(使用DMA传输)
 * @param x1    左上角X坐标
 * @param y1    左上角Y坐标
 * @param x2    右下角X坐标
 * @param y2    右下角Y坐标
 * @param color 填充颜色(16位565)
 */
void LCD_FillRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color) {
    if (x1 > x2) {
        uint16_t t = x1;
        x1 = x2;
        x2 = t;
    }
    if (y1 > y2) {
        uint16_t t = y1;
        y1 = y2;
        y2 = t;
    }

    uint16_t width = LCD_GetWidth();
    uint16_t height = LCD_GetHeight();

    if (x1 >= width || y1 >= height) return;
    if (x2 >= width) x2 = width - 1;
    if (y2 >= height) y2 = height - 1;

    LCD_SetWindow(x1, y1, x2, y2);
    LCD_WriteCommand(0x2C);

    uint8_t rgb[3];
    LCD_Color565To666(color, rgb);

    enum { PIXELS_PER_CHUNK = 1024 };
    static uint8_t tx_chunk[PIXELS_PER_CHUNK * 3];

    for (uint32_t i = 0; i < PIXELS_PER_CHUNK; i++) {
        uint32_t idx = i * 3;
        tx_chunk[idx] = rgb[0];
        tx_chunk[idx + 1] = rgb[1];
        tx_chunk[idx + 2] = rgb[2];
    }

    DC_HIGH();
    CS_LOW();

    uint32_t remain = (uint32_t)(x2 - x1 + 1) * (uint32_t)(y2 - y1 + 1);
    while (remain > 0) {
        uint32_t pixels = (remain > PIXELS_PER_CHUNK) ? PIXELS_PER_CHUNK : remain;

        dma_complete_flag = 0;
        dma_state = ILI9488_DMA_BUSY;

        if (HAL_SPI_Transmit_DMA(&hspi1, tx_chunk, (uint16_t)(pixels * 3)) != HAL_OK) {
            dma_state = ILI9488_DMA_ERROR;
            break;
        }

        LCD_WaitDMAComplete();
        if (dma_state == ILI9488_DMA_ERROR) {
            break;
        }

        remain -= pixels;
    }

    CS_HIGH();
}

/**
 * @brief 在指定矩形区域显示图像数据(使用DMA传输)
 * @param x1     左上角X坐标
 * @param y1     左上角Y坐标
 * @param x2     右下角X坐标
 * @param y2     右下角Y坐标
 * @param colors 图像颜色数组(16位565格式)
 */
void LCD_DrawImageRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, const uint16_t *colors) {
    if (colors == NULL) return;

    if (x1 > x2) {
        uint16_t t = x1;
        x1 = x2;
        x2 = t;
    }
    if (y1 > y2) {
        uint16_t t = y1;
        y1 = y2;
        y2 = t;
    }

    uint16_t width = LCD_GetWidth();
    uint16_t height = LCD_GetHeight();

    if (x1 >= width || y1 >= height) return;
    if (x2 >= width) x2 = width - 1;
    if (y2 >= height) y2 = height - 1;

    LCD_SetWindow(x1, y1, x2, y2);
    LCD_WriteCommand(0x2C);

    enum { PIXELS_PER_CHUNK = 512 };
    static uint8_t tx_chunk[PIXELS_PER_CHUNK * 3];

    uint32_t remain = (uint32_t)(x2 - x1 + 1) * (uint32_t)(y2 - y1 + 1);
    uint32_t offset = 0;

    DC_HIGH();
    CS_LOW();

    while (remain > 0) {
        uint32_t pixels = (remain > PIXELS_PER_CHUNK) ? PIXELS_PER_CHUNK : remain;

        for (uint32_t i = 0; i < pixels; i++) {
            uint8_t *rgb = &tx_chunk[i * 3];
            LCD_Color565To666(colors[offset + i], rgb);
        }

        dma_complete_flag = 0;
        dma_state = ILI9488_DMA_BUSY;

        if (HAL_SPI_Transmit_DMA(&hspi1, tx_chunk, (uint16_t)(pixels * 3)) != HAL_OK) {
            dma_state = ILI9488_DMA_ERROR;
            break;
        }

        LCD_WaitDMAComplete();
        if (dma_state == ILI9488_DMA_ERROR) {
            break;
        }

        offset += pixels;
        remain -= pixels;
    }

    CS_HIGH();
}

/**
 * @brief 用指定颜色填充整个屏幕(使用DMA传输)
 * @param color 填充颜色(16位565)
 */
void LCD_FillColor(uint16_t color) {
    uint16_t width = LCD_GetWidth();
    uint16_t height = LCD_GetHeight();
    LCD_SetWindow(0, 0, width - 1, height - 1);
    LCD_WriteCommand(0x2C);

    uint8_t rgb[3];
    LCD_Color565To666(color, rgb);

    enum { PIXELS_PER_CHUNK = 1024 };
    static uint8_t tx_chunk[PIXELS_PER_CHUNK * 3];

    for (uint32_t i = 0; i < PIXELS_PER_CHUNK; i++) {
        uint32_t idx = i * 3;
        tx_chunk[idx] = rgb[0];
        tx_chunk[idx + 1] = rgb[1];
        tx_chunk[idx + 2] = rgb[2];
    }

    DC_HIGH();
    CS_LOW();

    uint32_t remain = (uint32_t)width * height;
    while (remain > 0) {
        uint32_t pixels = (remain > PIXELS_PER_CHUNK) ? PIXELS_PER_CHUNK : remain;

        dma_complete_flag = 0;
        dma_state = ILI9488_DMA_BUSY;

        if (HAL_SPI_Transmit_DMA(&hspi1, tx_chunk, (uint16_t)(pixels * 3)) != HAL_OK) {
            dma_state = ILI9488_DMA_ERROR;
            break;
        }

        LCD_WaitDMAComplete();
        if (dma_state == ILI9488_DMA_ERROR) {
            break;
        }

        remain -= pixels;
    }

    CS_HIGH();
}

/**
 * @brief 初始化ILI9488并配置DMA传输相关资源
 * @note 该函数会调用LCD_Init(),并在初始化完成后清屏为黑色
 */
void LCD_InitDMA(void) {
    LCD_Init();

    dma_state = ILI9488_DMA_IDLE;
    dma_callback = NULL;
    dma_complete_flag = 0;
    
    LCD_FillColor(LCD_BLACK);
}

代码使用

注意在USER CODE BEGIN 2插入代码

  /* USER CODE BEGIN 2 */
  LCD_InitDMA(); //LCD初始化
  /* USER CODE END 2 */

以下测试代码的作用是屏幕每隔1秒变一种颜色

/* USER CODE BEGIN WHILE */
  while (1)
  {
	LCD_FillColor(LCD_RED);//红色
	HAL_Delay(1000);
	LCD_FillColor(LCD_YELLOW);//黄色
	HAL_Delay(1000);
	LCD_FillColor(LCD_BLUE);//蓝色
	HAL_Delay(1000);
    /* USER CODE END WHILE */

至此,我们基于STM32 HAL库完成了对ILI9488屏幕的驱动开发。

[!TIP]
后期可以移植LVGL图形库来做GUI。
其中LCD_DrawPixel(uint16_t x, uint16_t y, uint16_t color)为画点函数
也可以使用LCD_DrawImageRect(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, const uint16_t *colors)实现,速度更快