STM32 + FatFs + SD card via SPI【一】移植FatFS

前言

此篇文章將講述使用以SPI讀取SD card並實現FatFS,最終的成果如下圖能以UART CLI輸入指令來操作SD card



實驗內容簡略


本篇文章不會講的;但是需要的功能

  • SPI四線式收發
  • Timer
  • UART blocking收發
  • RTC(選項)


操作環境

  • Board:NUCLEO-F401RE
  • micro SD card模組
  • IDE:STM32CubeIDE
  • Code:STM32CubeMX
  • Software Packs:ffsample.zip (從FatFS官網下戴)

MCU I/O配置

  • SPI:CS(PB5),SCK(PA5),MOSI(PA7),MISO(PA6)
  • UART:TX(PA2),RX(PA3)
  • Timer:1ms
  • RTC(選項)
  • Button:PC13(選項)

FatFS使用到的檔案

從下戴的ffsample.zip裡找尋資料夾 \stm32,會須要用到下列幾個檔案

mmc_stm32f1_spi.c(diskio.c)使用SPI與SD card溝通,已建置SD card溝通的protocol,

大部分檔案都無須修改,就diskio.c要把SPI相關函式配合使用的平台做修改

  • main.c
  • mmc_stm32f1_spi.c (建議將檔案名改回diskio.c)
  • diskio.h
  • ff.c
  • ff.h
  • ffconf.h
  • ffunicode.c
  • xprintf.c
  • xprintf.h

SD card小常識

SPI連接時的設定

  • Vdd 3.3V
  • 訊號線上要加 10k~100k的升壓電阻

上電時SD card的一連串初始化程序這邊不一一介紹,就先講頭二個(下面會用到)

1. 先以不超過400Khz給予至少74個clock(CS pin為High)

2. 發送 CMD0進入idle state(0x40 0x00 0x00 0x00 0x00 0x95)(CS pin為Low)

CMD0在spec裡雖然是沒有response,但在command 6個bytes後再送2個0xFF正常第2個byte是可以收到0以外的值

3. CS pin回復為High



驗証MCU與SD socket間的硬體連接

在把FatFS檔案加入project之前要先驗証MCU的SPI跟SD card socket能正常溝通

所以先試著用SPI發送CMD0指令看是否能正常

  1. SPI頻率小於等於400Khz
  2. 送出74個以上的clock:0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
  3. CMD0指令:0x40 0x00 0x00 0x00 0x00 0x95
  4. Response:0xFF 0xFF

下圖最後的0x01表示有接收到SD card返回的資料



加入UART CLI介面

如果上面的驗証沒問題,接下來就把ffsample.zip下面列出的檔案加入project

先完成UART的Command Line Interface(CLI)。(UART功能請先自行驗証能否正常發送/接收)

  • diskio.h
  • ff.c
  • ff.h
  • ffconf.h
  • ffunicode.c
  • xprintf.c
  • xprintf.h

main.c

加入include

#include <stdio.h>
#include <string.h>
#include "ff.h"
#include "xprintf.h"
#include "diskio.h"

全域變數宣告

char Line[256];				/* Console input buffer */
uint8_t Buff[4096] __attribute__ ((aligned (4))) ;	/* Working buffer */

FATFS FatFs;				/* File system object for each logical drive */
FIL File[2];				/* File objects */
DIR Dir;					/* Directory object */
FILINFO Finfo;

volatile UINT Timer;

CLI菜單顯示內容

const char HelpMsg[] =
	"[Disk contorls]\n"
	" di <pd#> - Initialize disk\n"
	" dd [<pd#> <lba>] - Dump a secrtor\n"
	" ds <pd#> - Show disk status\n"
	"[Buffer controls]\n"
	" bd <ofs> - Dump working buffer\n"
	" be <ofs> [<data>] ... - Edit working buffer\n"
	" br <pd#> <lba> [<count>] - Read disk into working buffer\n"
	" bw <pd#> <lba> [<count>] - Write working buffer into disk\n"
	" bf <val> - Fill working buffer\n"
	"[File system controls]\n"
	" fi <ld#> [<mount>]- Force initialized the volume\n"
	" fs [<path>] - Show volume status\n"
	" fl [<path>] - Show a directory\n"
	" fo <mode> <file> - Open a file\n"
	" fc - Close the file\n"
	" fe <ofs> - Move fp in normal seek\n"
	" fd <len> - Read and dump the file\n"
	" fr <len> - Read the file\n"
	" fw <len> <val> - Write to the file\n"
	" fn <org.name> <new.name> - Rename an object\n"
	" fu <name> - Unlink an object\n"
	" fv - Truncate the file at current fp\n"
	" fk <name> - Create a directory\n"
	" fa <atrr> <mask> <object name> - Change attribute of an object\n"
	" ft <year> <month> <day> <hour> <min> <sec> <name> - Change timestamp of an object\n"
	" fx <src.file> <dst.file> - Copy a file\n"
	" fg <path> - Change current directory\n"
	" fq - Show current directory\n"
	" fb <name> - Set volume label\n"
	" fm <ld#> <type> <csize> - Create file system\n"
	" fz [<len>] - Change/Show R/W length for fr/fw/fx command\n"
	"[Misc commands]\n"
	" md[b|h|w] <addr> [<count>] - Dump memory\n"
	" mf <addr> <value> <count> - Fill memory\n"
	" me[b|h|w] <addr> [<value> ...] - Edit memory\n"
	" t [<year> <mon> <mday> <hour> <min> <sec>] - Set/Show RTC\n"
	"\n";

