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 |
这里将主频设置的非常低,目的是观察DMA传输过程。但也不要太低,太低会导致屏幕不工作
初始化一些我们需要控制的引脚
注意:片选引脚LCD_NSS通过硬件控制,不在这里初始化。
单向主模式,硬件片选,8bit传输
打开SPI传输中断。
添加SPI DMA传输通道
SPI引脚复用配置,按照原理图配
到此为止,硬件配置完成。
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);
}
}