【STM32H7教程】第88章 STM32H7的SDMMC總線應用之SD卡移植FatFs文件系統


完整教程下載地址:http://www.armbbs.cn/forum.php?mod=viewthread&tid=86980

第88章       STM32H7的SDMMC總線應用之SD卡移植FatFs文件系統

本章節為大家講解SD卡的FatFs文件系統移植。

88.1 初學者重要提示

88.2 SD卡硬件接口設計

88.3 SD卡基礎知識

88.4 各種存儲卡區別

88.5 關於SD卡內部是否自帶擦寫均衡

88.6 FatFs文件系統簡介

88.7 FatFs移植步驟

88.8 FatFs應用代碼測試

88.9 FatFs移植接口文件diskio.c說明

88.10 SDMMMC自帶IDMA的4字節對齊問題(重要)

88.11 實驗例程設計框架

88.12 實驗例程說明(MDK)

88.13 實驗例程說明(IAR)

88.14 總結

 

 

88.1 初學者重要提示

1、  SDMMC的相關知識點可以看第87章。

2、  操作SD卡是以扇區(512字節)為單位進行操作。

3、  SD卡聯盟強烈強烈建議使用此軟件來格式化SD/SDHC/SDXC卡,而不要使用各個操作系統隨附的格式化工具。通常,操作系統附帶的格式化工具可以格式化包括SD/SDHC/SDXC卡在內的各種存儲介質,但是可能無法針對SD/SDHC/SDXC卡進行優化,並且可能導致性能降低。

軟件下載:http://www.armbbs.cn/forum.php?mod=viewthread&tid=96181

4、 STM32H7的SDMMC也支持eMMC:

  •  【普及貼】各個廠家eMMC讀寫速度,鎂光,東芝,三星,ISSI和旺宏

http://www.armbbs.cn/forum.php?mod=viewthread&tid=95954

  •   H7的8線SDIO DMA驅動eMMC的裸機性能,讀43MB/S,寫18.8MB/S

http://www.armbbs.cn/forum.php?mod=viewthread&tid=95953

5、  支持128GB的大容量SD卡,需要大家使能FatFS的exFAT即可。

6、  FatFs的掛載函數f_mount可以上電后僅調用一次,本章配套例子為了測試方式,使用前掛載,使用完畢后卸載。

7、  FAT文件系統基礎:

http://elm-chan.org/fsw/ff/00index_e.html

8、  exFAT文件系統基礎:

http://elm-chan.org/fsw/ff/00index_e.html

9、  FatFS API列表:

http://elm-chan.org/fsw/ff/00index_e.html

10、  FatFS 應用筆記(含移植說明)

http://elm-chan.org/fsw/ff/doc/appnote.html

11、  FatFS中ffconf.h文件中各種配置說明:

http://elm-chan.org/fsw/ff/doc/config.html

12、  FatFs的RTOS版本移植:

  •   基於STM32H7的uCOS-III + FatFS + emWin + ST USB的綜合模板下載

http://www.armbbs.cn/forum.php?mod=viewthread&tid=100125

  •   基於STM32H7的FreeRTOS+ FatFS + emWin + ST USB的綜合模板下載

http://www.armbbs.cn/forum.php?mod=viewthread&tid=100127

  •   基於STM32H7的uCOS-II + FatFS + emWin + ST USB的綜合模板下載

http://www.armbbs.cn/forum.php?mod=viewthread&tid=100126

88.2 SD卡硬件接口設計

STM32H7驅動SD卡設計如下:

 

關於這個原理圖,要了解到以下幾個知識:

  •   大家自己設計推薦也接上拉電阻。
  •   這里采用SDMMC的4線方式。

88.3 SD卡基礎知識

這里將SD卡相關的基礎知識為大家做個普及。

88.3.1 SD卡分類

根據不同容量做的區分,主要包括Full SD,miniSD和microSD。

 

88.3.2 SD卡容量及其使用的文件系統

容量小於2GB(SD卡)使用FAT12或者FAT16,容量在2GB和32GB之間(SDHC卡)使用FAT32,容量大於32GB小於2TB(SDXC卡)使用exFAT。

 

88.3.3 SD卡總線速度和速度等級

SD卡速度:

 

SD卡速度等級:

 

88.4 各種存儲卡區別

市面上的卡種類非常多,容易把人搞糊塗,這里將這些卡種類為大家做個區分:

88.4.1 SD卡,miniSD卡,TF卡,MircoSD卡

TF卡是MicroSD卡的另一種叫法,無需做區分。SD卡,miniSD卡,MircoSD卡其實是一種卡,區別是引腳使用上。

88.4.2 SDIO卡

SDIO卡就是使用SDIO外設來接SD卡。

而為什么叫SDIO,根據wiki百科說明,其實就是SD卡接口規范的擴展,帶了輸入輸出功能,這個接口不僅可以接SD卡,還可以接其它外設,如條形碼讀卡器,WiFi,藍牙,調制解調器等。

https://en.wikipedia.org/wiki/SD_card#SDIO_cards

對於STM32的SDIO來說,他就是指STM32的一個外設接口,不僅能夠來接SD卡,還可以接其它外設。

 

88.4.3 MMC卡,eMMC

截止2018年,市場上已經沒有設備內置MMC卡槽,但eMMC得到了廣泛應用https://en.wikipedia.org/wiki/MultiMediaCard

88.4.4 CF卡

CF卡是早期最成功的存儲卡格式之一,像MMC/SD卡都是后來才推出的。CF卡仍然很受歡迎卡之一,並得到許多專業設備和高端消費類設備的支持。截至2017年,佳能和尼康都將CompactFlash用於其旗艦數碼相機。佳能還選擇了CompactFlash作為其專業高清無帶攝像機的記錄介質。

基礎規格:

 

實際效果:

 

 

88.4.5 總體區別

來自Wiki:https://en.wikipedia.org/wiki/SD_card#Micro

 

88.5 關於SD卡內部是否自帶擦寫均衡

根據網上搜的一個閃迪的規格書,里面說是帶擦寫均衡的:

http://www.armbbs.cn/forum.php?mod=viewthread&tid=102891

 

88.6 FatFs文件系統簡介

FatFs是用於小型嵌入式系統的通用FAT / exFAT文件系統模塊。FatFs是按照ANSI C(C89)編寫的並且與磁盤I / O層完全分開。因此,它獨立於平台。它可以並入資源有限的小型MCU中,例如8051,PIC,AVR,ARM,Z80,RX等。此處還提供了適用於小型MCU的Petit FatFs模塊。

特征:

  1.   DOS / Windows兼容的FAT / exFAT文件系統。
  2.   平台無關,容易移植。
  3.   程序代碼和工作區的占用空間非常小。
  4.   支持以下各種配置選項:
  •  ANSI / OEM或Unicode中的長文件名。
  •  exFAT文件系統,64位LBA和GPT可存儲大量數據。
  • RTOS的線程安全。
  •  多個卷(物理驅動器和分區)。
  •  可變扇區大小。
  •  多個代碼頁,包括DBCS。
  •   只讀,可選API,I / O緩沖區等

 

 

