小熊派 FreeRTOS+SPI+DMA 驱动 TFT-LCD

Posted JeckXu666

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了小熊派 FreeRTOS+SPI+DMA 驱动 TFT-LCD相关的知识,希望对你有一定的参考价值。

小熊派 FreeRTOS+SPI+DMA 驱动 TFT-LCD

一、文章前言

入手了一块小熊派开发板,看到他板子上搭载了一块 TFT-LCD 编写编写驱动代码来使用 TFT ,该 TFT 通过 ST7789 驱动芯片进行驱动,本文通过 CubeMX 软件配置硬件 SPI + DMA 方式来驱动 ST7789,同时配置 FreeRTOS 方便控制 DMA, 文章 ST7789 的驱动代码参考 Mculover666 大神的博客:

【STM32Cube_17】使用硬件SPI驱动TFT-LCD(ST7789)

二、SPI+DMA 配置

先看一下小熊派 TFT 接口原理图:

建立一个适合小熊派 STM32L431RCT6 工程,设置时钟

开启 SPI2 ,修改时钟 SPI_CLK 到 PB13,选择只发模式,配置基本参数 8 位位宽,选择空闲时钟为高电平,第二个跳边沿传输数据:

然后进入到 DMA 设置,添加 DMA 通道,参数保持默认:

之后再配置相关中断优先级:

除此之外,TFT 的驱动还需要开启许多 GPIO 控制口:

  • 控制背光的 LCD_PWR 原理图对应的 PB15
  • 控制读写的 LCD_WR_RS 原理图对应 PC6
  • 控制复位的 LCD_RST 原理图对应 PC7

GPIO 配置很简单不多说,具体步骤可以参考 Mculover666 大神的文章:

配置完成如下:

以上 SPI+DMA 驱动配置好了,下面配置 FreeRTOS

三、FreeRTOS 配置

开启 FreeRTOS 是为了更加方便 DMA 逻辑代码的实现,更重要的是因为后面我计划移植 LVGL 图形库,所以需要一个 RTOS 做支撑,FreeRTOS 的使用详细介绍可以看我的这篇文章:

CubeMX使用FreeRTOS编程指南

下面简单介绍流程

点击中间件,选择 FreeRTOS,选择 CMSIS V2 版本接口:

创建一个任务用于刷新显存到 TFT:

创建一个二值信号量用于 DMA 回调的同步:

修改 HAL_Delay 的延时函数时基定时器为 TIM1 防止使用和 RTOS 的 Systick 冲突:

配置完成生成代码,具体步骤可以看参考文章

四、代码编写

生成代码后我们就可以编写逻辑代码了,但在编写前我们先大致了解一下驱动思路:

我们基于 FreeRTOS 操作系统编写驱动代码,建立一个写缓存任务,用于将显存区域的显示数据通过 SPI+DMA 的方式写入到 TFT 中,因为开启的是 DMA ,所以实际数据的写入过程是由 DMA 收发器完成的,因为更新显存的时候需要写大量数据,每次传输数据都要写几毫秒,所以将这个任务交给 DMA 去完成,我们只需要等待他 DMA 传输完成后再传输下一组数据就行,配合 FreeRTOS 使用信号量,DMA 没有传输完成时不会有信号释放,这时更新缓存任务就会挂起,等他发送完成才会执行,在 72M 主频的单片机移植 FreeRTOS 时,任务的调度一般 5us 作用,所以切换效率是非常高的,这样配合 FreeRTOS 使用 DMA 基本上写显示屏对主机资源的占用会压缩的非常非常小,下面是我根据 mculover666 教程代码修改的驱动代码

lcd.c 文件代码:

#include "lcd.h"
#include "gpio.h"
#include "spi.h"
#include "cmsis_os.h"

extern osSemaphoreId_t DMA_SemaphoreHandle;
//显存定义
//显存总大小 240*240*(16bit) = 240*240*2 个字节
#define LCD_TOTAL_BUF_SIZE	(240*240*2)
//因为直接定义显存太大了,所以定义其 1/100 轮流刷新
#define LCD_Buf_Size 1152
static uint8_t lcd_buf[LCD_Buf_Size];

