Как стать автором
Обновить

Комментарии 45

Как же хорошо что есть простые шедулеры, где код выполняется последовательно. Даже если запрос выполняется из разных задач ты уверен что это все равно происходит в разное время.
(0 о)
Ну вот с моей точки зрения, основная проблема работы с EEPROM — как раз недостоверность записи, а не попытка отгородиться абстракцией от этого ее неприятного свойства ;) С этой точки зрения EEPROM Manager как раз лучше. В нем можно сделать два набора методов — один синхронный, другой асинхронный, и два класса достоверности — гарантированная (транзакционная) запись, и негарантированная. Каким образом будет реализована гарантия (чексуммой, битом версии, двойной копией итд — уже не столь принципиально)

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

Идея такая, что драйвер во время записи считает и добавляет CRC к записи, потом также вычитывает и проверяет. Если, что возвращает ошибку.

Зачем постоянно читать/записывать EEPROM, особенно внешний, если он медленный и не предназначен для этого. Данные как минимум должны проверяться на корректность. Кого вы там учите.
Код совершенно простой и подходит для любой архитектуры:
typedef struct
{
    int data1;
    bool data2;
    uint8_t data3[4];
    /* ... */
    uint16_t crc;
} tConfig;

EEMEM tConfig __config;
tConfig config;

void load()
{
    uint16_t crc;
    eeprom_read_block(&__config, &config, sizeof(config));
    crc = config.crc;
    config.crc = 0;
    if (crc != crc16(&config, sizeof(config)))
    {
        /* load deafult config */
    }
}

void save()
{
    config.crc = 0;
    config.crc = crc16(&config, sizeof(config));
    eeprom_write_block(&config, &__config, sizeof(config));
}

Я и писал. Что не надо все время лазить в EEPROM, только если данные отличаются. За корректностью следит драйвер. Может CRC считать, может просто, например, инверсную копию хранить.

А кто у вас адрес параметра такой структуры в EEPROM задает? Руками? А если не хочу структуру и CRC, а хочу инверснуюу копию хранить, структуру переделывать?

Не гоже в структуре хранить зависимость от алгоритма верификации.

А если я хочу чтобы у меня за границу страницы структура не залазила? Так как 2 страницы уже 10 - 15мс на запись, с верификацией. Много, если с блокировками делать. Мне руками все структуры перелопачивать?

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

это всего лишь пример… а уж как адрес задавать — это из логики работы программы…
но чтение структуры в память всяко лучше чем извраты в статье…

Это просто пример, который нерабочий, поэтому и выглядит так, но по факту, при добавлении новой функциональности, лапшекод получится.


Адресовать и явно задавать адрес, ну или высчитывать рантайм для каждого параметра — это и есть основной недочет. Это все известно на этапе компиляции, поэтому смысла что-от делать руками нет.


Не знаю, что вас так напугало, но пользовательский код простой.


 struct tConfig
{
    int data1;
    bool data2;
    uint8_t data3[4];
} ;

inline constexpr tConfig myConfigDataDefaultValue = {10, true, {1,2,3}};
constexpr CaсhedNvData<NvVarList, tConfig , myConfigDefaultValue,  nvDriver> myConfigData;

int main()
{
   tConfig config = {1, false, {1,2,3}}
   myConfigData.Set(config);
}

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


constexpr CaсhedNvData<NvVarList, tConfig , myConfigDefaultValue, eClassification::Important, nvDriver> myConfigData;

А то, что там на С++ писано, оно один раз сделалось, написали и забыли. И всяко это дружелюбнее, чем выше предлагается.

Runtime адрес высчитываете вы в своем примере, в моём примере высчитывает компилятор. Какими ручками? Вам похоже самому ещё подучится надо.

Нет, у меня компилятор считает — где вы там рантайм увидели?
А вот у вас нет, могу предположить, что у вас в настройках линкера задается отдельный сегмент EEMEM — но опять же, все не универсально(не переносимо просто так). Это не возможности компилятора и языка — это особенность линкера.

А у вас вразрез с сегментами работает что ли ;D
NvVarList<100U, myStrData, myFloatData, myUint32Data>

Здесь начальный адрес видимо святой дух пишет.