FatFS的官網的資料介紹非常詳細,大家哪里不清楚的都可以找到說明,如果打算使用FatFS,建議把官方的這些資料了解下:

  •   FAT文件系統基礎:

http://elm-chan.org/fsw/ff/00index_e.html

  •   exFAT文件系統基礎:

http://elm-chan.org/fsw/ff/00index_e.html

  •   FatFS API列表:

http://elm-chan.org/fsw/ff/00index_e.html

  •   FatFS 應用筆記(含移植說明)

http://elm-chan.org/fsw/ff/doc/appnote.html

  •   FatFS中ffconf.h文件中各種配置說明:

http://elm-chan.org/fsw/ff/doc/config.html

88.7 FatFs移植步驟

這里將FatFs的移植步驟為大家做個說明。

FatFs各個文件的依賴關系:

 

驅動一個磁盤或者多個磁盤的框圖:

 

88.7.1 第1步,了解整體設計框架

為了方便大家移植,需要大家先對移植好的工程有個整體認識:

 

88.7.2 第2步,添加FatFs和SDMMC驅動到工程

本教程前面章節配套的例子都可以作為模板使用,在模板的基礎上需要添加FatFs文件,SDMMC驅動文件和SD卡驅動文件,大家可以直接從本章教程提供的例子里面復制。

  •   SD卡驅動文件bsp_sdio_sd.c和bsp_sdio_sd.h添加到自己的工程里面,路徑不限。

配套例子是放在\User\bsp\src和\User\bsp\inc文件。

  •   SDMMMC驅動文件stm32h7xx_hal_sd.c和stm32h7xx_ll_sdmmc.c

這個是STM32H7的HAL庫自帶的。

  •   FatFs相關源文件。

大家可以將所有相關文件都復制到自己的工程里面,配套例子是放在\Libraries\FatFs。

88.7.3 第3步,添加工程路徑

當前需要添加的兩個FatFs路徑,大家根據自己添加的源文件位置,添加相關路徑即可:

 

88.7.4 第4步,配置GPIO和時鍾

根據大家使用SDMMC1或者SDMMC2配置相應時鍾和GPIO,當前V7板子是用的SDMMC1:

__weak void BSP_SD_MspInit(SD_HandleTypeDef *hsd, void *Params)
{
  GPIO_InitTypeDef gpio_init_structure;

  /* Enable SDIO clock */
  __HAL_RCC_SDMMC1_CLK_ENABLE();

  /* Enable GPIOs clock */
  __HAL_RCC_GPIOB_CLK_ENABLE();
  __HAL_RCC_GPIOC_CLK_ENABLE();
  __HAL_RCC_GPIOD_CLK_ENABLE();

  gpio_init_structure.Mode      = GPIO_MODE_AF_PP;
  gpio_init_structure.Pull      = GPIO_NOPULL;
  gpio_init_structure.Speed     = GPIO_SPEED_FREQ_VERY_HIGH;

  /* D0(PC8), D1(PC9), D2(PC10), D3(PC11), CK(PC12), CMD(PD2) */
  /* Common GPIO configuration */
  gpio_init_structure.Alternate = GPIO_AF12_SDIO1;

  /* GPIOC configuration */
  gpio_init_structure.Pin = GPIO_PIN_8 | GPIO_PIN_9 | GPIO_PIN_10 | GPIO_PIN_11 | GPIO_PIN_12;
  HAL_GPIO_Init(GPIOC, &gpio_init_structure);

  /* GPIOD configuration */
  gpio_init_structure.Pin = GPIO_PIN_2;
  HAL_GPIO_Init(GPIOD, &gpio_init_structure);

  __HAL_RCC_SDMMC1_FORCE_RESET();
  __HAL_RCC_SDMMC1_RELEASE_RESET();

  /* NVIC configuration for SDIO interrupts */
  HAL_NVIC_SetPriority(SDMMC1_IRQn, 5, 0);
  HAL_NVIC_EnableIRQ(SDMMC1_IRQn);
}

88.7.5 第5步,MPU配置

為了方便大家移植測試,我們這里直接關閉AXI SRAM的讀Cache和寫Cache(這樣就跟使用STM32F1或者STM32F4系列里面的SRAM一樣)。此配置是在bsp.c文件的MPU_Config函數里面實現:

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

#if 0
       /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

 #else
     /* 當前是采用下面的配置 */
    /* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif
    /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

88.7.6 第6步,FatFs的配置文件ffconf.h設置

移植階段,這個里面的配置可以不用動,無需任何修改。需要注意的是我們這里開啟了長文件名支持,對應的宏定義:

#define _USE_LFN     3    

88.7.7 第7步,添加應用代碼

這里將FatFs大部分操作函數都做了應用,專門整理到了文件demo_sd_fatfs.c。通過串口命令進行操作,大家可以直接將這個文件添加到自己的工程里面。

另外注意,如果自己的工程里面沒有移植我們其它的驅動,可以直接調用FatFs的測試函數,比如瀏覽SD根目錄文件,可以直接調用函數ViewRootDir。

88.8 FatFs應用代碼測試

這里將FatFs大部分函數都做了測試。注意,所有用到的函數在FatFs官網都有詳細說明。

88.8.1 注冊SD卡驅動

注冊SD卡功能是ST簡單封裝的一個函數,方便用戶實現FatFs驅動多個磁盤。

代碼如下:

char DiskPath[4]; /* SD卡邏輯驅動路徑,比盤符0,就是"0:/" */
/* 注冊SD卡驅動 */
FATFS_LinkDriver(&SD_Driver, DiskPath);

88.8.2 SD卡文件瀏覽

SD卡根目錄的文件瀏覽代碼實現如下:

