经验——DMA+FSMC加速LVGL

MCUstm32f407vet6

LCDILI 9488

传输方式FSMC

一、基本流程

        1,粗略介绍

        FSMC可将将片外外设LCD映射成MCU的内部地址,从而可以通过简单地向这个地址读取/写入数据,来完成MCU与LCD的数据通信。可是每次向这个地址读取/写入数据都需要CPU的加入,面对大量的图形数据,就难免对整体的性能产生影响。

        使用DMA则是省去了CPU参与每一个读取/写入数据的过程,只需要确定好要发送的缓冲区和发送的目的地,就可以直接开启DMA中断,让DMA负责传输数据,CPU则可以去忙其他事情。当DMA传输完成后,会“通知”一下CPU,让CPU去完成一些善后工作,即调用DMA的回调函数。

        需要说明的是DMA可以配置为不产生中断而在后台默默完成工作,而DMA传输完成后的“通知”通常是通过中断或DMA标志位实现,确实可以用来触发后续处理,如执行回调函数,进行后续任务调度或数据处理。

        2,流程实现

        现在目的就很清楚了,下面都将以DMA中断为例

                ①先启用DMA,即初始化DMA

                ②注册回调函数,即准备好DMA的善后工作

                ③开启DMA中断,即开始传输数据

二、过程

1,初始化DMA

先确定需要DMA哪个流和通道,这个需要自己查相关的芯片手册,找找DMA的请求映射

    hdma_memtomem_dma2_stream6.Instance = DMA2_Stream6;
    hdma_memtomem_dma2_stream6.Init.Channel = DMA_CHANNEL_0;

确定数据传输的方向,由于此次使用的是FSMC,LCD这个外设已经被映射为了地址,那么传输方向自然就是 存储器存储器

    hdma_memtomem_dma2_stream6.Init.Direction = DMA_MEMORY_TO_MEMORY;

需要确定好自己需要发送的缓冲区和要发送的地方。比如此刻,向LCD发送数据,那么发送缓冲区的地址无疑需要自增,由于要发送的地方一般为一个固定的地址,那么目的地址就可以配置为不自增

    hdma_memtomem_dma2_stream6.Init.PeriphInc = DMA_PINC_ENABLE;//源地址自增
    hdma_memtomem_dma2_stream6.Init.MemInc = DMA_MINC_DISABLE;  //目的地址不自增,因为LCD的地址是固定的

确定传输的数据格式。本次向LCD传输用的是16位,即半字

    hdma_memtomem_dma2_stream6.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_memtomem_dma2_stream6.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;

确定传输模式。DMA_NORMAL即进行一次传输过程,传输完成后就停止 ,如果需要再次传输,那么需要再次开启DMA中断

 hdma_memtomem_dma2_stream6.Init.Mode = DMA_NORMAL;

确定这个DMA传输的优先级。见名知义,如果只使用一个DMA,随便配不影响。多个的话,可以通过这个优先级来管理传输,比如说FSMC对外连接两个不同的的外设,线路是复用的,那么可以通过这个优先级来避免访问冲突。

 hdma_memtomem_dma2_stream6.Init.Mode = DMA_NORMAL;

⑦FIFO,队列嘛 ,一般不用改

 hdma_memtomem_dma2_stream6.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
 hdma_memtomem_dma2_stream6.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;

突发传输。说人话就是,传输过程中数据是一个单位一个单位传的,配置这个可以决定这个传输单位是多大,有单个,也有INC4、8、16。想要快一些就配置高一点。不过不能配置太高,不然外设和MCU总会有一个hold不住

 hdma_memtomem_dma2_stream6.Init.MemBurst = DMA_MBURST_INC8;   //源地址,16太快了,否则屏幕无反应
    hdma_memtomem_dma2_stream6.Init.PeriphBurst = DMA_MBURST_INC8;//目的地址,16太快了,否则白屏

⑨ 注册回调函数。下面那个LVGL_LCD_FSMC_DMA_pCallback就是你需要注册的回调函数,你可以自拟,在参数表里自然是指函数指针

  HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream6, HAL_DMA_XFER_CPLT_CB_ID, LVGL_LCD_FSMC_DMA_pCallback);