Это к сегментам не относится, это просто начальный адрес списка параметров, можно и убрать, будет с начального адреса писать всегда c 0 в EEPROM, внешней, про которую ни линкер ни компилятор знать не знает.

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

это просто начальный адрес списка параметров

Точно так же задается и начальный адрес сохраняемой/загружаемой структуры.

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

Вы должны считать всю структуру, а не всю EPROM. Но Вы же и так ее считываете, иначе зачем эти параметры вообще хранить, если их не нужно считывать?

Если у вас таких структур 50. Для каждой нужно задать адрес?

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

Если у вас таких структур 50. Для каждой нужно задать адрес?
Но я для структуры не задаю адрес.

А вот в этой Вашей строчке число 100U для чего?
NvVarList<100U, myStrData, myFloatData, myUint32Data>

А если создать 50 таких списков с одинаковым числом или без него? Это разве не то же самое?
С другой стороны — а зачем 50 структур параметров?

Зачем иметь 50 списков?

100 можно убрать, список обычно один на все параметры. Это 100 просто для примера, если вы хотите задать начальный адрес в EEPROM, вы можете это сделать. Например, вы не хотите начинать с адреса 0. Можно вообще убрать этот параметр, и автоматом первый параметр запишется с 0, дальше адреса сами посчитаются.

Но по факту нужен только один список на все параметры, каждый параметр, может представлять из себя либо простой параметр(единицы измерения, например), либо сложный, типа структуры в примере выше. Каждый параметр при записи будет защищён либо CRC, либо инверсной/обычной копией, это уже как вы хотите и как драйвер реализует поддержку целостности.

Но доступ к параметрам идёт по отдельности. И вам не нужно для каждого из них знать адрес и задавать его. Он сам определяется на этапе компиляции.

Зачем иметь 50 списков?

А зачем иметь 50 структур? :)
Это 100 просто для примера, если вы хотите задать начальный адрес в EEPROM, вы можете это сделать.

Точно так же и со структурой. Укажете конструктору класса работы с параметрами (драйверу в Вашем варианте) адрес — будет работать с этим адресом, не укажете, будет работать с началом EPROM :)

Про 50 структур, я ниже написал.

И все равно не понял, откуда драйвер будет знать про адрес какого нибудь 75 параметра в струтуре, к которому мы по имени обращаемся. Мы всю структуру переписывать в еепром будем , при изменении этого 75 параметра или драйвер должен знать про структуру и высчитывать адрес параметра? Или структур все таки много для нескольких сгруппированных по логике параметров? Но тогда, опять же как адрес 25 структуры определить?

Структура одна для всех параметров. Драйвер сохраняет структуру как просто массив байтов :) Да, при изменении хотя бы одного параметра пересохраняется вся структура.

Да, и в этом проблема, если структура будет хранить 1000 байт, к примеру, то это 32 страницы и 320 мс для записи. Это и есть проблема — отвечать нужно быстро(скажем 10 мс на ответ) + придется пересчитывать СRC для всей структуры.

1000 байт, к примеру, то это 32 страницы и 320 мс для записи.

Или 64 страницы и 320 мс. Или 16 страниц и 80 мс. Зависит от EPROM. Я ниже там отвечал в том числе и по времени записи.
И в Вашем варианте 10 мс на ответ никак нельзя гарантировать. Если адрес параметра с CRC попадает на границу страниц EPROM, то все, не успели ответить за 10 мс :)
Кстати, а что делать если на ответ нужно не больше 5 мс? :)

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

Если надо 5мс, то, по всей видимости, нужно либо делать отложенный ответ, либо менять EEPROM, например на FRAM

50 структур, это если параметров много.

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

Т.е. чтобы записать один параметр, размером в char, как минимум придется 2 страницы еепром переписать (там где хранится параметр + там где хранится контрольная сумма). А это время. На одну страницу необходимо до 10мс. На две уже 20мс, плюс поиск адреса изменного параметра по 100 параметрам, плюс пересчет контрольной суммы по 100 параметрам. Если мое устройство работает на 1 МГц, а отвечать нужно быстро, такой способ хранения не подойдет.

