You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

如何避免SD卡阻塞ESP32 SPI总线以保障ADC定时读取?

ESP32同一SPI总线实现ADC定时读取与SD卡抗卡顿共存方案

针对你在ESP-IDF开发中遇到的同一SPI总线共享ADC和SD卡、需保障ADC定时读取不受SD卡卡顿影响的问题,以下是几种可行的落地方案:

方案1:高优先级任务+SPI事务优先级调度

利用FreeRTOS的任务优先级机制,结合ESP-IDF SPI驱动的事务优先级配置,让ADC读取任务优先抢占总线资源:

  1. 任务优先级划分

    • 将ADC定时读取任务设为高优先级(比如优先级10),SD卡读写任务设为低优先级(比如优先级5)。高优先级任务会随时抢占低优先级任务的CPU时间,确保ADC读取的定时性。
    • vTaskDelayUntil实现ADC的精准定时读取,避免累计误差。
  2. SPI设备优先级配置
    在添加SPI设备时,给ADC设备设置更高的事务优先级,确保其SPI请求优先被处理:

    // ADC设备配置
    spi_device_interface_config_t adc_dev_cfg = {
        .mode = 0,                  // 匹配ADC的SPI模式
        .clock_speed_hz = 8*1000*1000, // 兼容SD卡的时钟(需匹配两者硬件规格)
        .spics_io_num = GPIO_NUM_15, // ADC的独立CS引脚
        .queue_size = 3,
        .priority = 1,              // 事务优先级高于SD卡(SD卡设为0)
    };
    spi_bus_add_device(SPI2_HOST, &adc_dev_cfg, &adc_spi_handle);
    
    // SD卡设备配置
    spi_device_interface_config_t sd_dev_cfg = {
        .mode = 0,
        .clock_speed_hz = 8*1000*1000,
        .spics_io_num = GPIO_NUM_14, // SD卡的独立CS引脚
        .queue_size = 5,
        .priority = 0,              // 低优先级
    };
    spi_bus_add_device(SPI2_HOST, &sd_dev_cfg, &sd_spi_handle);
    
  3. 任务实现示例

    // ADC定时读取任务
    void adc_read_task(void *arg) {
        TickType_t last_wake_time = xTaskGetTickCount();
        const TickType_t read_interval = pdMS_TO_TICKS(10); // 每10ms读取一次
        uint16_t adc_data;
        spi_transaction_t trans = {
            .length = 16,           // ADC的传输位数(根据型号调整)
            .rx_buffer = &adc_data,
        };
    
        while (1) {
            // 精准定时唤醒
            vTaskDelayUntil(&last_wake_time, read_interval);
    
            // 执行ADC SPI读取(驱动自动处理总线抢占)
            esp_err_t ret = spi_device_transmit(adc_spi_handle, &trans);
            if (ret == ESP_OK) {
                // 数据上报
                send_adc_data_to_server(adc_data);
                // 将数据发送到SD卡写入队列(非阻塞,避免卡主ADC任务)
                xQueueSend(sd_write_queue, &adc_data, 0);
            }
        }
    }
    
    // SD卡写入任务(低优先级)
    void sd_write_task(void *arg) {
        uint16_t data_buf;
        FILE *sd_file = fopen("/sdcard/adc_log.txt", "a");
        if (!sd_file) return;
    
        while (1) {
            // 阻塞等待ADC数据
            if (xQueueReceive(sd_write_queue, &data_buf, portMAX_DELAY)) {
                // 写入SD卡,即使卡顿也不影响ADC任务
                fprintf(sd_file, "%d\n", data_buf);
                // 定期刷新缓存,避免大缓存导致的突发卡顿
                fflush(sd_file);
            }
        }
    }
    

方案2:SPI DMA+硬件定时器触发的ADC读取

将ADC的SPI读取配置为DMA模式,结合硬件定时器触发,让读取过程不占用CPU时间,彻底规避SD卡任务的CPU占用影响:

  1. 配置ADC的SPI DMA传输

    spi_device_interface_config_t adc_dev_cfg = {
        .mode = 0,
        .clock_speed_hz = 10*1000*1000,
        .spics_io_num = GPIO_NUM_15,
        .queue_size = 5,
        .flags = SPI_DEVICE_DMA_RX, // 启用RX DMA
    };
    spi_bus_add_device(SPI2_HOST, &adc_dev_cfg, &adc_spi_handle);
    
  2. 用硬件定时器触发ADC DMA读取
    利用ESP32的硬件定时器,定时触发SPI DMA读取ADC,读取完成后通过中断回调处理数据上报和队列发送:

    void adc_dma_done_cb(spi_transaction_t *trans) {
        uint16_t *adc_data = (uint16_t*)trans->rx_buffer;
        // 数据上报
        send_adc_data_to_server(*adc_data);
        // 发送到SD卡队列
        xQueueSend(sd_write_queue, adc_data, 0);
        // 释放内存
        free(trans->rx_buffer);
        free(trans);
    }
    
    void adc_timer_init(void) {
        timer_config_t config = {
            .divider = 80, // 1MHz计数时钟
            .counter_dir = TIMER_COUNT_UP,
            .counter_en = TIMER_PAUSE,
            .alarm_en = TIMER_ALARM_EN,
            .auto_reload = true,
        };
        timer_init(TIMER_GROUP_0, TIMER_0, &config);
        timer_set_alarm_value(TIMER_GROUP_0, TIMER_0, 10000); // 10ms触发一次
        timer_isr_callback_add(TIMER_GROUP_0, TIMER_0, []() {
            // 触发SPI DMA读取
            spi_transaction_t *trans = malloc(sizeof(spi_transaction_t));
            memset(trans, 0, sizeof(spi_transaction_t));
            trans->length = 16;
            trans->rx_buffer = malloc(2);
            trans->user = adc_dma_done_cb;
            spi_device_queue_trans(adc_spi_handle, trans, 0);
        }, NULL, 0);
        timer_start(TIMER_GROUP_0, TIMER_0);
    }
    

关键注意事项

  • 时钟兼容性:确保SPI总线时钟同时满足ADC和SD卡的规格要求,避免因时钟过高导致某一设备工作异常。
  • SD卡卡顿缓解:SD卡的卡顿多来自大块擦写或缓存刷新,建议将SD卡写入拆分为小批量操作,定期调用fflushf_sync,避免单次操作占用总线过长时间。
  • 总线互斥:ESP-IDF的SPI驱动已内置总线互斥逻辑,无需手动添加互斥锁,只需确保每个设备使用独立的CS引脚。

内容的提问来源于stack exchange,提问作者bbqribs

火山引擎 最新活动