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

Keil MDK STM32系列(九) 基于HAL和FatFs的FAT格式SD卡TF卡读写的更多相关文章

  1. Keil MDK STM32系列(四) 基于抽象外设库HAL的STM32F401开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  2. Keil MDK STM32系列(六) 基于抽象外设库HAL的ADC模数转换

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  3. Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  4. Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  5. Keil MDK STM32系列(三) 基于标准外设库SPL的STM32F407开发

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  6. Keil MDK STM32系列(七) STM32F4基于HAL的PWM和定时器

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  7. Keil MDK STM32系列(八) STM32F4基于HAL的PWM和定时器输出音频

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  8. Keil MDK STM32系列(五) 使用STM32CubeMX创建项目基础结构

    Keil MDK STM32系列 Keil MDK STM32系列(一) 基于标准外设库SPL的STM32F103开发 Keil MDK STM32系列(二) 基于标准外设库SPL的STM32F401 ...

  9. keil MDK启动文件分析---基于LPC2100系列(其实都是相通的)

    转用MDK有一段时间了,越来越觉得MDK的强大,因为我之前都是用ADS1.2开发产品,所以更能体会到MDK的强大与易用性.MDK编译出来的代码与ADS1.2相比,代码量减少了很多,我的一个工程用ADS ...

随机推荐

  1. Sentinel-Go 源码系列(三)滑动时间窗口算法的工程实现

    要说现在工程师最重要的能力,我觉得工程能力要排第一. 就算现在大厂面试经常要手撕算法,也是更偏向考查代码工程实现的能力,之前在群里看到这样的图片,就觉得很离谱. 算法与工程实现 在 Sentinel- ...

  2. CF816A Karen and Morning 题解

    Content 给定一个时间 \(h:m\),求从现在这个时间开始到下一个离该时间最近的回文时间要多久? 数据范围:\(0\leqslant h\leqslant 23,0\leqslant m\le ...

  3. LuoguP7072 [CSP-J2020] 直播获奖 题解

    Update \(\texttt{2020.11.13}\) 修改了一个小细节. \(\texttt{2020.11.16}\) 修改了一个错误. Content 有一场 \(n\) 个人的比赛,计划 ...

  4. 聊一聊Yarp结合Nacos完成服务发现

    背景 Yarp 这个反向代理出来后,相信还是有不少人在关注的. 在 Yarp 中,反向代理的配置默认也是基于配置文件的,也有不少大佬已经把这个配置做成了数据库配置+可视化界面. 仔细想了想,做成数据库 ...

  5. 【LeetCode】996. Number of Squareful Arrays 解题报告(C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 回溯法 日期 题目地址:https://leetco ...

  6. 【LeetCode】938. Range Sum of BST 解题报告(Python & C++)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 递归 日期 题目地址:https://leetcod ...

  7. 【LeetCode】81. Search in Rotated Sorted Array II 解题报告(Python)

    作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 题目地址:https://leetcode.com/problems/search-in ...

  8. Lightoj1011 - Marriage Ceremonies

    1011 - Marriage Ceremonies   PDF (English) Statistics Forum Time Limit: 2 second(s) Memory Limit: 32 ...

  9. 15 - Vue3 UI Framework - 完工部署

    项目官网也基本完成了,接下来我们再讲一下如何将项目官网部署到 GitHub Pages 上 返回阅读列表点击 这里 项目配置 常见的模式有三种,即 History 模式 Hash 模式 Memory ...

  10. 【因果推断经典论文】Direct and Indirect Effects - Judea Pearl

    Direct and Indirect Effects Author: Judea Pearl UAI 2001 加州大学洛杉矶分校 论文链接:https://dl.acm.org/doi/pdf/1 ...