UART發送/接收模組,這是根據使用平台不同自行增加的,在main()裡會用指標定義給xprintf的函式

void uart2_putc(uint8_t x)
{
	uint8_t tx[2] = {x};
	HAL_UART_Transmit(&huart2, tx, 1, 0xFF);
}

uint8_t uart2_getc(void)
{
	uint8_t rx[2];
	HAL_UART_Receive(&huart2, rx, 1, HAL_MAX_DELAY);
	return rx[0];
}

main.c

static const char *ft[] = {"", "FAT12", "FAT16", "FAT32", "exFAT"};
static const char days[] = "Sun\0Mon\0Tue\0Wed\0Thu\0Fri\0Sat";
char *ptr, *ptr2;
long p1, p2, p3;
BYTE res, b, drv = 0;
UINT s1, s2, cnt, blen = sizeof Buff, acc_files, acc_dirs;
DWORD ofs = 0, sect = 0, blk[2], dw;
QWORD acc_size;
FATFS *fs;
    
int main(void)
{    
    xdev_in(uart2_getc);//將UART接收模組指到xprintf.c的xfunc_input
    xdev_out(uart2_putc);//將UART接收模組指到xprintf.c的xfunc_output
    
    while (1)
    {
        xputc('>');
        xgets(Line, sizeof Line);

        ptr = Line;
        switch (*ptr++) {
        case '?' :	/* Show Command List */
            xputs(HelpMsg);
            break;
        }
    }
}

完成以上步驟後,在超級終端機輸入 符號? 接下Enter就可以顯示出菜單(目前只有顯示;但無功能)



實現SD card溝通的協議

mmc_stm32f1_spi.c (diskio.c)加入project


diskio.c

加進diskio.c後編譯會出現不少error,下列I/O接口依您使用平台做修改

#include "main.h"
#include "ff.h"			/* Obtains integer types */
#include "diskio.h"		/* Declarations of disk functions */
#include "stm32f401xe.h"

#define	_BV(bit) (1<<(bit))

#define SPI_CH	1	/* SPI channel to use = 1: SPI1, 11: SPI1/remap, 2: SPI2 */

#define FCLK_SLOW() { SPI1->CR1 = (SPIx_CR1 & ~0x38) | SPI_BAUDRATEPRESCALER_256; }	/* Set SCLK = PCLK / 256 */
#define FCLK_FAST() { SPI1->CR1 = (SPIx_CR1 & ~0x38) | SPI_BAUDRATEPRESCALER_16; }	/* Set SCLK = PCLK / 16 */

#if SPI_CH == 1	/* PA4:MMC_CS, PA5:MMC_SCLK, PA6:MMC_DO, PA7:MMC_DI, PC4:MMC_CD */
#define CS_HIGH()	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_SET)//GPIOA_BSRR = _BV(4)
#define CS_LOW()	HAL_GPIO_WritePin(GPIOB,GPIO_PIN_5,GPIO_PIN_RESET)//GPIOA_BSRR = _BV(4+16)
#define	MMC_CD		1//!(GPIOC_IDR & _BV(4))	/* Card detect (yes:true, no:false, default:true) */
#define	MMC_WP		0 /* Write protected (yes:true, no:false, default:false) */
#define SPIx_CR1	SPI1->CR1//SPI1_CR1
#define SPIx_SR		SPI1->SR//SPI1_SR
#define SPIx_DR		SPI1->DR//SPI1_DR
#define	SPIxENABLE() {\
}

diskio.c

SPI發送/接收模組依使用平台做修改

static BYTE xchg_spi (
	BYTE dat	/* Data to send */
)
{
	//	SPIx_DR = dat;				/* Start an SPI transaction */
	//	while ((SPIx_SR & 0x83) != 0x03) ;	/* Wait for end of the transaction */
	//	return (BYTE)SPIx_DR;		/* Return received byte */
	uint8_t tx[2] = {dat};
	uint8_t rx[2] = {0};
	extern SPI_HandleTypeDef hspi1;

	HAL_SPI_TransmitReceive(&hspi1, tx, rx, 1, 0xFFFF);
	return rx[0];
}

