概要
基于官方的飞书文档中的1.13-立创开发板-3.5寸ILI9488彩色触摸屏幕软硬件SPI二合一移植成功案例修改
新手学习中,遇到不合理的地方请大佬们多多指教!
实现了FreeRTOS + LVGL使用硬件SPI+DMA 刷屏,顺便移植了easylogger方便调试打印。基础的移植就不多赘述了,网上一堆案例可以参考,我直接贴上我调试中遇到的好几个踩坑点。
FreeRTOS任务
void led_task(void)
{
while(1)
{
// 配置PB2引脚输出高电平
gpio_bit_write(GPIOB,GPIO_PIN_2,SET);
vTaskDelay(pdMS_TO_TICKS(500)); // 延时 500 毫秒
// 配置PB2引脚输出低电平
gpio_bit_write(GPIOB,GPIO_PIN_2,RESET);
vTaskDelay(pdMS_TO_TICKS(500));
}
vTaskDelete(NULL);
}
void lcd_task(void)
{
TickType_t xLastWakeTime;
const TickType_t xPeriod = pdMS_TO_TICKS( 5 );
xLastWakeTime = xTaskGetTickCount();
lv_init();
lv_port_disp_init();
lv_port_indev_init();
lv_demo_widgets();
while(1)
{
/* 调用系统延时函数,周期性阻塞5ms */
vTaskDelayUntil( &xLastWakeTime,xPeriod );
lv_task_handler();
}
vTaskDelete(NULL);
}
SPI初始化
踩坑点1:例程使用SPI1时,LVGL运行一段时间后会直接卡死。
如果使用SPI1,配置SPI为2分频的时候,LVGL运行一段时间后会直接卡死在当前画面中,只有配置成4分频才能正常工作,但是帧率低了很多。debug调试发现是卡死在了SPIv_WriteData函数中的while循环中里。
/* 配置 SPI 参数 */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; // 极性高相位2
spi_init_struct.nss = SPI_NSS_SOFT; // 软件cs
spi_init_struct.prescale = SPI_PSC_2; // 2分频
spi_init_struct.endian = SPI_ENDIAN_MSB; // 高位在前
spi_init(PORT_SPI, &spi_init_struct);
void SPIv_WriteData(u8 Data)
{
while(RESET == spi_i2s_flag_get(PORT_SPI, SPI_FLAG_TBE));
spi_i2s_data_transmit(PORT_SPI, Data);
while(RESET == spi_i2s_flag_get(PORT_SPI, SPI_FLAG_RBNE)); //会在此处卡死
spi_i2s_data_receive(PORT_SPI);
}
- 修改 startup_gd32f407_427.s 中 Stack_Size 和 Heap_Size的大小,仍然会卡死;
- 修改 lv_conf.h 中的 LV_MEM_SIZE 大小,无作用;
- 增加或修改RBNE为while(SET == spi_i2s_flag_get(PORT_SPI, SPI_FLAG_TRANS)),无作用;
- 增加超时退出或者删除掉此句,显示不正常,直接白屏;
- 后来翻了翻gd32的手册,发现SPI0和SPI1最大速度不同,尝试使用SPI0的二分频后,就没发生过卡死了。推测是SPI1二分频后速度太快导致发送异常?但是APB1是42MHz,二分频为21MHz,没超过官方文档所说的最大30MHz,反倒是APB2是84MHz,二分频就42MHz远远超过最大频率了,反而没出错,这里就百思不得其解了。
LCD颜色填充函数
踩坑点2:ILI9488使用SPI时,只有三字节的RGB666可以使用!
买屏幕的时候没具体了解屏幕的驱动方式,导致后面修改填充函数的时候踩了大坑,刷屏的时候要进行格式转换,导致LVGL帧率低了巨多。
一开始找资料发现,初始化中修改0x3A寄存器可以改成16BIT传输。但是试了一下发现不起作用,后面才知道3/4线SPI驱动屏幕时是不能使用16BIT,使用并口驱动修改才有用。
/* Interface Pixel Format (3Ah) */
LCD_WR_REG(0x3A);
LCD_WR_DATA(0x66); //0X55 16BIT/PIXEL
于是只能强行使用18bit然后转换一下格式才能正常显示,这一来一回浪费了太多时间跟内存,白白损失太多性能了,所以非常不推荐使用ILI94XX系列的驱动芯片使用SPI驱动,太坑了!! 如果是16BIT的话,可以完美适配LVGL,直接利用uint16_t指针进行dma发送,完全不需要格式转换。
uint8_t SPI_TX_BUFF[X_MAX_PIXEL * 10 * 3]; //宽480,10行
void LCD_Fill(u16 sx,u16 sy,u16 ex,u16 ey,uint16_t* color)
{
u16 width=ex-sx+1;
u16 height=ey-sy+1;
LCD_SetWindows(sx,sy,ex,ey);
u32 pixelNum = width*height;
uint32_t j = 0;
for(uint32_t i = 0; i < pixelNum; i++)
{
SPI_TX_BUFF[j++] = ((*color>>8)&0xF8);//RED
SPI_TX_BUFF[j++] = ((*color>>3)&0xFC);//GREEN
SPI_TX_BUFF[j++] = (*color<<3);//BLUE
color++;
}
LCD_CS_CLR;
LCD_RS_SET;
#if !USE_SPI_DMA
for(int i = 0;i < j;i++)
{
SPIv_WriteData(buf[i]);
}
LCD_CS_SET;
#else
// 配置发送DMA通道
dma_channel_disable(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH);
dma_flag_clear(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, DMA_FLAG_FTF);
dma_memory_address_config(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, DMA_MEMORY_0,(uint32_t)SPI_TX_BUFF);
dma_transfer_number_config(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, j);
dma_channel_enable(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH);
#endif
}
其他函数
#define BUFFER_ROW 10
static lv_disp_drv_t * disp_drv_temp = NULL;
void SPI_DMAInit(void)
{
//DMA初始化
rcu_periph_clock_enable(RCU_DMA_SPI);
//DMA发送
dma_single_data_parameter_struct dma_init_SPI_TX={0};
dma_deinit(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH); /* SPI0_Tx --> DMA1 Channel3 */
dma_init_SPI_TX.periph_addr = (uint32_t)(&SPI_DATA(PORT_SPI)); // 配置外设地址为SPI数据寄存器地址
dma_init_SPI_TX.periph_inc = DMA_PERIPH_INCREASE_DISABLE; // 禁止外设地址增量
dma_init_SPI_TX.memory0_addr= NULL; // 配置存储器地址
dma_init_SPI_TX.memory_inc = DMA_MEMORY_INCREASE_ENABLE; // 允许存储器地址增量
dma_init_SPI_TX.periph_memory_width = DMA_PERIPH_WIDTH_8BIT; // 配置外设宽度为8位
dma_init_SPI_TX.direction = DMA_MEMORY_TO_PERIPH; // 配置传输方向为存储器到外设
dma_init_SPI_TX.number = 0; // 配置传输数据个数
dma_init_SPI_TX.priority = DMA_PRIORITY_ULTRA_HIGH; // 配置DMA优先级
dma_init_SPI_TX.circular_mode = DMA_CIRCULAR_MODE_DISABLE; // 禁止循环模式
dma_single_data_mode_init(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, &dma_init_SPI_TX); // 初始化DMA0的DMA_CH4通道
dma_channel_subperipheral_select(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, DMA_SUBPERI3);
dma_channel_enable(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH);
spi_dma_enable(PORT_SPI,SPI_DMA_TRANSMIT);
//DMA中断
/* 使能DMA通道中断 */
nvic_irq_enable(DMA1_Channel3_IRQn, 0, 0);
/* 使能DMA中断 */
dma_interrupt_enable(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, DMA_CHXCTL_FTFIE); // 使能传输完成中断
}
void LCD_GPIOInit(void)
{
spi_parameter_struct spi_init_struct;
/* 开启各引脚时钟 */
rcu_periph_clock_enable(RCU_LCD_SCL);
rcu_periph_clock_enable(RCU_LCD_SDA);
rcu_periph_clock_enable(RCU_LCD_CS);
rcu_periph_clock_enable(RCU_LCD_DC);
rcu_periph_clock_enable(RCU_LCD_RES);
rcu_periph_clock_enable(RCU_LCD_BLK);
/* 使能SPI */
rcu_periph_clock_enable(RCU_SPI_HARDWARE);
/* 配置 SPI的SCK GPIO */
gpio_af_set(PORT_LCD_SCL, LINE_AF_SPI, GPIO_LCD_SCL);
gpio_mode_set(PORT_LCD_SCL, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_LCD_SCL);
gpio_output_options_set(PORT_LCD_SCL, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_LCD_SCL);
gpio_bit_set(PORT_LCD_SCL,GPIO_LCD_SCL);
/* 配置 SPI的MOSI GPIO */
gpio_af_set(PORT_LCD_SDA, LINE_AF_SPI, GPIO_LCD_SDA);
gpio_mode_set(PORT_LCD_SDA, GPIO_MODE_AF, GPIO_PUPD_NONE, GPIO_LCD_SDA);
gpio_output_options_set(PORT_LCD_SDA, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_LCD_SDA);
gpio_bit_set(PORT_LCD_SDA, GPIO_LCD_SDA);
/* 配置DC */
gpio_mode_set(PORT_LCD_DC,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_DC);
gpio_output_options_set(PORT_LCD_DC,GPIO_OTYPE_PP,GPIO_OSPEED_MAX,GPIO_LCD_DC);
gpio_bit_write(PORT_LCD_DC, GPIO_LCD_DC, SET);
/* 配置RES */
gpio_mode_set(PORT_LCD_RES,GPIO_MODE_OUTPUT,GPIO_PUPD_PULLUP,GPIO_LCD_RES);
gpio_output_options_set(PORT_LCD_RES,GPIO_OTYPE_PP,GPIO_OSPEED_MAX,GPIO_LCD_RES);
gpio_bit_write(PORT_LCD_RES, GPIO_LCD_RES, SET);
/* 配置BLK */
gpio_mode_set(PORT_LCD_BLK, GPIO_MODE_OUTPUT, GPIO_PUPD_PULLUP, GPIO_LCD_BLK);
gpio_output_options_set(PORT_LCD_BLK, GPIO_OTYPE_PP, GPIO_OSPEED_MAX, GPIO_LCD_BLK);
gpio_bit_write(PORT_LCD_BLK, GPIO_LCD_BLK, SET);
/*配置CS */
gpio_mode_set(PORT_LCD_CS,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,GPIO_LCD_CS);
gpio_output_options_set(PORT_LCD_CS,GPIO_OTYPE_PP,GPIO_OSPEED_MAX,GPIO_LCD_CS);
gpio_bit_write(PORT_LCD_CS, GPIO_LCD_CS, SET);
/* 配置 SPI 参数 */
spi_init_struct.trans_mode = SPI_TRANSMODE_FULLDUPLEX; // 传输模式全双工
spi_init_struct.device_mode = SPI_MASTER; // 配置为主机
spi_init_struct.frame_size = SPI_FRAMESIZE_8BIT; // 8位数据
spi_init_struct.clock_polarity_phase = SPI_CK_PL_HIGH_PH_2EDGE; // 极性高相位2
spi_init_struct.nss = SPI_NSS_SOFT; // 软件cs
spi_init_struct.prescale = SPI_PSC_2; // 2分频
spi_init_struct.endian = SPI_ENDIAN_MSB; // 高位在前
spi_init(PORT_SPI, &spi_init_struct);
/* 使能 SPI */
spi_enable(PORT_SPI);
#if USE_SPI_DMA
SPI_DMAInit();
#endif
}
void LCD_Init(void)
{
LCD_GPIOInit();//LCD GPIO初始化
LCD_RESET(); //LCD 复位
//************* ILI9488初始化**********//
LCD_WR_REG(0XF7);
LCD_WR_DATA(0xA9);
LCD_WR_DATA(0x51);
LCD_WR_DATA(0x2C);
LCD_WR_DATA(0x82);
/* Power Control 1 (C0h) */
LCD_WR_REG(0xC0);
LCD_WR_DATA(0x11);
LCD_WR_DATA(0x09);
/* Power Control 2 (C1h) */
LCD_WR_REG(0xC1);
LCD_WR_DATA(0x41);
/* VCOM Control (C5h) */
LCD_WR_REG(0XC5);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0x80);
/* Frame Rate Control (In Normal Mode/Full Colors) (B1h) */
LCD_WR_REG(0xB1);
LCD_WR_DATA(0xB0);
LCD_WR_DATA(0x11);
/* Display Inversion Control (B4h) */
LCD_WR_REG(0xB4);
LCD_WR_DATA(0x02);
/* Display Function Control (B6h) */
LCD_WR_REG(0xB6);
LCD_WR_DATA(0x02);
LCD_WR_DATA(0x42); //0x42 0X22
/* Entry Mode Set (B7h) */
LCD_WR_REG(0xB7);
LCD_WR_DATA(0xc6);
/* HS Lanes Control (BEh) */
LCD_WR_REG(0xBE);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x04);
/* Set Image Function (E9h) */
LCD_WR_REG(0xE9);
LCD_WR_DATA(0x00);
LCD_WR_REG(0x36);
//LCD_WR_DATA((1<<3)|(0<<7)|(1<<6)|(1<<5));
LCD_WR_DATA( 0x08 );
/* Interface Pixel Format (3Ah) */
LCD_WR_REG(0x3A);
LCD_WR_DATA(0x66); //0X55 16BIT/PIXEL
/* PGAMCTRL (Positive Gamma Control) (E0h) */
LCD_WR_REG(0xE0);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x07);
LCD_WR_DATA(0x10);
LCD_WR_DATA(0x09);
LCD_WR_DATA(0x17);
LCD_WR_DATA(0x0B);
LCD_WR_DATA(0x41);
LCD_WR_DATA(0x89);
LCD_WR_DATA(0x4B);
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0x0C);
LCD_WR_DATA(0x0E);
LCD_WR_DATA(0x18);
LCD_WR_DATA(0x1B);
LCD_WR_DATA(0x0F);
/* NGAMCTRL (Negative Gamma Control) (E1h) */
LCD_WR_REG(0XE1);
LCD_WR_DATA(0x00);
LCD_WR_DATA(0x17);
LCD_WR_DATA(0x1A);
LCD_WR_DATA(0x04);
LCD_WR_DATA(0x0E);
LCD_WR_DATA(0x06);
LCD_WR_DATA(0x2F);
LCD_WR_DATA(0x45);
LCD_WR_DATA(0x43);
LCD_WR_DATA(0x02);
LCD_WR_DATA(0x0A);
LCD_WR_DATA(0x09);
LCD_WR_DATA(0x32);
LCD_WR_DATA(0x36);
LCD_WR_DATA(0x0F);
LCD_WR_REG(0x11); //SLEEP OUT
delay_ms(120);
LCD_WR_REG(0x29); //DISPLAY ON
LCD_direction(USE_HORIZONTAL);//设置LCD显示方向
LCD_LED_SET; //点亮背光
LCD_Clear(WHITE);//清全屏白色
}
void lv_port_disp_init(void)
{
/*-------------------------
* Initialize your display
* -----------------------*/
disp_init();
/*-----------------------------
* Create a buffer for drawing
*----------------------------*/
/* Example for 2) */
static lv_disp_draw_buf_t draw_buf_dsc_2;
static lv_color_t buf_2_1[MY_DISP_HOR_RES * BUFFER_ROW]; /*A buffer for 10 rows*/
static lv_color_t buf_2_2[MY_DISP_HOR_RES * BUFFER_ROW];
lv_disp_draw_buf_init(&draw_buf_dsc_2, buf_2_1, buf_2_2, MY_DISP_HOR_RES * BUFFER_ROW); /*Initialize the display buffer*/
/*-----------------------------------
* Register the display in LVGL
*----------------------------------*/
static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set the resolution of the display*/
disp_drv.hor_res = MY_DISP_HOR_RES;
disp_drv.ver_res = MY_DISP_VER_RES;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
/*Set a display buffer*/
disp_drv.draw_buf = &draw_buf_dsc_2;
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
/**********************
* STATIC FUNCTIONS
**********************/
/*Initialize your display and the required peripherals.*/
static void disp_init(void)
{
/*You code here*/
LCD_Init();
}
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
if(disp_flush_enabled) {
disp_drv_temp = disp_drv;
LVGL_LCD_Fill(area->x1, area->y1, area->x2, area->y2, (uint16_t*)color_p);
}
}
void SPI_DMA_IRQHandler(void){
/* 传输完成中断 */
if(dma_interrupt_flag_get(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, DMA_INT_FLAG_FTF))
{
dma_interrupt_flag_clear(RCU_SPI_DMA, RCU_SPI_DMA_TX_CH, DMA_INT_FLAG_FTF);// 数据传输完成
LCD_CS_SET;
lv_disp_flush_ready(disp_drv_temp);
}
}
最后的问题
跑LVGL的demo的时候,动态图表有62帧,但是进行滑动的时候直接掉到6-8帧,此时CPU占用会直接到100%,LED任务就卡住了,灯不闪烁。不知道还有没有优化的办法,以后再慢慢研究了,也欢迎各位大佬指教一下。
天空星GD32F407VET6移植FreeRTOS+LVGL
完整代码
github.com/Elichikaa/GD32F407VET6-FreeRTOS-LVGL-SPI-DMA