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\r\n", 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\r\n", 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\r\n", 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\r\n", 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\r\n", 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\r\n", res2);
  if(res2 != FR_OK)
  {
    printf("f_mount failed\r\n");
    Error_Handler();
  }
  
  /* Check freeSpace space */
  if(f_getfree("", &fre_clust, &pfs) != FR_OK)
  {
    printf("f_getfree failed\r\n");
    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\r\n", totalSpace, freeSpace);

  /* free space is less than 1kb */
  if(freeSpace < 1)
  {
    printf("freeSpace not enough\r\n");
    Error_Handler();
  }

  /* Open file to write */
  printf("f_open first.txt\r\n");
  if(f_open(&fil, "first.txt", FA_OPEN_ALWAYS | FA_READ | FA_WRITE) != FR_OK)
  {
    printf("f_open failed\r\n");
    Error_Handler();
  }

  /* Writing text */
  f_puts("STM32 SD Card I/O Example via SPI\n", &fil);
  f_puts("Black Sheep Wall!!!", &fil);

  /* Close file */
  printf("f_close first.txt\r\n");
  if(f_close(&fil) != FR_OK)
  {
    printf("f_close failed\r\n");
    Error_Handler();
  }

  /* Open file to read */
  printf("f_open first.txt\r\n");
  if(f_open(&fil, "first.txt", FA_READ) != FR_OK)
  {
    printf("f_open failed\r\n");
    Error_Handler();
  }

  printf("f_gets first.txt\r\n");
  while(f_gets(buffer, sizeof(buffer), &fil))
  {
    /* SWV output */
    printf("%s", buffer);
    fflush(stdout);
  }
  printf("\r\ndone\r\n");

  /* Close file */
  printf("f_close first.txt\r\n");
  if(f_close(&fil) != FR_OK)
  {
    printf("f_close failed\r\n");
    Error_Handler();
  }

  /* Unmount SDCARD */
  printf("f_mount unmount\r\n");
  if(f_mount(NULL, "", 1) != FR_OK)
  {
    printf("f_mount failed\r\n");
    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\r\n", 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\r\n", res2);
  if(res2 != FR_OK)
  {
    printf("f_mount failed\r\n");
    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("\r\nCMD0\r\n");
  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+\r\n");
      /* 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\r\ntry 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\r\ntry 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\r\ntry 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\r\n");
        }
        else
        {
          printf("succeeded\r\nCMD58 ");
          /* 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\r\n", type);
          }
        }
      }
    }
    else
    {
      printf("failed, SDC V1 or MMC\r\n");
      /* 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\r\n", 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;
}

posted on 2021-10-04 23:44  Milton  阅读(3250)  评论(2编辑  收藏  举报

导航