这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 活动中心 » 板卡试用 » 【分享开发笔记,赚取电动螺丝刀】STM32F769驱动ST7789以及显示优化

共2条 1/1 1 跳转至

【分享开发笔记,赚取电动螺丝刀】STM32F769驱动ST7789以及显示优化

工程师
2025-03-05 21:38:08   被打赏 36 分(兑奖)     打赏

【前言】

手上有STM32F769-DISC1开发板,但是他这款开发板是没有配屏幕的,而且留出扩展的IO也就是标准的Ardunio接口,经综合分析,以及手上的配件,选择手上的四线SPI的ST7789屏屏幕。

【屏幕介绍】

手上的ST7789为国产的耀元鸿科技的ST7789为主控的2.8寸屏。

链接为:2.8寸TFT液晶屏显示屏ST7789彩屏ILI9341SP4线串口电容触摸GT911-淘宝网

【移植准备】

首先分析开发板的标准接口:


image.png

结合屏的IO,我先定义对应的接口:

image.png

【STM32CubeMX配置】

1、首先配置SPI2,模式、位宽、CPAH和CPOL极性,以及CS选择,如下图所示:

image.png

2、使用stm32cubeMX生成工程。

【代码移植】

1、使用vscode打开。复制正点原子的ST7789的驱动到工程中。

image.png

2、首先使用模拟SPI来驱动。先打开lcd.c中的模拟SPI模式,配置对应的宏:

image.png

3、根据IO对应的管脚对屏的时序脚进行修改,代码如下:

#define LCD_PWR(n) (n ? HAL_GPIO_WritePin(GPIOJ, GPIO_PIN_3, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOJ, GPIO_PIN_3, GPIO_PIN_RESET))
#define LCD_RST(n) (n ? HAL_GPIO_WritePin(GPIOJ, GPIO_PIN_4, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOJ, GPIO_PIN_4, GPIO_PIN_RESET))
#define LCD_WR(n) (n ? HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, GPIO_PIN_RESET))
#define LCD_CS(n) (n ? HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_RESET))

/*
LCD_SDA:PB15
LCD_SCK:PA12
*/
#define LCD_SDA(n) (n ? HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_RESET))
#define LCD_SCK(n) (n ? HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_SET) : HAL_GPIO_WritePin(GPIOA, GPIO_PIN_12, GPIO_PIN_RESET))

4、配置GPIO并做初始化,代码如下:

/**************************************************************
函数名称 : lcd_gpio_init
函数功能 : lcd gpio初始化
输入参数 : 无
返回值      : 无
备注       : 无
**************************************************************/
void lcd_gpio_init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    __HAL_RCC_GPIOA_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();
    __HAL_RCC_GPIOJ_CLK_ENABLE();
    __HAL_RCC_GPIOH_CLK_ENABLE();
    /*Configure GPIO pin : PA11 12 */
    GPIO_InitStruct.Pin = GPIO_PIN_11 | GPIO_PIN_12;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    /*Configure GPIO pins : PB15 */
    GPIO_InitStruct.Pin = GPIO_PIN_15;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;

    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    /*Configure GPIO pin : PH 6 */
    GPIO_InitStruct.Pin = GPIO_PIN_6;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);

    /*Configure GPIO pin : PJ 3 PJ4 */
    GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_4;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOJ, &GPIO_InitStruct);

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11 | GPIO_PIN_12, GPIO_PIN_SET);

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_15, GPIO_PIN_SET);

    /*Configure GPIO pin Output Level */
    HAL_GPIO_WritePin(GPI

5、到此LCD的移植就结束,具体的代码见附件。在main.c中添加测试函数:

  /* USER CODE BEGIN 2 */
  lcd_init();
  lcd_show_string(10, 0, 108, 12, (uint8_t *)"STM32F767LCD", 24, 1);
  lcd_show_chinese(72, 32, pandora_ch, 32, 4, 1);
  /* USER CODE END 2 */

6、效图如下图所示:

stm32f769LCD.png

说明驱动以及LCD连接是正常的。

接下来使用硬件SPI来驱动。

【SPI移植】

1、打开硬件SPI的宏开关:

image.png

2、重新配置RST、DC、CS、PWR四个管脚,代码如下:

/**************************************************************
函数名称 : lcd_gpio_init
函数功能 : lcd gpio初始化
输入参数 : 无
返回值  	 : 无
备注		 : 无
**************************************************************/
void lcd_gpio_init(void)
{
	GPIO_InitTypeDef GPIO_InitStruct;

	__HAL_RCC_GPIOA_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();
	__HAL_RCC_GPIOJ_CLK_ENABLE();
	__HAL_RCC_GPIOH_CLK_ENABLE();
	/*Configure GPIO pin : PA11  */
	GPIO_InitStruct.Pin = GPIO_PIN_11;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);


	/*Configure GPIO pin : PH 6 */
	GPIO_InitStruct.Pin = GPIO_PIN_6;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOH, &GPIO_InitStruct);

	/*Configure GPIO pin : PJ 3 PJ4 */
	GPIO_InitStruct.Pin = GPIO_PIN_3 | GPIO_PIN_4;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(GPIOJ, &GPIO_InitStruct);

	/*Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_11, GPIO_PIN_SET);
 

	/*Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOH, GPIO_PIN_6, GPIO_PIN_SET);

	/*Configure GPIO pin Output Level */
	HAL_GPIO_WritePin(GPIOJ, GPIO_PIN_3 | GPIO_PIN_4, GPIO_PIN_SET);

	// spi2_init(); /* spi2初始化 */
}

