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

STM32F103(Bluepill)SPI SD卡中断写入失败及主循环异常求助

问题原因分析

1. 中断服务函数内无法写入的核心原因

  • FatFs的核心API(f_mount、f_open、f_close等)不支持在中断上下文调用,这类函数会修改全局文件系统状态,中断里执行会和主程序的操作产生竞态条件,直接破坏文件系统一致性,导致写入失败。
  • 中断服务函数要求执行周期极短(远小于10ms),但SD卡的SPI读写、文件系统操作是阻塞式的,会大幅拉长中断响应时间,导致系统其他功能异常,甚至触发硬件看门狗复位。

2. main循环写入异常的原因

  • 数据采集和写入的时序不匹配:中断每10ms产生一组数据,但main循环如果没有及时读取缓存的采集数据,新数据会覆盖旧数据,导致丢失;同时如果写入操作耗时超过10ms,会错过多组数据,最终记录的数据量不足。
  • 每次写入都重复执行f_mount、f_open、f_close:这些操作开销极大(尤其是SD卡初始化和文件打开/关闭),频繁执行会导致写入速度跟不上采集速度,同时反复打开关闭文件可能导致文件系统缓存未及时刷新,出现重复写入的脏数据。

解决方案

步骤1:采用「中断采集+主循环写入」的异步架构

  • 中断服务函数只做最轻量化的操作:采集4个28bit数值,拼接成字符串后存入环形缓冲区,立即退出中断。
  • 主循环专注于从环形缓冲区读取数据,批量写入SD卡,避免频繁的文件操作。

步骤2:优化文件操作逻辑

  • 仅在系统初始化时执行一次f_mount,不要每次写入都挂载。
  • 系统启动时打开目标文件(f_open),保持文件句柄一直打开,直到系统停止或需要切换文件时再执行f_close。
  • 每次写入前无需调用f_lseek,文件以FA_WRITE打开后,写入指针会自动指向文件末尾;若需确认位置,可偶尔用f_size校验,但无需每次执行。

步骤3:实现线程安全的环形缓冲区

  • 用原子操作或关闭中断的方式保护读写指针,避免中断和主程序同时操作缓冲区导致数据错乱。
  • 缓冲区大小至少能存储100组数据(对应1秒采集量),避免主循环写入延迟时数据溢出。

优化后的代码示例

1. 全局变量定义

#define BUFFER_SIZE 128  // 可根据实际调整环形缓冲区大小
FIL fil;
FATFS fs;
char data_buffer[BUFFER_SIZE][64];  // 假设每组数据字符串最长64字节
uint8_t buf_write_ptr = 0;
uint8_t buf_read_ptr = 0;
volatile uint8_t buf_has_data = 0;  // 标记缓冲区是否有未处理数据

2. 中断服务函数

void EXTIx_IRQHandler(void) {
    if(EXTI_GetITStatus(EXTI_Linex) != RESET) {
        // 采集4个28bit数值
        uint32_t val1 = get_sensor_data(1);
        uint32_t val2 = get_sensor_data(2);
        uint32_t val3 = get_sensor_data(3);
        uint32_t val4 = get_sensor_data(4);
        
        // 拼接成字符串
        snprintf(data_buffer[buf_write_ptr], 64, "%lu,%lu,%lu,%lu\r\n", val1, val2, val3, val4);
        
        // 原子操作更新环形缓冲区写指针
        __disable_irq();
        buf_write_ptr = (buf_write_ptr + 1) % BUFFER_SIZE;
        buf_has_data = 1;
        __enable_irq();
        
        EXTI_ClearITPendingBit(EXTI_Linex);
    }
}

3. main循环中的写入逻辑

int main(void) {
    // 系统初始化:GPIO、SPI、EXTI、UART等
    SystemInit();
    HAL_Init();
    init_sensors();
    init_spi_sdcard();
    init_exti_interrupt();
    
    // 初始化FatFs
    FRESULT res = f_mount(&fs, "", 0);
    if(res != FR_OK) {
        // 挂载失败,处理错误(如点亮错误LED)
        while(1);
    }
    
    // 打开文件并保持句柄打开
    res = f_open(&fil, "data.txt", FA_OPEN_ALWAYS | FA_WRITE);
    if(res != FR_OK) {
        // 打开失败,处理错误
        while(1);
    }
    // 将写入指针移到文件末尾
    f_lseek(&fil, fil.fsize);
    
    while(1) {
        if(buf_has_data) {
            __disable_irq();
            // 读取缓冲区数据
            char* write_data = data_buffer[buf_read_ptr];
            buf_read_ptr = (buf_read_ptr + 1) % BUFFER_SIZE;
            // 检查缓冲区是否为空
            if(buf_read_ptr == buf_write_ptr) {
                buf_has_data = 0;
            }
            __enable_irq();
            
            // 写入SD卡
            UINT bw;
            res = f_write(&fil, write_data, strlen(write_data), &bw);
            if(res != FR_OK) {
                // 写入失败,处理错误
            }
            
            // 定期刷新缓存(每写入10组数据刷新一次,避免掉电丢失)
            static uint8_t write_count = 0;
            if(++write_count >= 10) {
                f_sync(&fil);
                write_count = 0;
            }
        }
        
        // 主循环可处理其他低优先级任务
        // ...
    }
    
    // 程序结束时关闭文件(实际项目可通过按键触发)
    f_close(&fil);
    f_mount(NULL, "", 0);
}

额外注意事项

  • 合理设置SPI总线时钟:STM32F103的SPI最高可达18MHz,但SD卡兼容性可能要求初始化为低频率(如400kHz),初始化完成后再切换到高频率。
  • 确保SD卡写保护关闭,硬件连接正确(尤其是CS引脚的电平控制)。
  • 若系统有看门狗,需保证主循环写入操作不会导致看门狗超时,必要时在循环中喂狗。

内容的提问来源于stack exchange,提问作者Mr. Black

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 21:44:57