void FSMC_DMA_Init()
{
    __HAL_RCC_DMA2_CLK_ENABLE();

    hdma_memtomem_dma2_stream6.Instance = DMA2_Stream6;
    hdma_memtomem_dma2_stream6.Init.Channel = DMA_CHANNEL_0;
    hdma_memtomem_dma2_stream6.Init.Direction = DMA_MEMORY_TO_MEMORY;
    hdma_memtomem_dma2_stream6.Init.PeriphInc = DMA_PINC_ENABLE;//源地址自增
    hdma_memtomem_dma2_stream6.Init.MemInc = DMA_MINC_DISABLE;  //目的地址不自增,因为LCD的地址是固定的
    hdma_memtomem_dma2_stream6.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
    hdma_memtomem_dma2_stream6.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
    hdma_memtomem_dma2_stream6.Init.Mode = DMA_NORMAL;//传输模式,此即传输一次停止
    hdma_memtomem_dma2_stream6.Init.Priority = DMA_PRIORITY_MEDIUM;
    hdma_memtomem_dma2_stream6.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    hdma_memtomem_dma2_stream6.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_FULL;
    hdma_memtomem_dma2_stream6.Init.MemBurst = DMA_MBURST_INC8;   //源地址,16太快了,否则屏幕无反应
    hdma_memtomem_dma2_stream6.Init.PeriphBurst = DMA_MBURST_INC8;//目的地址,16太快了,否则白屏
    if (HAL_DMA_Init(&hdma_memtomem_dma2_stream6) != HAL_OK)
    {
        Error_Handler();
    }

    /*不受FreeRTOS调度*/
    HAL_NVIC_SetPriority(DMA2_Stream6_IRQn, 3, 0);
    HAL_NVIC_EnableIRQ(DMA2_Stream6_IRQn);
    /*注册回调函数
    * 流程:开启DMA中断后,DMA开始传输数据,传输完之后回到【pCallback】,需要注意的是中断处理函数得要定义*/
    HAL_DMA_RegisterCallback(&hdma_memtomem_dma2_stream6, HAL_DMA_XFER_CPLT_CB_ID, LVGL_LCD_FSMC_DMA_pCallback);
}

2、配置中断

        这个没什么好说的,就是容易忘。如果你开启了中断却没有配置中断函数,那么程序运行启动文件时,由于找不到这个中断函数就会进入死循环 InitLoop什么来着

void DMA2_Stream6_IRQHandler(void)
{
    HAL_DMA_IRQHandler(&hdma_memtomem_dma2_stream6);
}

3,实现中断回调函数

由于是在lvgl里启用DMA,那么自然也要在这里善后(图方便),在lvgl_port_disp.h文件里实现,注意不要忘了声明

void LVGL_LCD_FSMC_DMA_pCallback(DMA_HandleTypeDef *_hdma)
{
   // lv_disp_flush_ready(&disp_drv);

   //为了减少栈帧的使用
   disp_drv.draw_buf->flushing = 0;
   disp_drv.draw_buf->flushing_last = 0;
}

需要说明的是,我这里的disp_drv其实就是函数

void lv_port_disp_init(void)

里的静态局部变量,只不过把它从函数里提到了外面,实际作用并没有发生变化

4,启用DMA中断

        这个应该见码知义

static void disp_flush(lv_disp_drv_t *disp_drv1, const lv_area_t *area, lv_color_t *color_p)
{
    _LCD_Set_Window(area->x1, area->y1, area->x2, area->y2);//设置LCD屏幕的扫描区域
    HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream6, (uint32_t) color_p, (uint32_t) TFT_DATA_ADDR,
                     ((area->x2 + 1) - area->x1) * ((area->y2 + 1) - area->y1));
}

三、总结

        先理清传输的过程,那么这个DMA的配置就会显得很轻松,很简单。如果你出现频闪、白屏、屏幕无反应,那么就可以考虑仔细检查前面DMA的初始化。至于下面这个应该不会忘吧

DMA_HandleTypeDef hdma_memtomem_dma2_stream6;

  • 15
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值