小熊派 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 的使用详细介绍可以看我的这篇文章:
下面简单介绍流程
点击中间件,选择 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);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 239
- 240
- 241
- 242
- 243
- 244
- 245
- 246
- 247
- 248
- 249
- 250
- 251
- 252
- 253
- 254
- 255
- 256
- 257
- 258
- 259
- 260
- 261
- 262
- 263
- 264
- 265
- 266
- 267
- 268
- 269
- 270
- 271
- 272
- 273
- 274
- 275
- 276
- 277
- 278
- 279
- 280
- 281
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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
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 */
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
五、实验现象
换色刷屏:
文章来源: blog.csdn.net,作者:JeckXu666,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/qq_45396672/article/details/122212346
- 点赞
- 收藏
- 关注作者
评论(0)