/*
*********************************************************************************************************
*    函 數 名: ViewRootDir
*    功能說明: 顯示SD卡根目錄下的文件名
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
extern SD_HandleTypeDef uSdHandle;
static void ViewRootDir(void)
{
    FRESULT result;
    uint32_t cnt = 0;
    FILINFO fno;
    
     /* 掛載文件系統 */
    result = f_mount(&fs, DiskPath, 0);    /* Mount a logical drive */
    if (result != FR_OK)
    {
        printf("掛載文件系統失敗 (%s)\r\n", FR_Table[result]);
    }

    /* 打開根文件夾 */
    result = f_opendir(&DirInf, DiskPath); /* 如果不帶參數,則從當前目錄開始 */
    if (result != FR_OK)
    {
        printf("打開根目錄失敗  (%s)\r\n", FR_Table[result]);
        return;
    }

    printf("屬性        |  文件大小 | 短文件名 | 長文件名\r\n");
    for (cnt = 0; ;cnt++)
    {
        result = f_readdir(&DirInf, &FileInf);         /* 讀取目錄項,索引會自動下移 */
        if (result != FR_OK || FileInf.fname[0] == 0)
        {
            break;
        }

        if (FileInf.fname[0] == '.')
        {
            continue;
        }

        /* 判斷是文件還是子目錄 */
        if (FileInf.fattrib & AM_DIR)
        {
            printf("(0x%02d)目錄  ", FileInf.fattrib);
        }
        else
        {
            printf("(0x%02d)文件  ", FileInf.fattrib);
        }

        f_stat(FileInf.fname, &fno);
        
        /* 打印文件大小, 最大4G */
        printf(" %10d", (int)fno.fsize);


        printf("  %s\r\n", (char *)FileInf.fname);    /* 長文件名 */
    }
 
    /* 打印卡速度信息 */
    if(uSdHandle.SdCard.CardSpeed == CARD_NORMAL_SPEED)
    {
        printf("Normal Speed Card <12.5MB/S, MAX Clock < 25MHz, Spec Version 1.01\r\n");           
    }
    else if (uSdHandle.SdCard.CardSpeed == CARD_HIGH_SPEED)
    {
        printf("High Speed Card <25MB/s, MAX Clock < 50MHz, Spec Version 2.00\r\n");            
    }
    else if (uSdHandle.SdCard.CardSpeed == CARD_ULTRA_HIGH_SPEED)
    {
        printf("UHS-I SD Card <50MB/S for SDR50, DDR50 Cards, MAX Clock < 50MHz OR 100MHz\r\n");
        printf("UHS-I SD Card <104MB/S for SDR104, MAX Clock < 108MHz, Spec version 3.01\r\n");   
    }    

    
    /* 卸載文件系統 */
     f_mount(NULL, DiskPath, 0);
}
  •   f_mount可以上電后僅調用一次,我們這里是為了測試方式,使用前掛載,使用完畢后卸載。
  •  代碼里面加入了SD卡速度信息打印功能,方便大家了解自己的卡類型。通過查詢全局結構體變量uSdHandle來實現。
  •   文件瀏覽通過函數f_readdir實現。

88.8.3 SD卡創建txt文件並寫入數據

代碼實現如下:

/*
*********************************************************************************************************
*    函 數 名: CreateNewFile
*    功能說明: 在SD卡創建一個新文件,文件內容填寫“www.armfly.com”
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CreateNewFile(void)
{
    FRESULT result;
    uint32_t bw;
    char path[32];


     /* 掛載文件系統 */
    result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
    if (result != FR_OK)
    {
        printf("掛載文件系統失敗 (%s)\r\n", FR_Table[result]);
    }

    /* 打開文件 */
    sprintf(path, "%sarmfly.txt", DiskPath);
    result = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);
    if (result == FR_OK)
    {
        printf("armfly.txt 文件打開成功\r\n");
    }
    else
    {
        printf("armfly.txt 文件打開失敗  (%s)\r\n", FR_Table[result]);
    }

    /* 寫一串數據 */
    result = f_write(&file, FsWriteBuf, strlen(FsWriteBuf), &bw);
    if (result == FR_OK)
    {
        printf("armfly.txt 文件寫入成功\r\n");
    }
    else
    {
        printf("armfly.txt 文件寫入失敗  (%s)\r\n", FR_Table[result]);
    }

    /* 關閉文件*/
    f_close(&file);

    /* 卸載文件系統 */
    f_mount(NULL, DiskPath, 0);
}
  •   f_mount可以上電后僅調用一次,我們這里是為了測試方式,使用前掛載,使用完畢后卸載。
  •   函數f_open用來創建並打開文件。
  •   函數f_write用來寫入數據。
  •   函數f_close用來關閉文件,注意調用完函數f_write后,內容還沒有實際寫入到SD卡中,調用了f_close后,數據才真正的寫入到SD卡。當然也可以調用函數f_sync,內容也會實際的寫入。

88.8.4 SD卡文件讀取

代碼實現如下:

/*
*********************************************************************************************************
*    函 數 名: ReadFileData
*    功能說明: 讀取文件armfly.txt前128個字符,並打印到串口
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
static void ReadFileData(void)
{
    FRESULT result;
    uint32_t bw;
    char path[64];

    
     /* 掛載文件系統 */
    result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
    if (result != FR_OK)
    {
        printf("掛載文件系統失敗 (%s)\r\n", FR_Table[result]);
    }

    /* 打開文件 */
    sprintf(path, "%sarmfly.txt", DiskPath);
    result = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
    if (result !=  FR_OK)
    {
        printf("Don't Find File : armfly.txt\r\n");
        return;
    }

    /* 讀取文件 */
    result = f_read(&file, FsReadBuf, sizeof(FsReadBuf), &bw);
    if (bw > 0)
    {
        FsReadBuf[bw] = 0;
        printf("\r\narmfly.txt 文件內容 : \r\n%s\r\n", FsReadBuf);
    }
    else
    {
        printf("\r\narmfly.txt 文件內容 : \r\n");
    }

    /* 關閉文件*/
    f_close(&file);

    /* 卸載文件系統 */
    f_mount(NULL, DiskPath, 0);
}
  •  f_mount可以上電后僅調用一次,我們這里是為了測試方式,使用前掛載,使用完畢后卸載。
  •   函數f_open用來打開文件。
  •   函數f_read用來讀取文件中的內容。
  •   函數f_close用來關閉打開的文件。

88.8.5 SD卡創建文件夾

代碼實現如下:

/*
*********************************************************************************************************
*    函 數 名: CreateDir
*    功能說明: 在SD卡根目錄創建Dir1和Dir2目錄,在Dir1目錄下創建子目錄Dir1_1
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CreateDir(void)
{
    FRESULT result;
    char path[64]; 

    
     /* 掛載文件系統 */
    result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
    if (result != FR_OK)
    {
        printf("掛載文件系統失敗 (%s)\r\n", FR_Table[result]);
    }

    /* 創建目錄/Dir1 */
    sprintf(path, "%sDir1", DiskPath);
    result = f_mkdir(path);
    if (result == FR_OK)
    {
        printf("f_mkdir Dir1 Ok\r\n");
    }
    else if (result == FR_EXIST)
    {
        printf("Dir1 目錄已經存在(%d)\r\n", result);
    }
    else
    {
        printf("f_mkdir Dir1 失敗 (%s)\r\n", FR_Table[result]);
        return;
    }

    /* 創建目錄/Dir2 */
    sprintf(path, "%sDir2", DiskPath);
    result = f_mkdir(path);
    if (result == FR_OK)
    {
        printf("f_mkdir Dir2 Ok\r\n");
    }
    else if (result == FR_EXIST)
    {
        printf("Dir2 目錄已經存在(%d)\r\n", result);
    }
    else
    {
        printf("f_mkdir Dir2 失敗 (%s)\r\n", FR_Table[result]);
        return;
    }

    /* 創建子目錄 /Dir1/Dir1_1       注意:創建子目錄Dir1_1時,必須先創建好Dir1 */
    sprintf(path, "%sDir1/Dir1_1", DiskPath);
    result = f_mkdir(path); /* */
    if (result == FR_OK)
    {
        printf("f_mkdir Dir1_1 成功\r\n");
    }
    else if (result == FR_EXIST)
    {
        printf("Dir1_1 目錄已經存在 (%d)\r\n", result);
    }
    else
    {
        printf("f_mkdir Dir1_1 失敗 (%s)\r\n", FR_Table[result]);
        return;
    }

    /* 卸載文件系統 */
    f_mount(NULL, DiskPath, 0);
}
  •   f_mount可以上電后僅調用一次,我們這里是為了測試方式,使用前掛載,使用完畢后卸載。
  •   創建目錄通過函數f_mkdir。