main.c

新增RTC模組(從ffsample.zip \stm32\main.c複製),內容依使用平台修改

uint32_t get_fattime (void)
{
	RTC_TimeTypeDef GetTime;
	RTC_DateTypeDef GetData;

	HAL_RTC_GetTime(&hrtc, &GetTime, RTC_FORMAT_BIN);
	HAL_RTC_GetDate(&hrtc, &GetData, RTC_FORMAT_BIN);
	return	  ((uint32_t)(GetData.Year - 1980) << 25)
			| ((uint32_t)GetData.Month << 21)
			| ((uint32_t)GetData.Date << 16)
			| ((uint32_t)GetTime.Hours << 11)
			| ((uint32_t)GetTime.Minutes << 5)
			| ((uint32_t)GetTime.Seconds >> 1);
}

main.c

新增(從ffsample.zip \stm32\main.c複製)

FRESULT scan_files (
	char* path,		/* Pointer to the path name working buffer */
	UINT* n_dir,
	UINT* n_file,
	QWORD* sz_file
)
{
	DIR dirs;
	FRESULT res;
	BYTE i;

	if ((res = f_opendir(&dirs, path)) == FR_OK) {
		i = strlen(path);
		while (((res = f_readdir(&dirs, &Finfo)) == FR_OK) && Finfo.fname[0]) {
			if (Finfo.fattrib & AM_DIR) {
				(*n_dir)++;
				*(path+i) = '/'; strcpy(path+i+1, Finfo.fname);
				res = scan_files(path, n_dir, n_file, sz_file);
				*(path+i) = '\0';
				if (res != FR_OK) break;
			} else {
			/*	xprintf("%s/%s\n", path, fn); */
				(*n_file)++;
				*sz_file += Finfo.fsize;
			}
		}
	}

	return res;
}

void put_rc (FRESULT rc)
{
	const char *str =
		"OK\0" "DISK_ERR\0" "INT_ERR\0" "NOT_READY\0" "NO_FILE\0" "NO_PATH\0"
		"INVALID_NAME\0" "DENIED\0" "EXIST\0" "INVALID_OBJECT\0" "WRITE_PROTECTED\0"
		"INVALID_DRIVE\0" "NOT_ENABLED\0" "NO_FILE_SYSTEM\0" "MKFS_ABORTED\0" "TIMEOUT\0"
		"LOCKED\0" "NOT_ENOUGH_CORE\0" "TOO_MANY_OPEN_FILES\0" "INVALID_PARAMETER\0";
	FRESULT i;

	for (i = 0; i != rc && *str; i++) {
		while (*str++) ;
	}
	xprintf("rc=%u FR_%s\n", (UINT)rc, str);
}

main.c

在1ms Timer的callback加入

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  extern void disk_timerproc (void);

  Timer++;
  disk_timerproc();
}

main.c

完善CLI菜單的選項,基本上都不用修改;case 't' RTC的功能,依使用者平台自行調整

