EEPROM

EEPROM в микроконтроллерах

EEPROM в микроконтроллерах

1. Физические основы и архитектура

1.1. Принцип хранения заряда 
EEPROM — это энергонезависимая память, основанная на поплавковом затворе (Floating Gate) МОП-транзистора (FAMOS — Floating-gate Avalanche-injection MOSFET).

  • Структура: Между каналом транзистора и управляющим затвором находится изолированный проводящий затвор (поплавковый), окруженный диэлектриком (обычно SiO₂).

  • Запись (Программирование — установка в «0»): На управляющий затвор подается высокое напряжение (~12-20 В, генерируемое внутренним инвертором). Электроны туннелируют через тонкий диэлектрик (Фаулера-Нордхейма) или инжектируются с горячими носителями на поплавковый затвор. Это повышает пороговое напряжение транзистора. При чтении нормальным напряжением такой транзистор не открывается — считывается «0».

  • Стирание (Установка в «1»): На исток или канал подается высокое напряжение, а затвор заземляется. Электроны туннелируют с поплавкового затвора. Пороговое напряжение снижается, транзистор открывается при чтении — считывается «1».

В микроконтроллерах AVR (ATmega328P) EEPROM стирается и записывается по байту, в отличие от флеш-памяти, где операция идет страницами.

1.2. Технологические пределы и износ 
Деградация диэлектрика при многократных циклах туннелирования — ключевой фактор ограничения.

  • Механизм износа: Каждый цикл туннелирования повреждает диэлектрик, создавая транспортируемые состояния (trap states). Это приводит к:

    1. Утечке заряда (Data Retention Loss): Сохраненный заряд стекает быстрее.

    2. Сдвигу порогового напряжения.

    3. Росту тока утечки (Standby Current).

  • Предел циклов: Гарантированный производителем минимум — 100 000 циклов записи/стирания на ячейку для AVR. Типичные значения могут достигать 1 000 000, но рассчитывать на это в проекте нельзя.


2. Математические модели и алгоритмы повышения надежности

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

Пусть P_s(N) — вероятность выживания ячейки после N циклов. Для оценки: 
P_s(N) ≈ exp( - (N / N_0)^β ), где:

  • N_0 — характеристическое число циклов (порядка 100 000),

  • β — параметр формы (для диэлектрического износа > 1).

Это означает, что после 100 000 циклов значительная часть ячеек еще жива, но риск отказа растет суперлинейно.

2.2. Алгоритм выравнивания износа (Wear Leveling)

Простейший метод — циклическая запись (Circular Buffer / Wear-Leveling Buffer).

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

Решение: Выделяем в EEPROM массив из M ячеек (M > D). Каждая запись происходит в следующую ячейку. При чтении ищем последнюю записанную версию.

  • Индекс записи (Write Index, wi) хранится отдельно (например, в последних байтах EEPROM).

  • Формула позиции для данных: 
    Address(data_byte_j) = (wi * D) + j, если рассматривать массив как матрицу размером M x D. 
    Более эффективно: использовать заголовок записи с меткой (timestamp/crc).

Пример на псевдокоде для хранения одного int с выравниванием износа:

cpp 
#
	
	
	
	define
	
	
	
	 
	
	EEPROM_SIZE
	
	 
	
	
	
	256
	
	
	
	   
	
	
	
	// Общий размер EEPROM
	
	
	
	
#
	
	
	
	define
	
	
	
	 
	
	SLOT_SIZE
	
	 
	
	
	
	sizeof
	
	
	
	(
	
	
	
	int
	
	
	
	)
	
	
	
	 
	
	
	
	+
	
	
	
	 
	
	
	
	2
	
	
	
	 
	
	
	
	// Данные + заголовок (CRC, статус)
	
	
	
	
#
	
	
	
	define
	
	
	
	 
	
	NUM_SLOTS
	
	 
	
	
	
	(
	
	
	
	EEPROM_SIZE 
	
	
	
	/
	
	
	
	 SLOT_SIZE
	
	
	
	)
	
	
	
	 
	
	
	
	// Число слотов
	
	
	
	

struct
	
	
	
	 Slot
	
	
	
	 {
	
	
	
	
  int
	
	
	
	 data;
	
	
	
	
  uint16_t
	
	
	
	 crc;
	
	
	
	 // Контрольная сумма для проверки целостности
	
	
	
	
};
	
	
	
	

int
	
	
	
	 wearLevelingRead
	
	
	
	()
	
	
	
	 {
	
	
	
	
  for
	
	
	
	 (
	
	
	
	int
	
	
	
	 i =
	
	
	
	 NUM_SLOTS -
	
	
	
	 1
	
	
	
	;
	
	
	
	 i >=
	
	
	
	 0
	
	
	
	;
	
	
	
	 i--
	
	
	
	)
	
	
	
	 {
	
	
	
	
    Slot s;
	
	
	
	
    EEPROM.
	
	
	
	get
	
	
	
	(
	
	
	
	i *
	
	
	
	 SLOT_SIZE,
	
	
	
	 s);
	
	
	
	
    if
	
	
	
	 (
	
	
	
	calculateCRC
	
	
	
	(
	
	
	
	s.
	
	
	
	data)
	
	
	
	 ==
	
	
	
	 s.
	
	
	
	crc)
	
	
	
	 {
	
	
	
	 // Функция проверки CRC
	
	
	
	
      return
	
	
	
	 s.
	
	
	
	data;
	
	
	
	 // Нашли последние валидные данные
	
	
	
	
    }
	
	
	
	
  }
	
	
	
	
  return
	
	
	
	 0
	
	
	
	;
	
	
	
	 // Данные не найдены
	
	
	
	
}
	
	
	
	

