此内容由EEWORLD论坛网友huo_hu原创,如需转载或用于商业用途需征得作者同意并注明出处
现在文件系统弄好了,如果你想以更省资源更快的方式读写sd卡,得想办法绕开文件系统。原因有两个:
一个是文件系统的簇表组织形式不适合连续读写。文件系统的组织形式是一个链表的形式,在文件目录表里存放文件的起始簇号,当访问到文件的下一个簇的时候必须读取文件分配表也就是fat表,每个簇号映射的地址位置存放着下一个簇的簇号,特殊的簇号代表文件结束。所以当跨簇访问时就会触发文件分配表的读取。这一系列的动作都封装在文件系统里了,你访问文件的时候是感觉不到的。那没有文件系统怎么去操作sd卡呢?如果文件是连续存放的那就变得很简单了,我们需要的就是一个文件起始的扇区号。我 这里用winhex就能看到这个起始扇区号,也叫做物理扇区号。之所以叫物理扇区是因为它是绝对的数值,而文件系统里的各种扇区号都是相对于逻辑分区的起始扇区来计算的。
另外一点要保证文件是连续存放的,最好是格式化一下一个文件一个文件拷贝进去,使用时不要删除或移动这个文件那它就一直在那里呆着了。还有一点是文件的分配是以簇为单位的,假设你希望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。
以上就是全部内容了,谢谢观赏。
本帖最后由 huo_hu 于 2018-3-10 01:01 编辑