I think there are multiple things going on in my project, and probably in a couple of files. I did some very careful debugging. Compiled it all with -O0, formatted the disk, and placed a file on it, and verified it. It has a CRC on the end. Then from the inside (via FatFS) read it to check the CRC. All good. For those who like to see code, this is the CRC func
/*
*
* CRC-32/JAMCRC. This is a "rolling" algorithm. Invert the *final* result for ISO-HDLC.
* Returns 0x098494f3 from "123456789" (9 bytes). Inverting this (at the end) gives 0xcbf43926.
* Polynomial is 0x04c11db7 and it holds it backwards (0xEDB88320).
* This version accepts one byte at a time, and maintains the CRC in crcvalue. This makes it suitable
* for calculating a CRC across a number of data blocks.
* Speed is approx 400kbytes/sec.
* crcvalue must be initialised to 0xffffffff by caller, and holds the accumulated CRC as you go along
* See e.g. [url]https://crccalc.com/[/url] [url]https://www.lammertbies.nl/comm/info/crc-calculation[/url]
* [url]https://reveng.sourceforge.io/crc-catalogue/17plus.htm#crc.cat-bits.32[/url]
*
*/
void crc32(uint8_t input_byte, uint32_t *crcvalue)
{
for (uint32_t j=0; j<8; j++)
{
uint32_t mask = (input_byte^*crcvalue) & 1;
*crcvalue>>=1;
if(mask)
*crcvalue=*crcvalue^0xEDB88320;
input_byte>>=1;
}
}
/*
*
* Check CRC, stored in last 4 bytes of a file.
* For speed, reads into a 512 byte buffer.
* This code is tricky, due to CRC potentially split across buffer boundaries.
* crcinit is normally initialised to 0xffffffff by the caller.
* No upper limit on file size. Minimum size = 5 bytes.
* Returns true if CRC is good, false otherwise (including if file doesn't exist)
* Exec time for 1MB: 15s.
*
*/
bool filecrc(char * filename, uint32_t crcinit)
{
FILINFO fno;
uint32_t crc=crcinit;
uint32_t filecrc=0;
uint32_t offset=0;
int32_t bytesleft;
uint8_t pagebuf[512];
uint32_t numread=0;
if ( KDE_get_file_properties ( filename, &fno ) == false ) return (false);
bytesleft=fno.fsize;
if ( bytesleft<5 ) return (false);
do
{
// Read up to 512 bytes into pagebuf
if ( KDE_file_read( filename, offset, 512, pagebuf, &numread) == false )
return (false);
offset+=512;
bytesleft-=numread;
if ((numread==512) && (bytesleft>=4))
// Most common case: 512 bytes read, not the last page, and no CRC bytes yet
{
for (int i=0; i<512; i++)
{
crc32(pagebuf[i], &crc);
}
}
else
{
if ((numread<=512) && (bytesleft==0))
// Last page, and contains entire CRC
{
for (int i=0; i<(numread-4); i++)
{
crc32(pagebuf[i], &crc);
}
filecrc=pagebuf[numread-4]|(pagebuf[numread-3]<<8)|(pagebuf[numread-2]<<16)|(pagebuf[numread-1]<<24);
}
else
{
// CRC is split (buffer contains only some of it). Calc CRC over all data in buffer
for (int i=0; i<(512+bytesleft-4); i++)
{
crc32(pagebuf[i], &crc);
}
// read just CRC bytes
if ( KDE_file_read( filename, offset+bytesleft-4, 512, pagebuf, &numread) == false )
return (false);
filecrc=pagebuf[0]|(pagebuf[1]<<8)|(pagebuf[2]<<16)|(pagebuf[3]<<24);
bytesleft=0; // ensure end of loop
}
}
}
while ( bytesleft > 0 );
crc=~crc; // invert JAMCRC to HDLC CRC which the file has on the end
if ( crc==filecrc ) return (true);
return (false);
}
Then I compiled the whole thing with -Og. The CRC calculation fails. There is nothing obviously wrong with the data, and ideally I could spend time repeating this with a small file, say 30 bytes, to narrow it down (the test file is 1MB). So definitely one problem here.
But even before the CRC fails, if FatFS is also compiled with -Og, FatFS cannot find the file on the disk! So I stepped through the code, and narrowed it down to somewhere deep in FatFS returning false - around here
/*-----------------------------------------------------------------------*/
/* Directory handling - Find an object in the directory */
/*-----------------------------------------------------------------------*/
static
FRESULT dir_find ( /* FR_OK(0):succeeded, !=0:error */
DIR* dp /* Pointer to the directory object with the file name */
)
{
FRESULT res;
FATFS *fs = dp->obj.fs;
BYTE c;
#if _USE_LFN != 0
BYTE a, ord, sum;
#endif
res = dir_sdi(dp, 0); /* Rewind directory object */
if (res != FR_OK) return res;
#if _FS_EXFAT
if (fs->fs_type == FS_EXFAT) { /* On the exFAT volume */
BYTE nc;
UINT di, ni;
WORD hash = xname_sum(fs->lfnbuf); /* Hash value of the name to find */
while ((res = dir_read(dp, 0)) == FR_OK) { /* Read an item */
#if _MAX_LFN < 255
if (fs->dirbuf[XDIR_NumName] > _MAX_LFN) continue; /* Skip comparison if inaccessible object name */
#endif
if (ld_word(fs->dirbuf + XDIR_NameHash) != hash) continue; /* Skip comparison if hash mismatched */
for (nc = fs->dirbuf[XDIR_NumName], di = SZDIRE * 2, ni = 0; nc; nc--, di += 2, ni++) { /* Compare the name */
if ((di % SZDIRE) == 0) di += 2;
if (ff_wtoupper(ld_word(fs->dirbuf + di)) != ff_wtoupper(fs->lfnbuf[ni])) break;
}
if (nc == 0 && !fs->lfnbuf[ni]) break; /* Name matched? */
}
return res;
}
#endif
/* On the FAT12/16/32 volume */
#if _USE_LFN != 0
ord = sum = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
#endif
do {
res = move_window(fs, dp->sect);
if (res != FR_OK) break;
c = dp->dir[DIR_Name];
if (c == 0) { res = FR_NO_FILE; break; } /* Reached to end of table */
#if _USE_LFN != 0 /* LFN configuration */
dp->obj.attr = a = dp->dir[DIR_Attr] & AM_MASK;
if (c == DDEM || ((a & AM_VOL) && a != AM_LFN)) { /* An entry without valid data */
ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
} else {
if (a == AM_LFN) { /* An LFN entry is found */
if (!(dp->fn[NSFLAG] & NS_NOLFN)) {
if (c & LLEF) { /* Is it start of LFN sequence? */
sum = dp->dir[LDIR_Chksum];
c &= (BYTE)~LLEF; ord = c; /* LFN start order */
dp->blk_ofs = dp->dptr; /* Start offset of LFN */
}
/* Check validity of the LFN entry and compare it with given name */
ord = (c == ord && sum == dp->dir[LDIR_Chksum] && cmp_lfn(fs->lfnbuf, dp->dir)) ? ord - 1 : 0xFF;
}
} else { /* An SFN entry is found */
if (!ord && sum == sum_sfn(dp->dir)) break; /* LFN matched? */
if (!(dp->fn[NSFLAG] & NS_LOSS) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* SFN matched? */
ord = 0xFF; dp->blk_ofs = 0xFFFFFFFF; /* Reset LFN sequence */
}
}
#else /* Non LFN configuration */
dp->obj.attr = dp->dir[DIR_Attr] & AM_MASK;
if (!(dp->dir[DIR_Attr] & AM_VOL) && !mem_cmp(dp->dir, dp->fn, 11)) break; /* Is it a valid entry? */
#endif
res = dir_next(dp, 0); /* Next entry */
} while (res == FR_OK);
return res;
}
Unfortunately with -Og half the variables are "optimised out" so difficult to work out what is what.
The "unable to format" from Windows is a different issue however. This is nothing to do with FatFS. It is probably low down in the ST stuff like this
HAL_StatusTypeDef B_HAL_SPI_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size, uint32_t Timeout)
{
// uint32_t tickstart;
HAL_StatusTypeDef errorcode = HAL_OK;
uint16_t initial_TxXferCount;
initial_TxXferCount = Size;
if (hspi->State != HAL_SPI_STATE_READY)
{
errorcode = HAL_BUSY;
goto error;
}
if ((pData == NULL) || (Size == 0U))
{
errorcode = HAL_ERROR;
goto error;
}
/* Set the transaction information */
hspi->State = HAL_SPI_STATE_BUSY_TX;
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->pTxBuffPtr = (uint8_t *)pData;
hspi->TxXferSize = Size;
hspi->TxXferCount = Size;
/*Init field not used in handle to zero */
hspi->pRxBuffPtr = (uint8_t *)NULL;
hspi->RxXferSize = 0U;
hspi->RxXferCount = 0U;
hspi->TxISR = NULL;
hspi->RxISR = NULL;
/* Configure communication direction : 1Line */
if (hspi->Init.Direction == SPI_DIRECTION_1LINE)
{
SPI_1LINE_TX(hspi);
}
/* Check if the SPI is already enabled */
if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
{
/* Enable SPI peripheral */
__HAL_SPI_ENABLE(hspi);
}
/* Transmit data in 8 Bit mode */
if ((hspi->Init.Mode == SPI_MODE_SLAVE) || (initial_TxXferCount == 0x01U))
{
*((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);
hspi->pTxBuffPtr += sizeof(uint8_t);
hspi->TxXferCount--;
}
while (hspi->TxXferCount > 0U)
{
/* Wait until TXE flag is set to send data */
if (__HAL_SPI_GET_FLAG(hspi, SPI_FLAG_TXE))
{
*((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);
hspi->pTxBuffPtr += sizeof(uint8_t);
hspi->TxXferCount--;
}
}
/* Clear overrun flag in 2 Lines communication mode because received is not read */
if (hspi->Init.Direction == SPI_DIRECTION_2LINES)
{
__HAL_SPI_CLEAR_OVRFLAG(hspi);
}
if (hspi->ErrorCode != HAL_SPI_ERROR_NONE)
{
errorcode = HAL_ERROR;
}
error:
hspi->State = HAL_SPI_STATE_READY;
return errorcode;
}
but there is something interesting in there which may be a clue, but which is above my pay grade to understand the code. It is this
*((__IO uint8_t *)&hspi->Instance->DR) = (*hspi->pTxBuffPtr);
__IO is #defined as volatile, but DR itself also is in the ST .h files. They appear to be creating a pointer to the hspi structure, whose member DR is the SPI data register. Yet the vast majority of references to hspi is not "volatile" qualified, and I don't know why it should be. OTOH the compiler sees hspi whole and may decide to prune unreferenced members (of which there are plenty; ST love a structure for absolutely everything). It could then re-pack it, but that should still work, eh?
Funnily enough FatFS has its own private memcpy. They call it mem_cpy. And others. Maybe they know something:
/*-----------------------------------------------------------------------*/
/* String functions */
/*-----------------------------------------------------------------------*/
/* Copy memory to memory */
static
void mem_cpy (void* dst, const void* src, UINT cnt) {
BYTE *d = (BYTE*)dst;
const BYTE *s = (const BYTE*)src;
if (cnt) {
do {
*d++ = *s++;
} while (--cnt);
}
}
/* Fill memory block */
static
void mem_set (void* dst, int val, UINT cnt) {
BYTE *d = (BYTE*)dst;
do {
*d++ = (BYTE)val;
} while (--cnt);
}
/* Compare memory block */
static
int mem_cmp (const void* dst, const void* src, UINT cnt) { /* ZR:same, NZ:different */
const BYTE *d = (const BYTE *)dst, *s = (const BYTE *)src;
int r = 0;
do {
r = *d++ - *s++;
} while (--cnt && r == 0);
return r;
}
/* Check if chr is contained in the string */
static
int chk_chr (const char* str, int chr) { /* NZ:contained, ZR:not contained */
while (*str && *str != chr) str++;
return *str;
}