MCU:stm32f407vet6
LCD:ILI 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;