通过没有竞争条件的缓冲区将数据写入SD卡

时间:2017-04-30 06:51:59

标签: io arduino embedded system deadlock

我正在为数据记录设备编写固件。它从频率为20Hz的传感器读取数据,并将数据写入SD卡。但是,将数据写入SD卡的时间并不一致(约200-300ms)。因此,一种解决方案是以一致的速率将数据写入缓冲区(使用定时器中断),并在缓冲区已满时使用第二个线程将数据写入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. 可能有竞争条件。
  3. 解决此问题的最佳方法是什么?使用循环缓冲区,还是双缓冲?我如何确保不会发生竞争条件?

1 个答案:

答案 0 :(得分:3)

你宁愿回答你自己的问题;你应该使用双缓冲或循环缓冲。双缓冲可能更容易实现,并且适用于诸如SD卡之类的设备,其中块写入通常更有效。

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

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

以下是对您的实现的双缓冲修改。我已经将缓冲区长度减少到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 ;
    }
}

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