SDFat для GPS логгера

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

Пытаюсь реализовать GPS логгер с функцией спидометра. Лог пишется на карту SD в формате GPX. Для работы с картой используется библиотека SDfat, т.к. требуются длинные имена файлов (имя файла - дата и время начала записи трека).

В процессе записи лога требуется в конце каждого цикла записи добавлять завершающие XML-теги (27 символов), чтобы потом файл можно было открыть без ошибки. Каждый последующий цикл записи должен начинаться со смещением на те же 27 символов, но смещения не происходит. Текст, который должен затиратья, дописывается в каждом цикле.

Код записи в GPX брал в этой теме: 
http://arduino.ru/forum/proekty/gps-logger-s-rgb-svetodiodom

там используется библиотека SD.h:

   dataFile = SD.open(filepath, FILE_WRITE);
    unsigned long filesize = dataFile.size();

// back up the file pointer to just before the closing tags
    filesize -= 27;
    dataFile.seek(filesize);
    dataFile.print(F("<trkpt lat=\""));

 

в моём варианте для SDfat.h код выглядит:

// открытие файла для записи
    file.open(filename, O_APPEND | O_WRITE);

// вычисление размера фала
    uint32_t filesize = file.fileSize();

// вычисление точки начала записи
    filesize -= 27;
    file.seekSet(filesize);

// вывод данных в файл
    file.print(F("<trkpt lat=\""));

собственно вопрос - что я делаю не так?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Torn пишет:

что я делаю не так?

Ну, для начала, Вы не проверяете, что Вам вернула seekSet. Как и не проверяете успешно ли открылся файл.

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

Итак, печатайте результат seekSet, а потом уж думайте.

Если она вернёт 0, то надо понять почему. Причин может быть три

1. Файл не открыт (а Вы, кстати, и не проверяли открылся он или нет)
2. Запрашиваемая позиция за границами файла (вроде, не Ваш случай, но ...)
3. Файл имеет кластерную организацию и с цепочкой кластеров что-то не так

Если уж совсем будет непонятно, то нужно лезть в библиотеку и вставлять возврат разных кодов на каждую причину.

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

