Работа с EEPROM в AVR-GCC

Перевод очень хорошей статьи от 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, dean_camera@hotmail.com
Переводчик: shadowalker, shadowalker.main@gmail.com

Комментарии (0)

RSS свернуть / развернуть

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