Либо каким то образом рантайм высчитывать адрес переписываемого параметра

Либо хранить его рядом с параметром, как это сделано у Вас :)
Если мое устройство работает на 1 МГц, а отвечать нужно быстро, такой способ хранения не подойдет.

Очень слабое и медленное устройство, которому надо отвечать очень быстро, и при этом позволительно разбрасываться сотнями байт SRAM (хранение адресов параметров) — это весьма специфичные условия :) А, и еще целая портянка Init() для всей сотни параметров.
Я не говорю, что Ваш метод непригоден, но я не вижу в нем каких-то существенных плюсов в обычном практическом применении. Если на сохранение настроек в данный момент нельзя отвести достаточное время, значит их следует сохранить позже, когда это станет возможно. Если будет ошибка при сохранении — выдать ошибку (чего почему-то нет в Вашем варианте). Это аварийный случай и тут уже отходит на десятый план то, что пользователь изменил единицы измерения на метры, а они втихаря остались в секундах.
Ну и о времени записи. Давайте представим, что есть 20 параметров общим размером 60 байт и нужно изменить 8 из них. Постраничное сохранение структуры вызовет перезапись 1-4 страниц (в зависимости от объема EPROM и стартового адреса), а отдельное сохранение каждого параметра и их персональных CRC вызовет перезапись 8-20 страниц :)
Кроме того, хранение настроек в структуре банально удобнее и нагляднее для программиста — у структуры одно «настроечное» имя и члены, чей список вываливается в подсказке после простановки точки. А не куча переменных, каждая для своего параметра.
Очень слабое и медленное устройство, которому надо отвечать очень быстро, и при этом позволительно разбрасываться сотнями байт SRAM (хранение адресов параметров) — это весьма специфичные условия :)

Совсем нет, все низкопотребляющие устройства, или например полевые промышленные датчики давления, расходомеры, термометры и т.д., (работающие на токовой петле 4-20мА — от неё же и питаются, и потреблять должны менее 3 мА на 10 Вольтах) могут используют довольно мощные, но дешевые микроконтроллеры по ОЗУ и ПЗУ, скажем ST32L серию, но работают на низких частотах ( нужно от этих 3 мА запитать сам датчик, какой-нибудь блтуз BLE, графический индикатор, модемы для интерфейсов, скажем HART ну и так далее) поэтому частота 1 МГц, вполне себе ходовая, до ARM, использовали AVR на 462 кГц, чтобы уложиться по потреблению.


Кстати, адреса параметров в SRAM не хранятся, они вообще не хранятся нигде. Они подставляются в коде в виде конкретных чисел. Т.е. расход ОЗУ тот же самый, храним только RAM копию значения параметра.


но я не вижу в нем каких-то существенных плюсов в обычном практическом применении

Плюс тут в том,


  1. ответ можно организовать по месту, надо записать один параметр, его и пишем, 10 мс и готово, сразу выдаем ошибку, если запись не прошла.
  2. для каждого параметра не надо задавать адрес, он рассчитывается сам на этапе компиляции
  3. работает с параметрами, как с обычными переменными, про то, что это параметры находятся в EEPROM или еще где-то мы не волнуемся.
  4. Можно заменить драйвер, и вуаля, все параметры автоматом переезжают в другое место (скажем из ЕЕПРОМ на другую плату в другой процессор) или у всех параметров автоматом меняется алгоритм проверки целостности. При этом код бизнес логики не меняется вообще.
  5. Можно как угодно формировать параметры и собственно свести все к вашему варианту — все параметры в одной структуре, а можно и нет — универсальность.

Если будет ошибка при сохранении — выдать ошибку (чего почему-то нет в Вашем варианте).

Да, тут согласен не описал — но проверка целостности ложиться на плечи драйвера, также как и алгоритм обеспечения этой целостности. Можно подсовывать любой драйвер, но обращение к параметрам как было так останется.


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

Это да, согласен, но можно параметры запихать в неймспейс — будет такой же удобный через единое имя доступ :)


Давайте представим, что есть 20 параметров общим размером 60 байт и нужно изменить 8 из них. Постраничное сохранение структуры вызовет перезапись 1-4 страниц (в зависимости от объема EPROM и стартового адреса), а отдельное сохранение каждого параметра и их персональных CRC вызовет перезапись 8-20 страниц :)

