4664|0

3783

帖子

0

资源

版主

2018新版cube之stm32f4 sd卡位图dma方式直写FSMC LCD(3) [复制链接]

本帖最后由 huo_hu 于 2018-3-10 01:01 编辑

此内容由EEWORLD论坛网友huo_hu原创,如需转载或用于商业用途需征得作者同意并注明出处


现在文件系统弄好了,如果你想以更省资源更快的方式读写sd卡,得想办法绕开文件系统。原因有两个:
       一个是文件系统的簇表组织形式不适合连续读写。文件系统的组织形式是一个链表的形式,在文件目录表里存放文件的起始簇号,当访问到文件的下一个簇的时候必须读取文件分配表也就是fat表,每个簇号映射的地址位置存放着下一个簇的簇号,特殊的簇号代表文件结束。所以当跨簇访问时就会触发文件分配表的读取。这一系列的动作都封装在文件系统里了,你访问文件的时候是感觉不到的。那没有文件系统怎么去操作sd卡呢?如果文件是连续存放的那就变得很简单了,我们需要的就是一个文件起始的扇区号。我 这里用winhex就能看到这个起始扇区号,也叫做物理扇区号。之所以叫物理扇区是因为它是绝对的数值,而文件系统里的各种扇区号都是相对于逻辑分区的起始扇区来计算的。
3_1.jpg


另外一点要保证文件是连续存放的,最好是格式化一下一个文件一个文件拷贝进去,使用时不要删除或移动这个文件那它就一直在那里呆着了。还有一点是文件的分配是以簇为单位的,假设你希望1.bin和2.bin是首位相接没有任何空隙的话,你要计算好,在格式化的时候给簇比较小的数值,比如我这个是image2lcd转换出来的,文件实际大小是800*480*2=768000个字节,如果一个簇是2个扇区则文件实际占用768000/1024=375个簇,正好填满。而如果是一个簇是4个扇区中间就会有空闲的数据。比较典型的应用是显示一个小动画,creatblock以后直接把所有的帧数据连续填充到lcd就完成整幅动画,而不是一帧一帧的画。事先算好就会简单了。
另外一个文件系统不好的地方是dma的等待完成,文件系统的固件库结构是这样的
从上到下的调用顺序
fatfs.c
ff.c
diskio.c sd_diskio.c
stm32f4XX_hal_sd.c  hal_dma.c
sdio.c  bsp_driver_sd.c 最底层的sd指令
这样的结构代码比较通用,我们看到这个sd卡的操作和dma都被封装在disk里,假设你使用的是u盘,那么替换diskio下面的部分就行了。这个disk就是按照设备驱动程序来做的,可以方便的移植到系统里。在sd_diskio.c里有读函数
DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
  if(BSP_SD_ReadBlocks_DMA((uint32_t*)buff,
                           (uint32_t) (sector),
                           count) == MSD_OK)
  {
    /* Wait that the reading process is completed or a timeout occurs */
    timeout = HAL_GetTick();
    while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
    {
    }

BSP_SD_ReadBlocks_DMA是启动dma读取扇区,它里面有分支读单个扇区或读多个扇区(使用的sd操作指令不一样),这个函数返回以后一个sd的读卡操作就已经启动了,但是并没有结束,一直到程序运行到while结束才会完成,要么超时,要么ReadStatus==0正常结束。
这里如果你是跑的系统多进程没问题,但是没有系统就不合适了,dma的作用完全没意义。这里要让dma发挥作用干脆直接调用下一层的函数,就是stm32f4xx_hal_sd.c里的
HAL_SD_ReadBlocks_DMA(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks)函数,
第一个参数是sd的设备句柄,注意这个设备句柄使用之前要初始化,也就是读取卡的各种参数,我们可以简单的执行mount来完成初始化,我放在fatfs.c里了

void MX_FATFS_Init(void)
{
  /*## FatFS: Link the SD driver ###########################*/
  retSD = FATFS_LinkDriver(&SD_Driver, SDPath);
  /*## FatFS: Link the USER driver ###########################*/
  retUSER = FATFS_LinkDriver(&USER_Driver, USERPath);

  /* USER CODE BEGIN Init */
retSD = f_mount(&SDFatFS, SDPath, 1);
  
  /* USER CODE END Init */
}

这个函数的第二个参数是读取数据的缓存地址,我们直接用lcd的数据地址来代替,也就是fsmc的数据地址
#define A16BIT   18
#define LCD_DATA_ADDR  (0x60000000+(uint32_t)(1<<(A16BIT+1)))
+1是因为每次两字节
第三个参数是BlockAdd也就是前面提到的绝对扇区
#define BMP_16_START 16529
第四个参数是读取的扇区数,我这里一整屏是800*480*2/512个扇区

所以调用形式为
if (hsd.State != HAL_SD_STATE_READY)
  return hsd.State;//错或忙
LCD_CreateBlock(0,0,800,480);
ret = HAL_SD_ReadBlocks_DMA(&hsd,(u8 *)LCD_DATA_ADDR,BMP_16_START ,(u32)800*480*2/512);

前面的条件就是dma传输整屏数据完成的条件,函数调用就启动,但是返回不意味着完成。

另外还有两个dma的地方需要修改,一个是dam传输完成中断里加回掉函数,上一节说过了。
还有一个重要的地方是dma的传输方式,如果sdio dma到内存缓冲区那么dma的地址要累加,而dma直接写到lcd地址不需要累加,还有传输的数据宽带也不一样,dma的配置参数已经保存在hdma_sdio_rx(sdio.c里定义的)结构里了,我们改它在调用一次dma配置就行了。
加一个函数完成修改和恢复
//设置sdiorxdma到lcd数据地址,参数1:设置,0:恢复成内存地址
u8 LCD_ReSetting_SdiorxDmaToLcd(u8 set) {
if (hdma_sdio_rx.State != HAL_DMA_STATE_READY)//忙或错误返回
  return hdma_sdio_rx.State;
if (set) {
  hdma_sdio_rx.Init.MemInc = DMA_MINC_DISABLE;  
  hdma_sdio_rx.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
} else {
  hdma_sdio_rx.Init.MemInc = DMA_MINC_ENABLE;
  hdma_sdio_rx.Init.MemDataAlignment = DMA_MDATAALIGN_WORD;   
}
    if (HAL_DMA_Init(&hdma_sdio_rx) != HAL_OK)
    {
      _Error_Handler(__FILE__, __LINE__);
    }
return 0;
}

大致的过程就是这样,mount以后改dma方式,掉函数直接写数据到lcd。
下面是效果,led翻转一次是刷一屏数据

视频暂缺

上面介绍的方法可以扩展到sd卡的高速连续写,能够达到很高的写入速度而且开销不大。
写卡的时候要注意一点,一次HAL_SD_WriteBlocks_DMA不能向sd卡写入超出sd内部缓存数量的数据,卡不同缓冲不一样,2G卡一次不要超4K,16G不要超16K。

以上就是全部内容了,谢谢观赏。




此帖出自单片机论坛

回复
您需要登录后才可以回帖 登录 | 注册

最新文章 更多>>
    关闭
    站长推荐上一条 1/6 下一条

    About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

    站点相关: 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

    北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

    电子工程世界版权所有 京ICP证060456号 京ICP备10001474号 电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright © 2005-2020 EEWORLD.com.cn, Inc. All rights reserved
    快速回复 返回顶部 返回列表