zoukankan      html  css  js  c++  java
  • Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

    Keil MDK STM32系列

    前言

    功能:

    1. 通过SPI读写SD卡/TF卡上的文件系统
    2. 支持FAT16, FAT32, exFAT, 即FatFs所支持的文件格式.
    3. 支持存储卡容量512MB至64GB, 更高容量未测试

    实现基于

    • STM32CubeMX, STM32F4 v1.26.2
    • FatFs, R0.12C(STM32CubeMX内建版本)

    大部分参考自STM32Cube配置SPI读sd卡

    这位韩国作者很详细地制作了博客, 视频和Github代码, 但是源码无法直接使用, 遇到的问题

    1. 代码有两个版本, 视频和博客基于前一个版本
    2. 代码并非在Windows下编译, 无法在Keil MDK中直接使用
    3. 代码存在bug, 经过几小时排查定位才解决.

    FatFs的说明

    FatFs是一个通用的FAT/exFAT文件系统封装库, 基本完整实现了FAT/exFAT文件系统的逻辑, 用于在嵌入式系统中实现FAT/exFAT文件系统. 这个库将存储IO操作做了抽象, 可以整合任何实现了存储IO的系统, 因为使用C语言编写, 耗费资源极小, 适合资源有限的MCU系统, 例如 8051, PIC, AVR, ARM, Z80, RX等等.

    因为FatFs将存储IO作了抽象, 整合时需要实现下面的函数来实现存储设备的读写. 底层磁盘IO模块并不是FatFs的一部分, 并且必须由用户提供. 在官方的代码仓库中有示例代码. 需要实现的函数有

    • disk_initialize - Initialize disk drive 初始化磁盘驱动器
    • disk_status - Get disk status 获取磁盘状态
    • disk_read - Read sector(s) 读扇区
    • disk_write - Write sector(s) 写扇区
    • disk_ioctl - Control device dependent features 设备相关的控制特性
    • get_fattime - Get current time 获取当前时间

    SD/TF卡的初始化和读写

    初始化

    1. 上电, 将CS片选信号拉低, 开启CLK给SD卡发送至少74个时钟周期, 让SD卡完成自身检查和初始化, 进入IDLE状态. 之后对SD卡发送CMD0, 进入SPI模式. SD卡从D_OUT线上的返回值如果是0x01, 说明CMD0操作是成功的, 此时SD卡处在IDLE状态.
    2. 发送CMD8. CMD8是检测SD卡版本的命令, 如果SD卡对此命令不识别, 说明SD卡为老版本, 如果SD卡有正确返回值(00 01 AA), 说明SD卡是2.0+版本的, 支持大容量储存, 也就是SDHC卡.
    3. 发送ACMD41, 让卡从IDLE状态进入读写就绪的状态. 这里要注意, SD卡命令有两种, CMD和ACMD, 直接发送命令默认为CMD, 如果要发送ACMD, 需要先发送CMD55, 接收到正常的返回值0X01, 接着发送ACMD41, 完成卡从IDLE状态到读写状态的初始化进程. SD卡退出IDLE状态的正确返回值为0X00. ACMD41的值有多种, 常用的为0x40000000.
    4. 发送CMD58, 读取OCR寄存器, OCR寄存器记录了SD卡可识别的电压范围; 是否支持大容量存储(即SDHC); 上电状态. 发送了CMD58后, SD卡的返回值为R1返回值+OCR寄存器的内容. 根据datasheet可以得到很多信息. 手册上推荐发送这个命令, 主要功能是知道V2.0SD卡是标准版本还是大容量的版本, 是否支持按块(512BYTE)寻址, 从而读写的方法也不同. 判断正常的方法, CMD58的返回值类型为R3, 即R1类型+OCR寄存器内容, 如果一切就绪, OCR的最高四位为1100. 这时候初始化就完成了, SD卡进入读写状态.

    实测CMD58返回的结果

    • 2GB TF卡: 80 FF 80 00
    • 8GB TF卡: C0 FF 80 00
    • 16GB TF卡: C0 FF 80 00

    读写数据

    • 无论读写数据还是接收发送CMD, 都会用到两个最基本的函数, 一个是read_byte(), 即从SD卡的DATA_OUT引脚上读取8bit(1byte)的数据; 另一个是write_byte(), 向SD卡的DATA_IN引脚写一个字节的数据.
    • 命令, 数据和返回值都是由多字节组合成的, 在一个操作中会多次调用这两个基本的函数. 如从指定的地址中读取512字节的数据, 在发送了读的命令后相应的要调用512次read_byte().
    • 读写函数的时序: 向SD卡写数据时, 时钟上升沿时数据有效; 从SD卡读数据时, 时钟在高电平时, MCU读到的数据有效.

    STM32CubeMX的配置

    选择芯片STM32F401CCU6, 创建新项目, 需要配置的部分有

    • SYS
    • RCC
    • SPI1
    • FATFS
    • USART1

    系统和时钟

    开启Debug, 系统时钟设为外置晶振, 使用最高频率84MHz

    • System Core -> SYS-> Debug: Serial Wire 避免无法再次写入
    • System Core -> RCC-> High Speed Clock (HSE): Crystal/Ceramic Resonator 启用外接高速晶振
    • Clock Configuration: (配置为最高84MHz)选择外部晶振, 连接HSE和PLLCLK, 在HCLK上输入84回车, 软件会自动调节各节点倍数

    SPI1

    • Mode: Full-Duplex Master
    • Hardware NSS Signal: Disable
    • Parameter Settings
      • Frame Format: Motorola
      • Data Size: 8 Bits
      • First Bit: MSB First
      • Prescaler: 16
      • Clock Polarity: Low
      • Clock Phase(CPHA): 1Edge
      • CRC Calculation: Disabled
      • NSS Signal Type: Software

    USART1

    • Mode: Asynchronous
    • Hardware Flow Control: Disable
    • 其他默认

    FATFS

    • User-defined: Checked
    • USE_LFN(Use Long Filename): Enabled with static working buffer on the BSS
    • MAX_SS(Maximum Sector Size): 4096
    • FS_EXFAT(Support of exFAT file system): Enabled
    • 其他默认

    连线说明

    SD卡/TF卡pin脚定义

    连线

    SD Card -> STM32

    DAT3/CS -> PB0
    CMD/DI  -> SPI1:MOSI:PA7
    VDD     -> 3V3
    CLK     -> SPI1:SCLK:PA5
    VSS     -> GND
    DAT0/DO -> SPI1:MISO:PA6
    

    串口PA9:TX, PA10:RX, 用于输出测试信息

    代码修改

    通过STM32CubeMX生成代码后, 将后面附录中的fatfs_sd.c和fatfs_sd.h添加到项目

    在user_diskio.c中添加IO实现

    添加对fatfs_sd.h的引用

    /* Private typedef -----------------------------------------------------------*/
    /* Private define ------------------------------------------------------------*/
    #include "fatfs_sd.h"
    

    给各个接口添加实现方法

    /**
      * @brief  Initializes a Drive
      * @param  pdrv: Physical drive number (0..)
      * @retval DSTATUS: Operation status
      */
    DSTATUS USER_initialize (
      BYTE pdrv           /* Physical drive nmuber to identify the drive */
    )
    {
      /* USER CODE BEGIN INIT */
      Stat = SD_disk_initialize(pdrv);
      //printf("Initialized, stat:%02X
    ", Stat);
      return Stat;
      /* USER CODE END INIT */
    }
    
    /**
      * @brief  Gets Disk Status
      * @param  pdrv: Physical drive number (0..)
      * @retval DSTATUS: Operation status
      */
    DSTATUS USER_status (
      BYTE pdrv       /* Physical drive number to identify the drive */
    )
    {
      /* USER CODE BEGIN STATUS */
      printf("USER_status... ");
      Stat = SD_disk_status(pdrv);
      printf("%02X
    ", Stat);
      return Stat;
      /* USER CODE END STATUS */
    }
    
    /**
      * @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 USER_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 */
    )
    {
      /* USER CODE BEGIN READ */
      //printf("USER_read... ");
      DRESULT res = SD_disk_read(pdrv, buff, sector, count);
      //printf("%02X
    ", res);
      return res;
      /* USER CODE END READ */
    }
    
    /**
      * @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
      */
    #if _USE_WRITE == 1
    DRESULT USER_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 */
    )
    {
      /* USER CODE BEGIN WRITE */
      /* USER CODE HERE */
      printf("USER_write... ");
      DRESULT res = SD_disk_write(pdrv, buff, sector, count);
      printf("%02X
    ", res);
      return res;
      /* USER CODE END WRITE */
    }
    #endif /* _USE_WRITE == 1 */
    
    /**
      * @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 USER_ioctl (
      BYTE pdrv,      /* Physical drive nmuber (0..) */
      BYTE cmd,       /* Control code */
      void *buff      /* Buffer to send/receive control data */
    )
    {
      /* USER CODE BEGIN IOCTL */
      printf("USER_ioctl... ");
      DRESULT res = SD_disk_ioctl(pdrv, cmd, buff);
      printf("%02X
    ", res);
      return res;
      /* USER CODE END IOCTL */
    }
    #endif /* _USE_IOCTL == 1 */
    

    在stm32f4xx_it.c的系统时间中断中添加倒计时

    Timer1和Timer2用于fatfs_sd.c中的超时判断

    /* USER CODE BEGIN 0 */
    extern uint16_t Timer1, Timer2;
    /* USER CODE END 0 */
    
    /**
      * @brief This function handles System tick timer.
      */
    void SysTick_Handler(void)
    {
      /* USER CODE BEGIN SysTick_IRQn 0 */
      if(Timer1 > 0)
        Timer1--;
    
      if(Timer2 > 0)
        Timer2--;
      /* USER CODE END SysTick_IRQn 0 */
      HAL_IncTick();
      HAL_SYSTICK_IRQHandler();
      /* USER CODE BEGIN SysTick_IRQn 1 */
    
      /* USER CODE END SysTick_IRQn 1 */
    }
    

    在main.c中开启串口,添加测试代码

    开启串口

    #include <stdio.h>
    
    /* USER CODE BEGIN 4 */
    #ifdef __GNUC__
    #define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
    #else
    #define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
    #endif /* __GNUC__ */
    
    PUTCHAR_PROTOTYPE
    {
        HAL_UART_Transmit(&huart1 , (uint8_t *)&ch, 1, 0xFFFF);
        return ch;
    }
    /* USER CODE END 4 */
    

    读写SD卡测试

    /* USER CODE BEGIN PV */
    FATFS fs;
    FATFS *pfs;
    FIL fil;
    FRESULT fres;
    DWORD fre_clust;
    uint32_t totalSpace, freeSpace;
    char buffer[100];
    /* USER CODE END PV */
    
    
    /**
      * @brief  The application entry point.
      * @retval int
      */
    int main(void)
    {
      HAL_Init();
      SystemClock_Config();
    
      MX_GPIO_Init();
      MX_SPI1_Init();
      MX_USART1_UART_Init();
      MX_FATFS_Init();
    
      /* USER CODE BEGIN 2 */
      /* Wait for SD module reset */
      HAL_Delay(500);
    
      /* Mount SD Card */
      FRESULT res2 = f_mount(&fs, "", 0x01);
      printf("f_mount result: %02X
    ", res2);
      if(res2 != FR_OK)
      {
        printf("f_mount failed
    ");
        Error_Handler();
      }
      
      /* Check freeSpace space */
      if(f_getfree("", &fre_clust, &pfs) != FR_OK)
      {
        printf("f_getfree failed
    ");
        Error_Handler();
      }
    
      totalSpace = (uint32_t)((pfs->n_fatent - 2) * pfs->csize * 0.5);
      freeSpace = (uint32_t)(fre_clust * pfs->csize * 0.5);
      printf("total:%dKB, free:%dKB
    ", totalSpace, freeSpace);
    
      /* free space is less than 1kb */
      if(freeSpace < 1)
      {
        printf("freeSpace not enough
    ");
        Error_Handler();
      }
    
      /* Open file to write */
      printf("f_open first.txt
    ");
      if(f_open(&fil, "first.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
      {
        printf("f_open failed
    ");
        Error_Handler();
      }
    
      /* Writing text */
      f_puts("STM32 SD Card I/O Example via SPI
    ", &fil);
      f_puts("Black Sheep Wall!!!", &fil);
    
      /* Close file */
      printf("f_close first.txt
    ");
      if(f_close(&fil) != FR_OK)
      {
        printf("f_close failed
    ");
        Error_Handler();
      }
    
      /* Open file to read */
      printf("f_open first.txt
    ");
      if(f_open(&fil, "first.txt", FA_READ) != FR_OK)
      {
        printf("f_open failed
    ");
        Error_Handler();
      }
    
      printf("f_gets first.txt
    ");
      while(f_gets(buffer, sizeof(buffer), &fil))
      {
        /* SWV output */
        printf("%s", buffer);
        fflush(stdout);
      }
      printf("
    done
    ");
    
      /* Close file */
      printf("f_close first.txt
    ");
      if(f_close(&fil) != FR_OK)
      {
        printf("f_close failed
    ");
        Error_Handler();
      }
    
      /* Unmount SDCARD */
      printf("f_mount unmount
    ");
      if(f_mount(NULL, "", 1) != FR_OK)
      {
        printf("f_mount failed
    ");
        Error_Handler();
      }
      
      /* USER CODE END 2 */
    
      while (1);
    }
    

    给写入的文件添加时间属性

    如果系统没有时间信息

    在 ffconf.h 中, 将#define _FS_NORTC 0改为#define _FS_NORTC 1, 这时候会使用底下的几个固定时间戳作为所有的文件创建时间, 具体可以这些变量后面的注释

    #define _NORTC_MON  6
    #define _NORTC_MDAY 4
    #define _NORTC_YEAR 2015
    

    如果有时间信息

    前往fatfs.c, 实现这个函数, 这里返回的时间在创建文件时会成为文件的创建时间

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

    列出卡上的目录和文件

    /* USER CODE BEGIN 0 */
    FRESULT scan_files (
        char* path        /* Start node to be scanned (***also used as work area***) */
    )
    {
        FRESULT res;
        DIR dir;
        UINT i;
        static FILINFO fno;
    
    
        res = f_opendir(&dir, path);                       /* Open the directory */
        if (res == FR_OK) {
            for (;;) {
                res = f_readdir(&dir, &fno);                   /* Read a directory item */
                if (res != FR_OK || fno.fname[0] == 0) break;  /* Break on error or end of dir */
                if (fno.fattrib & AM_DIR) {                    /* It is a directory */
                    i = strlen(path);
                    sprintf(&path[i], "/%s", fno.fname);
                    res = scan_files(path);                    /* Enter the directory */
                    if (res != FR_OK) break;
                    path[i] = 0;
                } else {                                       /* It is a file. */
                    printf("%s/%s %llu
    ", path, fno.fname, fno.fsize);
                }
            }
            f_closedir(&dir);
        }
        return res;
    }
    
    int main(void)
    {
      //...
      /* Mount SD Card */
      FRESULT res2 = f_mount(&fs, "", 0x01);
      printf("f_mount result: %02X
    ", res2);
      if(res2 != FR_OK)
      {
        printf("f_mount failed
    ");
        Error_Handler();
      }
      //...
      strcpy(buff, "");
      res2 = scan_files(buff);
    ...
    
    

    问题

    初始化错误

    初始化时, 会通过 user_diskio.c 中的USER_initialize()调用 fatfs_sd.c 中的SD_disk_initialize()函数, 在这个函数中添加串口输出检查初始化状态.

    注意1

    如果在ACMD41这一步, 如果结果一直返回1(SD卡非空闲), 可以试试在测试时给SD卡重新加电, 如果不断电, SD卡可能会一直卡在这个状态, 这时候怎样修改代码都不会改变它的返回值

    注意2

    注意这一行type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;, 这里判断了SD卡是否使用块寻址, 如果块寻址这个判断错误, 直接会导致初始化失败, 因为在SD_disk_read()操作中无法正确读取数据.

    原项目Bug

    STM32F4_HAL_SPI_SDCARD中存在问题的是 fatfs_sd.c 中SD_disk_read()SD_disk_write()的两处判断

    /* convert to byte address */
    if (!(CardType & CT_SD2)) sector *= 512;
    

    这里应该用CT_BLOCK判断, 否则产生的地址是错误的, 修改为下面的代码就能正常工作了

    /* if not block-addressing, convert to byte address */
    if (!(CardType & CT_BLOCK)) sector *= 512;
    

    排查工具

    除了printf打印结果, 必要时通过winhex查看TF卡的实际情况, 结合运行输出一起判断

    FAT32写入导致全盘乱码

    • 在一张16GB TF卡上出现, 换成exFAT格式没问题. 但是另一张也是FAT32格式的8GB TF卡没问题.
    • 减慢SPI速度不能解决问题

    代码附录: SD卡的底层读写方法

    fatfs_sd.h

    #ifndef __FATFS_SD_H
    #define __FATFS_SD_H
    
    #include "stm32f4xx_hal.h"
    #include "diskio.h"
    
    /* Definitions for MMC/SDC command */
    #define CMD0     (0x40+0)       /* GO_IDLE_STATE */
    #define CMD1     (0x40+1)       /* SEND_OP_COND */
    #define CMD8     (0x40+8)       /* SEND_IF_COND */
    #define CMD9     (0x40+9)       /* SEND_CSD */
    #define CMD10    (0x40+10)      /* SEND_CID */
    #define CMD12    (0x40+12)      /* STOP_TRANSMISSION */
    #define CMD16    (0x40+16)      /* SET_BLOCKLEN */
    #define CMD17    (0x40+17)      /* READ_SINGLE_BLOCK */
    #define CMD18    (0x40+18)      /* READ_MULTIPLE_BLOCK */
    #define CMD23    (0x40+23)      /* SET_BLOCK_COUNT */
    #define CMD24    (0x40+24)      /* WRITE_BLOCK */
    #define CMD25    (0x40+25)      /* WRITE_MULTIPLE_BLOCK */
    #define CMD41    (0x40+41)      /* SEND_OP_COND (ACMD) */
    #define CMD55    (0x40+55)      /* APP_CMD */
    #define CMD58    (0x40+58)      /* READ_OCR */
    
    /* MMC card type flags (MMC_GET_TYPE) */
    #define CT_MMC    0x01    /* MMC ver 3 */
    #define CT_SD1    0x02    /* SD ver 1 */
    #define CT_SD2    0x04    /* SD ver 2 */
    #define CT_SDC    0x06    /* SD */
    #define CT_BLOCK  0x08    /* Block addressing */
    
    #define ACMD41_HCS           0x40000000
    #define ACMD41_SDXC_POWER    0x10000000
    #define ACMD41_S18R          0x04000000
    #define ACMD41_VOLTAGE       0x00ff8000
    #define ACMD41_ARG_HC        (ACMD41_HCS|ACMD41_SDXC_POWER|ACMD41_VOLTAGE)
    #define ACMD41_ARG_SC        (ACMD41_VOLTAGE)  
    
    /* Functions */
    DSTATUS SD_disk_initialize (BYTE pdrv);
    DSTATUS SD_disk_status (BYTE pdrv);
    DRESULT SD_disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
    DRESULT SD_disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
    DRESULT SD_disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
    
    #define SPI_TIMEOUT 100
    
    extern SPI_HandleTypeDef  hspi1;
    #define HSPI_SDCARD     &hspi1
    #define SD_CS_PORT      GPIOB
    #define SD_CS_PIN     GPIO_PIN_1
    
    #endif
    

    fatfs_sd.c

    #define TRUE  1
    #define FALSE 0
    #define bool BYTE
    
    #include "fatfs_sd.h"
    #include <stdio.h>
    
    uint16_t Timer1, Timer2;          /* 1ms Timer Counter */
    
    static volatile DSTATUS Stat = STA_NOINIT;  /* Disk Status */
    static uint8_t CardType;                    /* Type 0:MMC, 1:SDC, 2:Block addressing */
    static uint8_t PowerFlag = 0;       /* Power flag */
    
    /***************************************
     * SPI functions
     **************************************/
    
    /* slave select */
    static void SELECT(void)
    {
      HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_RESET);
      HAL_Delay(1);
    }
    
    /* slave deselect */
    static void DESELECT(void)
    {
      HAL_GPIO_WritePin(SD_CS_PORT, SD_CS_PIN, GPIO_PIN_SET);
      HAL_Delay(1);
    }
    
    /* SPI transmit a byte */
    static void SPI_TxByte(uint8_t data)
    {
      while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
      HAL_SPI_Transmit(HSPI_SDCARD, &data, 1, SPI_TIMEOUT);
    }
    
    /* SPI transmit buffer */
    static void SPI_TxBuffer(uint8_t *buffer, uint16_t len)
    {
      while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
      HAL_SPI_Transmit(HSPI_SDCARD, buffer, len, SPI_TIMEOUT);
    }
    
    /* SPI receive a byte */
    static uint8_t SPI_RxByte(void)
    {
      uint8_t dummy, data;
      dummy = 0xFF;
    
      while(!__HAL_SPI_GET_FLAG(HSPI_SDCARD, SPI_FLAG_TXE));
      HAL_SPI_TransmitReceive(HSPI_SDCARD, &dummy, &data, 1, SPI_TIMEOUT);
    
      return data;
    }
    
    /* SPI receive a byte via pointer */
    static void SPI_RxBytePtr(uint8_t *buff) 
    {
      *buff = SPI_RxByte();
    }
    
    /***************************************
     * SD functions
     **************************************/
    
    /* wait SD ready */
    static uint8_t SD_ReadyWait(void)
    {
      uint8_t res;
    
      /* timeout 500ms */
      Timer2 = 500;
    
      /* if SD goes ready, receives 0xFF */
      do {
        res = SPI_RxByte();
      } while ((res != 0xFF) && Timer2);
    
      return res;
    }
    
    /* power on */
    static void SD_PowerOn(void) 
    {
      uint8_t args[6];
      uint32_t cnt = 0x1FFF;
    
      /* transmit bytes to wake up */
      DESELECT();
      for(int i = 0; i < 10; i++)
      {
        SPI_TxByte(0xFF);
      }
    
      /* slave select */
      SELECT();
    
      /* make idle state */
      args[0] = CMD0;   /* CMD0:GO_IDLE_STATE */
      args[1] = 0;
      args[2] = 0;
      args[3] = 0;
      args[4] = 0;
      args[5] = 0x95;   /* CRC */
    
      SPI_TxBuffer(args, sizeof(args));
    
      /* wait response */
      while ((SPI_RxByte() != 0x01) && cnt)
      {
        cnt--;
      }
    
      DESELECT();
      SPI_TxByte(0XFF);
    
      PowerFlag = 1;
    }
    
    /* power off */
    static void SD_PowerOff(void) 
    {
      PowerFlag = 0;
    }
    
    /* check power flag */
    static uint8_t SD_CheckPower(void) 
    {
      return PowerFlag;
    }
    
    /* receive data block */
    static bool SD_RxDataBlock(BYTE *buff, UINT len)
    {
      uint8_t token;
    
      /* timeout 200ms */
      Timer1 = 200;
    
      /* loop until receive a response or timeout */
      do {
        token = SPI_RxByte();
      } while((token == 0xFF) && Timer1);
    
      /* invalid response */
      if(token != 0xFE) return FALSE;
    
      /* receive data */
      do {
        SPI_RxBytePtr(buff++);
      } while(len--);
    
      /* discard CRC */
      SPI_RxByte();
      SPI_RxByte();
    
      return TRUE;
    }
    
    /* transmit data block */
    #if _USE_WRITE == 1
    static bool SD_TxDataBlock(const uint8_t *buff, BYTE token)
    {
      uint8_t resp;
      uint8_t i = 0;
    
      /* wait SD ready */
      if (SD_ReadyWait() != 0xFF) return FALSE;
    
      /* transmit token */
      SPI_TxByte(token);
    
      /* if it's not STOP token, transmit data */
      if (token != 0xFD)
      {
        SPI_TxBuffer((uint8_t*)buff, 512);
    
        /* discard CRC */
        SPI_RxByte();
        SPI_RxByte();
    
        /* receive response */
        while (i <= 64)
        {
          resp = SPI_RxByte();
    
          /* transmit 0x05 accepted */
          if ((resp & 0x1F) == 0x05) break;
          i++;
        }
    
        /* recv buffer clear */
        while (SPI_RxByte() == 0);
      }
    
      /* transmit 0x05 accepted */
      if ((resp & 0x1F) == 0x05) return TRUE;
    
      return FALSE;
    }
    #endif /* _USE_WRITE */
    
    /* transmit command */
    static BYTE SD_SendCmd(BYTE cmd, uint32_t arg)
    {
      uint8_t crc, res;
    
      /* wait SD ready */
      if (SD_ReadyWait() != 0xFF) return 0xFF;
    
      /* transmit command */
      SPI_TxByte(cmd);          /* Command */
      SPI_TxByte((uint8_t)(arg >> 24));   /* Argument[31..24] */
      SPI_TxByte((uint8_t)(arg >> 16));   /* Argument[23..16] */
      SPI_TxByte((uint8_t)(arg >> 8));  /* Argument[15..8] */
      SPI_TxByte((uint8_t)arg);       /* Argument[7..0] */
    
      /* prepare CRC */
      if(cmd == CMD0) crc = 0x95; /* CRC for CMD0(0) */
      else if(cmd == CMD8) crc = 0x87;  /* CRC for CMD8(0x1AA) */
      else crc = 1;
    
      /* transmit CRC */
      SPI_TxByte(crc);
    
      /* Skip a stuff byte when STOP_TRANSMISSION */
      if (cmd == CMD12) SPI_RxByte();
    
      /* receive response */
      uint8_t n = 10;
      do {
        res = SPI_RxByte();
      } while ((res & 0x80) && --n);
    
      return res;
    }
    
    /***************************************
     * user_diskio.c functions
     **************************************/
    
    /* initialize SD */
    DSTATUS SD_disk_initialize(BYTE drv) 
    {
      uint8_t n, type, ocr[4], t1, t2, f1 = 0x00;
    
      /* single drive, drv should be 0 */
      if(drv) return STA_NOINIT;
    
      /* no disk */
      if(Stat & STA_NODISK) return Stat;
    
      /* power on */
      SD_PowerOn();
    
      /* slave select */
      SELECT();
    
      /* check disk type */
      type = 0;
    
      /* send GO_IDLE_STATE command */
      printf("
    CMD0
    ");
      if (SD_SendCmd(CMD0, 0) == 1)
      {
        /* SDC V2+ accept CMD8 command, http://elm-chan.org/docs/mmc/mmc_e.html */
        printf("CMD8... ");
        if (SD_SendCmd(CMD8, 0x1AA) == 1)
        {
          printf("succeeded, SDC V2+
    ");
          /* operation condition register */
          for (n = 0; n < 4; n++)
          {
            ocr[n] = SPI_RxByte();
          }
    
          /* voltage range 2.7-3.6V */
          if (ocr[2] == 0x01 && ocr[3] == 0xAA)
          {
            printf("ACMD41 ACMD41_HCS.. ");
            /* timeout 1 sec */
            Timer1 = 1000;
            /* ACMD41 with HCS bit */
            do {
              t1 = SD_SendCmd(CMD55, 0);
              t2 = SD_SendCmd(CMD41, ACMD41_HCS);
              if (t1 <= 1 && t2 == 0)
              {
                f1 = 0x01;
                break;
              }
            } while (Timer1);
            
            if (f1 == 0x00)
            {
              printf("failed
    try ACMD41_SDXC_POWER... ");
              Timer1 = 1000;
              do {
                t1 = SD_SendCmd(CMD55, 0);
                t2 = SD_SendCmd(CMD41, ACMD41_SDXC_POWER);
                if (t1 <= 1 && t2 == 0)
                {
                  f1 = 0x01;
                  break;
                }
              } while (Timer1);
            }
            
            if (f1 == 0x00)
            {
              printf("failed
    try ACMD41_S18R... ");
              Timer1 = 1000;
              do {
                t1 = SD_SendCmd(CMD55, 0);
                t2 = SD_SendCmd(CMD41, ACMD41_S18R);
                if (t1 <= 1 && t2 == 0)
                {
                  f1 = 0x01;
                  break;
                }
              } while (Timer1);
            }
            
            if (f1 == 0x00)
            {
              printf("failed
    try ACMD41_VOLTAGE... ");
              Timer1 = 1000;
              do {
                t1 = SD_SendCmd(CMD55, 0);
                t2 = SD_SendCmd(CMD41, ACMD41_VOLTAGE);
                if (t1 <= 1 && t2 == 0)
                {
                  f1 = 0x01;
                  break;
                }
              } while (Timer1);
            }
    
            if (f1 == 0x00)
            {
              printf("failed, stop trying
    ");
            }
            else
            {
              printf("succeeded
    CMD58 ");
              /* READ_OCR */
              if (SD_SendCmd(CMD58, 0) == 0)
              {
                /* Check CCS bit */
                for (n = 0; n < 4; n++)
                {
                  ocr[n] = SPI_RxByte();
                  printf("%02X ", ocr[n]);
                }
    
                /* SDv2 (HC or SC) */
                type = (ocr[0] & 0x40) ? CT_SD2 | CT_BLOCK : CT_SD2;
                printf("type:%02X
    ", type);
              }
            }
          }
        }
        else
        {
          printf("failed, SDC V1 or MMC
    ");
          /* timeout 1 sec */
          Timer1 = 1000;
          /* SDC V1 or MMC */
          type = (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) <= 1) ? CT_SD1 : CT_MMC;
          do
          {
            if (type == CT_SD1)
            {
              if (SD_SendCmd(CMD55, 0) <= 1 && SD_SendCmd(CMD41, 0) == 0) break; /* ACMD41 */
            }
            else
            {
              if (SD_SendCmd(CMD1, 0) == 0) break; /* CMD1 */
            }
    
          } while (Timer1);
    
          /* SET_BLOCKLEN */
          if (!Timer1 || SD_SendCmd(CMD16, 512) != 0) type = 0;
        }
      }
    
      CardType = type;
    
      /* Idle */
      DESELECT();
      SPI_RxByte();
    
      /* Clear STA_NOINIT */
      if (type)
      {
        Stat &= ~STA_NOINIT;
      }
      else
      {
        /* Initialization failed */
        SD_PowerOff();
      }
      //printf("Stat:%02X
    ", Stat);
      return Stat;
    }
    
    /* return disk status */
    DSTATUS SD_disk_status(BYTE drv) 
    {
      if (drv) return STA_NOINIT;
      return Stat;
    }
    
    /* read sector */
    DRESULT SD_disk_read(BYTE pdrv, BYTE* buff, DWORD sector, UINT count) 
    {
      /* pdrv should be 0 */
      if (pdrv || !count) return RES_PARERR;
    
      /* no disk */
      if (Stat & STA_NOINIT) return RES_NOTRDY;
    
      /* if not block-addressing, convert to byte address */
      if (!(CardType & CT_BLOCK)) sector *= 512;
    
      SELECT();
    
      if (count == 1)
      {
        /* READ_SINGLE_BLOCK */
        if (SD_SendCmd(CMD17, sector) == 0)
        {
          if (SD_RxDataBlock(buff, 512))
          {
            count = 0;
          }
        }
      }
      else
      {
        /* READ_MULTIPLE_BLOCK */
        if (SD_SendCmd(CMD18, sector) == 0)
        {
          do {
            if (!SD_RxDataBlock(buff, 512)) break;
            buff += 512;
          } while (--count);
    
          /* STOP_TRANSMISSION */
          SD_SendCmd(CMD12, 0);
        }
      }
    
      /* Idle */
      DESELECT();
      SPI_RxByte();
    
      return count ? RES_ERROR : RES_OK;
    }
    
    /* write sector */
    #if _USE_WRITE == 1
    DRESULT SD_disk_write(BYTE pdrv, const BYTE* buff, DWORD sector, UINT count) 
    {
      /* pdrv should be 0 */
      if (pdrv || !count) return RES_PARERR;
    
      /* no disk */
      if (Stat & STA_NOINIT) return RES_NOTRDY;
    
      /* write protection */
      if (Stat & STA_PROTECT) return RES_WRPRT;
    
      /* convert to byte address */
      if (!(CardType & CT_BLOCK)) sector *= 512;
    
      SELECT();
    
      if (count == 1)
      {
        /* WRITE_BLOCK */
        if ((SD_SendCmd(CMD24, sector) == 0) && SD_TxDataBlock(buff, 0xFE))
          count = 0;
      }
      else
      {
        /* WRITE_MULTIPLE_BLOCK */
        if (CardType & CT_SD1)
        {
          SD_SendCmd(CMD55, 0);
          SD_SendCmd(CMD23, count); /* ACMD23 */
        }
    
        if (SD_SendCmd(CMD25, sector) == 0)
        {
          do {
            if(!SD_TxDataBlock(buff, 0xFC)) break;
            buff += 512;
          } while (--count);
    
          /* STOP_TRAN token */
          if(!SD_TxDataBlock(0, 0xFD))
          {
            count = 1;
          }
        }
      }
    
      /* Idle */
      DESELECT();
      SPI_RxByte();
    
      return count ? RES_ERROR : RES_OK;
    }
    #endif /* _USE_WRITE */
    
    /* ioctl */
    DRESULT SD_disk_ioctl(BYTE drv, BYTE ctrl, void *buff) 
    {
      DRESULT res;
      uint8_t n, csd[16], *ptr = buff;
      WORD csize;
    
      /* pdrv should be 0 */
      if (drv) return RES_PARERR;
      res = RES_ERROR;
    
      if (ctrl == CTRL_POWER)
      {
        switch (*ptr)
        {
        case 0:
          SD_PowerOff();    /* Power Off */
          res = RES_OK;
          break;
        case 1:
          SD_PowerOn();   /* Power On */
          res = RES_OK;
          break;
        case 2:
          *(ptr + 1) = SD_CheckPower();
          res = RES_OK;   /* Power Check */
          break;
        default:
          res = RES_PARERR;
        }
      }
      else
      {
        /* no disk */
        if (Stat & STA_NOINIT) return RES_NOTRDY;
    
        SELECT();
    
        switch (ctrl)
        {
        case GET_SECTOR_COUNT:
          /* SEND_CSD */
          if ((SD_SendCmd(CMD9, 0) == 0) && SD_RxDataBlock(csd, 16))
          {
            if ((csd[0] >> 6) == 1)
            {
              /* SDC V2 */
              csize = csd[9] + ((WORD) csd[8] << 8) + 1;
              *(DWORD*) buff = (DWORD) csize << 10;
            }
            else
            {
              /* MMC or SDC V1 */
              n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2;
              csize = (csd[8] >> 6) + ((WORD) csd[7] << 2) + ((WORD) (csd[6] & 3) << 10) + 1;
              *(DWORD*) buff = (DWORD) csize << (n - 9);
            }
            res = RES_OK;
          }
          break;
        case GET_SECTOR_SIZE:
          *(WORD*) buff = 512;
          res = RES_OK;
          break;
        case CTRL_SYNC:
          if (SD_ReadyWait() == 0xFF) res = RES_OK;
          break;
        case MMC_GET_CSD:
          /* SEND_CSD */
          if (SD_SendCmd(CMD9, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
          break;
        case MMC_GET_CID:
          /* SEND_CID */
          if (SD_SendCmd(CMD10, 0) == 0 && SD_RxDataBlock(ptr, 16)) res = RES_OK;
          break;
        case MMC_GET_OCR:
          /* READ_OCR */
          if (SD_SendCmd(CMD58, 0) == 0)
          {
            for (n = 0; n < 4; n++)
            {
              *ptr++ = SPI_RxByte();
            }
            res = RES_OK;
          }
        default:
          res = RES_PARERR;
        }
    
        DESELECT();
        SPI_RxByte();
      }
    
      return res;
    }
    
  • 相关阅读:
    document.all用法
    $.ajax同步/异步(async:false/true)
    link 和@import 的区别
    如何对网页的加载进行性能优化
    border-style有哪些值?
    CSS设置DIV居中
    jquery选择器
    jQuery库中获取jQuery对象的方式
    Observer,观察者模式,C++描述
    Iterator,迭代器模式,C++描述
  • 原文地址:https://www.cnblogs.com/milton/p/15367647.html
Copyright © 2011-2022 走看看