Работа с 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, [email protected]
Переводчик: shadowalker, [email protected]


0 комментариев на «“Работа с EEPROM в AVR-GCC”»

  1. Здравствуйте, я сам тот ещё новичок в AVR-микроконтроллерах. Прочитав данную статью (и огромное количество других) возник вопрос реализации чтение, записи данных энергонезависимой памяти. Работая с ардуино использовал библиотеку <EEPROM.h>. Во время работы программы данные записывались в энергонезависимую память МК, после программной перезагрузки в setup-е записанная информация (дата и время) устанавливалось в модуль часов DS 1303(пояснение==> таким образом обновлялась дата и время без пере подключения (перезаписи) программы в ардуино ). Тоже самое нужно реализовать и в esp-12e, но проблема в том, что я не могу даже считать что-либо не говоря и о записи. Помогите пожалуйста «наброском» кода, который записывает и читает данные в энергонезависимой памяти (выводя всё в монитор последовательного порта (как в библиотеке <EEPROM.h> )).

Добавить комментарий

Arduino

Что такое Arduino?
Зачем мне Arduino?
Начало работы с Arduino
Для начинающих ардуинщиков
Радиодетали (точка входа для начинающих ардуинщиков)
Первые шаги с Arduino

Разделы

  1. Преимуществ нет, за исключением читабельности: тип bool обычно имеет размер 1 байт, как и uint8_t. Думаю, компилятор в обоих случаях…

  2. Добрый день! Я недавно начал изучать программирование под STM32 и ваши уроки просто бесценны! Хотел узнать зачем использовать переменную типа…

3D-печать AI Arduino Bluetooth CraftDuino DIY Google IDE iRobot Kinect LEGO OpenCV Open Source Python Raspberry Pi RoboCraft ROS swarm ИК автоматизация андроид балансировать бионика версия видео военный датчик дрон интерфейс камера кибервесна манипулятор машинное обучение наше нейронная сеть подводный пылесос работа распознавание робот робототехника светодиод сервомашинка собака управление ходить шаг за шагом шаговый двигатель шилд юмор

OpenCV
Робототехника
Будущее за бионическими роботами?
Нейронная сеть - введение