Перевод очень хорошей статьи от Dean Camera на русский язык. В ней содержатся пояснения о самой EEPROM, работа с встроенной библиотекой eeprom.h, приведены примеры для записи\чтения byte, word и block, работа с EEMEM. В статье рассматривается компилятор AVR-GCC. Это больше адаптация, нежели перевод. Часть текста я выкинул, посчитав лишним, где-то добавил от себя. Статья рассчитана на тех, кто не работал с EEPROM и хочет на простых примерах научиться с ним работать.
Что такое память EEPROM?
Большинство МК AVR от Atmel содержат EEPROM (Electronically Erasable Read-Only Memory) — энергонезависимую память с довольно большим количеством циклов записи. Данные, записанные в эту память, не будут сбрасываться даже при отключении питания, что очень удобно, например, для хранения настроек или каких-то идентификационных данных.
EEPROM в AVR имеет ограниченное количество циклов записи — 100 000. Количество циклов чтения не ограничено.
Как осуществляется доступ?
Доступ к EEPROM в AVR можно получить с помощью специальных регистров, которые отвечают за адрес, информацию для записи (или прочитанную информацию) и совершаемое действие (запись/чтение).
В языке C нет каких-либо стандартов доступа или адресации памяти (не считая Flat Model для SRAM). Поэтому каждый компилятор использует свой метод хранения информации в памяти.
Мы будем рассматривать компилятор AVR GCC.
Использование библиотеки AVRLibC EEPROM
AVRLibC (обычно она входит в состав компилятора AVR-GCC) содержит готовую библиотеку для работы с EEPROM. Чтобы ее использовать, нужно добавить следующий заголовочный файл:
#include <avr/eeprom.h>
В этой библиотеке три основных типа данных: byte (1 байт), word (2 байта) и блок данных. В новых версиях добавлены еще два типа – dword (4 байта) и float (8 байт). Их мы рассматривать не будем — работа с ними идентична работе с byte \ word типами. Для каждого типа есть своя функция записи и чтения. Вот они:
// Читаем \ пишем по одному байту (byte) uint8_t eeprom_read_byte (const uint8_t *addr) void eeprom_write_byte (uint8_t *addr, uint8_t value) // Читаем \ пишем по два байта (word) uint16_t eeprom_read_word (const uint16_t *addr) void eeprom_write_word (uint16_t *addr, uint16_t value) // Читаем \ пишем блоками void eeprom_read_block (void *pointer_ram, const void *pointer_eeprom, size_t n) void eeprom_write_block (const void *pointer_ram, void *pointer_eeprom, size_t n)
Чтение byte \ word
Для начала, попробуем написать программу, считывающую 1 байт из EEPROM – например, по адресу 46.
#include <avr/eeprom.h> void main(void) { uint8_t ByteOfData; ByteOfData = eeprom_read_byte((uint8_t*)46); }
Здесь мы считываем байт данных по адресу 46 из EEPROM и записываем в переменную «ByteOfData». Объявляем переменную размером 1 байт, потом вызываем функцию eeprom_read_byte, которой в качестве параметра передаем указатель на байт в EEPROM.
Word-тип записывается и читается так же, за исключением того, что функции нужен указатель на int.
#include <avr/eeprom.h> void main(void) { uint16_t WordOfData; WordOfData = eeprom_read_word((uint16_t*)46); }
Работа в блоками данных в EEPROM
А что, если Вы хотите записать больше 2 байт информации? Неудобно будет делить их на кусочки по два байта… Для этого придумали блоки данных. Функции доступа к блокам отличаются от остальных функций чтения\записи. Обе функции ничего не возвращают и работают с переменными, переданными в качестве параметра. Давайте взглянем на объявление функции чтения:
void eeprom_read_block (void *pointer_ram, const void *pointer_eeprom, size_t n)
Выглядит сложно! Но на практике это не так. Она использует элементы, с которыми Вы, возможно, не знакомы – указатели на void. Обычные указатели ссылаются на определенный тип данных. Например, “uint8_t*” указывает на unsigned char, а “int16_t*” – на signed int. Но ведь void – это не тип данных, что же это значит?
Указатели на void полезны с случае, когда тип данных неизвестен или не важен. Указатель на void указывает на адрес в памяти, где хранится информация, и не важно, какого она типа.
Именно поэтому функции для работы с блоками данных используют их. Ей не важен тип, она просто копирует нужное количество байт.
Вернемся к нашим функциям. В качестве параметров нужно передавать: void-указатель на адрес в RAM, void-указатель на адрес в EEPROM и количество байт. Для начала, попробуем прочесть 10 байт из EEPROM, начинающиеся с адреса 12, в строку.
#include <avr/eeprom.h> void main(void) { uint8_t StringOfData[10]; eeprom_read_block((void*)&StringOfData, (const void*)12, 10); }
Все еще выглядит сложно? Давайте пройдемся по каждому аргументу отдельно:
• (void*)&StringOfData – это указатель на RAM. Функция записывает прочитанные данные сюда. Он имеет тип unit8_t, поэтому добавляем явное преобразование – (void*)
• (const void*)12 – это указатель на EEPROM. Функция чтения его не изменяет. Мы используем постоянный адрес (константу), поэтому преобразуем его к const void*
• 10 – количество байт, которое мы читаем. Функцию записи надо использовать так же, только первым аргументом посылаем данные, которые хотим записать, а вторым – место в EEPROM, куда данные будут записаны.
Использование EEMEM
Теперь Вы знаете, как читать и писать данные в EEPROM по фиксированному адресу. Но это не всегда удобно. В больших программах использование фиксированных адресов может привести к путанице, и часть данных может потеряться. Для таких случаев есть EEMEM.
Объявленный в той же библиотеке, макрос EEMEM указывает компилятору, что Вы хотите разместить переменную не в SRAM, а в EEPROM. К примеру, создадим несколько таких переменных:
#include <avr/eeprom.h> uint8_t EEMEM NonVolatileChar; uint16_t EEMEM NonVolatileInt; uint8_t EEMEM NonVolatileString[10];
Использовать их напрямую (var=value;) нельзя! Грубо говоря, эти переменные содержат адреса, автоматически выделенные компилятором. В примере выше, NonVolatileChar будет хранится по адресу 0, NonVolatileInt – по адресу 1..2, а NonVolatileString[10] – 3..12. Для корректной работы нужно подставлять их в функции, рассмотренные выше.
Приведем пример чтения трех переменных (byte, int и block) из EEPROM в обычные переменные (SRAM):
#include <avr/eeprom.h> uint8_t EEMEM NonVolatileChar; uint16_t EEMEM NonVolatileInt; uint8_t EEMEM NonVolatileString[10]; void main(void) { uint8_t SRAMchar; uint16_t SRAMint; uint8_t SRAMstring; SRAMchar = eeprom_read_byte(&NonVolatileChar); SRAMint = eeprom_read_word(&NonVolatileInt); eeprom_read_block((void*)&SRAMstring, (const void*)&NonVolatileString, 10); }
Установка значений по умолчанию
Последняя тема, которую мы разберем – установка значений по умолчанию. При использовании стандартного makefile, GCC создает два файла – файл прошивки .hex и файл EEPROM — .eep. Этой файл содержит значения по умолчанию для EEPROM. Чтобы установить EEPROM-переменной значение по умолчанию, просто присвойте его в объявлении, вот так:
uint8_t EEMEM SomeVariable = 12;
Запомните! Если Вы не загрузите файл .eep, Ваша программа будет работать с EEPROM, который был загружен в предыдущий раз, или с пустыми значениями (0xFF). Вы должны предусмотреть способ проверки правильности EEPROM – например, проверять известные значения по умолчанию.
Автор: Dean Camera, [email protected]
Переводчик: shadowalker, [email protected]
0 комментариев на «“Работа с EEPROM в AVR-GCC”»
Здравствуйте, я сам тот ещё новичок в AVR-микроконтроллерах. Прочитав данную статью (и огромное количество других) возник вопрос реализации чтение, записи данных энергонезависимой памяти. Работая с ардуино использовал библиотеку <EEPROM.h>. Во время работы программы данные записывались в энергонезависимую память МК, после программной перезагрузки в setup-е записанная информация (дата и время) устанавливалось в модуль часов DS 1303(пояснение==> таким образом обновлялась дата и время без пере подключения (перезаписи) программы в ардуино ). Тоже самое нужно реализовать и в esp-12e, но проблема в том, что я не могу даже считать что-либо не говоря и о записи. Помогите пожалуйста «наброском» кода, который записывает и читает данные в энергонезависимой памяти (выводя всё в монитор последовательного порта (как в библиотеке <EEPROM.h> )).