【前言】
手上有STM32F769-DISC1开发板,但是他这款开发板是没有配屏幕的,而且留出扩展的IO也就是标准的Ardunio接口,经综合分析,以及手上的配件,选择手上的四线SPI的ST7789屏屏幕。
【屏幕介绍】
手上的ST7789为国产的耀元鸿科技的ST7789为主控的2.8寸屏。
链接为:2.8寸TFT液晶屏显示屏ST7789彩屏ILI9341SP4线串口电容触摸GT911-淘宝网
【移植准备】
首先分析开发板的标准接口:
结合屏的IO,我先定义对应的接口:
【STM32CubeMX配置】
1、首先配置SPI2,模式、位宽、CPAH和CPOL极性,以及CS选择,如下图所示:
2、使用stm32cubeMX生成工程。
【代码移植】
1、使用vscode打开。复制正点原子的ST7789的驱动到工程中。
2、首先使用模拟SPI来驱动。先打开lcd.c中的模拟SPI模式,配置对应的宏:
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、效图如下图所示:
说明驱动以及LCD连接是正常的。
接下来使用硬件SPI来驱动。
【SPI移植】
1、打开硬件SPI的宏开关:
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效果一样。
【注意】此款屏屏的时钟极性需要配置如下:
如果你手上的屏幕也是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以及中断。
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来缓存,这样就可以真一次性的把整屏数据发送到屏上,进一步提升速度。
附件: