使用DMA+SPI驱动Aliyun Things 上的ST7789H2 LCD屏幕

前言

1:驱动程序参考自https://blog.csdn.net/BearPi/article/details/104311705.:
2:这是我的一个记录,实现的功能不多,只是将在内存中开辟的一片显存通过DMA的方式搬运到屏幕上。

硬件

在这里插入图片描述
MCU:STM32L496VGT6
屏幕:ST7789H2驱动的LCD屏幕,大小为240*240,色深2B,通过SPI1硬件方式连接。
在这里插入图片描述
查阅开发版=板原理图,相关的硬件连接如下,这里板子只设计了发送引脚,我们不可以从屏幕上读回数据

名称 引脚
LCD_RST PB2
LCD_DC PA6
LCD_PWR PE7
LCD_NSS PA4
SCLK PA5
DO PA7
LED PB6

CUBEMX

时钟树

这里将主频设置的非常低,目的是观察DMA传输过程。但也不要太低,太低会导致屏幕不工作
在这里插入图片描述

GPIO

初始化一些我们需要控制的引脚
注意:片选引脚LCD_NSS通过硬件控制,不在这里初始化。
在这里插入图片描述

SPI

单向主模式,硬件片选,8bit传输
在这里插入图片描述
打开SPI传输中断。
在这里插入图片描述
添加SPI DMA传输通道
在这里插入图片描述
SPI引脚复用配置,按照原理图配
在这里插入图片描述
到此为止,硬件配置完成。

代码部分

LCD驱动

ST7789.c


static void LCD_reset(void)
{
	/* 复位LCD */
    LCD_RST(0);
    HAL_Delay(100);
    LCD_RST(1);
}
static void LCD_Write_Cmd(uint8_t cmd)
{
    LCD_WR_RS(0);
    HAL_SPI_Transmit(&hspi1,&cmd,1,1000);
}

static void LCD_Write_Data(uint8_t dat)
{
    LCD_WR_RS(1);
     HAL_SPI_Transmit(&hspi1,&dat,1,1000);
}


static 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_Fill_first_half(uint8_t *data)
{
    /* 指定显存操作地址为全屏*/
    LCD_Address_Set(0, 0, LCD_Width - 1, LCD_Height/2 - 1);
    LCD_WR_RS(1);/* 指定接下来的数据为数据 */
	/* 写前半屏*/

	HAL_SPI_Transmit_DMA(&hspi1,data, LCD_RAM_SIZE/2);
}
void LCD_Fill_last_half(uint8_t *data)
{
    /* 指定显存操作地址为全屏*/
    LCD_Address_Set(0, LCD_Height/2, LCD_Width - 1, LCD_Height - 1);
    LCD_WR_RS(1);/* 指定接下来的数据为数据 */
    /*写后半屏*/
    HAL_SPI_Transmit_DMA(&hspi1,data+LCD_RAM_SIZE/2, LCD_RAM_SIZE/2);
}

void LCD_Init(void)
{
	/*关闭显示*/
    LCD_PWR(0);
    /* 初始化和LCD通信的引脚 */
    LCD_reset();
    HAL_Delay(120);
    /* 关闭睡眠模式 */
    LCD_Write_Cmd(0x11);
    HAL_Delay(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_PWR(1);
}


ST7789.h

#ifndef __ST7789_H
#define __ST7789_H
#include "main.h"
#include "spi.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_DCX_GPIO_Port,LCD_DCX_Pin,GPIO_PIN_SET):\
						HAL_GPIO_WritePin(LCD_DCX_GPIO_Port,LCD_DCX_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 LCD_RAM_SIZE LCD_Width*LCD_Height*2		//长240 宽240 色深2bit
#define Pixel_NUM (LCD_RAM_SIZE/2)

void LCD_Init(void);
void LCD_Fill_first_half(uint8_t *data);
void LCD_Fill_last_half(uint8_t *data);

#endif

大家能看到我这里将一帧图像分成了两半传输,分别使用 LCD_Fill_first_half 和 LCD_Fill_last_half 分开传输。这样做的原因是hal库函数里,HAL_SPI_Transmit_DMA的Size参数是使用uint16_t 定义的,最大支持6万5左右。而LCD_RAM_SIZE = LCD_WidthLCD_Height2 = 240*24-*2=115200,11万多,超出范围了。所以,掰两半传输就可以解决了。

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
{
  HAL_StatusTypeDef errorcode = HAL_OK;
  /* Check tx dma handle */
  assert_param(IS_SPI_DMA_HANDLE(hspi->hdmatx));
  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));
  /* Process Locked */
  __HAL_LOCK(hspi);
  .........
}

中断服务函数

我们已经开启了DMA传输,spi传输完成的时候触发一次中断,通知CPU传输完成。这里我们再spi.c的末尾重写一下HAL_SPI_TxCpltCallback。one_frame_done 是一个全局变量,标识传输完成。