while(1)
{
	xputc('>');
	xgets(Line, sizeof Line);

	ptr = Line;
	switch (*ptr++) {
		case '?' :	/* Show Command List */
			xputs(HelpMsg);
			break;

		case 'm' :	/* Memory dump/fill/edit */
			switch (*ptr++) {
			case 'd' :	/* md[b|h|w] <address> [<count>] - Dump memory */
				switch (*ptr++) {
				case 'w': p3 = 4; break;
				case 'h': p3 = 2; break;
				default: p3 = 1;
				}
				if (!xatoi(&ptr, &p1)) break;
				if (!xatoi(&ptr, &p2)) p2 = 128 / p3;
				for (ptr = (char*)p1; p2 >= 16 / p3; ptr += 16, p2 -= 16 / p3)
					put_dump(ptr, (DWORD)ptr, 16 / p3, p3);
				if (p2) put_dump((BYTE*)ptr, (UINT)ptr, p2, p3);
				break;
			case 'f' :	/* mf <address> <value> <count> - Fill memory */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2) || !xatoi(&ptr, &p3)) break;
				while (p3--) {
					*(BYTE*)p1 = (BYTE)p2;
					p1++;
				}
				break;
			case 'e' :	/* me[b|h|w] <address> [<value> ...] - Edit memory */
				switch (*ptr++) {	/* Get data width */
				case 'w': p3 = 4; break;
				case 'h': p3 = 2; break;
				default: p3 = 1;
				}
				if (!xatoi(&ptr, &p1)) break;	/* Get start address */
				if (xatoi(&ptr, &p2)) {	/* 2nd parameter is given (direct mode) */
					do {
						switch (p3) {
						case 4: *(DWORD*)p1 = (DWORD)p2; break;
						case 2: *(WORD*)p1 = (WORD)p2; break;
						default: *(BYTE*)p1 = (BYTE)p2;
						}
						p1 += p3;
					} while (xatoi(&ptr, &p2));	/* Get next value */
					break;
				}
				for (;;) {				/* 2nd parameter is not given (interactive mode) */
					switch (p3) {
					case 4: xprintf("%08X 0x%08X-", p1, *(DWORD*)p1); break;
					case 2: xprintf("%08X 0x%04X-", p1, *(WORD*)p1); break;
					default: xprintf("%08X 0x%02X-", p1, *(BYTE*)p1);
					}
					ptr = Line; xgets(ptr, sizeof Line);
					if (*ptr == '.') break;
					if ((BYTE)*ptr >= ' ') {
						if (!xatoi(&ptr, &p2)) continue;
						switch (p3) {
						case 4: *(DWORD*)p1 = (DWORD)p2; break;
						case 2: *(WORD*)p1 = (WORD)p2; break;
						default: *(BYTE*)p1 = (BYTE)p2;
						}
					}
					p1 += p3;
				}
				break;
			}
			break;

		case 'd' :	/* Disk I/O layer controls */
			switch (*ptr++) {
			case 'd' :	/* dd [<pd#> <sect>] - Dump secrtor */
				if (!xatoi(&ptr, &p1)) {
					p1 = drv; p2 = sect;
				} else {
					if (!xatoi(&ptr, &p2)) break;
				}
				drv = (BYTE)p1; sect = p2;
				res = disk_read(drv, Buff, sect, 1);
				if (res) { xprintf("rc=%d\n", (WORD)res); break; }
				xprintf("PD#:%u LBA:%lu\n", drv, sect++);
				for (ptr=(char*)Buff, ofs = 0; ofs < 0x200; ptr += 16, ofs += 16)
					put_dump((BYTE*)ptr, ofs, 16, 1);
				break;

			case 'i' :	/* di <pd#> - Initialize disk */
				if (!xatoi(&ptr, &p1)) break;
				xprintf("rc=%d\n", (WORD)disk_initialize((BYTE)p1));
				break;

			case 's' :	/* ds <pd#> - Show disk status */
				if (!xatoi(&ptr, &p1)) break;
				if (disk_ioctl((BYTE)p1, GET_SECTOR_COUNT, &p2) == RES_OK)
					{ xprintf("Drive size: %lu sectors\n", p2); }
				if (disk_ioctl((BYTE)p1, GET_BLOCK_SIZE, &p2) == RES_OK)
					{ xprintf("Block size: %lu sectors\n", p2); }
				if (disk_ioctl((BYTE)p1, MMC_GET_TYPE, &b) == RES_OK)
					{ xprintf("Media type: %u\n", b); }
				if (disk_ioctl((BYTE)p1, MMC_GET_CSD, Buff) == RES_OK)
					{ xputs("CSD:\n"); put_dump(Buff, 0, 16, 1); }
				if (disk_ioctl((BYTE)p1, MMC_GET_CID, Buff) == RES_OK)
					{ xputs("CID:\n"); put_dump(Buff, 0, 16, 1); }
				if (disk_ioctl((BYTE)p1, MMC_GET_OCR, Buff) == RES_OK)
					{ xputs("OCR:\n"); put_dump(Buff, 0, 4, 1); }
				if (disk_ioctl((BYTE)p1, MMC_GET_SDSTAT, Buff) == RES_OK) {
					xputs("SD Status:\n");
					for (s1 = 0; s1 < 64; s1 += 16) put_dump(Buff+s1, s1, 16, 1);
				}
				break;

			case 'c' :	/* Disk ioctl */
				switch (*ptr++) {
				case 's' :	/* dcs <pd#> - CTRL_SYNC */
					if (!xatoi(&ptr, &p1)) break;
					xprintf("rc=%d\n", disk_ioctl((BYTE)p1, CTRL_SYNC, 0));
					break;
				case 'e' :	/* dce <pd#> <s.lba> <e.lba> - CTRL_TRIM */
					if (!xatoi(&ptr, &p1) || !xatoi(&ptr, (long*)&blk[0]) || !xatoi(&ptr, (long*)&blk[1])) break;
					xprintf("rc=%d\n", disk_ioctl((BYTE)p1, CTRL_TRIM, blk));
					break;
				}
				break;
			}
			break;

		case 'b' :	/* Buffer controls */
			switch (*ptr++) {
			case 'd' :	/* bd <ofs> - Dump R/W buffer */
				if (!xatoi(&ptr, &p1)) break;
				for (ptr=(char*)&Buff[p1], ofs = p1, cnt = 32; cnt; cnt--, ptr+=16, ofs+=16)
					put_dump((BYTE*)ptr, ofs, 16, 1);
				break;

			case 'e' :	/* be <ofs> [<data>] ... - Edit R/W buffer */
				if (!xatoi(&ptr, &p1)) break;
				if (xatoi(&ptr, &p2)) {
					do {
						Buff[p1++] = (BYTE)p2;
					} while (xatoi(&ptr, &p2));
					break;
				}
				for (;;) {
					xprintf("%04X %02X-", (WORD)(p1), (WORD)Buff[p1]);
					xgets(Line, sizeof Line);
					ptr = Line;
					if (*ptr == '.') break;
					if (*ptr < ' ') { p1++; continue; }
					if (xatoi(&ptr, &p2))
						Buff[p1++] = (BYTE)p2;
					else
						xputs("???\n");
				}
				break;

			case 'r' :	/* br <pd#> <lba> [<num>] - Read disk into R/W buffer */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				if (!xatoi(&ptr, &p3)) p3 = 1;
				xprintf("rc=%u\n", (WORD)disk_read((BYTE)p1, Buff, p2, p3));
				break;

			case 'w' :	/* bw <pd#> <lba> [<num>] - Write R/W buffer into disk */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				if (!xatoi(&ptr, &p3)) p3 = 1;
				xprintf("rc=%u\n", (WORD)disk_write((BYTE)p1, Buff, p2, p3));
				break;

			case 'f' :	/* bf <val> - Fill working buffer */
				if (!xatoi(&ptr, &p1)) break;
				memset(Buff, (BYTE)p1, sizeof Buff);
				break;

			}
			break;

		case 'f' :	/* FatFS API controls */
			switch (*ptr++) {

			case 'i' :	/* fi [<opt>]- Initialize logical drive */
				if (!xatoi(&ptr, &p2)) p2 = 0;
				put_rc(f_mount(&FatFs, "", (BYTE)p2));
				break;

			case 's' :	/* fs [<path>] - Show volume status */
				while (*ptr == ' ') ptr++;
				res = f_getfree(ptr, (DWORD*)&p1, &fs);
				if (res) { put_rc(res); break; }
				xprintf("FAT type = %s\n", ft[fs->fs_type]);
				xprintf("Bytes/Cluster = %lu\n", (DWORD)fs->csize * 512);
				xprintf("Number of FATs = %u\n", fs->n_fats);
				if (fs->fs_type < FS_FAT32) xprintf("Root DIR entries = %u\n", fs->n_rootdir);
				xprintf("Sectors/FAT = %lu\n", fs->fsize);
				xprintf("Number of clusters = %lu\n", (DWORD)fs->n_fatent - 2);
				xprintf("Volume start (lba) = %lu\n", fs->volbase);
				xprintf("FAT start (lba) = %lu\n", fs->fatbase);
				xprintf("DIR start (lba,clustor) = %lu\n", fs->dirbase);
				xprintf("Data start (lba) = %lu\n\n", fs->database);
#if FF_USE_LABEL
				res = f_getlabel(ptr, (char*)Buff, (DWORD*)&p2);
				if (res) { put_rc(res); break; }
				xprintf(Buff[0] ? "Volume name is %s\n" : "No volume label\n", (char*)Buff);
				xprintf("Volume S/N is %04X-%04X\n", (DWORD)p2 >> 16, (DWORD)p2 & 0xFFFF);
#endif
				acc_size = acc_files = acc_dirs = 0;
				xprintf("...");
				res = scan_files(ptr, &acc_dirs, &acc_files, &acc_size);
				if (res) { put_rc(res); break; }
				xprintf("\r%u files, %llu bytes.\n%u folders.\n"
						"%lu KiB total disk space.\n%lu KiB available.\n",
						acc_files, acc_size, acc_dirs,
						(fs->n_fatent - 2) * (fs->csize / 2), (DWORD)p1 * (fs->csize / 2)
				);
				break;

			case 'l' :	/* fl [<path>] - Directory listing */
				while (*ptr == ' ') ptr++;
				res = f_opendir(&Dir, ptr);
				if (res) { put_rc(res); break; }
				acc_size = acc_dirs = acc_files = 0;
				for(;;) {
					res = f_readdir(&Dir, &Finfo);
					if ((res != FR_OK) || !Finfo.fname[0]) break;
					if (Finfo.fattrib & AM_DIR) {
						acc_dirs++;
					} else {
						acc_files++; acc_size += Finfo.fsize;
					}
					xprintf("%c%c%c%c%c %u/%02u/%02u %02u:%02u %9lu  %s\n",
							(Finfo.fattrib & AM_DIR) ? 'D' : '-',
							(Finfo.fattrib & AM_RDO) ? 'R' : '-',
							(Finfo.fattrib & AM_HID) ? 'H' : '-',
							(Finfo.fattrib & AM_SYS) ? 'S' : '-',
							(Finfo.fattrib & AM_ARC) ? 'A' : '-',
							(Finfo.fdate >> 9) + 1980, (Finfo.fdate >> 5) & 15, Finfo.fdate & 31,
							(Finfo.ftime >> 11), (Finfo.ftime >> 5) & 63,
							Finfo.fsize, Finfo.fname);
				}
				xprintf("%4u File(s),%10llu bytes total\n%4u Dir(s)", acc_files, acc_size, acc_dirs);
				res = f_getfree(ptr, &dw, &fs);
				if (res == FR_OK) {
					xprintf(", %10llu bytes free\n", (QWORD)dw * fs->csize * 512);
				} else {
					put_rc(res);
				}
				break;

			case 'o' :	/* fo <mode> <file> - Open a file */
				if (!xatoi(&ptr, &p1)) break;
				while (*ptr == ' ') ptr++;
				put_rc(f_open(&File[0], ptr, (BYTE)p1));
				break;

			case 'c' :	/* fc - Close a file */
				put_rc(f_close(&File[0]));
				break;

			case 'e' :	/* fe - Seek file pointer */
				if (!xatoi(&ptr, &p1)) break;
				res = f_lseek(&File[0], p1);
				put_rc(res);
				if (res == FR_OK) {
					xprintf("fptr=%lu(0x%lX)\n", File[0].fptr, File[0].fptr);
				}
				break;

			case 'd' :	/* fd <len> - read and dump file from current fp */
				if (!xatoi(&ptr, &p1)) break;
				ofs = File[0].fptr;
				while (p1) {
					if ((UINT)p1 >= 16) { cnt = 16; p1 -= 16; }
					else 				{ cnt = p1; p1 = 0; }
					res = f_read(&File[0], Buff, cnt, &cnt);
					if (res != FR_OK) { put_rc(res); break; }
					if (!cnt) break;
					put_dump(Buff, ofs, cnt, 1);
					ofs += 16;
				}
				break;

			case 'r' :	/* fr <len> - read file */
				if (!xatoi(&ptr, &p1)) break;
				p2 = 0;
				Timer = 0;
				while (p1) {
					if ((UINT)p1 >= blen) {
						cnt = blen; p1 -= blen;
					} else {
						cnt = p1; p1 = 0;
					}
					res = f_read(&File[0], Buff, cnt, &s2);
					if (res != FR_OK) { put_rc(res); break; }
					p2 += s2;
					if (cnt != s2) break;
				}
				xprintf("%lu bytes read with %lu kB/sec.\n", p2, Timer ? (p2 / Timer) : 0);
				break;

			case 'w' :	/* fw <len> <val> - write file */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				memset(Buff, (BYTE)p2, blen);
				p2 = 0;
				Timer = 0;
				while (p1) {
					if ((UINT)p1 >= blen) {
						cnt = blen; p1 -= blen;
					} else {
						cnt = p1; p1 = 0;
					}
					res = f_write(&File[0], Buff, cnt, &s2);
					if (res != FR_OK) { put_rc(res); break; }
					p2 += s2;
					if (cnt != s2) break;
				}
				xprintf("%lu bytes written with %lu kB/sec.\n", p2, Timer ? (p2 / Timer) : 0);
				break;

			case 'n' :	/* fn <org.name> <new.name> - Change name of an object */
				while (*ptr == ' ') ptr++;
				ptr2 = strchr(ptr, ' ');
				if (!ptr2) break;
				*ptr2++ = 0;
				while (*ptr2 == ' ') ptr2++;
				put_rc(f_rename(ptr, ptr2));
				break;

			case 'u' :	/* fu <name> - Unlink an object */
				while (*ptr == ' ') ptr++;
				put_rc(f_unlink(ptr));
				break;

			case 'v' :	/* fv - Truncate file */
				put_rc(f_truncate(&File[0]));
				break;

			case 'k' :	/* fk <name> - Create a directory */
				while (*ptr == ' ') ptr++;
				put_rc(f_mkdir(ptr));
				break;

			case 'a' :	/* fa <atrr> <mask> <name> - Change attribute of an object */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2)) break;
				while (*ptr == ' ') ptr++;
				put_rc(f_chmod(ptr, p1, p2));
				break;

			case 't' :	/* ft <year> <month> <day> <hour> <min> <sec> <name> - Change timestamp of an object */
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2) || !xatoi(&ptr, &p3)) break;
				Finfo.fdate = ((p1 - 1980) << 9) | ((p2 & 15) << 5) | (p3 & 31);
				if (!xatoi(&ptr, &p1) || !xatoi(&ptr, &p2) || !xatoi(&ptr, &p3)) break;
				Finfo.ftime = ((p1 & 31) << 11) | ((p2 & 63) << 5) | ((p3 >> 1) & 31);
				put_rc(f_utime(ptr, &Finfo));
				break;

			case 'x' : /* fx <src.name> <dst.name> - Copy a file */
				while (*ptr == ' ') ptr++;
				ptr2 = strchr(ptr, ' ');
				if (!ptr2) break;
				*ptr2++ = 0;
				while (*ptr2 == ' ') ptr2++;
				xprintf("Opening \"%s\"", ptr);
				res = f_open(&File[0], ptr, FA_OPEN_EXISTING | FA_READ);
				xputc('\n');
				if (res) {
					put_rc(res);
					break;
				}
				xprintf("Creating \"%s\"", ptr2);
				res = f_open(&File[1], ptr2, FA_CREATE_ALWAYS | FA_WRITE);
				xputc('\n');
				if (res) {
					put_rc(res);
					f_close(&File[0]);
					break;
				}
				xprintf("Copying file...");
				Timer = 0;
				p1 = 0;
				for (;;) {
					res = f_read(&File[0], Buff, blen, &s1);
					if (res || s1 == 0) break;   /* error or eof */
					res = f_write(&File[1], Buff, s1, &s2);
					p1 += s2;
					if (res || s2 < s1) break;   /* error or disk full */
				}
				xprintf("\n%lu bytes copied with %lu kB/sec.\n", p1, p1 / Timer);
				f_close(&File[0]);
				f_close(&File[1]);
				break;