/**
 *@brief    LCD控制引脚和通信接口初始化
 *@param    none
 *@retval   none
*/
static void LCD_GPIO_Init(void)

	/* 复位LCD */
	LCD_PWR(0);
	LCD_RST(0);
	HAL_Delay(100);
	LCD_RST(1);


/* USER CODE BEGIN 1 */
/**
 * @brief    SPI 发送字节函数
 * @param    TxData	要发送的数据
 * @param    size	发送数据的字节大小
 * @return  0:写入成功,其他:写入失败
 */
uint8_t SPI_WriteByte(uint8_t *TxData,uint16_t size)

	osStatus_t result;
	//获取信号,如果上一个DMA传输完成
	//信号就能获取到,没有传输完成任务就挂起
	//等到传输完成再恢复
	result = osSemaphoreAcquire(DMA_SemaphoreHandle,0xFFFF);
	
	if(result == osOK)
	
		//获取成功
		return HAL_SPI_Transmit_DMA(&hspi2,TxData,size);
	else
	
		//获取失败
		return 1;
	

//DMA 传输完成后会调用 SPI传输完成回调函数
//在该函数中我们释放信号
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)

	if(hspi->Instance == hspi2.Instance)
		osSemaphoreRelease(DMA_SemaphoreHandle);



/**
 * @brief   写命令到LCD
 * @param   cmd —— 需要发送的命令
 * @return  none
 */
static void LCD_Write_Cmd(uint8_t cmd)

    LCD_WR_RS(0);
    SPI_WriteByte(&cmd, 1);


/**
 * @brief   写数据到LCD
 * @param   dat —— 需要发送的数据
 * @return  none
 */
static void LCD_Write_Data(uint8_t dat)

    LCD_WR_RS(1);
    SPI_WriteByte(&dat, 1);


/**
 * @breif   打开LCD显示背光
 * @param   none
 * @return  none
 */
void LCD_DisplayOn(void)

    LCD_PWR(1);

/**
 * @brief   关闭LCD显示背光
 * @param   none
 * @return  none
 */
void LCD_DisplayOff(void)

    LCD_PWR(0);


/**
 * @brief   设置数据写入LCD显存区域
 * @param   x1,y1	—— 起点坐标
 * @param   x2,y2	—— 终点坐标
 * @return  none
 */
void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2)

    /* 指定X方向操作区域 */
    LCD_Write_Cmd(0x2a);
    LCD_Write_Data(x1 >> 8);
    LCD_Write_Data(x1);
    LCD_Write_Data(x2 >> 8);
    LCD_Write_Data(x2);

    /* 指定Y方向操作区域 */
    LCD_Write_Cmd(0x2b);
    LCD_Write_Data(y1 >> 8);
    LCD_Write_Data(y1);
    LCD_Write_Data(y2 >> 8);
    LCD_Write_Data(y2);

    /* 发送该命令,LCD开始等待接收显存数据 */
    LCD_Write_Cmd(0x2C);


/**
 * @brief   以一种颜色清空LCD屏
 * @param   color —— 清屏颜色(16bit)
 * @return  none
 */
void LCD_Clear(uint16_t color)

    uint16_t j;
    uint8_t data[2] = 0;  //color是16bit的,每个像素点需要两个字节的显存

    /* 将16bit的color值分开为两个单独的字节 */
    data[0] = color >> 8;
    data[1] = color;
    
    /* 显存的值需要逐字节写入显存 */
    for(j = 0; j < LCD_Buf_Size / 2; j++)
    
        lcd_buf[j * 2] =  data[0];
        lcd_buf[j * 2 + 1] =  data[1];
    
    /* 显存更新到 Flash */
    LCD_ReFlash();


/**
 * @brief   全屏更新显存到LCD
 * @param   none
 * @return  none
 */
