Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

时间:2023-03-09 07:15:46
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卡的初始化和读写

初始化

Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式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脚定义

Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写

连线

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;
}