3、因为spi2已经由stm32cubeMX生成,所示spi的初始化就不需要自己手工移植了。源代码详见附件。

4、编译下载后,经测试,与软件的SPI效果一样。

【注意】此款屏屏的时钟极性需要配置如下:

image.png

如果你手上的屏幕也是st7789有可能这两个参数不一样,如果不能点亮,则可以根据自己的屏的参数进行相应的修改。

【驱动优化】

正点原子的驱动是使用每一个像素点来刷新的,他的清屏函数如下:

/**************************************************************
函数名称 : lcd_clear
函数功能 : lcd清屏函数
输入参数 : color:要清屏的颜色
返回值  	 : 无
备注		 : 先关闭显示,等到所有颜色数据写入到RAM后再打开显示
**************************************************************/
void lcd_clear(uint16_t color)
{
	uint16_t i, j;
	lcd_display_off(); /* 关闭显示 */
	lcd_set_address(0, 0, LCD_WIDTH - 1, LCD_HEIHGT - 1);
	lcd_write_ram();

	for (i = 0; i < LCD_HEIHGT; i++)
	{
		for (j = 0; j < LCD_WIDTH; j++)
		{
			lcd_write_data(color >> 8);
			lcd_write_data(color & 0x00ff);
		}
	}
	lcd_display_on(); /* 打开显示 */
}

从代码上看需要向屏写入LCD_HEIHGT*LCD_WIDTH*2次数据,因此清一屏的时间是需要比较久的。

因此我准备采用DMA来优化刷新速度。具体优化步骤如下:

1、使用STM32CubeMX打开DMA以及中断。

image.png

image.png

2、使用stm32cubeMX重新生成工程。

3、在工程代码中,在spi自定义代码中添加中断回调函数。在spi.c最前面需要声明发送完毕中断的标志位。

/* USER CODE BEGIN 0 */
volatile  uint8_t spi2_dma_tx_flag = 0; /* spi2_dma_tx 发送完成标志 */
/* USER CODE END 0 */

/* USER CODE BEGIN 1 */
// SPI2 DMA发送完成回调函数
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi == &hspi2)
    {
      spi2_dma_tx_flag = 1;
      // 这里可以添加SPI2 DMA发送完成后的处理代码
      // 例如设置标志位,通知主程序发送完成
    }
}

// SPI2 DMA发送错误回调函数
#include "main.h"
#include "lcd.h"
#include "spi.h"

void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi)
{
  if (hspi == &hspi2)
  {
      HAL_DMA_Abort(&hdma_spi2_tx);
      HAL_SPI_Abort(&hspi2);
  }
}
/* USER CODE END 1 */