void LCD_ReFlash()

		uint16_t i;
    /* 指定显存操作地址 */
    LCD_Address_Set(0, 0, LCD_Width - 1, LCD_Height - 1);
    /* 指定接下来的数据为数据 */
    LCD_WR_RS(1);
    /* 循环将显存缓冲区的数据循环写入到LCD */
    for(i = 0; i < (LCD_TOTAL_BUF_SIZE / LCD_Buf_Size); i++)
    
        SPI_WriteByte(lcd_buf, (uint16_t)LCD_Buf_Size);
    



/**
 * @brief   LCD画点
 * @param   none
 * @return  none
 */
void LCD_Draw_Point(uint16_t x1, uint16_t y1, uint16_t color)

	uint8_t buf[2];
	buf[0]=color>>8;
	buf[1]=color;
	LCD_Address_Set(x1,y1,x1+1,y1);
	SPI_WriteByte(buf, 2);


/**
 * @brief   LCD初始化
 * @param   none
 * @return  none
 */
void LCD_Init(void)

		/* 初始化和LCD通信的引脚 */
		
		LCD_GPIO_Init();
		osDelay(120);
    /* 关闭睡眠模式 */
    LCD_Write_Cmd(0x11);
    osDelay(120);

    /* 开始设置显存扫描模式,数据格式等 */
    LCD_Write_Cmd(0x36);
    LCD_Write_Data(0x00);
    /* RGB 5-6-5-bit格式  */
    LCD_Write_Cmd(0x3A);
    LCD_Write_Data(0x65);
    /* porch 设置 */
    LCD_Write_Cmd(0xB2);
    LCD_Write_Data(0x0C);
    LCD_Write_Data(0x0C);
    LCD_Write_Data(0x00);
    LCD_Write_Data(0x33);
    LCD_Write_Data(0x33);
    /* VGH设置 */
    LCD_Write_Cmd(0xB7);
    LCD_Write_Data(0x72);
    /* VCOM 设置 */
    LCD_Write_Cmd(0xBB);
    LCD_Write_Data(0x3D);
    /* LCM 设置 */
    LCD_Write_Cmd(0xC0);
    LCD_Write_Data(0x2C);
    /* VDV and VRH 设置 */
    LCD_Write_Cmd(0xC2);
    LCD_Write_Data(0x01);
    /* VRH 设置 */
    LCD_Write_Cmd(0xC3);
    LCD_Write_Data(0x19);
    /* VDV 设置 */
    LCD_Write_Cmd(0xC4);
    LCD_Write_Data(0x20);
    /* 普通模式下显存速率设置 60Mhz */
    LCD_Write_Cmd(0xC6);
    LCD_Write_Data(0x0F);
    /* 电源控制 */
    LCD_Write_Cmd(0xD0);
    LCD_Write_Data(0xA4);
    LCD_Write_Data(0xA1);
    /* 电压设置 */
    LCD_Write_Cmd(0xE0);
    LCD_Write_Data(0xD0);
    LCD_Write_Data(0x04);
    LCD_Write_Data(0x0D);
    LCD_Write_Data(0x11);
    LCD_Write_Data(0x13);
    LCD_Write_Data(0x2B);
    LCD_Write_Data(0x3F);
    LCD_Write_Data(0x54);
    LCD_Write_Data(0x4C);
    LCD_Write_Data(0x18);
    LCD_Write_Data(0x0D);
    LCD_Write_Data(0x0B);
    LCD_Write_Data(0x1F);
    LCD_Write_Data(0x23);
    /* 电压设置 */
    LCD_Write_Cmd(0xE1);
    LCD_Write_Data(0xD0);
    LCD_Write_Data(0x04);
    LCD_Write_Data(0x0C);
    LCD_Write_Data(0x11);
    LCD_Write_Data(0x13);
    LCD_Write_Data(0x2C);
    LCD_Write_Data(0x3F);
    LCD_Write_Data(0x44);
    LCD_Write_Data(0x51);
    LCD_Write_Data(0x2F);
    LCD_Write_Data(0x1F);
    LCD_Write_Data(0x1F);
    LCD_Write_Data(0x20);
    LCD_Write_Data(0x23);
    /* 显示开 */
    LCD_Write_Cmd(0x21);
    LCD_Write_Cmd(0x29);
    
    /* 清屏为白色 */
    LCD_Clear(WHITE);

    /*打开显示*/
    LCD_PWR(1);