88.8.6 SD卡文件和文件夾刪除

代碼實現如下:

/*
*********************************************************************************************************
*    函 數 名: DeleteDirFile
*    功能說明: 刪除SD卡根目錄下的 armfly.txt 文件和 Dir1,Dir2 目錄
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
static void DeleteDirFile(void)
{
    FRESULT result;
    uint8_t i;
    char path[64]; 
    
     /* 掛載文件系統 */
    result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
    if (result != FR_OK)
    {
        printf("掛載文件系統失敗 (%s)\r\n", FR_Table[result]);
    }
    
    /* 刪除目錄/Dir1 【因為還存在目錄非空(存在子目錄),所以這次刪除會失敗】*/
    sprintf(path, "%sDir1", DiskPath);
    result = f_unlink(path);
    if (result == FR_OK)
    {
        printf("刪除目錄Dir1成功\r\n");
    }
    else if (result == FR_NO_FILE)
    {
        printf("沒有發現文件或目錄 :%s\r\n", "/Dir1");
    }
    else
    {
        printf("刪除Dir1失敗(錯誤代碼 = %d) 文件只讀或目錄非空\r\n", result);
    }

    /* 先刪除目錄/Dir1/Dir1_1 */
    sprintf(path, "%sDir1/Dir1_1", DiskPath);
    result = f_unlink(path);
    if (result == FR_OK)
    {
        printf("刪除子目錄/Dir1/Dir1_1成功\r\n");
    }
    else if ((result == FR_NO_FILE) || (result == FR_NO_PATH))
    {
        printf("沒有發現文件或目錄 :%s\r\n", "/Dir1/Dir1_1");
    }
    else
    {
        printf("刪除子目錄/Dir1/Dir1_1失敗(錯誤代碼 = %d) 文件只讀或目錄非空\r\n", result);
    }

    /* 先刪除目錄/Dir1 */
    sprintf(path, "%sDir1", DiskPath);
    result = f_unlink(path);
    if (result == FR_OK)
    {
        printf("刪除目錄Dir1成功\r\n");
    }
    else if (result == FR_NO_FILE)
    {
        printf("沒有發現文件或目錄 :%s\r\n", "/Dir1");
    }
    else
    {
        printf("刪除Dir1失敗(錯誤代碼 = %d) 文件只讀或目錄非空\r\n", result);
    }

    /* 刪除目錄/Dir2 */
    sprintf(path, "%sDir2", DiskPath);
    result = f_unlink(path);
    if (result == FR_OK)
    {
        printf("刪除目錄 Dir2 成功\r\n");
    }
    else if (result == FR_NO_FILE)
    {
        printf("沒有發現文件或目錄 :%s\r\n", "/Dir2");
    }
    else
    {
        printf("刪除Dir2 失敗(錯誤代碼 = %d) 文件只讀或目錄非空\r\n", result);
    }

    /* 刪除文件 armfly.txt */
    sprintf(path, "%sarmfly.txt", DiskPath);
    result = f_unlink(path);
    if (result == FR_OK)
    {
        printf("刪除文件 armfly.txt 成功\r\n");
    }
    else if (result == FR_NO_FILE)
    {
        printf("沒有發現文件或目錄 :%s\r\n", "armfly.txt");
    }
    else
    {
        printf("刪除armfly.txt失敗(錯誤代碼 = %d) 文件只讀或目錄非空\r\n", result);
    }

    /* 刪除文件 speed1.txt */
    for (i = 0; i < 20; i++)
    {
        sprintf(path, "%sSpeed%02d.txt", DiskPath, i);/* 每寫1次,序號遞增 */    
        result = f_unlink(path);
        if (result == FR_OK)
        {
            printf("刪除文件%s成功\r\n", path);
        }
        else if (result == FR_NO_FILE)
        {
            printf("沒有發現文件:%s\r\n", path);
        }
        else
        {
            printf("刪除%s文件失敗(錯誤代碼 = %d) 文件只讀或目錄非空\r\n", path, result);
        }
    }

    /* 卸載文件系統 */
    f_mount(NULL, DiskPath, 0);
}
  •   f_mount可以上電后僅調用一次,我們這里是為了測試方式,使用前掛載,使用完畢后卸載。
  •   文件夾和文件的刪除都是通過函數f_unlink實現,這里注意一點,刪除文件夾時,只有文件夾中的內容為空時,才可以刪除文件夾。

88.8.7 SD卡讀寫速度測試

代碼實現如下,主要是方便大家測試SD卡的讀寫性能。

