2025-01-22 01:56 AM
Currently using STM32L4R9I-Discovery.
I want to pass an entire buffer to a sd card using idma sdmmc double buffer mode.
The main goal is to pass a large amount of data into a small buffer divided in sub-buffers (in a circular way) to save memory.
In my case, I start with a small buffer just to try the sdmmc idma.
My code fills a buffer with dummy values.
I define 16 sectors of 4800 bytes (length of each IDMA buffers) (16*4800=76800).
I launch the sd write for a 76800-byte long (multiple of 512 so no zero-padding needed).
When the current idma buffer is filled, the HW switches address buffers (IDMABASE0 to 1 or IDMABASE1 to 0).
So I increment the inactive IDMABASEx by 2*IDMA_BUF_SIZE_BYTE so the whole buffer is passed through.
SDMMC is clocked at 120MHz/5=24MHz (but it shouldn't be relevant in this case study).
Here is my code:
#include "main.h"
#define BUF_LEN_BYTE (320*240) // 0x12c00
#define BLOCK_SIZE 512 // in byte
#define IDMABNDT_VAL 150 // see SDMMC_IDMABNDT
#define IDMA_BUF_SIZE_BYTE (IDMABNDT_VAL * 32) // 4800, see SDMMC_IDMABSIZER explanation
#define N_ITE_IDMA_BUF 16
/*
note: BUF_LEN_BYTE = IDMA_BUF_SIZE_BYTE * N_ITE_IDMA_BUF
320*240 = 16*4800 (= 150*32*16 = 150*512) = 76800
BUF_LEN_BYTE can be entirely restitued by writing 150 single blocks of 512 bytes, or by writing 16 buffers of 4800 bytes (using idma double buffer mode)
*/
// ===================== stm32l4xx_it.c =====================
SD_HandleTypeDef hsd1;
uint8_t buf[BUF_LEN_BYTE];
uint8_t write_complete = 0; // complete flag set with DATAEND from SDMMC_STAR in SDMMC1_IRQHandler
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_SDMMC1_SD_Init(void);
int main(void) {
for(uint8_t i = 0; i < N_ITE_IDMA_BUF; i++)
for(uint32_t j = 0; j < IDMA_BUF_SIZE_BYTE; j++)
buf[i*IDMA_BUF_SIZE_BYTE+j] = ((i&1)?0xB0:0xA0) | i; // 0xA for IDMA buffer 0 and 0xB for IDMA buffer 1 to see if idma switch is working
// to differenciate the different sectors (IDMA_BUF_SIZE_BYTE = 4800) with "i" values
// and see if the data are written in the right order
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_SDMMC1_SD_Init();
if(HAL_SD_Erase(&hsd1, 0, 64*1024*1024/BLOCK_SIZE) != HAL_OK) // reset whole SD memory (64 GB)
Error_Handler();
HAL_Delay(10); // wait a bit to avoid being stuck in IRQ_Handler
SDMMC1->IDMABASE0 = (uint32_t) &buf[0]; // src addr for IDMA
SDMMC1->IDMABASE1 = (uint32_t) &buf[0] + IDMA_BUF_SIZE_BYTE; //incremented by a "sector length of 4800"
SDMMC1->IDMABSIZE = ((SDMMC1->IDMABSIZE & 0xffffe01f) | // reserved
(IDMABNDT_VAL << 5)); // 150 << 5
if(HAL_SDEx_WriteBlocksDMAMultiBuffer(&hsd1, 0, N_ITE_IDMA_BUF * IDMA_BUF_SIZE_BYTE) != HAL_OK)
Error_Handler();
while(!write_complete);
}
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
if (HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1_BOOST) != HAL_OK) {
Error_Handler();
}
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
RCC_OscInitStruct.MSIState = RCC_MSI_ON;
RCC_OscInitStruct.MSICalibrationValue = 0;
RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_MSI;
RCC_OscInitStruct.PLL.PLLM = 1;
RCC_OscInitStruct.PLL.PLLN = 60;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV5;
RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
Error_Handler();
}
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) {
Error_Handler();
}
}
static void MX_SDMMC1_SD_Init(void) {
hsd1.Instance = SDMMC1;
hsd1.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING;
hsd1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE;
hsd1.Init.BusWide = SDMMC_BUS_WIDE_4B;
hsd1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;
hsd1.Init.ClockDiv = 5;
hsd1.Init.Transceiver = SDMMC_TRANSCEIVER_DISABLE;
if (HAL_SD_Init(&hsd1) != HAL_OK) {
Error_Handler();
}
}
static void MX_GPIO_Init(void) {
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
}
void HAL_SDEx_Write_DMADoubleBuffer0CpltCallback(SD_HandleTypeDef *hsd) {
hsd->Instance->IDMABASE0 += 2*IDMA_BUF_SIZE_BYTE;
}
void HAL_SDEx_Write_DMADoubleBuffer1CpltCallback(SD_HandleTypeDef *hsd) {
hsd->Instance->IDMABASE1 += 2*IDMA_BUF_SIZE_BYTE;
}
// ===================== stm32l4xx_it.c =====================
extern SD_HandleTypeDef hsd1;
void SDMMC1_IRQHandler(void) {
if((hsd1.Instance->STA & (1 << 8)) == (1 << 8)) { // transfer complete
__HAL_SD_CLEAR_FLAG(&hsd1, SDMMC_FLAG_DATAEND);
write_complete = 1;
}
}
The IDMABASEx increment works: all sectors are the same length (4800 bytes), in the right order (see file attached).
The complete flag is set at the right time when reaching the last block (76800 bytes).
However, the sd card isn't filled up to the 0x12c00 address, but up to 0xc00 (48KB) or 0x10000 (64KB), the rest are 00s as the card is previously cleared.
The stm32l4r9-discovery has a builtin sd port so the problem isn't from a wiring issue nor a sd card issue (I've tested plenty of them).
The "classical" DMA is limited to 65536 for a single transfer, but I haven't found any limit for IDMA single length transfer.
I even found example on AN5200 (stm32h7 though) as follows "The next figure illustrates an IDMA Double-buffer mode example of writing 25 Mo of data in one transaction with IDMA Double-buffer mode. The full application example is available in STM32Cube_FW_H7_V1.2.0".
Also, when I artificially increase the number of blocks to write in HAL_SDEx_WriteBlocksDMAMultiBuffer, my sd card is entirely and rightfully filled without any premature stop (a "buf" segmentation fault appears which is logical).
I tried using registers only before switching to HAL/LL functions and the result are the same, which is quite reassuring for in a sense.
As the dataend is properly called, I believe the IDMA stops transfering data and the sd card only write empty values 00s until SDMMC_DCNTR reaches 0.
Either it is a dumb error of mine, otherwise I don't feel like having much room for correction.
Did anyone go through a similar situation and know how to fix it ?
This is my first post, please let me know if some information is missing/unclear.