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

举报
JeckXu666 发表于 2022/01/15 01:19:08 2022/01/15
【摘要】 文章目录 小熊派 FreeRTOS+SPI+DMA 驱动 TFT-LCD一、文章前言二、SPI+DMA 配置三、FreeRTOS 配置四、代码编写五、实验现象 小熊派 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 接口原理图:

20211229114431

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

20211228131318

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

20211228132511

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

20211228132800

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

20211228132922

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

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

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

配置完成如下:

20211228133213

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

三、FreeRTOS 配置

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

CubeMX使用FreeRTOS编程指南

下面简单介绍流程

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

20211228133929

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

20211228134014

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

20211228134127

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

20211228134240

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

四、代码编写

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

20211229103150

我们基于 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

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。