You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.01 18:04:53