#if FF_FS_RPATH
			case 'g' :	/* fg <path> - Change current directory */
				while (*ptr == ' ') ptr++;
				put_rc(f_chdir(ptr));
				break;
#if FF_FS_RPATH >= 2
			case 'q' :	/* fq - Show current dir path */
				res = f_getcwd(Line, sizeof Line);
				if (res)
					put_rc(res);
				else
					xprintf("%s\n", Line);
				break;
#endif
#endif
#if FF_USE_LABEL
			case 'b' :	/* fb <name> - Set volume label */
				while (*ptr == ' ') ptr++;
				put_rc(f_setlabel(ptr));
				break;
#endif	/* FF_USE_LABEL */
#if FF_USE_MKFS
			case 'm' :	/* fm [<fs type> [<au size> [<align> [<n_fats> [<n_root>]]]]] - Create filesystem */
				{
					MKFS_PARM opt, *popt = 0;

					if (xatoi(&ptr, &p2)) {
						memset(&opt, 0, sizeof opt);
						popt = &opt;
						popt->fmt = (BYTE)p2;
						if (xatoi(&ptr, &p2)) {
							popt->au_size = p2;
							if (xatoi(&ptr, &p2)) {
								popt->align = p2;
								if (xatoi(&ptr, &p2)) {
									popt->n_fat = (BYTE)p2;
									if (xatoi(&ptr, &p2)) {
										popt->n_root = p2;
									}
								}
							}
						}
					}
					xprintf("The volume will be formatted. Are you sure? (Y/n)=");
					xgets(Line, sizeof Line);
					if (Line[0] == 'Y') put_rc(f_mkfs("", popt, Buff, sizeof Buff));
					break;
				}
