Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
Keil MDK STM32系列
- Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发
- Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发
- Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发
- Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发
- Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构
- Keil MDK STM32系列(六) 基于HAL的ADC模数转换
- Keil MDK STM32系列(七) 基于HAL的PWM和定时器
- Keil MDK STM32系列(八) 基于HAL的PWM和定时器输出音频
- Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写
前言
功能:
- 通过SPI读写SD卡/TF卡上的文件系统
- 支持FAT16, FAT32, exFAT, 即FatFs所支持的文件格式.
- 支持存储卡容量512MB至64GB, 更高容量未测试
实现基于
- STM32CubeMX, STM32F4 v1.26.2
- FatFs, R0.12C(STM32CubeMX内建版本)
大部分参考自STM32Cube配置SPI读sd卡
- https://controllerstech.com/sd-card-using-spi-in-stm32/
- 视频 https://www.youtube.com/watch?v=spVIZO-jbxE
- 项目代码 https://github.com/eziya/STM32_SPI_SDCARD/tree/master/STM32F4_HAL_SPI_SDCARD
- 博客(韩文) https://blog.naver.com/eziya76/221188701172
这位韩国作者很详细地制作了博客, 视频和Github代码, 但是源码无法直接使用, 遇到的问题
- 代码有两个版本, 视频和博客基于前一个版本
- 代码并非在Windows下编译, 无法在Keil MDK中直接使用
- 代码存在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卡的初始化和读写
初始化
- 上电, 将CS片选信号拉低, 开启CLK给SD卡发送至少74个时钟周期, 让SD卡完成自身检查和初始化, 进入IDLE状态. 之后对SD卡发送
CMD0
, 进入SPI模式. SD卡从D_OUT线上的返回值如果是0x01, 说明CMD0操作是成功的, 此时SD卡处在IDLE状态. - 发送
CMD8
.CMD8
是检测SD卡版本的命令, 如果SD卡对此命令不识别, 说明SD卡为老版本, 如果SD卡有正确返回值(00 01 AA), 说明SD卡是2.0+版本的, 支持大容量储存, 也就是SDHC卡. - 发送
ACMD41
, 让卡从IDLE状态进入读写就绪的状态. 这里要注意, SD卡命令有两种, CMD和ACMD, 直接发送命令默认为CMD, 如果要发送ACMD, 需要先发送CMD55
, 接收到正常的返回值0X01, 接着发送ACMD41
, 完成卡从IDLE状态到读写状态的初始化进程. SD卡退出IDLE状态的正确返回值为0X00.ACMD41
的值有多种, 常用的为0x40000000
. - 发送
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;
}
Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写的更多相关文章
- Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构
Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...
- keil MDK启动文件分析---基于LPC2100系列(其实都是相通的)
转用MDK有一段时间了,越来越觉得MDK的强大,因为我之前都是用ADS1.2开发产品,所以更能体会到MDK的强大与易用性.MDK编译出来的代码与ADS1.2相比,代码量减少了很多,我的一个工程用ADS ...
随机推荐
- Sentinel-Go 源码系列(三)滑动时间窗口算法的工程实现
要说现在工程师最重要的能力,我觉得工程能力要排第一. 就算现在大厂面试经常要手撕算法,也是更偏向考查代码工程实现的能力,之前在群里看到这样的图片,就觉得很离谱. 算法与工程实现 在 Sentinel- ...
- CF816A Karen and Morning 题解
Content 给定一个时间 \(h:m\),求从现在这个时间开始到下一个离该时间最近的回文时间要多久? 数据范围:\(0\leqslant h\leqslant 23,0\leqslant m\le ...
- LuoguP7072 [CSP-J2020] 直播获奖 题解
Update \(\texttt{2020.11.13}\) 修改了一个小细节. \(\texttt{2020.11.16}\) 修改了一个错误. Content 有一场 \(n\) 个人的比赛,计划 ...
- 聊一聊Yarp结合Nacos完成服务发现
背景 Yarp 这个反向代理出来后,相信还是有不少人在关注的. 在 Yarp 中,反向代理的配置默认也是基于配置文件的,也有不少大佬已经把这个配置做成了数据库配置+可视化界面. 仔细想了想,做成数据库 ...
- 【LeetCode】996. Number of Squareful Arrays 解题报告(C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 回溯法 日期 题目地址:https://leetco ...
- 【LeetCode】938. Range Sum of BST 解题报告(Python & C++)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcod ...
- 【LeetCode】81. Search in Rotated Sorted Array II 解题报告(Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/search-in ...
- Lightoj1011 - Marriage Ceremonies
1011 - Marriage Ceremonies PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 ...
- 15 - Vue3 UI Framework - 完工部署
项目官网也基本完成了,接下来我们再讲一下如何将项目官网部署到 GitHub Pages 上 返回阅读列表点击 这里 项目配置 常见的模式有三种,即 History 模式 Hash 模式 Memory ...
- 【因果推断经典论文】Direct and Indirect Effects - Judea Pearl
Direct and Indirect Effects Author: Judea Pearl UAI 2001 加州大学洛杉矶分校 论文链接:https://dl.acm.org/doi/pdf/1 ...