void
	
	
	
	 wearLevelingWrite
	
	
	
	(
	
	
	
	int
	
	
	
	 newData)
	
	
	
	 {
	
	
	
	
  static
	
	
	
	 uint8_t
	
	
	
	 currentSlot =
	
	
	
	 0
	
	
	
	;
	
	
	
	
  Slot s;
	
	
	
	
  s.
	
	
	
	data =
	
	
	
	 newData;
	
	
	
	
  s.
	
	
	
	crc =
	
	
	
	 calculateCRC
	
	
	
	(
	
	
	
	newData);
	
	
	
	
  
  EEPROM.
	
	
	
	put
	
	
	
	(
	
	
	
	currentSlot *
	
	
	
	 SLOT_SIZE,
	
	
	
	 s);
	
	
	
	
  currentSlot =
	
	
	
	 (
	
	
	
	currentSlot +
	
	
	
	 1
	
	
	
	)
	
	
	
	 %
	
	
	
	 NUM_SLOTS;
	
	
	
	 // Переход к следующему слоту
	
	
	
	
}
	
	
	
	

Коэффициент увеличения срока службы: 
Wear_Leveling_Factor = M (количество слотов). 
Если M = 10, то срок службы увеличивается примерно в 10 раз. Однако, чтение усложняется (необходим поиск последнего валидного слота).

2.3. Модель общего времени жизни системы 
Срок службы EEPROM в проекте можно оценить.

T_life = (N_max * M) / f_update, где:

  • N_max — максимальное число циклов для ячейки (100 000),

  • M — количество ячеек в алгоритме выравнивания износа,

  • f_update — частота обновления данных (записей в день/месяц).

Пример: Мы записываем температуру раз в час в ATmega328P (EEPROM 1Кб ≈ 1024 байта). Используем wear-leveling на всех ячейках для хранения одного float (4 байта + 2 байта заголовок = 6 байт на слот). M ≈ 1024 / 6 ≈ 170 слотов.

T_life = (100 000 * 170) / (24 записи/день) ≈ 708 333 дня ≈ 1940 лет.

Вывод: при грамотном wear-leveling EEPROM хватит на любой проект.


3. Практические аспекты, библиотеки и формулы расчета

3.1. Библиотека EEPROM.h в Arduino IDE 
Предоставляет простой доступ:

cpp 
#
	
	
	
	include
	
	
	
	 
	
	
	
	<EEPROM.h>
	
	
	
	

uint8_t
	
	
	
	 val =
	
	
	
	 EEPROM.
	
	
	
	read
	
	
	
	(
	
	
	
	address);
	
	
	
	      // Чтение байта
	
	
	
	
EEPROM.
	
	
	
	write
	
	
	
	(
	
	
	
	address,
	
	
	
	 val);
	
	
	
	              // Запись байта (с задержкой до 3.3 мс!)
	
	
	
	
EEPROM.
	
	
	
	put
	
	
	
	(
	
	
	
	address,
	
	
	
	 object);
	
	
	
	             // Безопасная запись любого типа данных
	
	
	
	
EEPROM.
	
	
	
	get
	
	
	
	(
	
	
	
	address,
	
	
	
	 object);
	
	
	
	             // Чтение любого типа данных
	
	
	
	
EEPROM.
	
	
	
	update
	
	
	
	(
	
	
	
	address,
	
	
	
	 val);
	
	
	
	             // Умная запись: записывает только если значение изменилось
	
	
	
	

Критически важно: Функция write вызывает физическую запись даже если записываемое значение совпадает с текущим! Это бесполезно расходует ресурс. Всегда используйте update().

3.2. Время доступа и энергопотребление

  • Время записи: ~3.3 мс на байт для ATmega328P. В течение этого времени питание должно быть стабильным! Резкий сброс во время записи может привести к повреждению данных и, в редких случаях, самой ячейки.

  • Мощность: Ток потребления во время записи значительно выше. Формула дополнительной энергии на запись: 
    E_write = Vcc * I_write * t_write. 
    Для ATmega328P при Vcc=5В, I_write≈10мА, t_write=0.0033с: E_write ≈ 165 мкДж на байт. Для батарейных систем это существенно.

3.3. Контроль целостности данных: CRC (Cyclic Redundancy Check) 
Простая проверочная сумма неэффективна для обнаружения многобитных ошибок. CRC — стандарт де-факто.

Формула расчета CRC (упрощенно): Обработка данных как коэффициентов полинома и деление на образующий полином. Остаток — CRC. 
Популярный полином для встроенных систем: CRC-16-CCITT (0x1021).