#endif	/* FF_USE_MKFS */
			case 'z' :	/* fz [<size>] - Change/Show R/W length for fr/fw/fx command */
				if (xatoi(&ptr, &p1) && p1 >= 1 && p1 <= (long)sizeof Buff)
					blen = p1;
				xprintf("blen=%u\n", blen);
				break;
			}
			break;

		case 't' :	/* t [<year> <mon> <mday> <hour> <min> <sec>] - Set/Show RTC */
			if (xatoi(&ptr, &p1)) {
				rtc.year = (WORD)p1;
				xatoi(&ptr, &p1); rtc.month = (BYTE)p1;
				xatoi(&ptr, &p1); rtc.mday = (BYTE)p1;
				xatoi(&ptr, &p1); rtc.hour = (BYTE)p1;
				xatoi(&ptr, &p1); rtc.min = (BYTE)p1;
 				if (!xatoi(&ptr, &p1)) break;
				rtc.sec = (BYTE)p1;
				rtc_settime(&rtc);
			}
			rtc_gettime(&rtc);
			xprintf("%u/%u/%u %s %02u:%02u:%02u\n", rtc.year, rtc.month, rtc.mday, &days[rtc.wday*4], rtc.hour, rtc.min, rtc.sec);
			break;
		}
}


驗收成果

