Почему EEPROM "кривится"?

Nano с чипом 328P, в скетче задействовано хранение данных в eeprom. По непонятным мне причинам, при компиляции и загрузки содержимое eeprom может “самостоятельно” измениться. Именно после загрузки. Изменение данных происходит не постоянно, не периодично, а хаотично.

Логи от двух скетчей - full и short. В полном скетче никаких дополнительных событий не происходит, после выполнения setup стоит, ждёт события от энкодера. Короткий скетч - это урезанный setup от полного. В части общения с eeprom скетчи идентичны. Короткий делал в надежде отловить ошибку.

Функции чтения/записи eeprom и вывода в Serial вынес в отдельный h-файл чтобы скетчи использовали единую версию.

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

При неожидаемых изменениях, по логам в Serial, события записи в eeprom отсутствуют.

Где-то сильно я наколбасил, а вот где… не понятно. Посмотрите, если кому не лень. Может найдётся мой косяк?

__Short.ino

Спойлер
#include <eeProm_.h>

//*********************//
void setup() {
  Serial.begin(115200);
  Serial.println("\nGo... short");
  
  //  если метки нет/искажена, то запись метки и ключей в EEPROM
  if (eeReadDefMarker() == DEF_MARKER) {
    Serial.println(F("Erase..."));
    eeClearROM();
    if (eeWriteDefMarker()) eeWriteROM();
    else {
      Serial.println(F("Error write <DefMarker>..."));
      while (1) {};
    }
  }
  eeReadROM();
}

//*********************//
void loop() {}

//*********************//

eeProm_.h

Спойлер
/*****************/
/*   eeProm_.h   */
/*****************/

#pragma once

//#include <Arduino.h>
#include <EEPROM.h>

struct eeprom_t {
  uint8_t key[8];
  char note[8];
};

const uint8_t eeDefMarkerAddress = 14;                                        // defMarker == 0x1812
constexpr uint8_t eeFirstAddress = eeDefMarkerAddress + sizeof(uint16_t);     //
const uint8_t eeCountKeys = 4;                                                //
constexpr uint8_t eeSizeKey = sizeof(eeprom_t);                               //
constexpr uint8_t eeMaxAddress = (eeFirstAddress + eeSizeKey * eeCountKeys);  //
eeprom_t data_rom[eeCountKeys];
const uint16_t DEF_MARKER = 0x1812U;

const uint8_t keys_main[4][8] = {
  { 0x01, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x1A },
  { 0x02, 0x02, 0x02, 0x02, 0x02, 0x00, 0x00, 0x2A },
  { 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x00, 0x3A },
  { 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x00, 0x4A }
};

const char* keys_name[eeCountKeys] = { "key_01_", "key_02_", "key_03_", "key_04_" };

//*********************//
void serial_eeprom_key(const uint8_t* key, bool lineFeed = true);
void serial_eeprom_item(uint8_t addr, const eeprom_t& ee_data_rom);
void serial_eeprom_item_hex(uint8_t addr, const eeprom_t& ee_data_rom);
void eeSerialAddr();
uint16_t eeReadDefMarker();
bool eeWriteDefMarker();
uint8_t eeCalcAddress(uint8_t idx);
void eeClearROM();
void eeReadROM();
void eeWriteROM();

//*********************//
//                     //
//*********************//
void serial_eeprom_key(const uint8_t* key, bool lineFeed) {
  for (uint8_t i = 0; i < 8; i++) {
    if (key[i] < 16) Serial.print(F("0"));
    Serial.print(key[i], HEX);
    if (i < 7) Serial.print(F(":"));
  }
  if (lineFeed) Serial.println();
}

//*********************//
void serial_eeprom_item(uint8_t addr, const eeprom_t& ee_data_rom) {
  Serial.print(addr);
  Serial.print(F(" "));
  for (uint8_t i = 0; i < 8; i++) Serial.printf("%c", ee_data_rom.note[i]);
  Serial.print(F(" "));
  serial_eeprom_key(ee_data_rom.key);
}

//*********************//
void serial_eeprom_item_hex(uint8_t addr, const eeprom_t& ee_data_rom) {
  Serial.printf("%2d ", addr);
  serial_eeprom_key((uint8_t*)ee_data_rom.note, false);
  Serial.print(F(" "));
  serial_eeprom_key(ee_data_rom.key);
}

//*********************//
uint16_t eeReadDefMarker() {
  uint16_t _1812;
  EEPROM.get(eeDefMarkerAddress, _1812);
  return _1812;
}

//*********************//
bool eeWriteDefMarker() {
  Serial.printf("write Marker:\n%2d 0x%04X\n", eeDefMarkerAddress, DEF_MARKER);
  EEPROM.put(eeDefMarkerAddress, DEF_MARKER);
  return eeReadDefMarker() == DEF_MARKER;
}

//*********************//
uint8_t eeCalcAddress(uint8_t idx) {
  return eeFirstAddress + (eeSizeKey * idx);
}

//*********************//
void eeClearROM() {
  const uint32_t zero = 0xFFFFFFFF;
  uint8_t idx = 0;
  Serial.println("eeClearROM:");
  for (uint8_t i = 0; i < eeMaxAddress; i += sizeof(zero)) {
    EEPROM.put(i, zero);
    if (idx++ == 0) Serial.printf("%2d", i);
    Serial.print(" 0x");
    Serial.print(zero, HEX);
    if (idx == 4) {
      idx = 0;
      Serial.println();
    }
  }
}

//*********************//
void eeReadROM() {
  uint8_t addr, i;
  Serial.println(F("eeReadROM:"));
  Serial.printf("%2d Marker  0x%04X\n", eeDefMarkerAddress, eeReadDefMarker());

  for (i = 0; i < eeCountKeys; i++) {  //  чтение в data_rom
    addr = eeCalcAddress(i);
    EEPROM.get(addr, data_rom[i]);
    serial_eeprom_item(addr, data_rom[i]);
  }

  eeprom_t _ee;
  for (i = 0; i < eeMaxAddress; i += eeSizeKey) {  //  чтение в _ee и вывод для перестраховки
    EEPROM.get(i, _ee);
    serial_eeprom_item_hex(i, _ee);
  }
}

//*********************//
void eeWriteROM() {
  uint8_t addr;
  eeprom_t ee;
  Serial.println(F("eeWriteROM:"));
  memset(ee.key, 0, 8);
  memset(ee.note, '\0', 8);
  for (uint8_t i = 0; i < eeCountKeys; i++) {
    addr = eeCalcAddress(i);
    memcpy(ee.key, keys_main[i], 8);
    memcpy(ee.note, keys_name[i], 7);
    EEPROM.put(addr, ee);
    serial_eeprom_item(addr, ee);
  }
}

//*********************//

/*** 01 ***/

Спойлер