volatile uint8_t one_frame_done;
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
	one_frame_done = 1;
}

测试代码

main.c中,首先定义两个色块,引用one_frame_done

/* USER CODE BEGIN PV */
volatile uint8_t data1[LCD_RAM_SIZE];
volatile uint8_t data2[LCD_RAM_SIZE];
extern volatile uint8_t one_frame_done;
/* USER CODE END PV */

然后编写测试程序

int main(void)
{
	uint16_t color = 0;
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	MX_DMA_Init();
	MX_SPI1_Init();
	LCD_Init();
	//初始化两个色块
	
	for(uint16_t j = 0; j < Pixel_NUM; j++)
    {//黄色
        data1[j * 2] = (uint8_t)(0x3333 >> 8);
        data1[j * 2 + 1] =  (uint8_t)(0x3333);
    }
	for(uint16_t j = 0; j < Pixel_NUM; j++)
    {//蓝色
        data2[j * 2] = (uint8_t)(0xeeee >> 8);
        data2[j * 2 + 1] =  (uint8_t)(0xeeee);
    }
	while (1)
	{
		/*显示第一帧*/
		one_frame_done = 0;
		LCD_Fill_first_half((uint8_t *)data1);
		while(!one_frame_done){/*release cpu and doing something else*/}
		one_frame_done = 0;
		LCD_Fill_last_half((uint8_t *)data1);
		while(!one_frame_done){/*release cpu and doing something else*/}	
		
		/*显示第二帧*/
		one_frame_done = 0;
		LCD_Fill_first_half((uint8_t *)data2);
		while(!one_frame_done){/*release cpu and doing something else*/}
		one_frame_done = 0;
		LCD_Fill_last_half((uint8_t *)data2);
		while(!one_frame_done){/*release cpu and doing something else*/}	
		
		HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
	}
}

现象

版权声明:本文为CSDN博主「_Winston_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42039294/article/details/122273904

前言

1:驱动程序参考自https://blog.csdn.net/BearPi/article/details/104311705.:
2:这是我的一个记录,实现的功能不多,只是将在内存中开辟的一片显存通过DMA的方式搬运到屏幕上。

硬件

在这里插入图片描述
MCU:STM32L496VGT6
屏幕:ST7789H2驱动的LCD屏幕,大小为240*240,色深2B,通过SPI1硬件方式连接。
在这里插入图片描述
查阅开发版=板原理图,相关的硬件连接如下,这里板子只设计了发送引脚,我们不可以从屏幕上读回数据

名称 引脚
LCD_RST PB2
LCD_DC PA6
LCD_PWR PE7
LCD_NSS PA4
SCLK PA5
DO PA7
LED PB6

CUBEMX

时钟树

这里将主频设置的非常低,目的是观察DMA传输过程。但也不要太低,太低会导致屏幕不工作
在这里插入图片描述

GPIO

初始化一些我们需要控制的引脚
注意:片选引脚LCD_NSS通过硬件控制,不在这里初始化。
在这里插入图片描述

SPI

单向主模式,硬件片选,8bit传输
在这里插入图片描述
打开SPI传输中断。
在这里插入图片描述
添加SPI DMA传输通道
在这里插入图片描述
SPI引脚复用配置,按照原理图配
在这里插入图片描述
到此为止,硬件配置完成。

代码部分

LCD驱动

ST7789.c


static void LCD_reset(void)
{
	/* 复位LCD */
    LCD_RST(0);
    HAL_Delay(100);
    LCD_RST(1);
}
static void LCD_Write_Cmd(uint8_t cmd)
{
    LCD_WR_RS(0);
    HAL_SPI_Transmit(&hspi1,&cmd,1,1000);
}

static void LCD_Write_Data(uint8_t dat)
{
    LCD_WR_RS(1);
     HAL_SPI_Transmit(&hspi1,&dat,1,1000);
}


static 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_Fill_first_half(uint8_t *data)
{
    /* 指定显存操作地址为全屏*/
    LCD_Address_Set(0, 0, LCD_Width - 1, LCD_Height/2 - 1);
    LCD_WR_RS(1);/* 指定接下来的数据为数据 */
	/* 写前半屏*/

	HAL_SPI_Transmit_DMA(&hspi1,data, LCD_RAM_SIZE/2);
}
void LCD_Fill_last_half(uint8_t *data)
{
    /* 指定显存操作地址为全屏*/
    LCD_Address_Set(0, LCD_Height/2, LCD_Width - 1, LCD_Height - 1);
    LCD_WR_RS(1);/* 指定接下来的数据为数据 */
    /*写后半屏*/
    HAL_SPI_Transmit_DMA(&hspi1,data+LCD_RAM_SIZE/2, LCD_RAM_SIZE/2);
}

