Io 在没有竞争条件的情况下,通过缓冲区将数据写入SD卡

Io 在没有竞争条件的情况下,通过缓冲区将数据写入SD卡,io,arduino,embedded,system,deadlock,Io,Arduino,Embedded,System,Deadlock,我正在为数据记录设备编写固件。它以20 Hz的频率从传感器读取数据,并将数据写入SD卡。但是,将数据写入SD卡的时间不一致(约200-300ms)。因此,一种解决方案是以一致的速率(使用计时器中断)将数据写入缓冲区,并具有第二个线程,在缓冲区满时将数据写入SD卡 以下是我的天真实现: #define N 64 char buffer[N]; int count; ISR() { if (count < N) { char a = analogRead(A0);

我正在为数据记录设备编写固件。它以20 Hz的频率从传感器读取数据,并将数据写入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;
    }
}
定义N 64 字符缓冲区[N]; 整数计数; ISR(){ 如果(计数 该代码存在以下问题:

  • 当缓冲区已满时,将数据写入SD卡会阻止读取
  • 它可能有一个竞赛条件

  • 解决这个问题的最好办法是什么?使用循环缓冲还是双缓冲?我如何确保比赛条件不会发生?

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

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

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

    下面是对双缓冲实现的修改。我已将缓冲区长度减少到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;
        }
    }
    
    //双缓冲区及其管理数据/常量
    静态易失性字符缓冲区[2][32];
    静态常量int BUFFLEN=sizeof(缓冲区[0]);
    静态常量unsigned char EMPTY=0xff;
    静态易失性无符号字符inbuffer=0;
    静态易失性无符号字符溢出=空;
    ISR()
    {
    静态整数计数=0;
    //写入当前缓冲区
    字符a=模拟读数(A0);
    缓冲区[inbuffer][count]=a;
    计数++;
    //如果缓冲区已满。。。
    如果(计数>=BUFFLEN)
    {
    //向循环()发送信号,表明数据可用(非空)
    突出=缓冲;
    //切换输入缓冲区
    inbuffer=inbuffer==0?1:0;
    计数=0;
    }
    }
    void循环()
    {
    //如果缓冲区可用。。。
    如果(突发!=空)
    {
    //写缓冲区
    myFile.open(“data.csv”,FILE\u WRITE);
    对于(int i=0;i

    请注意,共享数据使用了
    volatile
    unsigned char
    。重要的是,并发执行上下文之间共享的数据是以显式和原子方式访问的;访问基于8位AVR的Arduino上的
    int
    需要多条机器指令,中断可能部分通过读取/写入
    loop()
    发生,并导致读取不正确的值。

    太好了!出于好奇,如何在Arduino中实现互斥或信号量?信号量和互斥仅在先发制人的多任务操作系统中才相关。虽然完全可以在Arduino硬件上实现或部署RTOS,但Arduino也是一个应用程序框架和库,不基于RTOS,因此它不再是Arduino了。此外,您不能在ISR中使用互斥或等待信号量;您可能会有一个ADC线程和一个文件系统访问线程,并使用RTOS队列或管道作为它们之间的缓冲区,而不是滚动您自己的线程。。。例如:。注意“不做任何事情”的循环()函数;RTO通过使用
    setup()
    进行初始化和存根
    loop()
    在Arduino草图开发环境中工作。我担心的是,如果使用Arduino库,则没有关于线程安全或其他方面的文档。我想补充一点小评论。在Flash中写入数据时要考虑的另一件事是,根据闪存驱动器和闪存的类型,有时写入闪存不希望被任何ISR中断。需要记住的是。@Alex:在这种情况下,闪存是SD卡上的,并且有一个独立的控制器,而不是片上的NOR闪存,所以这个问题不适用。