Да для малого количества параметров, выигрыш небольшой, 1 страница, против 4. Это мелочи, но для 200 уже значительный.
Опять же, если мне надо записать несколько параметров, скажем 10, то их можно точно также группировать в отдельные структуры.


К пример, в HART протоколе, пользовательские команды очень атомарные — запись единиц изменения, запись верхнего диапазона, запись времени демпфирования, калибровка нуля, запись адрес опроса и т.д… Каждая команда должна записать один параметр.


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


Т.е. все зависит от приложения. Мой метод легко трансформировать в тот, что описываете вы, просто сделать структуру на все параметры и все, но тот, что описываете вы уже не так просто трансформировать под задачу, которую я вам описал. Собственно это и есть преимущество — он более универсальный.

2-4 — ничем не отличается от структуры. 1,5 — ну такие себе преимущества, очень специфичные :)
надо записать несколько параметров, скажем 10, то их можно точно также группировать в отдельные структуры

И это должен продумывать программист, так? От простой адресации мы его избавили, но заставили предусмотреть все случаи группового изменения параметров и поиграть в тетрис, укладывая их так, чтобы все эти случаи были выполнимы :)
тот, что описываете вы уже не так просто трансформировать под задачу, которую я вам описал.

Трансформировать довольно несложно, нужно просто хранить в структуре не только сами параметры, но и их адреса. Задать адреса на этапе компиляции — тоже задача элементарная.
В целом, как я и писал — Ваше решение имеет преимущества лишь для очень специфичных задач :)

Почти согласен,

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

Да, группировать может программист, может не группировать. Зависит от приложения и пользовательского интерфейса. Главное, что быстро ответили.

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

Тогда и стоило написать это как решение для конкретных условий: очень много параметров, очень быстрое сохранению любого отдельного параметра :)
Потому что для общего применения очевидной выгоды в Вашем методе как-то не видно :)

Ага согласен, не доработал, , но справедливости ради, оно ко всем случаям подходит. Но конечно решал конкретно под свой случай.

Да, но со структурой гораздо проще :)
EEMEM tConfig __config;

Не по теме, но замечу, что идентификаторы, которые начинаются с двух подчеркиваний, зарезервированы и не должны использоваться в пользовательском коде, это UB.


Spoiler header

C99 7.1.3 Reserved identifiers: "All identifiers that begin with an underscore and either an uppercase letter or another underscore are always reserved for any use."

Каким образом это приводит к неопределенному поведению, что-то вы ахинею говорите. Несколько раз перечитал спойлер, так и не понял о чем вы. Увидел только определение зарезервированных идентификаторов. У меня переменная так объявлена потому-что она используется только в спец функциях save и load. Для использования в остальном коде переменная находится в ОЗУ и называется config.
Каким образом это приводит к неопределенному поведению, что-то вы ахинею говорите. Несколько раз перечитал спойлер, так и не понял о чем вы.

Это цитата из стандарта С99. "Все идентификаторы, которые начинаются с подчеркивания и заглавной буквы или двух подчеркиваний являются зарезервированными" — т.е. их нельзя объявлять, они зарезервированы для использования компилятором или стандартной библиотекой.


Там же, далее "If the program declares or defines an identifier in a context in which it is reserved (other than as allowed by 7.1.4), or defines a reserved identifier as a macro name, the behavior is undefined." — т.е. использование таких идентификаторов приводит к неопределенному поведению (пардон, сразу не процитировал этот кусок).

Вы видимо сами не понимаете, что читаете, большой минус для программиста. Прочитайте всю главу 7 целиком, а не делайте удобные для вас вырезки сюда.

Мне, разумеется, кажется, что я вполне понимаю :) Если вам не трудно, поясните, в чем именно я не прав?


В пункте 6.2.1 Scopes of identifiers читаем "An identifier can denote an object; a function; a tag or a member of a structure, union, or enumeration; a typedef name; a label name; a macro name; or a macro parameter."


Т.е. идентификатор — это в т.ч. имя переменной. У вас __config — это имя переменной.