void LCD_Init(void)
{
	/*关闭显示*/
    LCD_PWR(0);
    /* 初始化和LCD通信的引脚 */
    LCD_reset();
    HAL_Delay(120);
    /* 关闭睡眠模式 */
    LCD_Write_Cmd(0x11);
    HAL_Delay(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_PWR(1);
}


ST7789.h

#ifndef __ST7789_H
#define __ST7789_H
#include "main.h"
#include "spi.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_DCX_GPIO_Port,LCD_DCX_Pin,GPIO_PIN_SET):\
						HAL_GPIO_WritePin(LCD_DCX_GPIO_Port,LCD_DCX_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 LCD_RAM_SIZE LCD_Width*LCD_Height*2		//长240 宽240 色深2bit
#define Pixel_NUM (LCD_RAM_SIZE/2)

void LCD_Init(void);
void LCD_Fill_first_half(uint8_t *data);
void LCD_Fill_last_half(uint8_t *data);

#endif

大家能看到我这里将一帧图像分成了两半传输,分别使用 LCD_Fill_first_half 和 LCD_Fill_last_half 分开传输。这样做的原因是hal库函数里,HAL_SPI_Transmit_DMA的Size参数是使用uint16_t 定义的,最大支持6万5左右。而LCD_RAM_SIZE = LCD_WidthLCD_Height2 = 240*24-*2=115200,11万多,超出范围了。所以,掰两半传输就可以解决了。

HAL_StatusTypeDef HAL_SPI_Transmit_DMA(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size)
{
  HAL_StatusTypeDef errorcode = HAL_OK;
  /* Check tx dma handle */
  assert_param(IS_SPI_DMA_HANDLE(hspi->hdmatx));
  /* Check Direction parameter */
  assert_param(IS_SPI_DIRECTION_2LINES_OR_1LINE(hspi->Init.Direction));
  /* Process Locked */
  __HAL_LOCK(hspi);
  .........
}

中断服务函数

我们已经开启了DMA传输,spi传输完成的时候触发一次中断,通知CPU传输完成。这里我们再spi.c的末尾重写一下HAL_SPI_TxCpltCallback。one_frame_done 是一个全局变量,标识传输完成。

volatile uint8_t one_frame_done;
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
	one_frame_done = 1;
}

测试代码

main.c中,首先定义两个色块,引用one_frame_done

/* USER CODE BEGIN PV */
volatile uint8_t data1[LCD_RAM_SIZE];
volatile uint8_t data2[LCD_RAM_SIZE];
extern volatile uint8_t one_frame_done;
/* USER CODE END PV */

然后编写测试程序

int main(void)
{
	uint16_t color = 0;
	HAL_Init();
	SystemClock_Config();
	MX_GPIO_Init();
	MX_DMA_Init();
	MX_SPI1_Init();
	LCD_Init();
	//初始化两个色块
	
	for(uint16_t j = 0; j < Pixel_NUM; j++)
    {//黄色
        data1[j * 2] = (uint8_t)(0x3333 >> 8);
        data1[j * 2 + 1] =  (uint8_t)(0x3333);
    }
	for(uint16_t j = 0; j < Pixel_NUM; j++)
    {//蓝色
        data2[j * 2] = (uint8_t)(0xeeee >> 8);
        data2[j * 2 + 1] =  (uint8_t)(0xeeee);
    }
	while (1)
	{
		/*显示第一帧*/
		one_frame_done = 0;
		LCD_Fill_first_half((uint8_t *)data1);
		while(!one_frame_done){/*release cpu and doing something else*/}
		one_frame_done = 0;
		LCD_Fill_last_half((uint8_t *)data1);
		while(!one_frame_done){/*release cpu and doing something else*/}	
		
		/*显示第二帧*/
		one_frame_done = 0;
		LCD_Fill_first_half((uint8_t *)data2);
		while(!one_frame_done){/*release cpu and doing something else*/}
		one_frame_done = 0;
		LCD_Fill_last_half((uint8_t *)data2);
		while(!one_frame_done){/*release cpu and doing something else*/}	
		
		HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);
	}
}

现象

版权声明:本文为CSDN博主「_Winston_」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42039294/article/details/122273904

生成海报
点赞 0

_Winston_

我还没有学会写个人说明!

暂无评论

发表评论

相关推荐

74HC138译码器的原理和使用

前言 译码器就是将每个输入的二进制代码译成对应的输出高低电平信号,和编码器互为逆过程。 百度百科 74HC138是一款高速CMOS器件,74HC138引脚兼容低功耗肖特基TTL(LSTTL&#xf

PCA9555详细学习

2022.01.27 控制寄存器和命令寄存器,及英文理解 一般8位作为地址,其中前四位时固定的,中间三位为地址配置,最后一位是读写位,即读是1,写是0 当地址