1. 初始化裝置

指令:di

參數:0 (裝置0)

返回:rc=0 (0 即成功;大於0的值皆為失敗)


2. 顯示裝置狀態

指令:ds

參數:0 (裝置0)

返回:顯示即成功



最後附上程式連結 F401_SDcard.7z

針對diskio.c的send_cmd()增加CRC計算 F401_SDcard_v04.7z

修正fw只能寫入一個字元的issue F401_SDcard_v05.7z

留言

2024

11-27SPI Flash 操作 (Read/Write/Erase)
11-19Rotary Encoder Switch 旋轉編碼開關
11-14Command Line Interface - CLI via UART
11-14【STM32】USB HID - Volume Control
11-13【STM32】USB Custom HID
11-12【STM32】USB HID Keyboard + Mouse
11-12【STM32】USB HID Keyboard
11-12【STM32】USB HID Mouse
10-15SSD1306 128x64 OLED 【五】Wokwi Animator
09-2432F429IDISCOVERY - - LTDC [3] + FMC (SDRAM) + FatFS
09-2432F429IDISCOVERY - - LTDC [2] + FMC (SDRAM)
09-20STM32 + FatFs + SD card via SPI【三】FatFS指令操作II
09-19STM32 + FatFs + SD card via SPI【二】FatFS指令操作
09-18STM32 + FatFs + SD card via SPI【一】移植FatFS
09-0232F429IDISCOVERY - - LTDC [1]
04-17SSD1306 128x64 OLED 【四】Adafruit / GFX Library
04-17Arduino - Serial Plotter繪圖儀
04-16SSD1306 128x64 OLED 【三】
04-15SSD1306 128x64 OLED 【二】 Datasheet
04-12SSD1306 128x64 OLED 【一】I2C版本
03-20【freeRTOS】vTaskDelay 與 vTaskDelayUntil 的差異
03-19【freeRTOS】API功能列表
03-18【freeRTOS】Day1
03-08MBR和Blank project的差別
03-05刪除註冊檔registry的資料
02-27DFU over Bluetooth Low Energy
02-27nRF Util - 使用手冊
02-26nRF Command Line Tools
02-20建立BootLoader settings
02-19Secure DFU packet (ZIP) build 建立含袐鑰的Zip檔
02-19Secure DFU via BLE
02-19Secure DFU via UART
02-16nRF Util 安裝
01-16nRF52840 ic升級成nRF52840 Dongle的程式

