Запись на SD карту (компромис между частотой записи и ресурсом карты)

Доброго всем дня. Есть прототип 10-ти канального измерителя температуры (esp32s3, запись на SD карту).

  1. Диапазон измерений от 0.0 до 300.0, дискретность 0.1С
  2. Измерения происходят 1 раз в 3 секунды
  3. Один блок измерений выполняется в течении 40 минут (это максимум, меньше может быть, больше нет). Т.е. получается в одном блоке 800 измерений
  4. Всего может быть максимум 10 блоков. После каждого цикла измерений блок может быть стерт и запись начнется заново в блок №1, а может каждый следующий цикл измерений начать записывать в N+1 блок, после записи 10-го блока, следующая запись будет производиться в блок №1.
  5. Измерения температуры выполняются по всем десяти каналам, если отсутствует какой-то из температурных датчиков, то по данному каналу выдается значение -99.9 (чтобы потом можно отсортировать и исключить данный канал)
  6. После каждого измерения все данные температуры собираются в строку char имеющую формат (№ измерения,t1,t2…t10) (т.е. максимальная длина строки 64 байта, пусть будет 65).

Таким образом минимальный размер данных может составлять 65 байт, максимальный = 10x800x65=520000 байт

В процессе измерений запись данных производится на SD карту (допустим 2Gb).