/*
*********************************************************************************************************
*    函 數 名: WriteFileTest
*    功能說明: 測試文件讀寫速度
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
static void WriteFileTest(void)
{
    FRESULT result;
    char path[64]; 
    uint32_t bw;
    uint32_t i,k;
    uint32_t runtime1,runtime2,timelen;
    uint8_t err = 0;
    static uint8_t s_ucTestSn = 0;

    
    for (i = 0; i < sizeof(g_TestBuf); i++)
    {
        g_TestBuf[i] = (i / 512) + '0';
    }

      /* 掛載文件系統 */
    result = f_mount(&fs, DiskPath, 0);            /* Mount a logical drive */
    if (result != FR_OK)
    {
        printf("掛載文件系統失敗 (%s)\r\n", FR_Table[result]);
    }

    /* 打開文件 */
    sprintf(path, "%sSpeed%02d.txt", DiskPath, s_ucTestSn++); /* 每寫1次,序號遞增 */    
    result = f_open(&file, path, FA_CREATE_ALWAYS | FA_WRITE);

    /* 寫一串數據 */
    printf("開始寫文件%s %dKB ...\r\n", path, TEST_FILE_LEN / 1024);
    
    runtime1 = bsp_GetRunTime();    /* 讀取系統運行時間 */
    for (i = 0; i < TEST_FILE_LEN / BUF_SIZE; i++)
    {
        result = f_write(&file, g_TestBuf, sizeof(g_TestBuf), &bw);
        if (result == FR_OK)
        {
            if (((i + 1) % 8) == 0)
            {
                printf(".");
            }
        }
        else
        {
            err = 1;
            printf("%s文件寫失敗\r\n", path);
            break;
        }
    }
    runtime2 = bsp_GetRunTime();    /* 讀取系統運行時間 */
    
    if (err == 0)
    {
        timelen = (runtime2 - runtime1);
        printf("\r\n  寫耗時 : %dms   平均寫速度 : %dB/S (%dKB/S)\r\n",
            timelen,
            (TEST_FILE_LEN * 1000) / timelen,
            ((TEST_FILE_LEN / 1024) * 1000) / timelen);
    }

    f_close(&file);        /* 關閉文件*/


    /* 開始讀文件測試 */
    result = f_open(&file, path, FA_OPEN_EXISTING | FA_READ);
    if (result !=  FR_OK)
    {
        printf("沒有找到文件: %s\r\n", path);
        return;
    }

    printf("開始讀文件 %dKB ...\r\n", TEST_FILE_LEN / 1024);
    
    runtime1 = bsp_GetRunTime();    /* 讀取系統運行時間 */
    for (i = 0; i < TEST_FILE_LEN / BUF_SIZE; i++)
    {
        result = f_read(&file, g_TestBuf, sizeof(g_TestBuf), &bw);
        if (result == FR_OK)
        {
            if (((i + 1) % 8) == 0)
            {
                printf(".");
            }

            /* 比較寫入的數據是否正確,此語句會導致讀卡速度結果降低到 3.5MBytes/S */
            for (k = 0; k < sizeof(g_TestBuf); k++)
            {
                if (g_TestBuf[k] != (k / 512) + '0')
                {
                      err = 1;
                    printf("Speed1.txt 文件讀成功,但是數據出錯\r\n");
                    break;
                }
            }
            if (err == 1)
            {
                break;
            }
        }
        else
        {
            err = 1;
            printf("Speed1.txt 文件讀失敗\r\n");
            break;
        }
    }

    runtime2 = bsp_GetRunTime();    /* 讀取系統運行時間 */
    
    if (err == 0)
    {
        timelen = (runtime2 - runtime1);
        printf("\r\n  讀耗時 : %dms   平均讀速度 : %dB/S (%dKB/S)\r\n", timelen,
            (TEST_FILE_LEN * 1000) / timelen, ((TEST_FILE_LEN / 1024) * 1000) / timelen);
    }

    /* 關閉文件*/
    f_close(&file);

    /* 卸載文件系統 */
    f_mount(NULL, DiskPath, 0);
}
  •   f_mount可以上電后僅調用一次,我們這里是為了測試方式,使用前掛載,使用完畢后卸載。
  •   為了實現更高性能的測試,大家可以加大宏定義

#define BUF_SIZE                           (4*1024)              /* 每次讀寫SD卡的最大數據長度 */

設置的緩沖大小,比如設置為64KB進行測試。

88.9 FatFs移植接口文件diskio.c說明

這里將FatFs的底層接口文件diskio.c的實現為大家簡單做個說明。

88.9.1 磁盤狀態函數disk_status

代碼如下:

/**
  * @brief  Gets Disk Status
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS disk_status (
    BYTE pdrv        /* Physical drive number to identify the drive */
)
{
  DSTATUS stat;

  stat = disk.drv[pdrv]->disk_status(disk.lun[pdrv]);
  return stat;
}

實際對應的函數在文件sd_diskio_dma.c

/**
  * @brief  Gets Disk Status
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_status(BYTE lun)
{
  return SD_CheckStatus(lun);
}

static DSTATUS SD_CheckStatus(BYTE lun)
{
  Stat = STA_NOINIT;

  if(BSP_SD_GetCardState() == MSD_OK)
  {
    Stat &= ~STA_NOINIT;
  }

  return Stat;
}

88.9.2 磁盤初始化函數disk_initialize

代碼如下:

/**
  * @brief  Initializes a Drive
  * @param  pdrv: Physical drive number (0..)
  * @retval DSTATUS: Operation status
  */
DSTATUS disk_initialize (
    BYTE pdrv                /* Physical drive nmuber to identify the drive */
)
{
  DSTATUS stat = RES_OK;

  if(disk.is_initialized[pdrv] == 0)
  {
    disk.is_initialized[pdrv] = 1;
    stat = disk.drv[pdrv]->disk_initialize(disk.lun[pdrv]);
  }
  return stat;
}

實際對應的函數在文件sd_diskio_dma.c:

/**
  * @brief  Initializes a Drive
  * @param  lun : not used
  * @retval DSTATUS: Operation status
  */
DSTATUS SD_initialize(BYTE lun)
{
#if !defined(DISABLE_SD_INIT)

  if(BSP_SD_Init() == MSD_OK)
  {
    Stat = SD_CheckStatus(lun);
  }

#else
  Stat = SD_CheckStatus(lun);
#endif
  return Stat;
}

88.9.3 磁盤讀函數disk_read

代碼如下:

/**
  * @brief  Reads Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT disk_read (
    BYTE pdrv,        /* Physical drive nmuber to identify the drive */
    BYTE *buff,        /* Data buffer to store read data */
    DWORD sector,            /* Sector address in LBA */
    UINT count        /* Number of sectors to read */
)
{
  DRESULT res;

  res = disk.drv[pdrv]->disk_read(disk.lun[pdrv], buff, sector, count);
  return res;
}

實際對應的函數在文件sd_diskio_dma.c:

下面代碼中最關鍵的處理是形參buff的4字節對齊問題(SDMMC自帶的IDMA需要4字節對齊),如果buff地址是4字節對齊的,不做處理,如果不是對齊,通過復制到一個4字節對齊的緩沖里面做DMA傳遞。這個是理解下面代碼的關鍵。

/**
  * @brief  Reads Sector(s)
  * @param  lun : not used
  * @param  *buff: Data buffer to store read data
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to read (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT SD_read(BYTE lun, BYTE *buff, DWORD sector, UINT count)
{
    DRESULT res = RES_ERROR;
    uint32_t timeout;
    ReadStatus = 0;

    if (!((uint32_t)buff & 0x3))
    {
        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))
            {
            }
            
            /* incase of a timeout return error */
            if (ReadStatus == 0)
            {
                res = RES_ERROR;
            }
            else
            {
                ReadStatus = 0;
                timeout = HAL_GetTick();

                while((HAL_GetTick() - timeout) < SD_TIMEOUT)
                {
                    if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
                    {
                        res = RES_OK;
                        
                        #if (ENABLE_SD_DMA_CACHE_MAINTENANCE_READ == 1)
                           SCB_CleanInvalidateDCache();
                        #endif
                        break;
                    }
                }
            }
        }
    }
    else
    {
        uint8_t ret;
        int i;

        for (i = 0; i < count; i++) 
        { 

            ret = BSP_SD_ReadBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);

            if(ret == MSD_OK)
            {
                /* Wait that the reading process is completed or a timeout occurs */
                timeout = HAL_GetTick();
                while((ReadStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
                {
                    
                }
                /* incase of a timeout return error */
                if (ReadStatus == 0)
                {
                    break;
                }
                else
                {
                    ReadStatus = 0;
                    timeout = HAL_GetTick();

                    while((HAL_GetTick() - timeout) < SD_TIMEOUT)
                    {
                        if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
                        {
                            #if (ENABLE_SD_DMA_CACHE_MAINTENANCE_READ == 1)
                                SCB_CleanInvalidateDCache();
                            #endif
                            
                            memcpy(buff, scratch, BLOCKSIZE);
                            buff += BLOCKSIZE;
                            
                            break;
                        }
                    }
                }
            }
            else
            {
                break;
            }
        }
        if ((i == count) && (ret == MSD_OK))
        {
           res = RES_OK;       
        }
    }
    return res;
}

