【前言】
手上有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(GPI5、到此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来缓存,这样就可以真一次性的把整屏数据发送到屏上,进一步提升速度。
附件:
我要赚赏金