2023

11-21[ SEGGER Embedded Studio ] 新增header files
11-21[ SEGGER Embedded Studio ] 編譯nRF52840時遇到的問題
11-07Arduino Nano ESP32 - Debugging除錯模式
11-03Git快速入手 - 使用Git GUI
10-30Git快速入手 - 使用Git Bash
10-12程式碼高亮顯示 -- google-code-prettify

2022

11-30[EZ-PD] CCG6DF CCG6SF的Host SDK遇到編譯錯誤(一)

2019

05-27[ Eagle PCB ] 合板成品
05-23#CASE_001_USB_TOOL_RL78_G12
05-22[ Eagle PCB ] 初次洗板
05-21[ Eagle PCB ] Panelize 併板
05-20[ Eagle PCB ] 建立自己的Library及元件
05-20[ Eagle PCB ] 添加library及元件
05-20[ Eagle PCB ] Introduce

2018

04-25[ TCP test Tool ] 好用的TCP Server/Client工具
01-16RZ/A1H -[0]- Renesas RZ/A1H YR0K77210S009BE BSP環境架設

2017

12-11EZ USB Suit使用JLink online debug FX3
10-20RL78 -[12]- CS+_CACX_Lab5_LowPower mode
10-16RL78 -[11]- CS+_CACX_Lab4_ADC_溫度感測
10-13RL78 -[10]- CS+_CACX_Lab4_ADC_內部參考電壓
10-13RL78 -[9]- CS+_Lab3_I2C + MPU6050
10-13RL78 -[8]- CS+_Lab2_Uart transmit
10-12RL78 -[7]- Renesas Flash Programmer 獨立燒錄軟體
10-12RL78 -[6]- CS+_雜記
10-12RL78 -[5]- CS+_tracking variables on debug mode
10-12RL78 -[4]- CS+_顯示ROM與RAM的使用size
10-12RL78 -[3]- CS+_Lab1_Led blinking
10-12RL78 -[2]- CS+專案建立
10-12RL78 -[1]- 開發環境介紹
10-06ESP-01 -[0]- 硬體設置
10-06LinkIt 7688 program Renesas RL78/G12 by 1-wire
10-06LinkIt Smart 7688 -[3]- Build the firmware from source codes
10-06LinkIt Smart 7688 -[2]- 使用UART進入bootloader / kernel console
10-06LinkIt Smart 7688 -[1]- 使用SSH連接kernel console
10-06LinkIt Smart 7688 -[0]- 初次使用
07-14LinkIt Smart 7688 -[9]- Using MRAA SPI in Python
07-13LinkIt Smart 7688 -[8]- Using MRAA UART in Python
07-12LinkIt Smart 7688 -[7]- Using MRAA I2C in Python
07-12LinkIt Smart 7688 -[6]- Using MRAA PWM in Python
07-12LinkIt Smart 7688 -[5]- Using MRAA GPIO in Python
07-10LinkIt Smart 7688 -[4]- 雜記
06-29輕乳酪蛋糕 Cotton Cheesecake
06-26VirtualBox 的 Ubuntu與Windows 共用資料夾

2015

04-29偵測USB PnP