88.9.4 磁盤寫函數disk_write

代碼如下:

/**
  * @brief  Writes Sector(s)
  * @param  pdrv: Physical drive number (0..)
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
DRESULT disk_write (
    BYTE pdrv,        /* Physical drive nmuber to identify the drive */
    const BYTE *buff,    /* Data to be written */
    DWORD sector,        /* Sector address in LBA */
    UINT count            /* Number of sectors to write */
)
{
  DRESULT res;

  res = disk.drv[pdrv]->disk_write(disk.lun[pdrv], buff, sector, count);
  return res;
}

實際對應的函數在文件sd_diskio_dma.c:

下面代碼中最關鍵的處理是形參buff的4字節對齊問題(SDMMC自帶的IDMA需要4字節對齊),如果buff地址是4字節對齊的,不做處理,如果不是對齊,通過復制到一個4字節對齊的緩沖里面做DMA傳遞。這個是理解下面代碼的關鍵。

/**
  * @brief  Writes Sector(s)
  * @param  lun : not used
  * @param  *buff: Data to be written
  * @param  sector: Sector address (LBA)
  * @param  count: Number of sectors to write (1..128)
  * @retval DRESULT: Operation result
  */
#if _USE_WRITE == 1
DRESULT SD_write(BYTE lun, const BYTE *buff, DWORD sector, UINT count)
{
    DRESULT res = RES_ERROR;
    uint32_t timeout;
    WriteStatus = 0;

#if (ENABLE_SD_DMA_CACHE_MAINTENANCE_WRITE == 1)
   SCB_CleanInvalidateDCache();
#endif

    if (!((uint32_t)buff & 0x3))
    {
        if(BSP_SD_WriteBlocks_DMA((uint32_t*)buff,
                                (uint32_t)(sector),
                                count) == MSD_OK)
        {
            /* Wait that writing process is completed or a timeout occurs */
            timeout = HAL_GetTick();
            while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
            {
            }
            
            /* incase of a timeout return error */
            if (WriteStatus == 0)
            {
                res = RES_ERROR;
            }
            else
            {
                WriteStatus = 0;
                timeout = HAL_GetTick();

                while((HAL_GetTick() - timeout) < SD_TIMEOUT)
                {
                    if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
                    {
                        res = RES_OK;
                        break;
                    }
                }
            }
        }
    }
    else
    {
        int i;
        uint8_t ret;
        
        for (i = 0; i < count; i++)
        {
            WriteStatus = 0;
            
            memcpy((void *)scratch, (void *)buff, BLOCKSIZE);
            buff += BLOCKSIZE;

            ret = BSP_SD_WriteBlocks_DMA((uint32_t*)scratch, (uint32_t)sector++, 1);
            if(ret == MSD_OK)
            {
                /* Wait that writing process is completed or a timeout occurs */

                timeout = HAL_GetTick();
                while((WriteStatus == 0) && ((HAL_GetTick() - timeout) < SD_TIMEOUT))
                {
                }
                
                /* incase of a timeout return error */
                if (WriteStatus == 0)
                {
                    break;
                }
                else
                {
                    WriteStatus = 0;
                    timeout = HAL_GetTick();

                    while((HAL_GetTick() - timeout) < SD_TIMEOUT)
                    {
                        if (BSP_SD_GetCardState() == SD_TRANSFER_OK)
                        {
                            break;
                        }
                    }
                }
            }
            else
            {
                break;
            }
        }

        if ((i == count) && (ret == MSD_OK))
        {
            res = RES_OK;           
        }
    }

    return res;
}

88.9.5 磁盤I/O控制函數disk_ioctl

代碼如下:

/**
  * @brief  I/O control operation
  * @param  pdrv: Physical drive number (0..)
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT disk_ioctl (
    BYTE pdrv,        /* Physical drive nmuber (0..) */
    BYTE cmd,        /* Control code */
    void *buff        /* Buffer to send/receive control data */
)
{
  DRESULT res;

  res = disk.drv[pdrv]->disk_ioctl(disk.lun[pdrv], cmd, buff);
  return res;
}
#endif /* _USE_IOCTL == 1 */

實際對應的函數在文件sd_diskio_dma.c

特別注意,如果大家要調用FatFs的API格式化SD卡,此函數比較重要。下面幾個cmd一定實現:

/**
  * @brief  I/O control operation
  * @param  lun : not used
  * @param  cmd: Control code
  * @param  *buff: Buffer to send/receive control data
  * @retval DRESULT: Operation result
  */
#if _USE_IOCTL == 1
DRESULT SD_ioctl(BYTE lun, BYTE cmd, void *buff)
{
  DRESULT res = RES_ERROR;
  BSP_SD_CardInfo CardInfo;

  if (Stat & STA_NOINIT) return RES_NOTRDY;

  switch (cmd)
  {
  /* Make sure that no pending write process */
  case CTRL_SYNC :
    res = RES_OK;
    break;

  /* Get number of sectors on the disk (DWORD) */
  case GET_SECTOR_COUNT :
    BSP_SD_GetCardInfo(&CardInfo);
    *(DWORD*)buff = CardInfo.LogBlockNbr;
    res = RES_OK;
    break;

  /* Get R/W sector size (WORD) */
  case GET_SECTOR_SIZE :
    BSP_SD_GetCardInfo(&CardInfo);
    *(WORD*)buff = CardInfo.LogBlockSize;
    res = RES_OK;
    break;

  /* Get erase block size in unit of sector (DWORD) */
  case GET_BLOCK_SIZE :
    BSP_SD_GetCardInfo(&CardInfo);
    *(DWORD*)buff = CardInfo.LogBlockSize / SD_DEFAULT_BLOCK_SIZE;
    res = RES_OK;
    break;

  default:
    res = RES_PARERR;
  }

  return res;
}
#endif /* _USE_IOCTL == 1 */

88.9.6 RTC時間獲取函數get_fattime

我們這里未使用這個函數,此函數的作用是用戶創建文件時,可以將創建文件時間設置為此函數的獲取值

/**
  * @brief  Gets Time from RTC
  * @param  None
  * @retval Time in DWORD
  */
__weak DWORD get_fattime (void)
{
  return 0;
}

88.10          SDMMC自帶IDMA的4字節對齊問題(重要)

由於本章教程配套例子使用了SDMMC自帶的IDMA,所以也專門做了4字節對齊處理。處理思路就是底層的讀寫函數里面如果地址是4字節對齊的,不做處理,如果不是對齊,通過復制到一個4字節對齊的緩沖里面做DMA傳遞。

