Pull to refresh

Comments 13

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

С2_12.backup(37.2F); 
SuperComplexParameter.backup({1,10.f, tSring6"Hello"});
B1_1.backup(tString6{"Hello"});
LcdContarst.backup(50);  //Пишем в EERPOM Один параметр c контрольной суммой

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

using tParams1 = NvVarList1 <С2_12, B1_1, LcdConrast>;
tParams1::get(0).set(37.2F); //Запись в ОЗУ
tParams1::get(2).set(50);
tParams1::backup() -  //пишем в EEPROM сразу все параметры с контрольной суммой для списка

Можно сделать несколько таких списков и делать ссылку друг на друга

using tParams = NvVarList <С2_12, B1_1, LcdConrast, Params1>;
Params1::get(0).set(10);
tParams::get(0).set(37.2F); //Запись в ОЗУ
tParams::get(2).set(50);
tParams::backup() -  //пишем в EEPROM сразу все вместе со списком Params1

Но это правда на С++, зато там нет никаких указателей.

Если что описание параметра будет выглядеть как то так:

constexpr CachedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;

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

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

Мне кажется, вот это вот момент:

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

таит в себе огромную опасность. А что, если питание внезапно выключили? А что, если питание выключили посреди записи структуры?
Кажется, что в структуре нужно добавить поле count, и когда возникает необходимость записать структуру, наращивать count и записывать структуру рядом. При чтении считывать все доступные целые структуры, и выбирать экземпляр с максимальным (по соответствующему модулю, конечно( count. Естественно, не забывать стирать их по кругу, оставляя, например, 3 экземпляра в памяти.
Но вот как решать проблему с тем, что при изменении одного параметра надо всю структуру переписать (изнашивая память), остаётся вопросом.

В общем как ни крути, а без файловой системы с журналированием и контролем четности в микроконтроллере никуда…

А если задуматься о безопасном удалённом обновлении прошивки устройства, вообще оторопь берёт.

Для этого есть целый гайдлайны от производителей микроконтроллеров. И там обычно описывается варианты rotating записи в EEPROM. И сделано это вполне из осознанных вещей. У большинства EEPROM на рынке, плюс-минус количество гарантированных циклов перезаписи ячеек порядка 100000. Есть правда и high endurance варианты - но ценник выше. И этого может быть как много - так и мало, в зависимости от приложения (может оно раз в месяц будет обновлять ячейку,) а вот если обновлять раз в секунду - то пичалька наступит быстро - через примерно 28 часов работы устройства . А если EEPROM вшит в кристалл контроллера - ну как то будет жалко. Поэтому существуют множество вариантов как работать с EEPROM - как минимум используя кольцевую запись структур данных, создавая rotating EEPROM библиотеки, типа таких https://tinkerman.cat/post/eeprom-rotation-for-esp8266-and-esp32. Ну и плюсом будет, целосность данных - при частичной записи последний записаный вариант будет поврежден и будет взят предыдущий.

UFO just landed and posted this here

Обычно в проектах на МК использую вариант:


#define SETTING_ADDR 0xROMADDRESS
#define SETTING_KEY 0xRANDOMKEY

struct myParamStruct {
    uint32_t key;
    size_t size;
    struct param_pam_pam;
    uint32_t crc;
} paramSet;

settings_read(&param_pam_pam);
serrings_write(param_pam_pam);

В функции settings_read происходит чтение памяти по указанному адресу хранения настроек, после чего вычитывается размер записанных данных, этот размер сравнивается с sizeof(param_pam_pam) если равен — то сверяем CRC и если все проверки пройдены — выгружаем себе в работу param_pam_pam.
Если что то пошло не так, например размер или CRC не сходится — грузим дефолтные настройки в param_pam_pam и вызываем serrings_write(param_pam_pam);
В param_pam_pam можем так же делать структуры из структур.
Даже школьник разберется

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

В противовес иерархической структуре можно использовать плоскую таблицу с парой полей parent - child.
Да, проход по таблице займёт больше тактов чем по ветвям дерева. Но разница в этих расхода несущественны при количестве параметров в районе сотен. Но при этом сильно упростится автогенерация сорсов с таблицей параметров.
Вообще кардинальное решение - не писать вручную организацию и объявление параметров в исходниках. Тогда совершенно не будет важно представлены они деревом или простой структурой.
Параметры удобно создавать в специализированной утилите с GUI на PC. После чего утилита автоматически генерирует исходники для объявления, управления и представления параметров.

При чем ведь надо ещё помнить что параметры часто должны быть доступны для просмотра и редактирования из различных интерфейсов: через терминал, через WEB, через IoT протоколы типа MQTT... И тогда приходит идея хранить все в JSON, как самом гибком для структур данных. А потом этот формат транслировать в структуру на С для доступа из программы, и в чистом виде пересылать, если надо сделать WEB интерфейс.
Сам JSON в сжатом виде хранить во Flash микроконтроллера. Для этого даже специальная файловая система придумана

Для количества данных в тысячи и более уникальных записей применяют уже настоящие базы данных типа SQLite и полнофункциональную файловую систему FAT32 на uSD карте или eMMC чипе. Где-то видел портированную версию SQLite под STM32. Но не могу вспомнить где.

Целостность обеспечивается контрольной суммой

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

Странно, что обычно никто не вспоминает о такой классной штуке, как ОЗУ с батарейкой типа DS1230. Если у контроллера есть полноценная параллельная шина (адрес и данные), то это прямо идеальный вариант. Пока на ОЗУ поступает питание, она читается и пишется обычными обращениями по шине без всяких SPI или I2C, т е. просто обращаемся по указателю в нужный адрес. Как только питание пропадает, включается встроенная батарейка. Гарантированный срок хранения данных - 10 лет, на практике намного больше. И никаких проблем с долгим стиранием и ограничениями на циклы перезаписи.

Sign up to leave a comment.