ILI9341显示驱动实现
前言
实验硬件:STM32F407ZGT6;2.4寸TFT-LCD模块(ILI9341驱动芯片、XTP2046触摸芯片)
硬件实物图:
最终目标:使用STMF407ZGT6驱动2.4寸TFT-LCD触摸屏,并移植LVGL库
本节目标:ILI9341显示芯片驱动实现
软件:STM32CubeMX;Keil
完整项目在文章末尾github链接中
一、TFT-LCD介绍
目前最常用的触摸屏有两种:电阻式触摸屏和电容式触摸屏。
1、电阻屏
电阻屏的主要部分是一块与显示器表面配合非常好的电阻薄膜屏,这是一种多层的复合薄膜,由一层玻璃或有机玻璃作为基层,表面涂有一层秀明的导电层,上面再盖有一层外表硬化处理、光滑防刮的塑料层,它的内表面也涂有一层透明导电层,在两层导电层之间有许多细小(小于千分之一英寸)的透明隔离点把它们隔开绝缘。当手指触摸屏幕时,平常绝缘的两层导电层在触摸点位置就有了一个接触,控制器侦测到这个接通后,其中一面导电层接通У轴方向的5Ⅴ均匀电压场,另一导电层将接触点的电压引至控制卡进行A/D转换,得到电压值后与5Ⅴ相比即可得触摸点的у轴坐标,同理得出Χ轴的坐标,这就是所有电阻技术触摸屏共同的最基本原理。
2、ILI9341介绍
ILI9341控制器支持的最大分辨率为240×320,拥有一个172800字节大小的GRAM。同时支持8位、9位、16位、18位并口数据总线,还支持3线制和4线制SPI串口。由于并行控制需要大量的IO口,所以最常用的还是SPI串口控制。ILI9341还支持65K、262K RGB颜色显示,显示色彩很丰富,同时支持旋转显示和滚动显示以及视频播放,显示方式多样。
ILI9341控制器使用16bit(RGB565)来控制一个像素点显示,因此可以每个像素点显示颜色多达65K种。像素点地址设置按照行列的顺序进行,遵增递减方向由扫描方式决定。
3、硬件接口
模块引脚 | 引脚说明 |
---|---|
VCC | 电源 |
GND | 地 |
CS | LCD片选信号 |
RESET | LCD复位信号 |
DC | LCD寄存器/数据选择信号 |
SDI(MOSI) | LCD SPI总线写信号 |
SCK | LCD SPI总线时钟线 |
LED | 背光控制(高电平亮) |
SDO(MISO) | LCD SPI总线读信号 |
T_CLK | 触摸 SPI总线时钟 |
T_CS | 触摸片选信号 |
T_DIN | 触摸 SPI总线读信号 |
T_DO | 触摸 SPI总线写信号 |
T_IRQ | 触摸中断信号 |
4、SPI通讯
自己随便找下学一下吧
二、CubeMx设置
根据自己所用芯片自行调整
1、配置外部高速晶振
2、配置SYS
3、SPI配置,用于驱动LCD屏
4、TIM1配置,提供us延时函数
5、GPIO配置,只实现显示只需配置PB12、PB13、PB14、PB15即可,其他为触摸驱动IO,可暂不配置
6、时钟树配置168MHZ
三、LCD显示驱动实现
本文只展示部分关键代码,完整程序请至github自行获取。此外,由于本文的最终目标为移植LVGL库,ILI9341驱动只完成了基本功能,其他GUI功能可自行编写。
- ili9341.h
#ifndef __ILI9341_H__
#define __ILI9341_H__
#include <stdint.h>
#include "main.h"
#include "spi.h"
#include "gpio.h"
#include "stm32f4xx_hal.h"
#include "delay.h"
//LCD重要参数集
typedef struct
{
uint16_t width; //LCD 宽度
uint16_t height; //LCD 高度
uint16_t id; //LCD ID
uint8_t dir; //横屏还是竖屏控制:0,竖屏;1,横屏。
uint16_t wramcmd; //开始写gram指令
uint16_t setxcmd; //设置x坐标指令
uint16_t setycmd; //设置y坐标指令
}_lcd_dev;
//LCD参数
extern _lcd_dev lcddev; //管理LCD重要参数
#define USE_HORIZONTAL 0//定义液晶屏顺时针旋转方向 0-0度旋转,1-90度旋转,2-180度旋转,3-270度旋转
// LCD尺寸定义
#define LCD_WIDTH 240 // LCD宽度
#define LCD_HEIGHT 320 // LCD高度
// 定义GPIO端口和引脚
#define LCD_RST_GPIO_Port GPIOB
#define LCD_RST_Pin GPIO_PIN_12
#define LCD_DC_GPIO_Port GPIOB
#define LCD_DC_Pin GPIO_PIN_14
#define LCD_CS_GPIO_Port GPIOB
#define LCD_CS_Pin GPIO_PIN_15
#define LCD_LED_GPIO_Port GPIOB
#define LCD_LED_Pin GPIO_PIN_13
// 控制信号宏定义
#define LCD_RST_Clr() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_RESET)
#define LCD_RST_Set() HAL_GPIO_WritePin(LCD_RST_GPIO_Port, LCD_RST_Pin, GPIO_PIN_SET)
#define LCD_DC_Clr() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_RESET)
#define LCD_DC_Set() HAL_GPIO_WritePin(LCD_DC_GPIO_Port, LCD_DC_Pin, GPIO_PIN_SET)
#define LCD_CS_Clr() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_RESET)
#define LCD_CS_Set() HAL_GPIO_WritePin(LCD_CS_GPIO_Port, LCD_CS_Pin, GPIO_PIN_SET)
#define LCD_LED_ON() HAL_GPIO_WritePin(LCD_LED_GPIO_Port, LCD_LED_Pin, GPIO_PIN_SET)
#define LCD_LED_OFF() HAL_GPIO_WritePin(LCD_LED_GPIO_Port, LCD_LED_Pin, GPIO_PIN_RESET)
//画笔颜色
#define WHITE 0xFFFF
#define BLACK 0x0000
#define BLUE 0x001F
#define BRED 0XF81F
#define GRED 0XFFE0
#define GBLUE 0X07FF
#define RED 0xF800
#define MAGENTA 0xF81F
#define GREEN 0x07E0
#define CYAN 0x7FFF
#define YELLOW 0xFFE0
#define BROWN 0XBC40 //棕色
#define BRRED 0XFC07 //棕红色
#define GRAY 0X8430 //灰色
//GUI颜色
#define DARKBLUE 0X01CF //深蓝色
#define LIGHTBLUE 0X7D7C //浅蓝色
#define GRAYBLUE 0X5458 //灰蓝色
//以上三色为PANEL的颜色
#define LIGHTGREEN 0X841F //浅绿色
#define LIGHTGRAY 0XEF5B //浅灰色(PANNEL)
#define LGRAY 0XC618 //浅灰色(PANNEL),窗体背景色
#define LGRAYBLUE 0XA651 //浅灰蓝色(中间层颜色)
#define LBBLUE 0X2B12 //浅棕蓝色(选择条目的反色)
void LCD_Init(void);
void LCD_Clear(uint16_t color);
void LCD_BackLight_On(void);
void LCD_BackLight_Off(void);
void LCD_DrawPoint(uint16_t startX, uint16_t startY, uint16_t color);
void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color);
void LCD_DrawFilledCircle(uint16_t centerX, uint16_t centerY, uint16_t radius, uint16_t color);
#endif // __LCD_H__
- ili9341.c
#include "ili9341.h"
_lcd_dev lcddev;
/**
* @brief 发送命令到ST7789显示屏
* @param cmd: 要发送的命令字节
* @note 发送命令时,DC引脚需要置低,CS引脚先置低后置高
* @retval None
*/
void LCD_SendCmd(uint8_t cmd)
{
LCD_DC_Clr(); // 命令模式
LCD_CS_Clr(); // 片选使能
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
LCD_CS_Set(); // 片选禁用
}
/**
* @brief 发送数据到ST7789显示屏
* @param data: 要发送的数据字节
* @note 发送数据时,DC引脚需要置高,CS引脚先置低后置高
* @retval None
*/
void LCD_SendData(uint8_t data)
{
LCD_DC_Set(); // 数据模式
LCD_CS_Clr(); // 片选使能
HAL_SPI_Transmit(&hspi1, &data, 1, HAL_MAX_DELAY);
LCD_CS_Set(); // 片选禁用
}
/**
* @brief 向LCD写入寄存器和数据
* @param reg: 寄存器地址
* @param data: 要写入的数据
* @retval None
*/
void LCD_WriteReg(uint8_t reg, uint16_t data)
{
LCD_SendCmd(reg);
LCD_SendData(data);
}
/**
* @brief 准备向LCD GRAM写入数据
* @note 在写入像素数据之前需要调用此函数
* @param None
* @retval None
*/
void LCD_WriteRAM_Prepare(void)
{
LCD_SendCmd(lcddev.wramcmd);
}
/**
* @brief 向LCD写入16位数据
* @param data: 要写入的16位数据
* @retval None
*/
void LCD_WriteData_16Bit(uint16_t data)
{
LCD_DC_Set(); // 数据模式
LCD_CS_Clr(); // 片选使能
// 先发送高8位,再发送低8位
HAL_SPI_Transmit(&hspi1, (uint8_t*)&data + 1, 1, HAL_MAX_DELAY);
HAL_SPI_Transmit(&hspi1, (uint8_t*)&data, 1, HAL_MAX_DELAY);
LCD_CS_Set(); // 片选禁用
}
void LCD_RESET(void)
{
LCD_RST_Clr();
delay_ms(50);
LCD_RST_Set();
delay_ms(50);
}
void LCD_BackLight_On(void){
HAL_GPIO_WritePin(LCD_LED_GPIO_Port, LCD_LED_Pin, GPIO_PIN_SET);
}
void LCD_BackLight_Off(void){
HAL_GPIO_WritePin(LCD_LED_GPIO_Port, LCD_LED_Pin, GPIO_PIN_RESET);
}
/**
* @brief 设置LCD显示窗口
* @details 设置显示窗口后可以连续发送颜色数据,无需重复设置坐标
* @param startX: 窗口起点x轴坐标
* @param startY: 窗口起点y轴坐标
* @param width: 窗口宽度
* @param height: 窗口高度
* @note 设置显示区域后会自动开启写入模式(RAMWR)
* @retval None
*/
void LCD_SetWindow(uint16_t startX, uint16_t startY, uint16_t width, uint16_t height)
{
uint16_t endX = startX + width - 1;
uint16_t endY = startY + height - 1;
// 设置列地址范围
LCD_SendCmd(0x2A); // Column Address Set
LCD_SendData(startX >> 8); // 起始列地址高8位
LCD_SendData(startX & 0xFF); // 起始列地址低8位
LCD_SendData(endX >> 8); // 结束列地址高8位
LCD_SendData(endX & 0xFF); // 结束列地址低8位
// 设置行地址范围
LCD_SendCmd(0x2B); // Row Address Set
LCD_SendData(startY >> 8); // 起始行地址高8位
LCD_SendData(startY & 0xFF); // 起始行地址低8位
LCD_SendData(endY >> 8); // 结束行地址高8位
LCD_SendData(endY & 0xFF); // 结束行地址低8位
// 开始写入显存
LCD_SendCmd(0x2C); // Memory Write
}
/**
* @brief 设置LCD光标位置
* @param x: x坐标
* @param y: y坐标
* @retval None
*/
void LCD_SetCursor(uint16_t x, uint16_t y)
{
LCD_SetWindow(x, y, 1, 1);
}
/**
* @brief 使用指定颜色填充整个屏幕
* @param color: 16位RGB565格式的颜色值
* @note 使用连续写入模式快速填充
* @retval None
*/
void LCD_Clear(uint16_t color)
{
uint32_t total_pixels = lcddev.width * lcddev.height;
uint8_t color_high = color >> 8;
uint8_t color_low = color & 0xFF;
// 设置全屏显示窗口
LCD_SetWindow(0, 0, lcddev.width-1, lcddev.height-1);
// 连续发送颜色数据
LCD_DC_Set(); // 数据模式
LCD_CS_Clr(); // 片选使能
for(uint32_t i = 0; i < total_pixels; i++) {
HAL_SPI_Transmit(&hspi1, &color_high, 1, HAL_MAX_DELAY);
HAL_SPI_Transmit(&hspi1, &color_low, 1, HAL_MAX_DELAY);
}
LCD_CS_Set(); // 片选禁用
}
/**
* @brief 设置LCD显示方向
* @param direction: 显示方向
* 0: 0度 (默认竖屏)
* 1: 90度 (顺时针转90度)
* 2: 180度 (顺时针转180度)
* 3: 270度 (顺时针转270度)
* @note 设置方向的同时会更新LCD设备的宽度和高度参数
* @retval None
*/
void LCD_SetDirection(uint8_t direction)
{
// 设置基本的LCD命令
lcddev.setxcmd = 0x2A; // 列地址设置命令
lcddev.setycmd = 0x2B; // 行地址设置命令
lcddev.wramcmd = 0x2C; // 写GRAM命令
switch(direction)
{
case 0: // 0度
lcddev.width = LCD_WIDTH;
lcddev.height = LCD_HEIGHT;
// BGR=1,MY=0,MX=0,MV=0: 正常显示,不翻转,不旋转
LCD_SendCmd(0x36);
LCD_SendData((1<<3)|(0<<6)|(0<<7));
break;
case 1: // 90度
lcddev.width = LCD_HEIGHT;
lcddev.height = LCD_WIDTH;
// BGR=1,MY=0,MX=1,MV=1: X镜像,行列交换
LCD_SendCmd(0x36);
LCD_SendData((1<<3)|(0<<7)|(1<<6)|(1<<5));
break;
case 2: // 180度
lcddev.width = LCD_WIDTH;
lcddev.height = LCD_HEIGHT;
// BGR=1,MY=1,MX=1,MV=0: X和Y都镜像
LCD_SendCmd(0x36);
LCD_SendData((1<<3)|(1<<6)|(1<<7));
break;
case 3: // 270度
lcddev.width = LCD_HEIGHT;
lcddev.height = LCD_WIDTH;
// BGR=1,MY=1,MX=0,MV=1: Y镜像,行列交换
LCD_SendCmd(0x36);
LCD_SendData((1<<3)|(1<<7)|(1<<5));
break;
default:
break;
}
}
void LCD_Init(void)
{
// 复位LCD
LCD_RESET();
delay_ms(100);
// 开始初始化LCD
LCD_SendCmd(0xCF);
LCD_SendData(0x00);
LCD_SendData(0xD9);
LCD_SendData(0X30);
LCD_SendCmd(0xED);
LCD_SendData(0x64);
LCD_SendData(0x03);
LCD_SendData(0X12);
LCD_SendData(0X81);
LCD_SendCmd(0xE8);
LCD_SendData(0x85);
LCD_SendData(0x10);
LCD_SendData(0x7A);
LCD_SendCmd(0xCB);
LCD_SendData(0x39);
LCD_SendData(0x2C);
LCD_SendData(0x00);
LCD_SendData(0x34);
LCD_SendData(0x02);
LCD_SendCmd(0xF7);
LCD_SendData(0x20);
LCD_SendCmd(0xEA);
LCD_SendData(0x00);
LCD_SendData(0x00);
// Power Control
LCD_SendCmd(0xC0);
LCD_SendData(0x1B);
LCD_SendCmd(0xC1);
LCD_SendData(0x12);
// VCM Control
LCD_SendCmd(0xC5);
LCD_SendData(0x08);
LCD_SendData(0x26);
LCD_SendCmd(0xC7);
LCD_SendData(0XB7);
// Memory Access Control
LCD_SendCmd(0x36);
LCD_SendData(0x08);
LCD_SendCmd(0x3A);
LCD_SendData(0x55);
LCD_SendCmd(0xB1);
LCD_SendData(0x00);
LCD_SendData(0x1A);
// Display Function Control
LCD_SendCmd(0xB6);
LCD_SendData(0x0A);
LCD_SendData(0xA2);
// 3Gamma Function
LCD_SendCmd(0xF2);
LCD_SendData(0x00);
LCD_SendCmd(0x26);
LCD_SendData(0x01);
// Set Gamma
LCD_SendCmd(0xE0);
LCD_SendData(0x0F);
LCD_SendData(0x1D);
LCD_SendData(0x1A);
LCD_SendData(0x0A);
LCD_SendData(0x0D);
LCD_SendData(0x07);
LCD_SendData(0x49);
LCD_SendData(0x66);
LCD_SendData(0x3B);
LCD_SendData(0x07);
LCD_SendData(0x11);
LCD_SendData(0x01);
LCD_SendData(0x09);
LCD_SendData(0x05);
LCD_SendData(0x04);
LCD_SendCmd(0XE1);
LCD_SendData(0x00);
LCD_SendData(0x18);
LCD_SendData(0x1D);
LCD_SendData(0x02);
LCD_SendData(0x0F);
LCD_SendData(0x04);
LCD_SendData(0x36);
LCD_SendData(0x13);
LCD_SendData(0x4C);
LCD_SendData(0x07);
LCD_SendData(0x13);
LCD_SendData(0x0F);
LCD_SendData(0x2E);
LCD_SendData(0x2F);
LCD_SendData(0x05);
// 设置显示区域
LCD_SendCmd(0x2B);
LCD_SendData(0x00);
LCD_SendData(0x00);
LCD_SendData(0x01);
LCD_SendData(0x3F);
LCD_SendCmd(0x2A);
LCD_SendData(0x00);
LCD_SendData(0x00);
LCD_SendData(0x00);
LCD_SendData(0xEF);
// 退出睡眠模式
LCD_SendCmd(0x11);
HAL_Delay(120);
// 开启显示
LCD_SendCmd(0x29);
// 设置显示方向
LCD_SetDirection(USE_HORIZONTAL);
// 开启背光并清屏
LCD_BackLight_On();
LCD_Clear(WHITE);
}
/*************************以下为GUI**********************************************************************************/
/**
* @brief 在指定位置画点
* @param startX: 点的X坐标
* @param startY: 点的Y坐标
* @param color: 点的颜色(RGB565格式)
* @note 使用SetWindow函数设置1x1的显示区域后写入颜色数据
* @retval None
*/
void LCD_DrawPoint(uint16_t startX, uint16_t startY, uint16_t color)
{
// 设置1x1的显示窗口
LCD_SetWindow(startX, startY, 1, 1);
LCD_WriteData_16Bit(color);
}
/*******************************************************************
* @name :画线
* @function :Draw a line between two points
* @parameters : x1:the bebinning x coordinate of the line
y1:the bebinning y coordinate of the line
x2:the ending x coordinate of the line
y2:the ending y coordinate of the line
* @retvalue :None
********************************************************************/
void LCD_DrawLine(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t color)
{
uint16_t t;
int xerr=0,yerr=0,delta_x,delta_y,distance;
int incx,incy,uRow,uCol;
delta_x=x2-x1; //计算坐标增量
delta_y=y2-y1;
uRow=x1;
uCol=y1;
if(delta_x>0)incx=1; //设置单步方向
else if(delta_x==0)incx=0;//垂直线
else {incx=-1;delta_x=-delta_x;}
if(delta_y>0)incy=1;
else if(delta_y==0)incy=0;//水平线
else{incy=-1;delta_y=-delta_y;}
if( delta_x>delta_y)distance=delta_x; //选取基本增量坐标轴
else distance=delta_y;
for(t=0;t<=distance+1;t++ )//画线输出
{
LCD_DrawPoint(uRow,uCol, color);//画点
xerr+=delta_x ;
yerr+=delta_y ;
if(xerr>distance)
{
xerr-=distance;
uRow+=incx;
}
if(yerr>distance)
{
yerr-=distance;
uCol+=incy;
}
}
}
/**
* @brief 在指定位置画实心圆
* @param centerX: 圆心的X坐标
* @param centerY: 圆心的Y坐标
* @param radius: 圆的半径
* @param color: 圆的颜色(RGB565格式)
* @retval None
*/
void LCD_DrawFilledCircle(uint16_t centerX, uint16_t centerY, uint16_t radius, uint16_t color)
{
int16_t x = 0;
int16_t y = radius;
int16_t d = 1 - radius;
int16_t deltaE = 3;
int16_t deltaSE = -2 * radius + 5;
// 画初始的八个点
LCD_DrawLine(centerX - radius, centerY, centerX + radius, centerY, color);
while (y > x)
{
if (d < 0) // 选择东点
{
d += deltaE;
deltaE += 2;
deltaSE += 2;
}
else // 选择东南点
{
d += deltaSE;
deltaE += 2;
deltaSE += 4;
y--;
}
x++;
// 画对称的八个点
LCD_DrawLine(centerX - x, centerY + y, centerX + x, centerY + y, color);
LCD_DrawLine(centerX - x, centerY - y, centerX + x, centerY - y, color);
LCD_DrawLine(centerX - y, centerY + x, centerX + y, centerY + x, color);
LCD_DrawLine(centerX - y, centerY - x, centerX + y, centerY - x, color);
}
}
github链接:https://github.com/Syy001023/ILI9341_lvgl