其實有個更簡單,性能也最高的解決辦法,核心思想如下(ffconf.h文件里面設置的扇區大小基本都是512字節):

  •   當要寫入和讀取的數據小於扇區大小時,會直接使用FATFS結構體里面的數組win[_MAX_SS]做DMA寫操作到,正好1個扇區大小。由於數組win[_MAX_SS]的地址是4字節對齊的,所以無需做處理。
  •  當要寫入和讀取的數據大於等於扇區大小時,扇區整數倍的地方將直接使用用戶提供的收發緩沖區發送,而不足一個扇區的地方將使用FATFS結構體里面的數組。這種情況下用戶要做的就是直接定義個4字節對齊的讀寫緩沖區即可。

 

針對本章教程配套的例子,我們直接做了32字節對齊,同時也方便了Cache處理:

ALIGN_32BYTES(char FsReadBuf[1024]);
ALIGN_32BYTES(char FsWriteBuf[1024]) = {"FatFS Write Demo \r\n www.armfly.com \r\n"};
ALIGN_32BYTES(uint8_t g_TestBuf[BUF_SIZE]);

88.11          實驗例程設計框架

通過程序設計框架,讓大家先對配套例程有一個全面的認識,然后再理解細節,本次實驗例程的設計框架如下:

 第1階段,上電啟動階段:

  • 這部分在第14章進行了詳細說明。

  第2階段,進入main函數:

  • 第1步,硬件初始化,主要是MPU,Cache,HAL庫,系統時鍾,滴答定時器,LED和串口。
  • 第2步,FatFs應用程序設計部分。

88.12          實驗例程說明(MDK)

配套例子:

V7-025_FatFS文件系統例子(SD卡 V1.1)

實驗目的:

  1. 學習SD卡的FatFS移植實現。

實驗內容:

  1. 上電啟動了一個軟件定時器,每100ms翻轉一次LED2。
  2. V7開發板的SD卡接口是用的SDMMC1,而這個接口僅支持AXI SRAM區訪問,其它SRAM和TCP均不支持。

實驗操作:

  1. 測試前務必將SD卡插入到開發板左上角的卡座中。
  2. 支持以下6個功能,用戶通過電腦端串口軟件發送數字1-6給開發板即可
  3. printf("1 - 顯示根目錄下的文件列表\r\n");
  4. printf("2 - 創建一個新文件armfly.txt\r\n");
  5. printf("3 - 讀armfly.txt文件的內容\r\n");
  6. printf("4 - 創建目錄\r\n");
  7. printf("5 - 刪除文件和目錄\r\n");
  8. printf("6 - 讀寫文件速度測試\r\n");

上電后串口打印的信息:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1

 

程序設計:

  系統棧大小分配:

 

  RAM空間用的AXI SRAM:

 

  硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鍾:
       - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。
       - 設置NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鍾到400MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();        /* 初始化LED */    
}

 MPU配置和Cache配置:

數據Cache和指令Cache都開啟。配置了AXI SRAM區和FMC的擴展IO區。

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

#if 0
       /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

 #else
     /* 當前是采用下面的配置 */
    /* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif
    /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

  主功能:

主程序實現如下操作:

  •   上電啟動了一個軟件定時器,每100ms翻轉一次LED2。
  •    支持以下6個功能,用戶通過電腦端串口軟件發送數字給開發板即可:
  •   1 - 顯示根目錄下的文件列表
  •   2 - 創建一個新文件armfly.txt
  •   3 - 讀armfly.txt文件的內容
  •   4 - 創建目錄
  •   5 - 刪除文件和目錄
  •   6 - 讀寫文件速度測試
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    bsp_Init();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名稱和版本等信息 */

    DemoFatFS();    /* SD卡測試 */
}

/*
*********************************************************************************************************
*    函 數 名: DemoFatFS
*    功能說明: FatFS文件系統演示主程序
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoFatFS(void)
{
    uint8_t cmd;

    /* 打印命令列表,用戶可以通過串口操作指令 */
    DispMenu();
    
    /* 注冊SD卡驅動 */
    FATFS_LinkDriver(&SD_Driver, DiskPath);
    
    bsp_StartAutoTimer(0, 500);    /* 啟動1個500ms的自動重裝的定時器 */
    
    while (1)
    {
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {            
            /* 每隔500ms 進來一次 */  
            bsp_LedToggle(2);
        }

        if (comGetChar(COM1, &cmd))    /* 從串口讀入一個字符(非阻塞方式) */
        {
            printf("\r\n");
            switch (cmd)
            {
                case '1':
                    printf("【1 - ViewRootDir】\r\n");
                    ViewRootDir();        /* 顯示SD卡根目錄下的文件名 */
                    break;

                case '2':
                    printf("【2 - CreateNewFile】\r\n");
                    CreateNewFile();    /* 創建一個新文件,寫入一個字符串 */
                    break;

                case '3':
                    printf("【3 - ReadFileData】\r\n");
                    ReadFileData();        /* 讀取根目錄下armfly.txt的內容 */
                    break;

                case '4':
                    printf("【4 - CreateDir】\r\n");
                    CreateDir();        /* 創建目錄 */
                    break;

                case '5':
                    printf("【5 - DeleteDirFile】\r\n");
                    DeleteDirFile();    /* 刪除目錄和文件 */
                    break;

                case '6':
                    printf("【6 - TestSpeed】\r\n");
                    WriteFileTest();    /* 速度測試 */
                    break;
                
                default:
                    DispMenu();
                    break;
            }
        }
    }
}

88.13          實驗例程說明(IAR)

配套例子:

V7-025_FatFS文件系統例子(SD卡 V1.1)

實驗目的:

  1. 學習SD卡的FatFS移植實現。

實驗內容:

  1. 上電啟動了一個軟件定時器,每100ms翻轉一次LED2。
  2. V7開發板的SD卡接口是用的SDMMC1,而這個接口僅支持AXI SRAM區訪問,其它SRAM和TCP均不支持。

實驗操作:

  1. 測試前務必將SD卡插入到開發板左上角的卡座中。
  2. 支持以下6個功能,用戶通過電腦端串口軟件發送數字1-6給開發板即可
  3. printf("1 - 顯示根目錄下的文件列表\r\n");
  4. printf("2 - 創建一個新文件armfly.txt\r\n");
  5. printf("3 - 讀armfly.txt文件的內容\r\n");
  6. printf("4 - 創建目錄\r\n");
  7. printf("5 - 刪除文件和目錄\r\n");
  8. printf("6 - 讀寫文件速度測試\r\n");

上電后串口打印的信息:

波特率 115200,數據位 8,奇偶校驗位無,停止位 1

 

程序設計:

  系統棧大小分配:

  RAM空間用的AXI SRAM:

 

  硬件外設初始化

硬件外設的初始化是在 bsp.c 文件實現:

/*
*********************************************************************************************************
*    函 數 名: bsp_Init
*    功能說明: 初始化所有的硬件設備。該函數配置CPU寄存器和外設的寄存器並初始化一些全局變量。只需要調用一次
*    形    參:無
*    返 回 值: 無
*********************************************************************************************************
*/
void bsp_Init(void)
{
    /* 配置MPU */
    MPU_Config();
    
    /* 使能L1 Cache */
    CPU_CACHE_Enable();

    /* 
       STM32H7xx HAL 庫初始化,此時系統用的還是H7自帶的64MHz,HSI時鍾:
       - 調用函數HAL_InitTick,初始化滴答時鍾中斷1ms。
       - 設置NVIV優先級分組為4。
     */
    HAL_Init();

    /* 
       配置系統時鍾到400MHz
       - 切換使用HSE。
       - 此函數會更新全局變量SystemCoreClock,並重新配置HAL_InitTick。
    */
    SystemClock_Config();

    /* 
       Event Recorder:
       - 可用於代碼執行時間測量,MDK5.25及其以上版本才支持,IAR不支持。
       - 默認不開啟,如果要使能此選項,務必看V7開發板用戶手冊第xx章
    */    
#if Enable_EventRecorder == 1  
    /* 初始化EventRecorder並開啟 */
    EventRecorderInitialize(EventRecordAll, 1U);
    EventRecorderStart();
#endif
    
    bsp_InitKey();        /* 按鍵初始化,要放在滴答定時器之前,因為按鈕檢測是通過滴答定時器掃描 */
    bsp_InitTimer();      /* 初始化滴答定時器 */
    bsp_InitUart();    /* 初始化串口 */
    bsp_InitExtIO();    /* 初始化FMC總線74HC574擴展IO. 必須在 bsp_InitLed()前執行 */    
    bsp_InitLed();        /* 初始化LED */    
}

  MPU配置和Cache配置:

數據Cache和指令Cache都開啟。配置了AXI SRAM區和FMC的擴展IO區。

/*
*********************************************************************************************************
*    函 數 名: MPU_Config
*    功能說明: 配置MPU
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void MPU_Config( void )
{
    MPU_Region_InitTypeDef MPU_InitStruct;

    /* 禁止 MPU */
    HAL_MPU_Disable();

#if 0
       /* 配置AXI SRAM的MPU屬性為Write back, Read allocate,Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);

 #else
     /* 當前是采用下面的配置 */
    /* 配置AXI SRAM的MPU屬性為NORMAL, NO Read allocate,NO Write allocate */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x24000000;
    MPU_InitStruct.Size             = MPU_REGION_SIZE_512KB;
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_NOT_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER0;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL1;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;

    HAL_MPU_ConfigRegion(&MPU_InitStruct);
#endif
    /* 配置FMC擴展IO的MPU屬性為Device或者Strongly Ordered */
    MPU_InitStruct.Enable           = MPU_REGION_ENABLE;
    MPU_InitStruct.BaseAddress      = 0x60000000;
    MPU_InitStruct.Size             = ARM_MPU_REGION_SIZE_64KB;    
    MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
    MPU_InitStruct.IsBufferable     = MPU_ACCESS_BUFFERABLE;
    MPU_InitStruct.IsCacheable      = MPU_ACCESS_NOT_CACHEABLE;
    MPU_InitStruct.IsShareable      = MPU_ACCESS_NOT_SHAREABLE;
    MPU_InitStruct.Number           = MPU_REGION_NUMBER1;
    MPU_InitStruct.TypeExtField     = MPU_TEX_LEVEL0;
    MPU_InitStruct.SubRegionDisable = 0x00;
    MPU_InitStruct.DisableExec      = MPU_INSTRUCTION_ACCESS_ENABLE;
    
    HAL_MPU_ConfigRegion(&MPU_InitStruct);

    /*使能 MPU */
    HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
}

/*
*********************************************************************************************************
*    函 數 名: CPU_CACHE_Enable
*    功能說明: 使能L1 Cache
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
static void CPU_CACHE_Enable(void)
{
    /* 使能 I-Cache */
    SCB_EnableICache();

    /* 使能 D-Cache */
    SCB_EnableDCache();
}

  主功能:

主程序實現如下操作:

  •   上電啟動了一個軟件定時器,每100ms翻轉一次LED2。
  •    支持以下6個功能,用戶通過電腦端串口軟件發送數字給開發板即可:
  •  1 - 顯示根目錄下的文件列表
  •   2 - 創建一個新文件armfly.txt
  •  3 - 讀armfly.txt文件的內容
  •  4 - 創建目錄
  •   5 - 刪除文件和目錄
  •   6 - 讀寫文件速度測試
/*
*********************************************************************************************************
*    函 數 名: main
*    功能說明: c程序入口
*    形    參: 無
*    返 回 值: 錯誤代碼(無需處理)
*********************************************************************************************************
*/
int main(void)
{
    bsp_Init();        /* 硬件初始化 */
    
    PrintfLogo();    /* 打印例程名稱和版本等信息 */

    DemoFatFS();    /* SD卡測試 */
}

/*
*********************************************************************************************************
*    函 數 名: DemoFatFS
*    功能說明: FatFS文件系統演示主程序
*    形    參: 無
*    返 回 值: 無
*********************************************************************************************************
*/
void DemoFatFS(void)
{
    uint8_t cmd;

    /* 打印命令列表,用戶可以通過串口操作指令 */
    DispMenu();
    
    /* 注冊SD卡驅動 */
    FATFS_LinkDriver(&SD_Driver, DiskPath);
    
    bsp_StartAutoTimer(0, 500);    /* 啟動1個500ms的自動重裝的定時器 */
    
    while (1)
    {
        
        /* 判斷定時器超時時間 */
        if (bsp_CheckTimer(0))    
        {            
            /* 每隔500ms 進來一次 */  
            bsp_LedToggle(2);
        }

        if (comGetChar(COM1, &cmd))    /* 從串口讀入一個字符(非阻塞方式) */
        {
            printf("\r\n");
            switch (cmd)
            {
                case '1':
                    printf("【1 - ViewRootDir】\r\n");
                    ViewRootDir();        /* 顯示SD卡根目錄下的文件名 */
                    break;

                case '2':
                    printf("【2 - CreateNewFile】\r\n");
                    CreateNewFile();    /* 創建一個新文件,寫入一個字符串 */
                    break;

                case '3':
                    printf("【3 - ReadFileData】\r\n");
                    ReadFileData();        /* 讀取根目錄下armfly.txt的內容 */
                    break;

                case '4':
                    printf("【4 - CreateDir】\r\n");
                    CreateDir();        /* 創建目錄 */
                    break;

                case '5':
                    printf("【5 - DeleteDirFile】\r\n");
                    DeleteDirFile();    /* 刪除目錄和文件 */
                    break;

                case '6':
                    printf("【6 - TestSpeed】\r\n");
                    WriteFileTest();    /* 速度測試 */
                    break;
                
                default:
                    DispMenu();
                    break;
            }
        }
    }
}

88.14   總結

本章節就為大家講解這么多,需要大家實現操作一遍來熟練掌握FatFs的移植,然后FatFs相關的知識點可以到FatFs官網查看,資料非常詳細。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM