Об инициализации структур

вот эту фразу обьясните, плиз
Откуда вообще может взяться NaN при чтении байта или инта из структуры?

Не знаю. В #54 меня озадачил @Мишутк . Может быть поэтому, а может быть @andriano в #45:

А может быть и то и другое.

я не об этом спрашивал. Чисто практически, какая операция возвращает NaN при работе с ЕЕПРОМ?
Желательно с примером кода

Вот эта процедура пишет в структуру.
Затем: Serial.println(k1.a); , Serial.println(k1.b); ... выводятся nan. Это после прошивки. Но после первой записи в структуру из программы и следующих циклов записей - чтений (#53) работает корректно.

void from_eeprom(void *x, int adress, int razmer)
{
  byte* ptr = (byte*) x;
  int adr = adress;
  for (int i = 0; i < razmer; i++)
  {
    *(ptr + i) = EEPROM.read(adr);
    adr++;
  }
}

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

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

Это очень плохая идея. В общем случае, стандарт языка трактует это как “неопределённое поведение”.

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

Что Вам мешает сразу писать и читать структуры в еепром правильно, с учётом типов элементов так, чтобы результат не зависел ни от выравнивания, ни от чего другого и всегда была стабилен? Вы не знаете как это организовать или у Вас спортивный интерес добить именно такой подход?

1 лайк

Спортивного интереса нет. Просто не знаю как это организовать.
А делаю так вот из каких соображений.
Я предполагаю, что глобальная структура расположена в памяти какой-то непрерывной лентой - последовательностью байтов. И эта последовательность имеет какой-то адрес. И с этим адресом я пытаюсть работать. Ленту отправил в EEPROM, ленту получил из неё. А каждое поле в этой структуре имеет свой адрес в этой ленте. И каждое поле “знает” о своём типе. Но, скорее всего, это не так.

Это интересно, однако думаю что проблема у ТС не в этом.
Если писать и читать в/из ЕЕПРОМ хоть и не вполне верно, но туда и обратно одинаковым образом, таких ошибок как у автора, быть не должно.

Например, используя int32_t.

В данном случае как раз хуже.
int32_t - данные вполне определенного размера, а у signed short размер является лишь общепринятой, но не официально стандартизованной величиной.
Т.е. на практике это одно и то же, а в теории signed short допускает варианты.

Вот начать следовало с того, чтобы распечатать sizeof(k1) и сравнить полученную величину с той, что получается при расчете ручками. Ну нельзя работать вслепую!

В принципе - правильно предполагаете, но не учитываете один момент:

А вот этот набор адресов (равно как полное занимаемое структурой место) зависит от реализации. Поэтому, если Вы не предприняли специальных мер для обеспечения идентичности адресов, сама собой эта идентичность не появится (точнее - как повезет).

@Optron
я вижу в этой ситуации два пути.

  1. Можно сидеть на попе и ждать, пока кто-то найдет ошибку или укажет правильный путь.
  2. А можно пойти путем дебага и найти ошибку самостоятельно. Я бы, на вашем месте, заполнил структуру такими данными, чтобы у каждого байта было бы уникальное значение. Потом прошил бы структуру USBASP-ом в ЕЕПРОМ, а в Ардуине побайтно прочитал первые 30-40 ячеек и сравнил с байтами структуры. И сразу было бы видно, что и куда пишется… или не пишется вовсе.

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

Действительно, для тривиально-копируемых типов (а Ваша структура такова), имеет место утверждение, что (позже поясню, зачем там подчёркивание)

и при этом также требуется, чтобы:

Казалось бы, в целом всё прекрасно, но грабли прячутся в деталях. А детали таковы, что первая из приведённых выше цитат относится к “object representation” (потому я и подчеркнул), а вторая относится к “value representation“. А это две большие разницы!

Различие между понятиями “object representation“ и “value representation“ дано в том же пункте стандарта, откуда взята первая цитата:

Т.е. “object representation” может содержать биты, которые не входят в “value representation“ и которые называются “биты выравнивания”. А как там и что выравнивается, это "implementation-defined” – здрасьти, девочки! Приплыли!

Ну, вот, стоило копнуть в детали, от Вашего предположения (которое я процитировал в самом начале) не осталось ничего полезного, т.к. оно верно для “object representation”, которое отличается от “value representation“ каким-то “зависимым от реализации” образом.

Что здесь можно делать? Ну, конечно, можно управлять выравниванием (что Вам тут и советуют) и добиться стабильного результата. Более того, почти все (и я тоже) обычно так и поступают. Но можно сделать так, чтобы никак не зависеть ни от какого выравнивания, сделать так. чтобы это было правильно во всех случаях. Для этого нам и дано ООП, один из принципов которого гласит: все операции над типом должны быть инкапсулированы в сам тип, а не исполняться внешними функциями. В нашем случае, Вам следует добавить к своей структуре два метода “записать в eeprom” и “прочитать из eeprom“.

Кстати, в этом случае Вы бесплатно получите ещё один ништяк - Вы не будете обязаны хранить там всю структуру целиком. Вот представьте, что у Вас лишь половина полей структуры содержит независимые данные, а вторая половина полей вычисляется из данных первой. Тогда ведь Вам достаточно хранить в ttprom только первую половину полей, а вторые вычислять после того, как вычитали из eeprom первую. Экономите место в eeprom и время на чтение оттуда.

Нужен небольшой пример?

4 лайка

Ваши примеры очень поучительны! Нужен!

лентяй…

Если @ЕвгенийП предлагает примеры, то грех этим не воспользоваться!

Ваше #65 я обязательно отработаю.

А пока проделал следующее.

  1. В програме несколко раз обменял структуру с EEPROM. Работает корректно.
  2. Перезагрузил МК. В setup() читаю структуру. Работает корректно.
  3. Программатором создал файл eeprom.eep;
  4. Через программатор залил программу. Прочитал структуру. nan-ы !!!
  5. Через программатор залил eeprom.epp. В структуру получаю верные данные.

Как вариант - в сетапе перед инициализацией структуры проверять корректность данных в EEPROM (например, чтением какого-то флага), и в случае некорректности присваивать данные по умолчанию и сохранять их в EEPROM

Залейте программатором hex файл. Считайте его в eeprom_hex.eep.

Программно “обменяйте” правильные данные с EEPROM. Считайте данные в eeprom.eep.

Сравнивайте оба файла и понимайте разницу.

1 лайк

а чего вы ждали? По умолчанию ЕЕПРОМ очищается при прошивке!

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

Вы, наверное, имеете в виду стандартную прошивку через бутлоадер. Тогда все верно - прошивка через бут не затрагивает ЕЕПРОМ.
В данной ветке другая ситуация - ТС прошивает через внешний программатор. При прошивке через программатор по умолчанию выполняется очистка всего чипа, включая бут и ЕЕПРОМ.

1 лайк

Очистку EEPROM можно отключить фьюзами вообще то.
Нужно включить фьюз EESAVE, применительно для 328 меги.

1 лайк