lcd.h 文件代码:

#include "main.h"

#define	LCD_PWR(n)		(n?\\
						HAL_GPIO_WritePin(LCD_PWR_GPIO_Port,LCD_PWR_Pin,GPIO_PIN_SET):\\
						HAL_GPIO_WritePin(LCD_PWR_GPIO_Port,LCD_PWR_Pin,GPIO_PIN_RESET))
#define	LCD_WR_RS(n)	(n?\\
						HAL_GPIO_WritePin(LCD_WR_RS_GPIO_Port,LCD_WR_RS_Pin,GPIO_PIN_SET):\\
						HAL_GPIO_WritePin(LCD_WR_RS_GPIO_Port,LCD_WR_RS_Pin,GPIO_PIN_RESET))
#define	LCD_RST(n)		(n?\\
						HAL_GPIO_WritePin(LCD_RST_GPIO_Port,LCD_RST_Pin,GPIO_PIN_SET):\\
						HAL_GPIO_WritePin(LCD_RST_GPIO_Port,LCD_RST_Pin,GPIO_PIN_RESET))

//LCD屏幕分辨率定义
#define LCD_Width   240
#define LCD_Height  240
//颜色定义
#define WHITE   0xFFFF	//白色
#define YELLOW  0xFFE0	//黄色
#define BRRED   0XFC07  //棕红色
#define PINK    0XF81F	//粉色
#define RED     0xF800	//红色
#define BROWN   0XBC40  //棕色
#define GRAY    0X8430  //灰色
#define GBLUE   0X07FF	//兰色
#define GREEN   0x07E0	//绿色
#define BLUE    0x001F  //蓝色
#define BLACK   0x0000	//黑色


//初始化GPIO
static void LCD_GPIO_Init(void);
//SPI 写入接口
uint8_t SPI_WriteByte(uint8_t *TxData,uint16_t size);
//LCD 写命令
static void LCD_Write_Cmd(uint8_t cmd);
//LCD 写数据
static void LCD_Write_Data(uint8_t dat);
//LCD 开显示
void LCD_DisplayOn(void);
//LCD 关显示
void LCD_DisplayOff(void);
//LCD 设置写入范围
void LCD_Address_Set(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2);
//LCD 循环更新显存
void LCD_ReFlash(void);
//LCD 清屏
void LCD_Clear(uint16_t color);
//LCD 初始化
void LCD_Init(void);
//LCD 画点
void LCD_Draw_Point(uint16_t x1, uint16_t y1, uint16_t color);

freertos.c 文件代码

刷新任务的代码:

/* USER CODE END Header_TFT_ReFlash_Task */
__weak void TFT_ReFlash_Task(void *argument)

  /* USER CODE BEGIN TFT_ReFlash_Task */
  LCD_Init();
  /* Infinite loop */
  for(;;)
  
		LCD_Clear(RED);
    osDelay(1000);
		LCD_Clear(BLUE);
    osDelay(1000);
		LCD_Clear(GREEN);
    osDelay(1000);
		LCD_Clear(PINK);
    osDelay(1000);
  
  /* USER CODE END TFT_ReFlash_Task */

五、实验现象

换色刷屏:

以上是关于小熊派 FreeRTOS+SPI+DMA 驱动 TFT-LCD的主要内容,如果未能解决你的问题,请参考以下文章

华为云技术分享小熊派华为物联网操作系统LiteOS裸机驱动移植02-LCD驱动移植及使用

LVGL v8学习笔记 | 03 - 移植LVGL 8.2到小熊派开发板(SPI屏)

STM32CubeMX-SPI+DMA 驱动 2812 灯带

STM32CubeMX-SPI+DMA 驱动 2812 灯带

小熊派 LVGL 移植文件系统

小熊派LiteOS移植LVGL