Возможно из за того что вы не учитываете размер блока для SDfat и пытаетесь выполнить seek не кратный размеру блока.
Почему то пример на гитхабе выполняет seek на кратные 512-байт, может только для low latency, а может и нет ;-)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ну, вот, Алексей :(

Я тут скромненько пишу: "Файл имеет кластерную организацию и с цепочкой кластеров что-то не так" чтобы ТС сам решил проблему, а Вы прямо на тарелочке :(

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

Я уж стек исходников раскрутил, да постеснялся запостить, только ссылку дал на какой то не проверенный код, пардоньте, уж торлить не хотелось.

П.С.
ардуина-иде не плоха для быстрого старта, но редактор это как ёжик в тумане, пользоваться слегка совсем неудобно.
эклипсом пользуюсь оч.давно, собираю им же, в метод тык - и прототипы и реализация перед тобой, полный релакс.

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

Спасибо за ответы.

Файл открывается успешно. В Setup я формирую имя файла по дате и времени из GPS, открываю файл и записываю в него заголовок XML, в конец добавляю закрывающие теги, которые должны затираться в каждом цикле записи. 

  if (!sd.begin(SD_CS_PIN, SD_SCK_MHZ(50))) {

        oled.setCursor(0, 0);
        oled.println("SD ERROR!");
        delay(5000); 
        oled.clear();
        sd.initErrorHalt();
   } // Ошибка открытия SD карты
// открытие файла  
    sprintf(filename, "gps_%02d%02d%02d-%02d%02d%02d.gpx", dd.year(), dd.month(), dd.day(), hour, tt.minute(), tt.second());
    file.open(filename, O_CREAT | O_WRITE | O_EXCL);
    //заголовок файла
    file.print(F(
                 "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
                 "<gpx version=\"1.1\" creator=\"Torn\" xmlns=\"http://www.topografix.com/GPX/1/1\" \r\n"
                 "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
                 "xsi:schemaLocation=\"http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd\">\r\n"
                 "\t<trk>\r\n<trkseg>\r\n"));  //heading of gpx file
    file.print(F("</trkseg>\r\n</trk>\r\n</gpx>\r\n")); // закрывающие теги XML
    file.close();

В loop файл снова открывается и в него идёт запись данных и файл закрывается. При отключении питания не будет потери данных.

Посижу, поковыряю с возвращаемым значением seekSet.

С кластерной организацией файла дела не имел, пока не понимаю о чём речь.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Torn пишет:

С кластерной организацией файла дела не имел, пока не понимаю о чём речь.

А Вы откройте файл библиотеки и посмотрите как написана seekSet - поймёте.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Если. Поймете. 

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

DetSimen пишет:

Если. Поймете. 

да уж, с наскока не разобрался откуда берутся всякие там m_curCluster и т.д....

 

seekSet возвращает false

размер файла высчитывается корректно.

попробовал функцию seekEnd. как я понял из её описания, результатом выполнения является установка позиции в конец файла + смещение offset.

bool FatFile::seekEnd ( int32_t  offset = 0 )  
inline

Set the files position to end-of-file + offset. See seekSet(). Can't be used for directory files since file size is not defined.

Parameters
[in] offset The new position in bytes from end-of-file.
Returns
true for success or false for failure.

 

результат не изменился. смещение на заданное кол-во символов не происходит.

 

может быть попробовать формировать блоки, кратные 512 и записывать их? т.е. размер файла будет всегда кратен 512 и тогда seekSet заработает.

b707
Offline
Зарегистрирован: 26.05.2017

Алексей, Евгений

по моему, мужики, вы не правы, кластеры тут не при чем. С кластерами или без них Seek() должен отрабатывать верно.

Просто seek меняет только позицию чтения, а позиция записи от seek() не зависит. Позиция записи зависит от режима. В APPEND и WRITE принт всегда пишет в конец файла, в TRUNCATE - в начало. Чтобы писать в произвольное место файла. надо открывать файл не в аппенде, а в режиме Read-Write

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

b707 пишет:

по моему, мужики, вы не правы, ... позиция записи от seek() не зависит. 

Может быть. Сейчас посмотрю в библиотеку и отпишусь.

Вы правы. Вот кусок из функции write

  // seek to end of file if append flag
  if ((flags_ & O_APPEND) && curPosition_ != fileSize_) {
    if (!seekEnd()) goto writeErrorReturn;
  }

Т.е. если режим append и указатель не на конце файла, то она сама выполняет seekEnd

ua6em
ua6em аватар
Offline
Зарегистрирован: 17.08.2016

DetSimen пишет:

Если. Поймете. 

да поймет, чего там сверхзаумного

b707
Offline
Зарегистрирован: 26.05.2017

выражусь немного более точно - в файле, открытым в режиме APPEND - текущая позиция записи автоматически отрабатывает в конец файла. вне зависмости от параметра seek()

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Таки да, Вы правы, я там выше даже кусок кода библиотеки привёл.

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

хз, хз... я с ардуиной всего неделю-полторы ковыряюсь. с С++ дружили лет 15-20 назад, по этому начинаю разбираться почти с нуля.

в примере LowLatencyLogger из библиотеки SDfat есть функция recoverTmpFile и использование в ней seekSet, но ни чего волшебного, что мне бы помогло, я не вижу, разве, что только режим открытия файла O_RDWR

void recoverTmpFile() {
  uint16_t count;
  if (!binFile.open(TMP_FILE_NAME, O_RDWR)) {
    return;
  }
  if (binFile.read(&count, 2) != 2 || count != DATA_DIM) {
    error("Please delete existing " TMP_FILE_NAME);
  }
  Serial.println(F("\nRecovering data in tmp file " TMP_FILE_NAME));
  uint32_t bgnBlock = 0;
  uint32_t endBlock = binFile.fileSize()/512 - 1;
  // find last used block.
  while (bgnBlock < endBlock) {
    uint32_t midBlock = (bgnBlock + endBlock + 1)/2;
    binFile.seekSet(512*midBlock);
    if (binFile.read(&count, 2) != 2) error("read");
    if (count == 0 || count > DATA_DIM) {
      endBlock = midBlock - 1;
    } else {
      bgnBlock = midBlock;
    }
  }
  // truncate after last used block.
  if (!binFile.truncate(512*(bgnBlock + 1))) {
    error("Truncate " TMP_FILE_NAME " failed");
  }
  renameBinFile();
}

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Torn пишет:
разве, что только режим открытия файла O_RDWR
Так ото ж :)

b707
Offline
Зарегистрирован: 26.05.2017

Torn пишет:

разве, что только режим открытия файла O_RDWR

это не "только", это ого-го-го сколько :)

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

Заработала! Спасибо огромное за помощь!

оставил код в таком виде:

    file.open(filename, O_RDWR);
    file.seekSet(file.fileSize()-27);

p.s.: теперь можно переходить к следующему этапу - корректная настройка вывода данных на OLED дисплей, но об этом в другой теме, если опять сам не осилю. пока тестовый режим работает, но хочется большего, а вот с ним проблема...