/*** 01 ***/
Исходное содержимое при if (eeReadDefMarker() != DEF_MARKER) {
---- Открыт последовательный порт COM3 ----

Go… full
eeReadROM:
14 Marker 0x1812
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:12:18 FF:FF:FF:FF:FF:FF:FF:FF
16 6B:65:79:5F:30:31:5F:00 01:01:01:01:01:00:00:1A
32 6B:65:79:5F:30:32:5F:00 02:02:02:02:02:00:00:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A
---- Закрыт последовательный порт COM3 ----
Изменение на if (eeReadDefMarker() == DEF_MARKER) {
т.е. должен всё потереть и записать новое, т.к. Marker == 0x1812
Компиляция и загрузка
---- Открыт последовательный порт COM3 ----

Go… full
eeReadROM:
14 Marker 0xFFFF
16 ÿÿÿÿÿÿÿÿ FF:FF:FF:FF:FF:FF:FF:FF
32 key_02_ FF:FF:FF:FF:FF:FF:FF:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
16 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
32 6B:65:79:5F:30:32:5F:00 FF:FF:FF:FF:FF:FF:FF:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A
---- Закрыт последовательный порт COM3 ----
По логам записи в EEPROM нет, но содержимое покорёжено.
Закрыл “полную”, запустил “короткую” версию. см. ниже

/*** 02 ***/

Спойлер

/*** 02 ***/
Продолжение в “короткой”
Компиляция и загрузка при if (eeReadDefMarker() == DEF_MARKER) {
содержимое не изменилось.
---- Открыт последовательный порт COM3 ----

Go… short
eeReadROM:
14 Marker 0xFFFF
16 ÿÿÿÿÿÿÿÿ FF:FF:FF:FF:FF:FF:FF:FF
32 key_02_ FF:FF:FF:FF:FF:FF:FF:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
16 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
32 6B:65:79:5F:30:32:5F:00 FF:FF:FF:FF:FF:FF:FF:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A
---- Закрыт последовательный порт COM3 ----
Изменяю условие
if (eeReadDefMarker() == DEF_MARKER) {
на
if (eeReadDefMarker() != DEF_MARKER) {
т.е. должен всё потереть и записать новое, т.к. Marker != 0x1812
Компиляция и загрузка
---- Открыт последовательный порт COM3 ----

Go… short
eeReadROM:
14 Marker 0x1812
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:12:18 FF:FF:FF:FF:FF:FF:FF:FF
16 6B:65:79:5F:30:31:5F:00 01:01:01:01:01:00:00:1A
32 6B:65:79:5F:30:32:5F:00 02:02:02:02:02:00:00:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A
---- Закрыт последовательный порт COM3 ----
В логах нет и следа записи, но содержимое пришло в норму.

/*** 03 ***/

Спойлер

/*** 03 ***/

IDE 2.3.2 несколько компиляций и загрузок подряд
при if (eeReadDefMarker() == DEF_MARKER) {
Тут всё правильно - идёт запись в eeprom.

Go… short
Erase…
eeClearROM:
0 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
16 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
32 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
48 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
64 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
write Marker:
14 0x1812
eeWriteROM:
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
eeReadROM:
14 Marker 0x1812
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:12:18 FF:FF:FF:FF:FF:FF:FF:FF
16 6B:65:79:5F:30:31:5F:00 01:01:01:01:01:00:00:1A
32 6B:65:79:5F:30:32:5F:00 02:02:02:02:02:00:00:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A

Условие осталось прежним:
if (eeReadDefMarker() == DEF_MARKER) {
после очередной компиляции и загрузки получил

Go… short
eeReadROM:
14 Marker 0xFFFF
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
16 6B:65:79:5F:30:31:5F:00 01:01:01:01:01:00:00:1A
32 6B:65:79:5F:30:32:5F:00 02:02:02:02:02:00:00:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A
По логам записи нет, а Marker стал равен 0xFFFF

/*** 04 ***/

Спойлер

/*** 04 ***/
Исходное содержимое
---- Открыт последовательный порт COM3 ----

Go… full
eeReadROM:
14 Marker 0x1812
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:12:18 FF:FF:FF:FF:FF:FF:FF:FF
16 6B:65:79:5F:30:31:5F:00 01:01:01:01:01:00:00:1A
32 6B:65:79:5F:30:32:5F:00 02:02:02:02:02:00:00:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A
---- Закрыт последовательный порт COM3 ----
N-ая компиляция и загрузка при условии
if (eeReadDefMarker() != DEF_MARKER) {
привела к
---- Открыт последовательный порт COM3 ----

Go… full
eeReadROM:
14 Marker 0xFFFF
16 ÿÿÿÿÿÿÿÿ FF:FF:FF:FF:FF:FF:FF:FF
32 ÿÿÿÿÿÿÿÿ FF:FF:FF:FF:FF:FF:FF:FF
48 ÿÿÿÿÿÿÿÿ FF:FF:FF:FF:FF:FF:FF:FF
64 key_04_ FF:FF:FF:04:04:00:00:4A
0 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
16 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
32 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
48 FF:FF:FF:FF:FF:FF:FF:FF FF:FF:FF:FF:FF:FF:FF:FF
64 6B:65:79:5F:30:34:5F:00 FF:FF:FF:04:04:00:00:4A
---- Закрыт последовательный порт COM3 ----
Опять искажения.

Данных же не так много, почему не отступаешь между адресами, а прям «друг за другом» пишешь?

В логах упоминается это условие, по __Short.ino будет со строки #9

Спойлер
if (eeReadDefMarker() == DEF_MARKER) {
    Serial.println(F("Erase..."));
    eeClearROM();
    if (eeWriteDefMarker()) eeWriteROM();
    else {
      Serial.println(F("Error write <DefMarker>..."));
      while (1) {};
    }
  }

В “лучшем” случае это замаскирует ошибку. А она-то есть. И надо её найти и прибить.

А вот этот “y с точками” - какой там аскикод?
И, я бы перед записью блока в eeprom выводил в сериал, а потом сразу же этот блок читал и выводил опять в сериал. Это поможет понять - из RAM порченное валит или МК шалит.

Я, конечно, ставлю на то, что где-то в RAM однл на другое наезжает.

1 лайк

Не понял о каком месте “y с точками” идёт речь. Где/что смотреть?

Тут, например.

Второй вывод, например:

0xFF


Левый столбец это адреса. С 16 по 64 верх и с 0 по 64 низ.

Надо отсекать баги рама от багов еепрома.

При “искривлении” eeprom’а код не входит в режим записи. Изменения данных по адресу(ам) 14 и/или 15 происходят в какой-то момент времени до выполнения if. Предполагаю, что при загрузке кода в mcu.
О записи в логах нет ни слова. Сейчас поставил дополнительный вывод при записи. Но увы…

Очередной

Условие: if (eeReadDefMarker() == DEF_MARKER) {…}
Go… short
Erase…
eeClearROM:
0 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
16 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
32 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
48 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF
64 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF 0xFFFFFFFF

into eeWriteDefMarker():write:0x1812
into eeWriteDefMarker():read :0x1812; const: 0x1812

eeWriteROM:
before - сформированная структура до put
after - структура обнулена, затем get в неё
before: 16 key_01_ 01:01:01:01:01:00:00:1A
after : 16 key_01_ 01:01:01:01:01:00:00:1A
before: 32 key_02_ 02:02:02:02:02:00:00:2A
after : 32 key_02_ 02:02:02:02:02:00:00:2A
before: 48 key_03_ 03:03:03:03:03:00:00:3A
after : 48 key_03_ 03:03:03:03:03:00:00:3A
before: 64 key_04_ 04:04:04:04:04:00:00:4A
after : 64 key_04_ 04:04:04:04:04:00:00:4A

eeReadROM:
14 Marker 0x1812
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A

Read 80 bytes in HEX:
0 FF:FF:FF:FF:FF:FF:12:18 FF:FF:FF:FF:FF:FF:FF:FF
16 6B:65:79:5F:30:31:5F:00 01:01:01:01:01:00:00:1A
32 6B:65:79:5F:30:32:5F:00 02:02:02:02:02:00:00:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A

Условие не изменялось: if (eeReadDefMarker() == DEF_MARKER) {…}
Компиляция и загрузка

Go… short
eeReadROM:
14 Marker 0x18FF - изменился!
16 key_01_ 01:01:01:01:01:00:00:1A
32 key_02_ 02:02:02:02:02:00:00:2A
48 key_03_ 03:03:03:03:03:00:00:3A
64 key_04_ 04:04:04:04:04:00:00:4A

Read 80 bytes in HEX:
0 FF:FF:FF:FF:FF:FF:FF:18 FF:FF:FF:FF:FF:FF:FF:FF
16 6B:65:79:5F:30:31:5F:00 01:01:01:01:01:00:00:1A
32 6B:65:79:5F:30:32:5F:00 02:02:02:02:02:00:00:2A
48 6B:65:79:5F:30:33:5F:00 03:03:03:03:03:00:00:3A
64 6B:65:79:5F:30:34:5F:00 04:04:04:04:04:00:00:4A

По адресу 14 (2 байта) теперь появилось 0x18FF, что не равно 0x1812, а в if должен быть равен, чтобы начать запись.

У меня было похожее поведение на PIC, EEPROM рассыпался при попытке его читать из-за низкого напряжения питания. В datasheet на 328P есть такой фрагмент.

Спойлер

12.4.2. Preventing EEPROM Corruption
During periods of low VCC, the EEPROM data can be corrupted because the supply voltage is too low for
the CPU and the EEPROM to operate properly. These issues are the same as for board level systems
using EEPROM, and the same design solutions should be applied.
An EEPROM data corruption can be caused by two situations when the voltage is too low. First, a regular
write sequence to the EEPROM requires a minimum voltage to operate correctly. Secondly, the CPU itself
can execute instructions incorrectly, if the supply voltage is too low.
EEPROM data corruption can easily be avoided by following this design recommendation:
Keep the AVR RESET active (low) during periods of insufficient power supply voltage. This can be done
by enabling the internal Brown-out Detector (BOD). If the detection level of the internal BOD does not
match the needed detection level, an external low VCC reset Protection circuit can be used. If a reset
occurs while a write operation is in progress, the write operation will be completed provided that the
power supply voltage is sufficient.

Проверьте питание, возможно ваша проблема в этом.

1 лайк

Питается от USB компьютера. На днях замерял, мультиметр показал 4.94v. Другого оборудования нет.

Ядро может запуститься уже от 1,8 вольта и если код чтения EEPROM находится в самом начале то к моменту чтения напряжение питания может еще не достигнуть приемлемого уровня. Варианты лечения BOD или пауза в начале программы. Можно еще попытаться замерить напряжение питания, гуглить “секретный вольтметр”.

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

Это не опция, это настройка мк и она «зашита» во fuse-bit. При прошивке из ардуино иде fuse-bit не изменяются.

Это в ядре miniCore