From 71335dc65d53dbd1f60e4d0e08a1d154d23b7596 Mon Sep 17 00:00:00 2001 From: Nicholas Sherlock Date: Mon, 23 Nov 2015 18:47:48 +1300 Subject: [PATCH] Support enabling DMA for sdcard write at runtime --- src/main/drivers/sdcard.c | 142 +++++++++++++++++++++----------------- src/main/drivers/sdcard.h | 2 +- src/main/main.c | 16 ++++- 3 files changed, 96 insertions(+), 64 deletions(-) diff --git a/src/main/drivers/sdcard.c b/src/main/drivers/sdcard.c index 5685bb510..c39d0d246 100644 --- a/src/main/drivers/sdcard.c +++ b/src/main/drivers/sdcard.c @@ -40,11 +40,14 @@ // Chosen so that CMD8 will have the same CRC as CMD0: #define SDCARD_IF_COND_CHECK_PATTERN 0xAB +/* Break up 512-byte SD card sectors into chunks of this size when writing without DMA to reduce the peak overhead + * per call to sdcard_poll(). + */ +#define SDCARD_NON_DMA_CHUNK_SIZE 256 + #define STATIC_ASSERT(condition, name ) \ typedef char assert_failed_ ## name [(condition) ? 1 : -1 ] -#define SDCARD_USE_DMA_FOR_TX - typedef enum { // In these states we run at the initialization 400kHz clockspeed: SDCARD_STATE_NOT_PRESENT = 0, @@ -62,8 +65,8 @@ typedef enum { typedef struct sdcard_t { struct { uint8_t *buffer; - int error; uint32_t blockIndex; + uint8_t chunkIndex; sdcard_operationCompleteCallback_c callback; uint32_t callbackData; @@ -83,6 +86,13 @@ typedef struct sdcard_t { static sdcard_t sdcard; +#ifdef SDCARD_DMA_CHANNEL_TX + static bool useDMAForTx; +#else + // DMA channel not available so we can hard-code this to allow the non-DMA paths to be stripped by optimization + static const bool useDMAForTx = false; +#endif + STATIC_ASSERT(sizeof(sdcardCSD_t) == 16, sdcard_csd_bitfields_didnt_pack_properly); static void sdcard_select() @@ -299,48 +309,41 @@ static bool sdcard_sendDataBlockFinish() } /** - * Write the buffer of `count` bytes to the SD card. - * - * Returns true if the write was begun (card will enter a busy state). + * Begin sending a buffer of SDCARD_BLOCK_SIZE bytes to the SD card. */ -static bool sdcard_sendDataBlock(uint8_t *buffer, int count) +static void sdcard_sendDataBlockBegin(uint8_t *buffer) { spiTransferByte(SDCARD_SPI_INSTANCE, SDCARD_SINGLE_BLOCK_WRITE_START_TOKEN); -#ifdef SDCARD_USE_DMA_FOR_TX - // Queue the transmission of the sector payload - DMA_InitTypeDef DMA_InitStructure; + if (useDMAForTx) { + // Queue the transmission of the sector payload + DMA_InitTypeDef DMA_InitStructure; - DMA_StructInit(&DMA_InitStructure); - DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &SDCARD_SPI_INSTANCE->DR; - DMA_InitStructure.DMA_Priority = DMA_Priority_Low; - DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; - DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; - DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; + DMA_StructInit(&DMA_InitStructure); + DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) &SDCARD_SPI_INSTANCE->DR; + DMA_InitStructure.DMA_Priority = DMA_Priority_Low; + DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; + DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; + DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; - DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; - DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) buffer; - DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; + DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; + DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) buffer; + DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; - DMA_InitStructure.DMA_BufferSize = count; - DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; - DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; + DMA_InitStructure.DMA_BufferSize = SDCARD_BLOCK_SIZE; + DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; + DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; - DMA_DeInit(SDCARD_DMA_CHANNEL_TX); - DMA_Init(SDCARD_DMA_CHANNEL_TX, &DMA_InitStructure); + DMA_DeInit(SDCARD_DMA_CHANNEL_TX); + DMA_Init(SDCARD_DMA_CHANNEL_TX, &DMA_InitStructure); - DMA_Cmd(SDCARD_DMA_CHANNEL_TX, ENABLE); + DMA_Cmd(SDCARD_DMA_CHANNEL_TX, ENABLE); - SPI_I2S_DMACmd(SDCARD_SPI_INSTANCE, SPI_I2S_DMAReq_Tx, ENABLE); - - return true; -#else - // Send the sector payload now - spiTransfer(SDCARD_SPI_INSTANCE, NULL, buffer, count); - - // And check the SD card's acknowledgement - return sdcard_sendDataBlockFinish(); -#endif + SPI_I2S_DMACmd(SDCARD_SPI_INSTANCE, SPI_I2S_DMAReq_Tx, ENABLE); + } else { + // Send the first chunk now + spiTransfer(SDCARD_SPI_INSTANCE, NULL, buffer, SDCARD_NON_DMA_CHUNK_SIZE); + } } static bool sdcard_receiveCID() @@ -425,8 +428,18 @@ static bool sdcard_checkInitDone() { return status == 0x00; } -bool sdcard_init() +/** + * Begin the initialization process for the SD card. This must be called first before any other sdcard_ routine. + */ +void sdcard_init(bool useDMA) { +#ifdef SDCARD_DMA_CHANNEL_TX + useDMAForTx = useDMA; +#else + // DMA is not available + (void) useDMA; +#endif + // Max frequency is initially 400kHz spiSetDivisor(SDCARD_SPI_INSTANCE, SDCARD_SPI_INITIALIZATION_CLOCK_DIVIDER); @@ -443,8 +456,6 @@ bool sdcard_init() } sdcard.state = SDCARD_STATE_RESET; - - return true; } static bool sdcard_setBlockLength(uint32_t blockLen) @@ -464,6 +475,7 @@ static bool sdcard_setBlockLength(uint32_t blockLen) void sdcard_poll() { uint8_t initStatus; + bool sendComplete; doMore: switch (sdcard.state) { @@ -542,8 +554,10 @@ void sdcard_poll() } // else keep waiting for the CID to arrive break; case SDCARD_STATE_SENDING_WRITE: - // Has the DMA write finished yet? - if (DMA_GetFlagStatus(SDCARD_DMA_CHANNEL_TX_COMPLETE_FLAG) == SET) { + // Have we finished sending the write yet? + sendComplete = false; + + if (useDMAForTx && DMA_GetFlagStatus(SDCARD_DMA_CHANNEL_TX_COMPLETE_FLAG) == SET) { DMA_ClearFlag(SDCARD_DMA_CHANNEL_TX_COMPLETE_FLAG); DMA_Cmd(SDCARD_DMA_CHANNEL_TX, DISABLE); @@ -559,6 +573,19 @@ void sdcard_poll() SPI_I2S_DMACmd(SDCARD_SPI_INSTANCE, SPI_I2S_DMAReq_Tx, DISABLE); + sendComplete = true; + } + + if (!useDMAForTx) { + // Send another chunk + spiTransfer(SDCARD_SPI_INSTANCE, NULL, sdcard.pendingOperation.buffer + SDCARD_NON_DMA_CHUNK_SIZE * sdcard.pendingOperation.chunkIndex, SDCARD_NON_DMA_CHUNK_SIZE); + + sdcard.pendingOperation.chunkIndex++; + + sendComplete = sdcard.pendingOperation.chunkIndex == SDCARD_BLOCK_SIZE / SDCARD_NON_DMA_CHUNK_SIZE; + } + + if (sendComplete) { // Finish up by sending the CRC and checking the SD-card's acceptance/rejectance if (sdcard_sendDataBlockFinish()) { // The SD card is now busy committing that write to the card @@ -646,6 +673,9 @@ void sdcard_poll() /** * Write the 512-byte block from the given buffer into the block with the given index. * + * If the write does not complete immediately, your callback will be called later. If the write was successful, the + * buffer pointer will be the same buffer you originally passed in, otherwise the buffer will be set to NULL. + * * Returns: * SDCARD_OPERATION_IN_PROGRESS - Your buffer is currently being transmitted to the card and your callback will be * called later to report the completion. The buffer pointer must remain valid until @@ -656,12 +686,6 @@ void sdcard_poll() */ sdcardOperationStatus_e sdcard_writeBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData) { -#ifndef SDCARD_USE_DMA_FOR_TX - // When not using DMA, the writes complete before this routine returns, so we don't need the callback - (void) callback; - (void) callbackData; -#endif - if (sdcard.state != SDCARD_STATE_READY) return SDCARD_OPERATION_BUSY; @@ -673,30 +697,20 @@ sdcardOperationStatus_e sdcard_writeBlock(uint32_t blockIndex, uint8_t *buffer, // Card wants 8 dummy clock cycles after the command response to become ready spiTransferByte(SDCARD_SPI_INSTANCE, 0xFF); - if (status == 0 && sdcard_sendDataBlock(buffer, SDCARD_BLOCK_SIZE)) { -#ifdef SDCARD_USE_DMA_FOR_TX + if (status == 0) { + sdcard_sendDataBlockBegin(buffer); + sdcard.pendingOperation.buffer = buffer; sdcard.pendingOperation.blockIndex = blockIndex; sdcard.pendingOperation.callback = callback; sdcard.pendingOperation.callbackData = callbackData; + sdcard.pendingOperation.chunkIndex = 1; // (for non-DMA transfers) we've sent chunk #0 already sdcard.state = SDCARD_STATE_SENDING_WRITE; return SDCARD_OPERATION_IN_PROGRESS; -#else - /* The data has already been received by the SD card (buffer has been transmitted), we only have to wait for the - * card to commit it. Let the caller know it can free its buffer. - * - * Leave the card selected while the write is in progress. - */ - sdcard.operationStartTime = millis(); - sdcard.state = SDCARD_STATE_WRITING; - - return SDCARD_OPERATION_SUCCESS; -#endif } else { sdcard_deselect(); - // Writes shouldn't really be failing unless we gave a bad address, so reset the card sdcard_reset(); return SDCARD_OPERATION_FAILURE; @@ -706,10 +720,14 @@ sdcardOperationStatus_e sdcard_writeBlock(uint32_t blockIndex, uint8_t *buffer, /** * Read the 512-byte block with the given index into the given 512-byte buffer. * - * Returns true if the operation was successfully queued for later completion, or false if the operation could - * not be started due to the card being busy (try again later). + * When the read completes, your callback will be called. If the read was successful, the buffer pointer will be the + * same buffer you originally passed in, otherwise the buffer will be set to NULL. * * You must keep the pointer to the buffer valid until the operation completes! + * + * Returns: + * true - The operation was successfully queued for later completion, your callback will be called later + * false - The operation could not be started due to the card being busy (try again later). */ bool sdcard_readBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData) { diff --git a/src/main/drivers/sdcard.h b/src/main/drivers/sdcard.h index d20e1b8ca..05ff6ef55 100644 --- a/src/main/drivers/sdcard.h +++ b/src/main/drivers/sdcard.h @@ -258,7 +258,7 @@ typedef enum { typedef void(*sdcard_operationCompleteCallback_c)(sdcardBlockOperation_e operation, uint32_t blockIndex, uint8_t *buffer, uint32_t callbackData); -bool sdcard_init(); +void sdcard_init(bool useDMA); bool sdcard_readBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData); sdcardOperationStatus_e sdcard_writeBlock(uint32_t blockIndex, uint8_t *buffer, sdcard_operationCompleteCallback_c callback, uint32_t callbackData); diff --git a/src/main/main.c b/src/main/main.c index 431b501e1..1fc6c797d 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -538,7 +538,21 @@ void init(void) #endif #ifdef USE_SDCARD - sdcard_init(); + bool sdcardUseDMA = false; + +#ifdef SDCARD_DMA_CHANNEL_TX + +#if defined(LED_STRIP) && defined(WS2811_DMA_CHANNEL) + // Ensure the SPI Tx DMA doesn't overlap with the led strip + sdcardUseDMA = !feature(FEATURE_LED_STRIP) || SDCARD_DMA_CHANNEL_TX != WS2811_DMA_CHANNEL; +#else + sdcardUseDMA = true; +#endif + +#endif + + sdcard_init(sdcardUseDMA); + afatfs_init(); #endif