Add sd card support + savegames
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
/* f_util.c
|
||||
Copyright 2021 Carl John Kugler III
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the License); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
#include "ff.h"
|
||||
|
||||
const char *FRESULT_str(FRESULT i) {
|
||||
switch (i) {
|
||||
case FR_OK:
|
||||
return "Succeeded";
|
||||
case FR_DISK_ERR:
|
||||
return "A hard error occurred in the low level disk I/O layer";
|
||||
case FR_INT_ERR:
|
||||
return "Assertion failed";
|
||||
case FR_NOT_READY:
|
||||
return "The physical drive cannot work";
|
||||
case FR_NO_FILE:
|
||||
return "Could not find the file";
|
||||
case FR_NO_PATH:
|
||||
return "Could not find the path";
|
||||
case FR_INVALID_NAME:
|
||||
return "The path name format is invalid";
|
||||
case FR_DENIED:
|
||||
return "Access denied due to prohibited access or directory full";
|
||||
case FR_EXIST:
|
||||
return "Access denied due to prohibited access (exists)";
|
||||
case FR_INVALID_OBJECT:
|
||||
return "The file/directory object is invalid";
|
||||
case FR_WRITE_PROTECTED:
|
||||
return "The physical drive is write protected";
|
||||
case FR_INVALID_DRIVE:
|
||||
return "The logical drive number is invalid";
|
||||
case FR_NOT_ENABLED:
|
||||
return "The volume has no work area (mount)";
|
||||
case FR_NO_FILESYSTEM:
|
||||
return "There is no valid FAT volume";
|
||||
case FR_MKFS_ABORTED:
|
||||
return "The f_mkfs() aborted due to any problem";
|
||||
case FR_TIMEOUT:
|
||||
return "Could not get a grant to access the volume within defined "
|
||||
"period";
|
||||
case FR_LOCKED:
|
||||
return "The operation is rejected according to the file sharing "
|
||||
"policy";
|
||||
case FR_NOT_ENOUGH_CORE:
|
||||
return "LFN working buffer could not be allocated";
|
||||
case FR_TOO_MANY_OPEN_FILES:
|
||||
return "Number of open files > FF_FS_LOCK";
|
||||
case FR_INVALID_PARAMETER:
|
||||
return "Given parameter is invalid";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
FRESULT delete_node (
|
||||
TCHAR* path, /* Path name buffer with the sub-directory to delete */
|
||||
UINT sz_buff, /* Size of path name buffer (items) */
|
||||
FILINFO* fno /* Name read buffer */
|
||||
)
|
||||
{
|
||||
UINT i, j;
|
||||
FRESULT fr;
|
||||
DIR dir;
|
||||
|
||||
|
||||
fr = f_opendir(&dir, path); /* Open the sub-directory to make it empty */
|
||||
if (fr != FR_OK) return fr;
|
||||
|
||||
for (i = 0; path[i]; i++) ; /* Get current path length */
|
||||
path[i++] = '/';
|
||||
|
||||
for (;;) {
|
||||
fr = f_readdir(&dir, fno); /* Get a directory item */
|
||||
if (fr != FR_OK || !fno->fname[0]) break; /* End of directory? */
|
||||
j = 0;
|
||||
do { /* Make a path name */
|
||||
if (i + j >= sz_buff) { /* Buffer over flow? */
|
||||
fr = 100; break; /* Fails with 100 when buffer overflow */
|
||||
}
|
||||
path[i + j] = fno->fname[j];
|
||||
} while (fno->fname[j++]);
|
||||
if (fno->fattrib & AM_DIR) { /* Item is a sub-directory */
|
||||
fr = delete_node(path, sz_buff, fno);
|
||||
} else { /* Item is a file */
|
||||
fr = f_unlink(path);
|
||||
}
|
||||
if (fr != FR_OK) break;
|
||||
}
|
||||
|
||||
path[--i] = 0; /* Restore the path name */
|
||||
f_closedir(&dir);
|
||||
|
||||
if (fr == FR_OK) fr = f_unlink(path); /* Delete the empty sub-directory */
|
||||
return fr;
|
||||
}
|
||||
@@ -0,0 +1,463 @@
|
||||
/* ff_stdio.c
|
||||
Copyright 2021 Carl John Kugler III
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the License); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
// For compatibility with FreeRTOS+FAT API
|
||||
|
||||
#include <errno.h>
|
||||
#include <limits.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "my_debug.h"
|
||||
//
|
||||
#include "f_util.h"
|
||||
#include "ff_stdio.h"
|
||||
|
||||
#define TRACE_PRINTF(fmt, args...) {}
|
||||
//#define TRACE_PRINTF printf
|
||||
|
||||
static BYTE posix2mode(const char *pcMode) {
|
||||
if (0 == strcmp("r", pcMode)) return FA_READ;
|
||||
if (0 == strcmp("r+", pcMode)) return FA_READ | FA_WRITE;
|
||||
if (0 == strcmp("w", pcMode)) return FA_CREATE_ALWAYS | FA_WRITE;
|
||||
if (0 == strcmp("w+", pcMode)) return FA_CREATE_ALWAYS | FA_WRITE | FA_READ;
|
||||
if (0 == strcmp("a", pcMode)) return FA_OPEN_APPEND | FA_WRITE;
|
||||
if (0 == strcmp("a+", pcMode)) return FA_OPEN_APPEND | FA_WRITE | FA_READ;
|
||||
if (0 == strcmp("wx", pcMode)) return FA_CREATE_NEW | FA_WRITE;
|
||||
if (0 == strcmp("w+x", pcMode)) return FA_CREATE_NEW | FA_WRITE | FA_READ;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fresult2errno(FRESULT fr) {
|
||||
switch (fr) {
|
||||
case FR_OK:
|
||||
return 0;
|
||||
case FR_DISK_ERR:
|
||||
return EIO;
|
||||
case FR_INT_ERR:
|
||||
return EIO;
|
||||
case FR_NOT_READY:
|
||||
return EIO;
|
||||
case FR_NO_FILE:
|
||||
return ENOENT;
|
||||
case FR_NO_PATH:
|
||||
return ENOENT;
|
||||
case FR_INVALID_NAME:
|
||||
return ENAMETOOLONG;
|
||||
case FR_DENIED:
|
||||
return EACCES;
|
||||
case FR_EXIST:
|
||||
return EEXIST;
|
||||
case FR_INVALID_OBJECT:
|
||||
return EIO;
|
||||
case FR_WRITE_PROTECTED:
|
||||
return EACCES;
|
||||
case FR_INVALID_DRIVE:
|
||||
return ENOENT;
|
||||
case FR_NOT_ENABLED:
|
||||
return ENOENT;
|
||||
case FR_NO_FILESYSTEM:
|
||||
return ENOENT;
|
||||
case FR_MKFS_ABORTED:
|
||||
return EIO;
|
||||
case FR_TIMEOUT:
|
||||
return EIO;
|
||||
case FR_LOCKED:
|
||||
return EACCES;
|
||||
case FR_NOT_ENOUGH_CORE:
|
||||
return ENOMEM;
|
||||
case FR_TOO_MANY_OPEN_FILES:
|
||||
return ENFILE;
|
||||
case FR_INVALID_PARAMETER:
|
||||
return ENOSYS;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
FF_FILE *ff_fopen(const char *pcFile, const char *pcMode) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);
|
||||
// /* Open or create a file */ FRESULT f_open (
|
||||
// FIL* fp, /* [OUT] Pointer to the file object structure */
|
||||
// const TCHAR* path, /* [IN] File name */
|
||||
// BYTE mode /* [IN] Mode flags */
|
||||
//);
|
||||
FIL *fp = malloc(sizeof(FIL));
|
||||
if (!fp) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
FRESULT fr = f_open(fp, pcFile, posix2mode(pcMode));
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK != fr) {
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
free(fp);
|
||||
fp = 0;
|
||||
}
|
||||
return fp;
|
||||
}
|
||||
int ff_fclose(FF_FILE *pxStream) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_close (
|
||||
// FIL* fp /* [IN] Pointer to the file object */
|
||||
//);
|
||||
FRESULT fr = f_close(pxStream);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
free(pxStream);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
// Populates an ff_stat_struct with information about a file.
|
||||
int ff_stat(const char *pcFileName, FF_Stat_t *pxStatBuffer) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_stat (
|
||||
// const TCHAR* path, /* [IN] Object name */
|
||||
// FILINFO* fno /* [OUT] FILINFO structure */
|
||||
//);
|
||||
myASSERT(pxStatBuffer);
|
||||
FILINFO filinfo;
|
||||
FRESULT fr = f_stat(pcFileName, &filinfo);
|
||||
pxStatBuffer->st_size = filinfo.fsize;
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
size_t ff_fwrite(const void *pvBuffer, size_t xSize, size_t xItems,
|
||||
FF_FILE *pxStream) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_write (
|
||||
// FIL* fp, /* [IN] Pointer to the file object structure */
|
||||
// const void* buff, /* [IN] Pointer to the data to be written */
|
||||
// UINT btw, /* [IN] Number of bytes to write */
|
||||
// UINT* bw /* [OUT] Pointer to the variable to return number of
|
||||
// bytes written */
|
||||
//);
|
||||
UINT bw = 0;
|
||||
FRESULT fr = f_write(pxStream, pvBuffer, xSize * xItems, &bw);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
return bw / xSize;
|
||||
}
|
||||
size_t ff_fread(void *pvBuffer, size_t xSize, size_t xItems,
|
||||
FF_FILE *pxStream) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_read (
|
||||
// FIL* fp, /* [IN] File object */
|
||||
// void* buff, /* [OUT] Buffer to store read data */
|
||||
// UINT btr, /* [IN] Number of bytes to read */
|
||||
// UINT* br /* [OUT] Number of bytes read */
|
||||
//);
|
||||
UINT br = 0;
|
||||
FRESULT fr = f_read(pxStream, pvBuffer, xSize * xItems, &br);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
return br / xSize;
|
||||
}
|
||||
int ff_chdir(const char *pcDirectoryName) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_chdir (
|
||||
// const TCHAR* path /* [IN] Path name */
|
||||
//);
|
||||
FRESULT fr = f_chdir(pcDirectoryName);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
char *ff_getcwd(char *pcBuffer, size_t xBufferLength) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_getcwd (
|
||||
// TCHAR* buff, /* [OUT] Buffer to return path name */
|
||||
// UINT len /* [IN] The length of the buffer */
|
||||
//);
|
||||
char buf[xBufferLength];
|
||||
FRESULT fr = f_getcwd(buf, xBufferLength);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
// If the current working directory name was successfully written to
|
||||
// pcBuffer then pcBuffer is returned. Otherwise NULL is returned.
|
||||
if (FR_OK == fr) {
|
||||
if ('/' != buf[0]) {
|
||||
// Strip off drive prefix:
|
||||
char *p = strchr(buf, ':');
|
||||
if (p)
|
||||
++p;
|
||||
else
|
||||
p = buf;
|
||||
strncpy(pcBuffer, p, xBufferLength);
|
||||
}
|
||||
return pcBuffer;
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
int ff_mkdir(const char *pcDirectoryName) {
|
||||
TRACE_PRINTF("%s(pxStream=%s)\n", __func__, pcDirectoryName);
|
||||
FRESULT fr = f_mkdir(pcDirectoryName);
|
||||
if (FR_OK != fr && FR_EXIST != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr || FR_EXIST == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
int ff_fputc(int iChar, FF_FILE *pxStream) {
|
||||
// TRACE_PRINTF("%s(iChar=%c,pxStream=%p)\n", __func__, iChar, pxStream);
|
||||
// FRESULT f_write (
|
||||
// FIL* fp, /* [IN] Pointer to the file object structure */
|
||||
// const void* buff, /* [IN] Pointer to the data to be written */
|
||||
// UINT btw, /* [IN] Number of bytes to write */
|
||||
// UINT* bw /* [OUT] Pointer to the variable to return number of
|
||||
// bytes written */
|
||||
//);
|
||||
UINT bw = 0;
|
||||
uint8_t buff[1];
|
||||
buff[0] = iChar;
|
||||
FRESULT fr = f_write(pxStream, buff, 1, &bw);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
// On success the byte written to the file is returned. If any other value
|
||||
// is returned then the byte was not written to the file and the task's
|
||||
// errno will be set to indicate the reason.
|
||||
if (1 == bw)
|
||||
return iChar;
|
||||
else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
int ff_fgetc(FF_FILE *pxStream) {
|
||||
// TRACE_PRINTF("%s(pxStream=%p)\n", __func__, pxStream);
|
||||
// FRESULT f_read (
|
||||
// FIL* fp, /* [IN] File object */
|
||||
// void* buff, /* [OUT] Buffer to store read data */
|
||||
// UINT btr, /* [IN] Number of bytes to read */
|
||||
// UINT* br /* [OUT] Number of bytes read */
|
||||
//);
|
||||
uint8_t buff[1] = {0};
|
||||
UINT br;
|
||||
FRESULT fr = f_read(pxStream, buff, 1, &br);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
// On success the byte read from the file system is returned. If a byte
|
||||
// could not be read from the file because the read position is already at
|
||||
// the end of the file then FF_EOF is returned.
|
||||
if (1 == br)
|
||||
return buff[0];
|
||||
else
|
||||
return FF_EOF;
|
||||
}
|
||||
int ff_rmdir(const char *pcDirectory) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_unlink (
|
||||
// const TCHAR* path /* [IN] Object name */
|
||||
//);
|
||||
FRESULT fr = f_unlink(pcDirectory);
|
||||
// If the directory was removed successfully then zero is returned. If the
|
||||
// directory could not be removed then -1 is returned and the task's errno
|
||||
// is set to indicate the reason.
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
int ff_remove(const char *pcPath) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
FRESULT fr = f_unlink(pcPath);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
long ff_ftell(FF_FILE *pxStream) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FSIZE_t f_tell (
|
||||
// FIL* fp /* [IN] File object */
|
||||
//);
|
||||
FSIZE_t pos = f_tell(pxStream);
|
||||
myASSERT(pos < LONG_MAX);
|
||||
return pos;
|
||||
}
|
||||
int ff_fseek(FF_FILE *pxStream, int iOffset, int iWhence) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
FRESULT fr = -1;
|
||||
switch (iWhence) {
|
||||
case FF_SEEK_CUR: // The current file position.
|
||||
if ((int)f_tell(pxStream) + iOffset < 0) return -1;
|
||||
fr = f_lseek(pxStream, f_tell(pxStream) + iOffset);
|
||||
break;
|
||||
case FF_SEEK_END: // The end of the file.
|
||||
if ((int)f_size(pxStream) + iOffset < 0) return -1;
|
||||
fr = f_lseek(pxStream, f_size(pxStream) + iOffset);
|
||||
break;
|
||||
case FF_SEEK_SET: // The beginning of the file.
|
||||
if (iOffset < 0) return -1;
|
||||
fr = f_lseek(pxStream, iOffset);
|
||||
break;
|
||||
default:
|
||||
myASSERT(!"Bad iWhence");
|
||||
}
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
int ff_findfirst(const char *pcDirectory, FF_FindData_t *pxFindData) {
|
||||
TRACE_PRINTF("%s(%s)\n", __func__, pcDirectory);
|
||||
// FRESULT f_findfirst (
|
||||
// DIR* dp, /* [OUT] Poninter to the directory object */
|
||||
// FILINFO* fno, /* [OUT] Pointer to the file information structure
|
||||
// */ const TCHAR* path, /* [IN] Pointer to the directory name to be
|
||||
// opened */ const TCHAR* pattern /* [IN] Pointer to the matching pattern
|
||||
// string */
|
||||
//);
|
||||
char buf1[ffconfigMAX_FILENAME] = {0};
|
||||
if (pcDirectory[0]) {
|
||||
FRESULT fr = f_getcwd(buf1, sizeof buf1);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK != fr) return -1;
|
||||
fr = f_chdir(pcDirectory);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK != fr) return -1;
|
||||
}
|
||||
char buf2[ffconfigMAX_FILENAME] = {0};
|
||||
FRESULT fr = f_getcwd(buf2, sizeof buf2);
|
||||
TRACE_PRINTF("%s: f_findfirst(path=%s)\n", __func__, buf2);
|
||||
fr = f_findfirst(&pxFindData->dir, &pxFindData->fileinfo, buf2, "*");
|
||||
errno = fresult2errno(fr);
|
||||
pxFindData->pcFileName = pxFindData->fileinfo.fname;
|
||||
pxFindData->ulFileSize = pxFindData->fileinfo.fsize;
|
||||
TRACE_PRINTF("%s: fname=%s\n", __func__, pxFindData->fileinfo.fname);
|
||||
if (pcDirectory[0]) {
|
||||
FRESULT fr2 = f_chdir(buf1);
|
||||
errno = fresult2errno(fr2);
|
||||
if (FR_OK != fr2) return -1;
|
||||
}
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
int ff_findnext(FF_FindData_t *pxFindData) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_findnext (
|
||||
// DIR* dp, /* [IN] Poninter to the directory object */
|
||||
// FILINFO* fno /* [OUT] Pointer to the file information structure
|
||||
// */
|
||||
//);
|
||||
FRESULT fr = f_findnext(&pxFindData->dir, &pxFindData->fileinfo);
|
||||
errno = fresult2errno(fr);
|
||||
pxFindData->pcFileName = pxFindData->fileinfo.fname;
|
||||
pxFindData->ulFileSize = pxFindData->fileinfo.fsize;
|
||||
TRACE_PRINTF("%s: fname=%s\n", __func__, pxFindData->fileinfo.fname);
|
||||
if (FR_OK == fr && pxFindData->fileinfo.fname[0]) {
|
||||
return 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
FF_FILE *ff_truncate(const char *pcFileName, long lTruncateSize) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
FIL *fp = malloc(sizeof(FIL));
|
||||
if (!fp) {
|
||||
errno = ENOMEM;
|
||||
return NULL;
|
||||
}
|
||||
FRESULT fr = f_open(fp, pcFileName, FA_OPEN_APPEND | FA_WRITE);
|
||||
if (FR_OK != fr)
|
||||
printf("%s: f_open error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK != fr) return NULL;
|
||||
while (f_tell(fp) < (FSIZE_t)lTruncateSize) {
|
||||
UINT bw = 0;
|
||||
char c = 0;
|
||||
fr = f_write(fp, &c, 1, &bw);
|
||||
if (FR_OK != fr)
|
||||
TRACE_PRINTF("%s error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
errno = fresult2errno(fr);
|
||||
if (1 != bw) return NULL;
|
||||
}
|
||||
fr = f_lseek(fp, lTruncateSize);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK != fr)
|
||||
printf("%s: f_lseek error: %s (%d)\n", __func__, FRESULT_str(fr), fr);
|
||||
if (FR_OK != fr) return NULL;
|
||||
fr = f_truncate(fp);
|
||||
if (FR_OK != fr)
|
||||
printf("%s: f_truncate error: %s (%d)\n", __func__, FRESULT_str(fr),
|
||||
fr);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return fp;
|
||||
else
|
||||
return NULL;
|
||||
}
|
||||
int ff_seteof(FF_FILE *pxStream) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
FRESULT fr = f_truncate(pxStream);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return FF_EOF;
|
||||
}
|
||||
int ff_rename(const char *pcOldName, const char *pcNewName,
|
||||
int bDeleteIfExists) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
// FRESULT f_rename (
|
||||
// const TCHAR* old_name, /* [IN] Old object name */
|
||||
// const TCHAR* new_name /* [IN] New object name */
|
||||
//);
|
||||
// Any object with this path name except old_name must not be exist, or the
|
||||
// function fails with FR_EXIST.
|
||||
if (bDeleteIfExists) f_unlink(pcNewName);
|
||||
FRESULT fr = f_rename(pcOldName, pcNewName);
|
||||
errno = fresult2errno(fr);
|
||||
if (FR_OK == fr)
|
||||
return 0;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
char *ff_fgets(char *pcBuffer, size_t xCount, FF_FILE *pxStream) {
|
||||
TRACE_PRINTF("%s\n", __func__);
|
||||
TCHAR *p = f_gets(pcBuffer, xCount, pxStream);
|
||||
// On success a pointer to pcBuffer is returned. If there is a read error
|
||||
// then NULL is returned and the task's errno is set to indicate the reason.
|
||||
if (p == pcBuffer)
|
||||
return pcBuffer;
|
||||
else {
|
||||
errno = EIO;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
/* glue.c
|
||||
Copyright 2021 Carl John Kugler III
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the License); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Low level disk I/O module SKELETON for FatFs (C)ChaN, 2019 */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* If a working storage control module is available, it should be */
|
||||
/* attached to the FatFs via a glue function rather than modifying it. */
|
||||
/* This is an example of glue functions to attach various exsisting */
|
||||
/* storage control modules to the FatFs module with a defined API. */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
#include <stdio.h>
|
||||
//
|
||||
#include "ff.h" /* Obtains integer types */
|
||||
//
|
||||
#include "diskio.h" /* Declarations of disk functions */
|
||||
//
|
||||
#include "hw_config.h"
|
||||
#include "my_debug.h"
|
||||
#include "sd_card.h"
|
||||
|
||||
#define TRACE_PRINTF(fmt, args...)
|
||||
//#define TRACE_PRINTF printf // task_printf
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Get Drive Status */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DSTATUS disk_status(BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
|
||||
sd_card_t *p_sd = sd_get_by_num(pdrv);
|
||||
if (!p_sd) return RES_PARERR;
|
||||
sd_card_detect(p_sd); // Fast: just a GPIO read
|
||||
return p_sd->m_Status; // See http://elm-chan.org/fsw/ff/doc/dstat.html
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Inidialize a Drive */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DSTATUS disk_initialize(
|
||||
BYTE pdrv /* Physical drive nmuber to identify the drive */
|
||||
) {
|
||||
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
|
||||
sd_card_t *p_sd = sd_get_by_num(pdrv);
|
||||
if (!p_sd) return RES_PARERR;
|
||||
return sd_init(p_sd); // See http://elm-chan.org/fsw/ff/doc/dstat.html
|
||||
}
|
||||
|
||||
static int sdrc2dresult(int sd_rc) {
|
||||
switch (sd_rc) {
|
||||
case SD_BLOCK_DEVICE_ERROR_NONE:
|
||||
return RES_OK;
|
||||
case SD_BLOCK_DEVICE_ERROR_UNUSABLE:
|
||||
case SD_BLOCK_DEVICE_ERROR_NO_RESPONSE:
|
||||
case SD_BLOCK_DEVICE_ERROR_NO_INIT:
|
||||
case SD_BLOCK_DEVICE_ERROR_NO_DEVICE:
|
||||
return RES_NOTRDY;
|
||||
case SD_BLOCK_DEVICE_ERROR_PARAMETER:
|
||||
case SD_BLOCK_DEVICE_ERROR_UNSUPPORTED:
|
||||
return RES_PARERR;
|
||||
case SD_BLOCK_DEVICE_ERROR_WRITE_PROTECTED:
|
||||
return RES_WRPRT;
|
||||
case SD_BLOCK_DEVICE_ERROR_CRC:
|
||||
case SD_BLOCK_DEVICE_ERROR_WOULD_BLOCK:
|
||||
case SD_BLOCK_DEVICE_ERROR_ERASE:
|
||||
case SD_BLOCK_DEVICE_ERROR_WRITE:
|
||||
default:
|
||||
return RES_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Read Sector(s) */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DRESULT disk_read(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
BYTE *buff, /* Data buffer to store read data */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to read */
|
||||
) {
|
||||
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
|
||||
sd_card_t *p_sd = sd_get_by_num(pdrv);
|
||||
if (!p_sd) return RES_PARERR;
|
||||
int rc = sd_read_blocks(p_sd, buff, sector, count);
|
||||
return sdrc2dresult(rc);
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Write Sector(s) */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
#if FF_FS_READONLY == 0
|
||||
|
||||
DRESULT disk_write(BYTE pdrv, /* Physical drive nmuber to identify the drive */
|
||||
const BYTE *buff, /* Data to be written */
|
||||
LBA_t sector, /* Start sector in LBA */
|
||||
UINT count /* Number of sectors to write */
|
||||
) {
|
||||
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
|
||||
sd_card_t *p_sd = sd_get_by_num(pdrv);
|
||||
if (!p_sd) return RES_PARERR;
|
||||
int rc = sd_write_blocks(p_sd, buff, sector, count);
|
||||
return sdrc2dresult(rc);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*-----------------------------------------------------------------------*/
|
||||
/* Miscellaneous Functions */
|
||||
/*-----------------------------------------------------------------------*/
|
||||
|
||||
DRESULT disk_ioctl(BYTE pdrv, /* Physical drive nmuber (0..) */
|
||||
BYTE cmd, /* Control code */
|
||||
void *buff /* Buffer to send/receive control data */
|
||||
) {
|
||||
TRACE_PRINTF(">>> %s\n", __FUNCTION__);
|
||||
sd_card_t *p_sd = sd_get_by_num(pdrv);
|
||||
if (!p_sd) return RES_PARERR;
|
||||
switch (cmd) {
|
||||
case GET_SECTOR_COUNT: { // Retrieves number of available sectors, the
|
||||
// largest allowable LBA + 1, on the drive
|
||||
// into the LBA_t variable pointed by buff.
|
||||
// This command is used by f_mkfs and f_fdisk
|
||||
// function to determine the size of
|
||||
// volume/partition to be created. It is
|
||||
// required when FF_USE_MKFS == 1.
|
||||
static LBA_t n;
|
||||
n = sd_sectors(p_sd);
|
||||
*(LBA_t *)buff = n;
|
||||
if (!n) return RES_ERROR;
|
||||
return RES_OK;
|
||||
}
|
||||
case GET_BLOCK_SIZE: { // Retrieves erase block size of the flash
|
||||
// memory media in unit of sector into the DWORD
|
||||
// variable pointed by buff. The allowable value
|
||||
// is 1 to 32768 in power of 2. Return 1 if the
|
||||
// erase block size is unknown or non flash
|
||||
// memory media. This command is used by only
|
||||
// f_mkfs function and it attempts to align data
|
||||
// area on the erase block boundary. It is
|
||||
// required when FF_USE_MKFS == 1.
|
||||
static DWORD bs = 1;
|
||||
*(DWORD *)buff = bs;
|
||||
return RES_OK;
|
||||
}
|
||||
case CTRL_SYNC:
|
||||
return RES_OK;
|
||||
default:
|
||||
return RES_PARERR;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
/* my_debug.c
|
||||
Copyright 2021 Carl John Kugler III
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the License); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include "my_debug.h"
|
||||
|
||||
void my_printf(const char *pcFormat, ...) {
|
||||
char pcBuffer[256] = {0};
|
||||
va_list xArgs;
|
||||
va_start(xArgs, pcFormat);
|
||||
vsnprintf(pcBuffer, sizeof(pcBuffer), pcFormat, xArgs);
|
||||
va_end(xArgs);
|
||||
printf("%s", pcBuffer);
|
||||
fflush(stdout);
|
||||
}
|
||||
|
||||
|
||||
void my_assert_func(const char *file, int line, const char *func,
|
||||
const char *pred) {
|
||||
printf("assertion \"%s\" failed: file \"%s\", line %d, function: %s\n",
|
||||
pred, file, line, func);
|
||||
fflush(stdout);
|
||||
__asm volatile("cpsid i" : : : "memory"); /* Disable global interrupts. */
|
||||
while (1) {
|
||||
__asm("bkpt #0");
|
||||
}; // Stop in GUI as if at a breakpoint (if debugging, otherwise loop
|
||||
// forever)
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
/* rtc.c
|
||||
Copyright 2021 Carl John Kugler III
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the License); you may not use
|
||||
this file except in compliance with the License. You may obtain a copy of the
|
||||
License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
Unless required by applicable law or agreed to in writing, software distributed
|
||||
under the License is distributed on an AS IS BASIS, WITHOUT WARRANTIES OR
|
||||
CONDITIONS OF ANY KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations under the License.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <time.h>
|
||||
//
|
||||
#include "hardware/rtc.h"
|
||||
#include "pico/stdio.h"
|
||||
#include "pico/stdlib.h"
|
||||
#include "pico/util/datetime.h"
|
||||
//
|
||||
#include "ff.h"
|
||||
#include "util.h" // calculate_checksum
|
||||
//
|
||||
#include "rtc.h"
|
||||
|
||||
static time_t epochtime;
|
||||
|
||||
// Make an attempt to save a recent time stamp across reset:
|
||||
typedef struct rtc_save {
|
||||
uint32_t signature;
|
||||
datetime_t datetime;
|
||||
uint32_t checksum; // last, not included in checksum
|
||||
} rtc_save_t;
|
||||
static rtc_save_t rtc_save __attribute__((section(".uninitialized_data")));
|
||||
|
||||
static void update_epochtime() {
|
||||
bool rc = rtc_get_datetime(&rtc_save.datetime);
|
||||
if (rc) {
|
||||
rtc_save.signature = 0xBABEBABE;
|
||||
struct tm timeinfo = {
|
||||
.tm_sec = rtc_save.datetime
|
||||
.sec, /* Seconds. [0-60] (1 leap second) */
|
||||
.tm_min = rtc_save.datetime.min, /* Minutes. [0-59] */
|
||||
.tm_hour = rtc_save.datetime.hour, /* Hours. [0-23] */
|
||||
.tm_mday = rtc_save.datetime.day, /* Day. [1-31] */
|
||||
.tm_mon = rtc_save.datetime.month - 1, /* Month. [0-11] */
|
||||
.tm_year = rtc_save.datetime.year - 1900, /* Year - 1900. */
|
||||
.tm_wday = 0, /* Day of week. [0-6] */
|
||||
.tm_yday = 0, /* Days in year.[0-365] */
|
||||
.tm_isdst = -1 /* DST. [-1/0/1]*/
|
||||
};
|
||||
rtc_save.checksum = calculate_checksum((uint32_t *)&rtc_save,
|
||||
offsetof(rtc_save_t, checksum));
|
||||
epochtime = mktime(&timeinfo);
|
||||
rtc_save.datetime.dotw = timeinfo.tm_wday;
|
||||
// configASSERT(-1 != epochtime);
|
||||
}
|
||||
}
|
||||
|
||||
time_t time(time_t *pxTime) {
|
||||
update_epochtime();
|
||||
if (pxTime) {
|
||||
*pxTime = epochtime;
|
||||
}
|
||||
return epochtime;
|
||||
}
|
||||
|
||||
void time_init() {
|
||||
rtc_init();
|
||||
datetime_t t = {0, 0, 0, 0, 0, 0, 0};
|
||||
rtc_get_datetime(&t);
|
||||
if (!t.year && rtc_save.datetime.year) {
|
||||
uint32_t xor_checksum = calculate_checksum(
|
||||
(uint32_t *)&rtc_save, offsetof(rtc_save_t, checksum));
|
||||
if (rtc_save.signature == 0xBABEBABE &&
|
||||
rtc_save.checksum == xor_checksum) {
|
||||
// Set rtc
|
||||
rtc_set_datetime(&rtc_save.datetime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Called by FatFs:
|
||||
DWORD get_fattime(void) {
|
||||
datetime_t t = {0, 0, 0, 0, 0, 0, 0};
|
||||
bool rc = rtc_get_datetime(&t);
|
||||
if (!rc) return 0;
|
||||
|
||||
DWORD fattime = 0;
|
||||
// bit31:25
|
||||
// Year origin from the 1980 (0..127, e.g. 37 for 2017)
|
||||
uint8_t yr = t.year - 1980;
|
||||
fattime |= (0b01111111 & yr) << 25;
|
||||
// bit24:21
|
||||
// Month (1..12)
|
||||
uint8_t mo = t.month;
|
||||
fattime |= (0b00001111 & mo) << 21;
|
||||
// bit20:16
|
||||
// Day of the month (1..31)
|
||||
uint8_t da = t.day;
|
||||
fattime |= (0b00011111 & da) << 16;
|
||||
// bit15:11
|
||||
// Hour (0..23)
|
||||
uint8_t hr = t.hour;
|
||||
fattime |= (0b00011111 & hr) << 11;
|
||||
// bit10:5
|
||||
// Minute (0..59)
|
||||
uint8_t mi = t.min;
|
||||
fattime |= (0b00111111 & mi) << 5;
|
||||
// bit4:0
|
||||
// Second / 2 (0..29, e.g. 25 for 50)
|
||||
uint8_t sd = t.sec / 2;
|
||||
fattime |= (0b00011111 & sd);
|
||||
return fattime;
|
||||
}
|
||||
Reference in New Issue
Block a user