STM32高频写入MicroSD卡出现FR_DISK_ERR问题求助
高频SD卡写入问题:STM32三缓冲区方案FR_DISK_ERR崩溃与优化
问题背景
- 基于MicroPeta指南实现STM32向MicroSD卡写入TXT文件,当前可正常运行,目标实现10kHz写入频率,用于CAN网络传感器数据的边缘场景测试,通过TXT保存百微秒级时间戳做可视化调试。
- 初始采用双乒乓缓冲区+定时器中断方案,成功生成1分钟、8MB的记录文件,但存在少量丢数,推测是缓冲区切换时SD卡写入阻塞导致。
- 尝试添加第三个缓冲区解决丢数问题,却出现随机
FR_DISK_ERR错误导致程序崩溃。
核心代码(CubeIDE开发)
/* Includes ------------------------------------------------------------------*/ #include "main.h" #include "fatfs.h" /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include <string.h> /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ /* USER CODE BEGIN PTD */ typedef struct tim{ uint8_t day;//1-31 uint8_t month;//1-12 uint8_t year;//since 1900 uint8_t hour;//0-23 uint8_t minute;//0-59 uint8_t second;//0-59 uint16_t millis;//0-999 uint8_t micro100;//0-9 }Datetime; /* USER CODE END PTD */ /* Private define ------------------------------------------------------------*/ /* USER CODE BEGIN PD */ #define BUF_SIZE 2048 /* USER CODE END PD */ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ SPI_HandleTypeDef hspi1; DMA_HandleTypeDef hdma_spi1_rx; DMA_HandleTypeDef hdma_spi1_tx; TIM_HandleTypeDef htim2; /* USER CODE BEGIN PV */ //SD FATFS fs; FIL fil; FRESULT res; UINT bw; //Timer char time[15]; Datetime now; uint32_t lastSyncTime; //Write Control uint8_t writing = 0; uint16_t sync_counter = 0; uint8_t arrayA[BUF_SIZE], arrayB[BUF_SIZE], arrayC[BUF_SIZE]; uint8_t* bufferPool[3] = {arrayA, arrayB, arrayC}; uint8_t* currentArray = arrayA; volatile uint8_t fillIdx = 0; volatile uint8_t saveIdx = 0; volatile uint8_t buffersReady = 0; uint16_t array_index = 0; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_DMA_Init(void); static void MX_SPI1_Init(void); static void MX_TIM2_Init(void); /* USER CODE BEGIN PFP */ uint8_t TimToStr(char* iso8601, Datetime time); void TimInit(); /* USER CODE END PFP */ /* Private user code ---------------------------------------------------------*/ /* USER CODE BEGIN 0 */ /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration--------------------------------------------------------*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); MX_SPI1_Init(); MX_FATFS_Init(); MX_TIM2_Init(); /* USER CODE BEGIN 2 */ TimInit(); HAL_Delay(500); res = f_mount(&fs, "", 1); res = f_open(&fil, "timer_test10.txt", FA_OPEN_ALWAYS | FA_WRITE | FA_READ); res = f_lseek(&fil, fil.fsize); f_puts("HEADER: Same test as 07, but now we use a 2048 buffer size.\n", &fil); HAL_TIM_Base_Start_IT(&htim2); /* USER CODE END 2 */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ if(writing){ if (HAL_SPI_GetState(&hspi1) == HAL_SPI_STATE_READY) { writing = 0; } } if(buffersReady>0 && !writing){ res = f_write(&fil, bufferPool[saveIdx], BUF_SIZE, &bw); if(res!= FR_OK){ break; } saveIdx = (saveIdx + 1) % 3; buffersReady--; } if (HAL_GetTick() - lastSyncTime > 5000 && !writing) { // sync every 5 seconds f_sync(&fil); lastSyncTime = HAL_GetTick(); } } /* USER CODE END 3 */ } /* USER CODE BEGIN 4 */ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if(htim->Instance == TIM2){ //10kHz now.micro100++; if(now.micro100>9){ now.millis++; now.micro100 = 0; } if(now.millis>999){ now.second++; now.millis = 0; } if(now.second>59){ now.minute++; now.second = 0; } if(now.minute>59){ now.hour++; now.minute=0; } uint8_t len = TimToStr(time, now); if(array_index+len >= BUF_SIZE){ fillIdx = (fillIdx + 1) % 3; // Move to next buffer in the circle currentArray = bufferPool[fillIdx]; array_index = 0; buffersReady++; } memcpy((currentArray+array_index),time,len); array_index+=len; } } uint8_t TimToStr(char* iso8601, Datetime time){//00:00:00.0000 iso8601[0] = time.hour/10 + '0'; iso8601[1] = time.hour%10 + '0'; iso8601[2] = ':'; iso8601[3] = time.minute/10 + '0'; iso8601[4] = time.minute%10 + '0'; iso8601[5] = ':'; iso8601[6] = time.second/10 + '0'; iso8601[7] = time.second%10 + '0'; iso8601[8] = '.'; iso8601[9] = time.millis/100 + '0'; iso8601[10] = (time.millis%100)/10 + '0'; iso8601[11] = time.millis%10 + '0'; iso8601[12] = time.micro100 + '0'; iso8601[13] = '\n'; iso8601[14] = '\0'; return 15; } void TimInit(){ now.micro100 = 0; now.millis = 0; now.second = 0; now.minute = 0; now.hour = 0; } /* USER CODE END 4 */
补充配置说明
- SPI配置:72MHz系统时钟下最高18MB/s传输速度
- SD卡扇区大小设置为4096
- 定时器中断频率10kHz
- 缓冲区大小固定为2048(其他大小无法正常运行)
- 尝试过DMA传输但未取得明显效果
解决方案与优化建议
1. 修复共享变量竞态问题
当前代码中buffersReady、fillIdx、saveIdx等变量在定时器中断和主循环中同时读写,未做互斥保护,这是导致随机FR_DISK_ERR的核心原因。修复方法:
- 访问共享变量时使用临界区保护,关闭/开启中断:
// 主循环中处理缓冲区写入 if(!writing){ __disable_irq(); uint8_t ready_count = buffersReady; __enable_irq(); if(ready_count > 0){ __disable_irq(); uint8_t write_idx = saveIdx; buffersReady--; saveIdx = (saveIdx + 1) % 3; __enable_irq(); res = f_write(&fil, bufferPool[write_idx], BUF_SIZE, &bw); if(res != FR_OK){ // 错误处理,比如重启文件系统或记录错误 } // 检查实际写入字节数 if(bw != BUF_SIZE){ // 处理部分写入情况 } } } - 中断中修改
buffersReady时,也需确保原子性,可使用MCU的原子操作指令(如__atomic_add_fetch)。
2. 限制缓冲区待写数量
当三个缓冲区全部被填满时,中断继续写入会覆盖未保存的数据,导致索引混乱。在中断中添加缓冲区满判断:
if(array_index+len >= BUF_SIZE){ __disable_irq(); if(buffersReady < 2){ // 三缓冲最多允许2个待写缓冲区,留1个供当前填充 fillIdx = (fillIdx + 1) % 3; currentArray = bufferPool[fillIdx]; array_index = 0; buffersReady++; } else { // 缓冲区已满,可添加丢数计数或LED提示 } __enable_irq(); }
3. FatFS配置优化
- 确保FatFS配置中
_MAX_SS设置为4096,与SD卡实际扇区大小匹配,避免扇区对齐错误。 - 开启
_USE_WRITE和_USE_FASTSEEK选项,提升写入和定位效率。 - 避免频繁调用
f_sync,可延长同步间隔至10秒以上,减少SD卡写入阻塞。
4. 高频写入替代方案
若TXT格式无法满足10kHz写入需求,可尝试以下方案:
- 二进制格式写入:将时间戳转为二进制数据(如4字节毫秒+1字节百微秒),每条数据仅5字节,相比TXT的15字节,写入数据量减少2/3,大幅降低SD卡负载。
- 裸扇区写入:跳过FatFS,直接操作SD卡扇区,将缓冲区数据按4096字节对齐后写入,避免文件系统开销,但需自行管理文件结构和扇区分配。
- DMA+中断优化:确保SPI传输使用DMA,在DMA完成中断中标记写入完成,替代主循环轮询,提升响应速度。
内容的提问来源于stack exchange,提问作者arcanorum
相关产品推荐
相关产品推荐