p.p.s.: на всякий случай оставлю в этой теме ссылку на памятку

 

 

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

b707 пишет:

Алексей, Евгений

по моему, мужики, вы не правы, кластеры тут не при чем. С кластерами или без них Seek() должен отрабатывать верно.

Просто seek меняет только позицию чтения, а позиция записи от seek() не зависит. Позиция записи зависит от режима. В APPEND и WRITE принт всегда пишет в конец файла, в TRUNCATE - в начало. Чтобы писать в произвольное место файла. надо открывать файл не в аппенде, а в режиме Read-Write

Согласен полностью, не те исходники смотрел.
Начал с libraries/SD/src/utility/SdFat.h совсем не обратил внимание на сигнатуру функции open, сразу стал раскручивать seekSet, дошел до чтения блока и не встретил проверки на разрешение чтения (мож. пропустил чего), решил что скластерами не сходится.
Второй раз ошибся - дал ссылку а сам не проверил,  там как раз всё очевидно, используется иная библиотека SdFat, там то и сигнатура для open та что у ТС, и там же в чтении блока, русским по белому
// error if not open for read
if (!isOpen() || !(m_flags & F_READ)) {

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

Всем доброго времени суток.

Возникла ещё одна проблема при работе с SD картой. При пропадании питания на карте памяти периодически слетает FAT. В файловой системе появляется куча мучора в виде битых файлов и папок. Файлы с логами создаются корректно, но информация в них записывается не всегда. Надо как-то корректно закрывать файл и отключать карту при пропадании питания, возможно с дополнительным аккумулятором.

Как с минимальными затратами по памяти решить эту проблему?

p.s.: Мой скетч сейчас кушает 85%, в него планирую впихнуть обработчик двух кнопок и вывод информации на вторую страницу OLED дисплея. В перспективе нужно будет добавить обработку второго потока NMEA-0183. Есть подозрение, что Nano на всё это не хватит. 

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

собственно, в продолжение предыдущего поста возник вопрос, будет ли работать такая схема?

C1 - ионистор
VD1 - диод шотке (пока не разобрался с номиналом)
S1 - двухполярный выключатель
V1 - внешний источник питания

Во время работы устройства на пине A0 контролируется наличие напряжения, при его пропадании файл на карте SD закрывается и устройство останавливается или на какое-то время переводится в режим ожидания до появления питания на A0.

b707
Offline
Зарегистрирован: 26.05.2017

по моему схема работать не будет. Или я не понял, что вы хотите сделать.

Green
Онлайн
Зарегистрирован: 01.10.2015

Диод наоборот. И токоограничитель заряда ионистора - такой же диод с резистором параллельно.

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

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

если файл корректно не закрывать, то возможна потеря записанных данных. так же периодически на карте памяти слетает таблица FAT.

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

на аналоговом пине А0 контролируется уровень напряжения, в моём случае контролируется его наличие.

Green пишет:

Диод наоборот. И токоограничитель заряда ионистора.

по диоду согласен, исправил

подскажите пожалуйста, какой токоограничитель можно использовать. может быть для простоты обычный кондёр поставить? что-то я совсем не силён в электронике :)

Green
Онлайн
Зарегистрирован: 01.10.2015

Я написал выше.

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

Развернете диод суперкап заряжать некому будет.

            +---------------------+
источник    |   __                |   нагрузка
------------+--[__]--|>|--+--|>|--+-----------
                          |
                         ===
                          |
--------------------------+-------------------

Измеряете напряжение на нагрузке, при просадке принимаете решение о сохранении данных.

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

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

собственно, если я ни чего не напутал, получается так:

какого сопротивления будет достаточно для R1?

 

 

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

t=R*C
t - постоянная времени в секундах
R - сопротивление в омах
C - емкость в фарадах
1*220=220 секунд для того чтоб напряжение на суперкапе подросло на 63,2% от подоваемого напряжения (падение на диоде не учтено).

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

почитал статью про RC-цепь, но не понял, каким образом будет происходить защита от перезаряда. максимум, будет увеличено время заряда.

время работы устройства может быть как несколько секунд так и несколько часов. может быть стоит использовать какой-то контроллер заряда, который отключит ионистор от источника питания при достижении заданного напряжения (типа ЗУ)? 

Алексей.
Алексей. аватар
Offline
Зарегистрирован: 02.02.2018

У вас источник 5В плюс минус падение на диоде, суперкап на 5,5В о каком перезаряде идет речь?

Torn
Torn аватар
Offline
Зарегистрирован: 29.01.2019

эээ... согласен. туплю. осталось проверить реальное напряжение, выдаваемое автомобильным ЗУ для телефона. 

Спасибо за помощь!