现在我们将编写显示刷新函数。当 LVGL 准备好将内容刷新到显示器时,LVGL 会调用此函数。这是此文件最重要的功能,因为它包含将数据发送到显示器的方法。
在本教程中,我们将仅介绍绘制位图和使用 DMA 绘制位图的简单方法。
不带 DMA
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
ILI9341_SetWindow(area->x1, area->y1, area->x2, area->y2);
int height = area->y2 - area->y1 + 1;
int width = area->x2 - area->x1 + 1;
ILI9341_DrawBitmap(width, height, (uint8_t *)color_p);
lv_disp_flush_ready(disp_drv);
}
我们首先调用函数 setwindow 来设置需要修改的区域。
然后计算需要修改的区域的高度和宽度
然后调用 DrawBitmap 函数将数据发送到上述函数中设置的区域。
将数据传输到显示器后,调用函数lv_disp_flush_ready以通知 LVGL 我们已准备好进行 ew 传输。
在这里,DrawBitmap 函数在阻塞模式下通过 SPI 将数据发送到显示器。下面是此函数在 ILI9341 库中的实现。
void ILI9341_DrawBitmap(uint16_t w, uint16_t h, uint8_t *s)
{
LCD_WR_REG(0x2c);
DC_H();
ConvHL(s, (int32_t)w*h*2);
HAL_SPI_Transmit(&hspi1, (uint8_t*)s, w*h*2, HAL_MAX_DELAY);
}
由于数据是在阻塞模式下发送的,因此该函数仅在传输完所有数据后才会退出。因此,在 DrawBitmap 函数之后立即调用函数lv_disp_flush_ready是有意义的。
使用 DMA
如果我们使用 2 个缓冲区来提高性能,使用 DMA 发送数据将帮助我们获得良好的性能。以下是使用 DMA 发送数据的实现。
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
ILI9341_SetWindow(area->x1, area->y1, area->x2, area->y2);
int height = area->y2 - area->y1 + 1;
int width = area->x2 - area->x1 + 1;
ILI9341_DrawBitmapDMA(width, height, (uint8_t *)color_p);
}
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
lv_disp_flush_ready(&disp_drv);
}
设置窗口和计算高度和宽度的初始过程在这里是相同的。唯一的变化是,我们不再调用 DrawBitmap,而是调用函数 DrawBitmapDMA。此函数使用 DMA 发送数据,而不是阻塞模式 SPI 传输。
DMA 在后台将数据发送到 SPI,而无需使用 anu CPU。同时,LVGL 将使用新数据更新另一个缓冲区。DMA 完成传输后,将触发中断并调用 TxCpltCallback。
在此回调中,我们将调用函数 lv_disp_flush_ready 来通知 LVGL 我们已准备好刷新另一个缓冲区。整个系统的工作方式是刷新和呈现变得并行。
下面是 ILI9341 源文件中 DrawBitmapDMA 函数的实现。
void ILI9341_DrawBitmapDMA(uint16_t w, uint16_t h, uint8_t *s)
{
LCD_WR_REG(0x2c);
DC_H();
ConvHL(s, (int32_t)w*h*2);
HAL_SPI_Transmit_DMA(&hspi1, (uint8_t*)s, w * h *2);
正如你所看到的,唯一的变化是数据是通过SPI DMA发送的,而不是使用阻塞模式发送的。
|