4、在lcd.c中extern一下spi2_dma_tx_flag。

5、添加一个dma发送函数:

/**************************************************************
函数名称 : lcd_write_data_dma
函数功能 : 向lcd驱动芯片使用DMA写入size个数据
输入参数 : data:要写入的数据
返回值  	 : 无
备注		 : 
**************************************************************/
void lcd_write_data_dma(uint16_t *data, uint16_t size)
{
	// 配置 SPI 为 16 位数据大小
	hspi2.Init.DataSize = SPI_DATASIZE_16BIT;
	if (HAL_SPI_Init(&hspi2) != HAL_OK)
	{
		printf("SPI re-init failed for 16-bit mode\r\n");
		return;
	}
	LCD_CS(0);
	LCD_WR(1);
	// 使用 DMA 发送数据
	if (HAL_SPI_Transmit_DMA(&hspi2, (uint8_t *)data, size) != HAL_OK)
	{
		printf("DMA transmission failed\r\n");
	}
	// 等待DMA传输完成
	// while (HAL_SPI_GetState(&hspi2) != HAL_SPI_STATE_READY)
	// 	;
	while(spi2_dma_tx_flag == 0)
	   ;
	spi2_dma_tx_flag = 0;  //重置标志
	// 配置 SPI 为 8 位数据大小
	hspi2.Init.DataSize = SPI_DATASIZE_8BIT;
	if (HAL_SPI_Init(&hspi2) != HAL_OK)
	{
		printf("SPI re-init failed for 8-bit mode\r\n");
	}
}

在代码中,我们首先要把发送数据位的带宽修改为16bit,在发送完毕之后需要把他再修改回8bit,因为发送命令需要使用8bit来传输。

6、修改全屏更新颜色的代码:

/**************************************************************
函数名称 : lcd_clear
函数功能 : lcd清屏函数
输入参数 : color:要清屏的颜色
返回值  	 : 无
备注		 : 先关闭显示,等到所有颜色数据写入到RAM后再打开显示
**************************************************************/
void lcd_clear(uint16_t color)
{
	
	lcd_display_off(); /* 关闭显示 */
	lcd_set_address(0, 0, LCD_WIDTH - 1, LCD_HEIHGT - 1);
	lcd_write_ram();
#if USE_DMA
	uint16_t i;
	uint16_t buffer[LCD_WIDTH]; // 创建一个缓冲区,每个像素需要2个字节
								   // 填充缓冲区
	for (uint16_t i = 0; i < LCD_WIDTH; i++)
	{
		buffer[i] = color;
	}
	for (i = 0; i < LCD_HEIHGT; i++)
	{
		// 使用 DMA 发送缓冲区中的数据
		lcd_write_data_dma(buffer, sizeof(buffer));
	}
#else
	uint16_t i, j;
	for (i = 0; i < LCD_HEIHGT; i++)
	{
		for (j = 0; j < LCD_WIDTH; j++)
		{
			lcd_write_data(color >> 8);
			lcd_write_data(color & 0x00ff);
		}
	}
#endif
	lcd_display_on(); /* 打开显示 */
}

【注】在源代码最前面,我们需要定义一个USE_DMA开关,来选择是否使用DMA更新。

从上面的代码来看,我们发送的数据是按行来发送,同时也使用16bit来替代8bit数据传输,因此速度提升是非常高的。

【总结】

在stm32f769的SPI驱动中,我先使用模拟SPI来点亮的屏,后面使用spi的阻塞式来优化,最后使用SPI_DMA来提高速度。

当然这还不是最优化版本,我们可以扩大一个缓冲区,使用板载的SDRAM来缓存,这样就可以真一次性的把整屏数据发送到屏上,进一步提升速度。

附件:

ST7789.zip




关键词: STM32F769     ST7789     DMA     SPI    

专家
2025-03-09 12:05:34     打赏
2楼

很细致的开发过程。

其实我一直都有个疑问,如果工作主频高,模拟SPI的速度,应该不会比使用硬件的SPI操作差吧。但以前用雅特力的板子测试发现,结果好像还是硬件SPI快,不知道是什么原因。



共2条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]