Пример использования:

cpp 
uint16_t
	
	
	
	 calculateCRC16
	
	
	
	(
	
	
	
	const
	
	
	
	 byte*
	
	
	
	 data,
	
	
	
	 size_t length)
	
	
	
	 {
	
	
	
	
  uint16_t
	
	
	
	 crc =
	
	
	
	 0xFFFF
	
	
	
	;
	
	
	
	
  for
	
	
	
	 (
	
	
	
	size_t i =
	
	
	
	 0
	
	
	
	;
	
	
	
	 i <
	
	
	
	 length;
	
	
	
	 ++
	
	
	
	i)
	
	
	
	 {
	
	
	
	
    crc ^=
	
	
	
	 (
	
	
	
	uint16_t
	
	
	
	)
	
	
	
	data[
	
	
	
	i]
	
	
	
	 <<
	
	
	
	 8
	
	
	
	;
	
	
	
	
    for
	
	
	
	 (
	
	
	
	uint8_t
	
	
	
	 bit =
	
	
	
	 0
	
	
	
	;
	
	
	
	 bit <
	
	
	
	 8
	
	
	
	;
	
	
	
	 ++
	
	
	
	bit)
	
	
	
	 {
	
	
	
	
      if
	
	
	
	 (
	
	
	
	crc &
	
	
	
	 0x8000
	
	
	
	)
	
	
	
	 crc =
	
	
	
	 (
	
	
	
	crc <<
	
	
	
	 1
	
	
	
	)
	
	
	
	 ^
	
	
	
	 0x1021
	
	
	
	;
	
	
	
	
      else
	
	
	
	 crc =
	
	
	
	 crc <<
	
	
	
	 1
	
	
	
	;
	
	
	
	
    }
	
	
	
	
  }
	
	
	
	
  return
	
	
	
	 crc;
	
	
	
	
}
	
	
	
	
// При записи структуры:
	
	
	
	
struct
	
	
	
	 Data
	
	
	
	 {
	
	
	
	
  int
	
	
	
	 sensorValue;
	
	
	
	
  float
	
	
	
	 temperature;
	
	
	
	
  uint16_t
	
	
	
	 crc;
	
	
	
	 // Рассчитывается по первым двум полям
	
	
	
	
};
	
	
	
	

3.4. Выбор размера EEPROM для проекта 
Оценочная формула:

EEPROM_needed = N_vars * (S_var + S_meta) * WL_factor, где:

  • N_vars — количество часто обновляемых переменных,

  • S_var — размер переменной (байт),

  • S_meta — размер метаданных (CRC, штамп времени, статус) (~2-10 байт),

  • WL_factor — коэффициент wear-leveling (рекомендуется >= 10 для частой записи).


4. Сравнение технологий в контексте Arduino

 
 
ПараметрEEPROM (внутренняя)Флеш (PROGMEM)FRAM (например, MB85RC)
Механизм записиТуннелирование Фаулера-НордхеймаИнжекция горячих носителейИзменение поляризации сегнетоэлектрика
ЭнергопотреблениеВысокое при записиОчень высокое при записиКрайне низкое
Время записи~3.3 мс/байт~3-5 мс/страницу (64-128 байт)~0.1 мкс/байт (быстрее в 30 000 раз)
Циклы перезаписи10⁵ - 10⁶10⁴ - 10⁵10¹⁴ (практически бесконечно)
ПлотностьНизкая (1-4 Кб в AVR)Высокая (32-256 Кб)Средняя (выше EEPROM)
СтоимостьВходит в МКВходит в МКОтдельная микросхема, дороже

Вывод: Для очень частой записи (логгеры, счетчики) внутренняя EEPROM неидеальна. Следует рассмотреть внешнюю EEPROM с большим ресурсом или, что лучше, FRAM, которая сочетает энергонезависимость, скорость RAM и почти неограниченный ресурс.


5. Итог: Инженерный чек-лист при работе с EEPROM

  1. Всегда используйте EEPROM.update() вместо write.

  2. Реализуйте хотя бы простой wear-leveling, если запись в одну переменную происходит чаще 1 раза в минуту.

  3. Всегда добавляйте контроль целостности (CRC) для структур данных.

  4. Минимизируйте длину записи в прерываниях. Используйте флаги и записывайте в loop.

  5. Обеспечьте стабильное питание во время записи. Используйте источники с достаточной емкостью и, возможно, схемы детектора сброса (brown-out detection).

  6. Имейте в коде стратегию восстановления при повреждении данных (значения по умолчанию, резервная копия).

  7. Для объемных или часто меняющихся данных рассмотрите использование внешней FRAM или SPI Flash.

Понимание физических ограничений EEPROM и применение математических моделей для wear-leveling и оценки срока службы позволяет создавать надежные профессиональные устройства на Arduino, которые проработают десятилетия даже в условиях интенсивной записи данных.

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 


 


Оставить комментарий

Ваш адрес электронной почты не будет опубликован. Email необязателен. Обязательные поля отмечены *

Ваш опыт работы на этом сайте будет улучшен за счет использования файлов cookie Cookie Policy