文章目錄
- 小熊派 FreeRTOS+SPI+DMA 驅動 TFT-LCD
- 一、文章前言
- 二、SPI+DMA 配置
- 三、FreeRTOS 配置
- 四、代碼撰寫
- 五、實驗現象
小熊派 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 */
}
五、實驗現象
換色刷屏:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/397480.html
標籤:其他
上一篇:2022屆安防展觀展體會