Весь пункт 7.1.3 описывает зарезервированные идентификаторы; идентификаторы, которые начинаются с двух подчеркиваний — зарезервированы всегда (это я уже цитировал), т.е. в любых областях видимости.
Использование зарезервированных идентификаторов в контекстах, в которых они зарезервированы — UB. Т.е. использовать два подчеркивания в начале идентификаторов нельзя вообще, в любых контекстах.


Зачем читать всю главу 7 (которая описывает стандартную библиотеку), я не очень понимаю, это вроде к делу не относится.


Я честное слово не понимаю, в чем я не прав, объясните, пожалуйста!

Побольше бы таких статей. Статей от программистов а не от войтишников.

Классная статья!

Судя по статье автор занимается обучением, а ни одного реального устройства в продакшен не запускал. Обычно в реальных девайсах используется вообще другой подход, и ничего не надо изобретать, всё описано, разжёвано и даже с примерами кода есть в аппноутах микрочипа и атмела (который тоже теперь микрочип)

Нет, вы не правы. Обучение просто хобби.

Причем тут апноут? Тут идея, как к параметрам обращаться универсально, не задумываясь, про то, как они хранятся. Конкретно, здесь в EEPROM.

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

То есть два int со значенинием по умолчанию 0 уже не прокатит?

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

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

А в следующей версии прошивки 1-й параметр будет изменен с float на char и все сохраненные параметры пойдут прахом?

Получается, что да — вы же поменяли карту памяти, и вообще структуру параметров. Но если, нужно сильно сохранить адреса, то можно убрать расчет адреса в драйвер. А здесь только рассчитывать индекс параметра. Драйвер же сам по индексу должен будет адрес высчитывать. Но тогда это будет уже в райнтайме.

В статье автор обозначает две «ортогональных» дилеммы:


  1. «Работа с настройками из единого места» против «Пишем все сразу по месту»
  2. «Отложенная запись» против «Блокировка»

Если по первому пункту, лично у меня сомнений нет — работа из единого места есть лучший вариант, то второй пункт для меня не столь очевиден и я ожидал, что автор рассмотрит эту тему в своей статье, но этого не произошло. Честно говоря у меня возникло чувство легкого когнитивного диссонанса.


Предложный уважаемым автором способ доступа к настройкам, кроме уже выше перечисленных обладает еще парой недостатков:


  1. При изменении подряд двух параметров, запись на носитель информации будет произведена дважды.
  2. Потенциально больший расход памяти: один раз память выделяется для хранения кешированных значений, и второй раз в драйвере может быть выделена память под хранение всего буфера/страницы, например для расчета контрольной суммы.

В любом случае статья очень познавательна, особенно, в части особенностей С++17, например автор использует константы для значений по-умолчанию


inline constexpr float myFloatDataDefaultValue = 10.0f;
constexpr CaсhedNvData<NvVarList, float, myFloatDataDefaultValue, nvDriver> myFloatData;

вместо


constexpr CaсhedNvData<NvVarList, float, 10.0f, nvDriver> myFloatData;

, потому что по-другому в С++17 работать не будет, если поменять описание параметра с const T& defaultValue, на T defaultValue, то компилятор выдаст ошибку a non-type template parameter cannot have type 'float' before C++20. За это автору + за статью.

Спасибо,

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

По вашим замечаниям.

  1. Да, совершенно верно, если надо изменить два параметра, то и запись будет делаться дважды. Но тут уже от пользователя зависит и приложения. Можно сгруппировать параметры, которые ожидаемо будут изменятся одним запросом, в структуру, и уже структуру использовать как параметр. В устройствах, которые я разрабатываю, интерфейс с пользователем, как раз такой, что параметров много, и они могут быть как простыми(например, установка единиц измерения - это отдельная команда, так и сложные, набор коэффициентов 2-параметрического полинома.

  2. Вторая тема, использование параметров по месту, я хотел расписать отдельно, при описании QSPI, там есть замечательный аппаратный механизм, который может использоваться как Mutex для блокировки ресурса EEPROM. Таким образом, можно обращаться к параметрам по месту, без боязни, что будет сбой.

Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.