通过无竞争条件的缓冲区将数据写入 SD 卡
Write data to an SD card through a buffer without a race condition
我正在为数据记录设备编写固件。它以 20 Hz 的频率从传感器读取数据并将数据写入 SD 卡。但是,向SD卡写入数据的时间并不一致(大约200-300 ms)。因此,一种解决方案是以一致的速率(使用定时器中断)将数据写入缓冲区,并在缓冲区已满时使用第二个线程将数据写入 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;
}
}
代码存在以下问题:
- 当缓冲区已满时,向 SD 卡写入数据会阻塞读取
- 它可能有竞争条件。
解决这个问题的最佳方法是什么?使用循环缓冲区,还是双缓冲?如何确保不会发生竞争条件?
你已经回答了你自己的问题;您应该使用双缓冲或循环缓冲区。双缓冲可能更易于实现,适用于块写入通常更有效的 SD 卡等设备。
缓冲区长度的选择可能需要一些考虑;通常,您会使缓冲区与 SD 扇区缓冲区大小(通常为 512 字节)相同,但这可能不切实际,并且采样率低至 20 sps,优化 SD 写入性能可能不是问题。
另一个考虑因素是您需要通过选择适当的缓冲区大小来使采样率与文件系统延迟相匹配。在这种情况下,64 个样本缓冲区缓冲区将填充三秒多一点,但块写入最多只需要 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;
}
}
请注意 volatile
和 unsigned char
用于 共享数据 。重要的是,在并发执行上下文之间共享的数据被显式访问并且原子地;在基于 8 位 AVR 的 Arduino 上访问 int
需要多条机器指令,并且中断可能会在 loop()
中的 read/write 中途发生并导致读取不正确的值。
我正在为数据记录设备编写固件。它以 20 Hz 的频率从传感器读取数据并将数据写入 SD 卡。但是,向SD卡写入数据的时间并不一致(大约200-300 ms)。因此,一种解决方案是以一致的速率(使用定时器中断)将数据写入缓冲区,并在缓冲区已满时使用第二个线程将数据写入 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;
}
}
代码存在以下问题:
- 当缓冲区已满时,向 SD 卡写入数据会阻塞读取
- 它可能有竞争条件。
解决这个问题的最佳方法是什么?使用循环缓冲区,还是双缓冲?如何确保不会发生竞争条件?
你已经回答了你自己的问题;您应该使用双缓冲或循环缓冲区。双缓冲可能更易于实现,适用于块写入通常更有效的 SD 卡等设备。
缓冲区长度的选择可能需要一些考虑;通常,您会使缓冲区与 SD 扇区缓冲区大小(通常为 512 字节)相同,但这可能不切实际,并且采样率低至 20 sps,优化 SD 写入性能可能不是问题。
另一个考虑因素是您需要通过选择适当的缓冲区大小来使采样率与文件系统延迟相匹配。在这种情况下,64 个样本缓冲区缓冲区将填充三秒多一点,但块写入最多只需要 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;
}
}
请注意 volatile
和 unsigned char
用于 共享数据 。重要的是,在并发执行上下文之间共享的数据被显式访问并且原子地;在基于 8 位 AVR 的 Arduino 上访问 int
需要多条机器指令,并且中断可能会在 loop()
中的 read/write 中途发生并导致读取不正确的值。