В день планируется выполнять 2 цикла измерений. Если записывать построчно, то в день максимально будет выполнятся 1600 циклов записи на SD карту. Интернет пишет, что у SD карты число циклов стирания-записи (P/E cycles) составляет порядка 1000-5000. Конечно хочется надеяться, что запись будет производится равномерно по всем ячейкам.
Т.е. для карты 2Gb этот ресурс составит (при учете ежедневного использования):
минимум: (2048000байт x 1000циклов)/(2x800x65) = 19692 дней (53 года)
максимум: (2048000x5000/(2x800x65) = 98461 дней (250 лет)

Что-то мне не верится в такой ресурс карты и видится, что правильней было бы записывать данные не построчно, а объединять данные в блоки (к примеру по поминутно) и потом записывать разово.

Тут возникает конфликт интересов:

  1. Нужно выделить памяти, чтобы хранить такой массив данных (65байт x 20блоков/минута)=1300 байт. Наверное не много в рамках ESP32S3. Документация говорит, что у ESP32S3 520 КБ встроенной SRAM, при этом по факту в зависимости от используемых компонентов и вызываемых им процедур, остается что-то около 200-250Кб, что все равно много.
  2. Цена возможной потери данных за время измерения (выключилось питание и стало все плохо), когда измеренные данные записались в массив, но не успели записаться на sd карту (в моем случаем я готов пожертвовать минутой данных).
  3. Думал еще время записи большого блока на SD карту займет много времени и сдвинет по времени измерение температуры (но потом проверил, оно ничтожно мало по отношению к 3 секундам между измерения, поэтому это можно в расчет не брать)

Очень хотелось бы услышать мнение в рамках данной задачи и используемого железа - как правильно выбрать размер массива поl данные с учетом ресурса SD карты. Различных логгеров много кто делал. А может реально ресурс карты такой и есть, и плановая замена карты раз в 5 лет, точно перекрывает риск ее порчи и можно спокойно писать построчно.

Прилагаю немного утрированные скетч, без описания функций, не относящихся к записи данных на карту

#include <FS.h>
#include <SD_MMC.h>

//SPI FOR LTC2984 PIN GPIO CONFIGURATION
#define ADC_MISO_PIN 13 // 13
#define ADC_MOSI_PIN 11 // 11
#define ADC_CLK_PIN 12 // 12
#define ADC_INT1_PIN 35 // 35
#define BUTTON_PRESS 8 // GPIO8

//BUTTON FOR MEASURE CONFIG
int LastState = LOW;
int CurState;
boolean messure_flag = false; 

float ch_res[11] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

char tmp2[70] = "%d,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f,%.1f\n";  

unsigned int num_mes;

#define MSC_CLK_PIN 39 //39
#define MSC_CMD_PIN 38 //38
#define MSC_D0_PIN 37 //37       
bool onebit = true;  // true for 1-bit. 1-bit will ignore the d1-d3 pins. d3 not used in 1-line SD mode, but card's D3 pin must have a 10k pullup

void setup() {
  Serial.begin(115200);
  pinMode(BUTTON_PRESS, INPUT_PULLUP);
  
  Serial.println("Mounting SDcard");
  SD_MMC.setPins(MSC_CLK_PIN, MSC_CMD_PIN, MSC_D0_PIN);
  if (!SD_MMC.begin("/sdcard", onebit)) {
    Serial.println("Mount Failed");
    return;
  }
   else
  {
    Serial.println("Mount Complete");
  }

  num_mes = 0;
  removeDir(SD_MMC, "/B1");
  createDir(SD_MMC, "/B1");
  writeFile(SD_MMC, "/B1/result.txt", "num,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10\n");
}

void loop() {
  if (messure_flag)
  {
   num_mes++;
   measure_channel(Ch1, TEMPERATURE);
   measure_channel(Ch2, TEMPERATURE);
   measure_channel(Ch3, TEMPERATURE);  
   measure_channel(Ch4, TEMPERATURE);
   measure_channel(Ch5, TEMPERATURE); 
   measure_channel(Ch6, TEMPERATURE); 
   measure_channel(Ch7, TEMPERATURE); 
   measure_channel(Ch8, TEMPERATURE); 
   measure_channel(Ch9, TEMPERATURE); 
   measure_channel(Ch10, TEMPERATURE); 

   char tmp[70] = "";
   sprintf(tmp, tmp2, num_mes, ch_res[1], ch_res[2], ch_res[3], ch_res[4], ch_res[5], ch_res[6], ch_res[7], ch_res[8], ch_res[9], ch_res[10]);
   appendFile(SD_MMC, "/B1/result.txt", tmp);
   delay(100);
   if (num_mes>799) {messure_flag = false;}
  }
  
  int CurState = digitalRead(BUTTON_PRESS);
  if (LastState == HIGH && CurState == LOW) 
  {
   messure_flag = true;
  }  
  LastState = CurState; 
}

Потеря данных возможна всегда - если не по причине пропадания питания, то просто вследствие производственного брака.

Нет никакой гарантии, что контроллер карты пишет по кругу.

В соответствии с этими фактами можно реализовать следующие алгоритмы:

  1. писать не на карту, а прямо “на сервер”;
  2. если писать на карту, то самостоятельно использовать всё пространство карты - создавать и заполнять файлы с именами, содержащими инкрементируемое число. При этом нужно будет вычислять имя следующего файла и проверять место на карте, стирая самый старый;
  3. исхитриться и всунуть наборы данных в блоки по 512 байт, чтобы лишний раз не тратить ресурс.
1 лайк

Ни чего не имею против такой задумки, но просто интересно как можно обеспечить такую точность? Какие датчики и какие преобразователи в цифру используются?

Использую LTC2984, она позволяет обеспечить 0.5C точности и 0.1С дискретности.

  1. К сожалению возможности писать на сервер нет от слова совсем. Физическое ограничение. Поэтому только на карту.
  2. 512 байт - это я так понимаю, чтобы связать размер записываемого блока с размером кластера sd карты? Но ведь это больше относится к оптимизации места на sd карте при большом количестве файлов. В моем случае на sd карте будет хранится не более 22 файлов.

Нужно выяснить размер блока вашей карты. Интернет пишет, что у SDSC он составляет один байт, у других 512.

Я уж не помню деталей, но, возможно, что в том случае, когда в блоке будет лежать хвост одного файла, то, при записи другого в оставшееся пространство блока, последний будет считан, стёрт и записан заново, будучи дополненым головой следующего файла. Двойной расход ресурса выходит.

Почему все так любят хранить данные текстом? Это же медленно и неэффективно.

У вас температуры от 0 до 300 с дискретностью 0.1 - то есть их можно домножить на 10 и хранить в виде целых от 0 до 3000, отведя под каждое значение два байта. В итоге пакет из десяти измерений будет занимать на карте 22 байта вместо 65-ти - экономия в три раза.

Да в коде избавление от float только на пользу.

Опять не особо эффективный вариант. Если хранить в виде текста, то слишком много знаков, достаточно было писать -1. А в бинарном виде можно писать 0xffff

2 лайка

Еще добавлю - если хранить данные в бинарном виде - каждое измерение всегда будет занимать два байта. То есть фактически это будет массив, из которого, например, 453-е значение можно будет вычитать в одно действие - просто смещением на 453*2 байта.
А теперь сравните с текстом, где для того же самого нужно будет последовательно распарсить предыдущие 452 строки.

1 лайк

Когда я работал с флешкой на специализированном устройстве (не SD, распаяный модуль, без стандартной файловой системы), я применял следующий алгоритм. Пишу в длинный циклический буфер (вы можете себе позволить весь объем носителя). Перед записью идет проверка начала блока. Если начинается новый блок, идет его подготовка (стирание). Дальше я мог писать побайтно. Расход ресурса - одно стирание и одна запись. Частота записи вобще не влияет на ресурс (при одном и том же количестве регистрируемой информации). Без буферизации (риск потерять данные ничтожно мал). По факту получаем флешку полностью забитую данными с одним-двумя стертыми блоками рядом. Это заодно и признак начала и конца записи.

Если носитель позволяет такую работу, это достаточно щадящее его использование.
Можно писать и поблочно. Но тогда нужно гарантировать себе при потере питания время на сброс буфера. Прерывание потери питания и резервная емкость гарантирующая определенное время работы. У меня такое счастье в приборе было.

Обратите внимание, как работает автомобильный видеорегистратор. Каждый день флешка переписывается бывает по нескольку раз. Достаточно в случае возникновения ошибок просто заменять носитель. С вашими объемами это можно позволить - не в космос же навсегда оправляете. Просто рассчетная плановая замена носителя.

По поводу компактности - при необходимости делается побитовая упаковка. Надо 12 бит на число - пишем эти 12 бит. Набегает целый байт - идет его запись.