基于STM32 HAL库与SPI的ILI9488液晶显示驱动实现
2026-03-30
[!INFO]
文章最后修改时间为2026年03月30日,请注意部分内容可能已改变。如有问题可在评论区提出哦~~~
ILI9488介绍
在这个文档中说明了ILI9488的寄存器应该如何配置。使用SPI通信。其中,当D/CX(DC/RS)为低电平为写命令;当D/CX(DC/RS)为高电平时为写数据。
硬件准备
这里使用的是3.5寸的TFT 480x320屏幕(无触摸)
屏幕与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配置
选择你使用的芯片创建工程
我是用的是f407系列开发板,所以填入168,其他系列按照下面显示的最高频率填入
这里选择开启DMA
这里将PE11连接CS,PE12连接DC,PE13连接RST
这样项目就生成好了
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)实现,速度更快