【问题标题】:Write data to an SD card through a buffer without a race condition在没有竞争条件的情况下通过缓冲区将数据写入 SD 卡
【发布时间】:2017-04-30 06:51:59
【问题描述】:

我正在为数据记录设备编写固件。它以 20 Hz 的频率从传感器读取数据并将数据写入 SD 卡。但是,将数据写入 SD 卡的时间并不一致(大约 200-300 毫秒)。因此,一种解决方案是以一致的速率将数据写入缓冲区(使用定时器中断),并在缓冲区已满时使用第二个线程将数据写入 SD 卡。

这是我的幼稚实现:

#define N 64
char buffer[N];
int count;

ISR() {
    if (count < N) {
        char a = analogRead(A0);
        buffer[count] = a;
        count = count + 1;
    }
}

void loop() {
    if (count == N) {
        myFile.open("data.csv", FILE_WRITE);

        int i = 0;
        for (i = 0; i < N; i++) {
            myFile.print(buffer[i]);
        }
        myFile.close();
        count = 0;
    }
}

代码存在以下问题:

  1. 当缓冲区已满时,向 SD 卡写入数据会阻塞读取
  2. 它可能有竞争条件。

解决此问题的最佳方法是什么?使用循环缓冲区还是双缓冲?如何确保不会发生竞态条件?

【问题讨论】:

    标签: io arduino embedded system deadlock


    【解决方案1】:

    您已经回答了自己的问题;您应该使用双缓冲或循环缓冲区。双缓冲可能更易于实现,并且适用于 SD 卡等通常块写入效率更高的设备。

    缓冲区长度的选择可能需要一些考虑;通常你会使缓冲区与 SD 扇区缓冲区大小相同(通常为 512 字节),但这可能不切实际,并且在采样率低至 20 sps 的情况下,优化 SD 写入性能可能不是问题。

    另一个考虑因素是您需要通过选择适当的缓冲区大小来使采样率与文件系统延迟相匹配。在这种情况下,64 个样本缓冲区缓冲区将在 3 秒多一点的时间内填满,但块写入最多只需要 300 毫秒 - 因此如果需要,您可以使用更小的缓冲区 - 8 个样本就足够了 - 尽管要小心,您可能已经观察到 300 毫秒的延迟,但是当物理闪存中跨越特定边界时,延迟可能会更大 - 我已经看到某些卡在 1 MB 边界上存在明显的延迟 - 此外,卡的性能因尺寸和制造商而异。

    下面是对双缓冲实现的改编。我已将缓冲区长度减少到 32 个样本,但使用双缓冲后,总数不变为 64,但写入延迟减少到 1.6 秒。

    // Double buffer and its management data/constants
    static volatile char buffer[2][32];
    static const int BUFFLEN = sizeof(buffer[0]);
    static const unsigned char EMPTY = 0xff;
    static volatile unsigned char inbuffer = 0;
    static volatile unsigned char outbuffer = EMPTY;
    
    ISR()
    {
        static int count = 0;
    
        // Write to current buffer
        char a = analogRead(A0);
        buffer[inbuffer][count] = a;
        count++ ;
    
        // If buffer full...
        if( count >= BUFFLEN )
        {
            // Signal to loop() that data available (not EMPTY)
            outbuffer = inbuffer;
    
            // Toggle input buffer
            inbuffer = inbuffer == 0 ? 1 : 0;
            count = 0;
        }
    }
    
    void loop()
    {
        // If buffer available...
        if( outbuffer != EMPTY )
        {
            // Write buffer
            myFile.open("data.csv", FILE_WRITE);
            for( int i = 0; i < BUFFLEN; i++)
            {
                myFile.print(buffer[outbuffer][i]);
            }
            myFile.close();
    
            // Set the buffer to empty
            outbuffer = EMPTY;
        }
    }
    

    注意volatileunsigned char 用于共享数据。重要的是并发执行上下文之间共享的数据被显式地原子地访问;访问基于 8 位 AVR 的 Arduino 上的int 需要多个机器指令,并且中断可能在loop() 的读/写过程中发生,并导致读取不正确的值。

    【讨论】:

    • 太棒了!出于好奇,如何在 Arduino 中实现互斥锁或信号量?
    • 信号量和互斥量仅在抢占式多任务操作系统的上下文中相关。尽管完全可以在 Arduino 硬件上实现或部署 RTOS,但 Arduino 也是一个不基于 RTOS 的应用程序框架和库,因此它不再是 Arduino。此外,您不能在 ISR 中使用互斥锁或等待信号量;您可能会改为拥有一个 ADC 线程和一个文件系统访问线程,并使用 RTOS 队列或管道作为它们之间的缓冲区,而不是自己滚动。
    • .. 例如:hackster.io/feilipu/…。注意“无所事事”loop() 功能; RTOS 在 Arduino Sketch 开发环境中工作,使用setup() 进行初始化和存根loop()。我担心的是没有关于线程安全的文档,或者如果 Arduino 库没有其他文档。
    • 我想补充一点意见。将数据写入闪存时要考虑的另一件事是,根据闪存驱动程序和闪存类型,有时写入闪存不希望被任何 ISR 中断。只是要记住的事情。
    • @Alex : 这个例子中的闪存是在 SD 卡上并且有一个独立的控制器而不是片上的 NOR 闪存,所以这个问题不应该存在。
    猜你喜欢
    • 1970-01-01
    • 2013-12-23
    • 1970-01-01
    • 1970-01-01
    • 2021-02-14
    • 2013-07-16
    • 2012-03-31
    • 2023-02-09
    • 1970-01-01
    相关资源
    最近更新 更多