Учебный курс AVR. Работа с EEPROM. Объявление переменных. Чтение и запись данных. Ч1

19/07/2013 - 00:00 Павел Бобков

Введение

   При программировании микроконтроллеров AVR иногда возникает потребность сохранять данные, которые бы после выключения питания или сброса контроллера не изменяли свое значение. Для этих целей в составе AVR есть энергонезависимая память данных EEPROM (Electrically Erasable Programmable Read-Only Memory — электрически стираемое перепрограммируемое ПЗУ). 
   EEPROM имеет адресное пространство отличное от адресных пространств ОЗУ и flash памяти, в котором можно читать и записывать одиночные байты. В зависимости от модели микроконтроллера EEPROM может иметь объем от 512 байт (как, например, в микроконтроллере atmega16) до нескольких килобайт. Гарантированное количество циклов перезаписи этой памяти составляет не меньше 100000. 
   В этой статье на примере atmega16 мы разберемся, как работать с этим типом памяти, какие возможные проблемы при этом могут возникать и как с ними бороться.

Объявление переменных в EEPROM

   Использование EEPROM начинается с объявления переменных, хранящиеся в этой памяти. Синтаксис объявления таких переменных отличается от объявлений обычных переменных (хранящихся в ОЗУ) только наличием ключевого слова. В зависимости от компилятора данное ключевое слово может разным. 


Объявление переменной в EEPROM для IAR AVR и CodeVision AVR:


//8-ми разрядная переменная
__eeprom uint8_t setting_2

//объявление массива 16-ти разрядных переменных 
__eeprom uint16_t set[8];

//объявление массива и его инициализация
__eeprom uint8_t data[3] = {11, 3, 13};

//указатель на 8-ми разрядную переменную в EEPROM, который сам хранится в RAM
uint8_t __eeprom * pSetting;

//указатель на 8-ми разрядную переменную в EEPROM, который сам храниться в EEPROM
__eeprom uint8_t __eeprom*pSetting;


Объявление переменной в EEPROM для AtmelStudio 6: 


#include <avr/eeprom.h>

//8-ми разрядная переменная в EEPROM
uint8_t setting EEMEM;

//объявление массива в EEPROM
uint16_t set[8] EEMEM;

//указатель на 8-ми разрядную переменную в EEPROM, который сам хранится в RAM
uint8_t *pSetting;

//указатель на 8-ми разрядную переменную в EEPROM, который сам храниться в EEPROM
uint8_t *pSetting EEMEM;

Инициализация переменных в EEPROM

   При объявлении переменных в EEPROM их можно инициализировать, то есть присвоить начальные значения. 


//для IAR AVR, CodeVision AVR
__eeprom uint8_t counter = 100;
__eeprom uint16_t data[3] = {20, 08, 1981};

//для AtmelStudio 6
uint8_t counter EEMEM = 23;
uint8_t data[3] EEMEM = {21, 04, 1979};


   Инициализацию переменных хранящихся в ОЗУ компилятор "запихивает" в начало программы микроконтроллера - перед вызовом функции main. И она выполняется каждый раз, когда на микроконтроллер подается питание или происходит его сброс. 
   С EEPROM переменными ситуация немного другая, их должен инициализировать сам пользователь путем программирования EEPROM специальным файлом (с расширением .eep). 
   
Как сгенерировать файл для EEPROM? Если в коде есть инициализация EEPROM переменных, то AtmelStudio 6 и CodeVision AVR создадут этот файл автоматически. А вот в IAR`e для этого нужно прописывать линкеру команды. Делается это так.
   
Меню Project > Options...>Linker вкладка Output. Устанавливаем значения по умолчанию - галочка Override default снята, формат - Debug information for C-SPY. На вкладке Extra Options ставим галочку Use Command Options и в окошке ниже прописываем такие строчки:

-Ointel-standard,(CODE)=.hex
-Ointel-standard,(XDATA)=.eep




   После компиляции и сборки проекта IAR создаст файл прошивки - .hex и файл для EEPROM`a - .eep 

   Полученный eep файл записывается с помощью программатора в микроконтроллер.

Чтение и запись EEPROM

   В IAR`e и CodeVision AVR использование EEPROM переменных по сути ничем не отличается от использования обычных переменных (хранящихся в ОЗУ и регистрах). Вся работа по организации чтения и записи в эти EEPROM переменные выполняется компилятором.


//для IAR AVR, CodeVision AVR
__eeprom uint8_t data;
...
//читаем из EEPROM
uint8_t tmp = data;

//записываем в EEPROM
data = 129;


   В AtmelStudio для чтения/записи EEPROM переменных используются специальные макросы. Они определены в файле eeprom.h. Вот некоторые из них:


uint8_t eeprom_read_byte (const uint8_t *__p) - прочитать байт 
uint16_t eeprom_read_word (const uint16_t *__p) - прочитать слово (2 байта)
uint32_t eeprom_read_dword (const uint32_t *__p) - прочитать двойное слово (4 байта)

void eeprom_write_byte (uint8_t *__p, uint8_t __value) - запись байта
void eeprom_write_word (uint16_t *__p, uint16_t __value) - запись слова (2 байта)
void eeprom_write_dword (uint32_t *__p, uint32_t __value) - запись дв. слова (4 байта)


   Макросы принимают в качестве параметра адрес переменной размещенной в EEPROM. Для взятия адреса переменной используется оператор &. Примеры ниже поясняют использование этих макросов. 


//для AtmelStudio

#include <avr/eeprom.h>

//объявляем переменные
uint8_t data EEMEM;
uint16_t pin_code EEMEM;
...
//читаем байт из eeprom
uint8_t tmp = eeprom_read_byte(&data);

//записываем слово в eeprom
eeprom_write_word (&pin_code, 5327)

Заключение

Из этой статьи вы узнали:

- как объявить переменную в EEPROM памяти,
- как создать файл для инициализации EEPROM,
- как прочитать и записать данные в EEPROM.

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

Comments   

# nickx 2013-08-20 19:08
Есть 2 переменные
__eeprom __no_init float koef1;
float koef2;
При записи
if (koef2>1)
koef1=koef2;
При выполнении условия коэфициент записывается. Но при сбросе питания koef1 не сохраняет предыдущего значения. В чем моя ошибка?
# _Артём_ 2013-08-20 20:07
Ключевое слово __no_init как бы лишнее в данном случае, но значение koef1 должно сохранятся (переменные объявленные как __eeprom после сброса не инициализируютс я). Код правильный. Возможно у вас проблемы с питанием МК или BOD не включен или ещё что.
# nickx 2013-08-21 16:32
Если не использовать __no_init, то надо генерировать файл для еепром и шить его отдельно. Это не удобно и должно работать и без этого. А BOD он контролирует напряжение питания, Питается у меня от компьютерного ИБП, и 5В там стабильны и запитаны все ноги меги.
# _Артём_ 2013-08-21 18:55
Quoting nickx:
Если не использовать __no_init, то надо генерировать файл для еепром и шить его отдельно.


Не понял чем вам __no_init помогает. На генерируемый код программы он не влияет - размер программы и данных получается один и тот же, содержимое не проверял, но думаю отличий нет.
Шить eeprom всё равно ведь нужно, программатором или ещё как - не важно. Иначе eeprom будет содержать FF-ы или другой мусор вместо осмысленных данных.
Или вы как-то по-другому делаете?

Quoting nickx:

Это не удобно и должно работать и без этого.

Если eeprom не используется, то работать будет, иначе - от программы зависит.

Quoting nickx:

А BOD он контролирует напряжение питания,

Ну да, правильно.
Почитайте даташит на любую мегу (п. "Preventing EEPROM Corruption"):

Quote:
Keep the AVR RESET active (low) during periods of insufficient power supply voltage.


Quoting nickx:

Питается у меня от компьютерного ИБП, и 5В там стабильны и запитаны все ноги меги.


И что блок питание обеспечивает мгновенное нарастание VDD до +5 В при включении и мгновенное падение напряжения до 0 В при выключении? И мега не выполняет код низком VDD?
# Nikls 2013-12-18 08:44
спасибо, разжевали, пол дня убил..искал как работать в АтмелСтудио6 с ЕЕПРОМ :-)
# Pashgan 2013-12-20 01:48
Пожалуйста.
# XanderEVG 2014-01-27 10:07
интересно макросы void eeprom_write_by te как работают?
я имею в виду то что запись в еепром занимает время. так вот эти макросы ждут завершения записи или нет?
# _Артём_ 2014-01-28 06:55
Quote:
макросы void eeprom_write_by te
Это не макрос, а функция.
Quote:
так вот эти макросы ждут завершения записи или нет?
Скорее всего ждут - иначе как они будут записывать очередной байт, если предъидущий ещё не записался? Можно конечно организовать буфер записи и по прерыванию готовности eeprom запускать запись следующего байта. Но врядли так делается в большинстве проектов...
# Pashgan 2014-01-28 11:58
Да, ждут в цикле.
# Виктор 2014-03-21 16:53
Quoting Pashgan:
Да, ждут в цикле.

Тогда встречный вопрос:
в макросе присутствует временное запрещение прерываний? чтобы цикл был атомарным.
# alex2014 2014-01-30 10:03
а библиотеки надо какие нибудь подключать? у меня при объявлении __eeprom uint8_t data; выдает ошибку: '('expected
Не могу понять почему, подскажите кто знает.
# _Артём_ 2014-01-30 10:18
Для объявления eeprom-переменн ых библиотеки не нужны.
Для использования uint8_t - подключите stdint.h.
Но возможно ошибка не в этом - может забыли символ ")" или ";" - мало данных.
# alex2014 2014-01-30 14:43
Большое спасибо, заработало))). Но есть другая проблема, я пытаюсь собрать устройство, которое будет поддерживать заданную температуру в автомобиле. И в eeprom сохраняю заданную температуру. я понимаю, что человек в здравом уме не будет выставлять темпер. ниже нуля, но все таки какой тип данных нужно взять чтоб это стало возможным? Я новичек так сильно не ругайте)) Заранее спасибо!
# _Артём_ 2014-01-30 16:20
Тип данных зависит не только от нижнего и верхнего порога, но и от точности.
Допустим,
нижний порог: -10
шаг температуры: 1 градус.
Тогда 0(число хранимое в eeprom) может соответствовать минус 10 градусам.
Если нужна бОльшая точность, то можно взято тип uint16_t - для температуры этого хватит.
То есть eeprom-переменн ые можно линейно преобразовать по формуле:
Tx=k*t_eeprom+eepro_offset и наоборот.
# alex2014 2014-02-06 11:13
Спасибо, понял!
# Pashgan 2014-01-31 11:58
Можно просто ограничить пользователя в возможностях и не позволять ему выставить отрицательную температуру.
# alex2014 2014-02-06 11:14
да, я поначалу так и сделал))
# Valera1989 2014-03-12 09:18
А как работать с массивами в EEPROM в Atmel Studio? Допустим, перед выключением питания я хочу синхронизироват ь массив в оперативной памяти и в EEPROM, а при включении обратно. Как это сделать? Нужно заморочится с циклами и указателями?
# Pashgan 2014-03-18 20:13
Если по простому, то делаешь цикл, котором проверяешь данные ОЗУ и ЕЕПРОМа. Те которые отличаются между собой, перезаписываешь , а остальные оставляешь. По сути, такая же работа как и с обычным массивом в ОЗУ.
# Виктор 2014-03-21 16:54
сори повторюсь.

Цитирую Pashgan:
Да, ждут в цикле.

Тогда встречный вопрос:
в макросе присутствует временное запрещение прерываний? чтобы цикл был атомарным.
# Pashgan 2014-03-23 16:20
Если речь идет об ИАРе, то прерывания запрещаются только во время выполнения этих операций.
Code:
CLI
SBI EECR,EEMWE
SBI EECR,EEWE
OUT SREG,T0
# Виктор 2014-03-24 17:55
А если CVAVR нужно ли предварять запрещением прерываний запись типа eep_x=x; или компилятор сам отменит прерывания, затем войдет в цикл ожидания завершения процесса записи, и после этого вновь разрешит прерывания? Тоесть проделает всю грязную работу за пользователя. Как я понял там просто макроподстановк а. Стоит ли мне париться что запись не будет успешной если вдруг произойдет прерывание
# _Артём_ 2014-03-24 18:25
Quoting Виктор:
А если CVAVR нужно ли предварять запрещением прерываний запись типа eep_x=x; или компилятор сам отменит прерывания
Смотрите листинг. Чего гадать-то?
Quoting Виктор:
Тоесть проделает всю грязную работу за пользователя. Как я понял там просто макроподстановка.

Работа не столь уж грязная:
Code:
void WriteByteToEEPROM(unsigned int addr, unsigned char new_byte)
{
unsigned char flag=0;
if (PwrCtrl.Flags.DisableFlash)
return ;
if (new_byte==ReadByteFromEEPROM(addr))
return ;
EEARH=addr/256;
EEARL=addr%256;
EEDR=new_byte;
if (SREG&0x80) flag=1; else flag=0;
disable_interrupt();
EECR|=(1<<EEMWE);
EECR|=(1<<EEWE);
if (flag) {
enable_interrupt();
}
while (EECR&(1<<EEWE)) ;
}
void main()
{
//...
WriteByteToEEPROM((unsigned short)&eeprom_var, new_value);
}
# Виктор 2014-03-24 22:41
в приведенном листинге вижу что есть
disable_interrupt();
enable_interrupt();
но это от куда?
# _Артём_ 2014-03-25 08:57
Quote:

enable_interrupt();
но это от куда?
Наверное где-то в IAR-е определено. Какая разница - это всего лишь cli и sei.
# Виктор 2014-03-24 22:45
Это самописная ф-ция?
Я спрашиваю что компилятор CVAVR подставит вместо вот такого выражения :
переменная_ЕЕпром=переменная_СРАМ;
# _Артём_ 2014-03-25 08:58
Quoting Виктор:
Я спрашиваю что компилятор CVAVR подставит вместо вот такого выражения :
переменная_ЕЕпром=переменная_СРАМ;

В листинге посмотрите или в симуляторе.
# JoJo 2014-03-25 09:21
В CodeVision прерывания запрещаются на время всей процедуры записи.

Code:
...
IN R25,SREG
CLI
OUT EEARL,R26
OUT EEARH,R27
SBI EECR,EERE
IN R24,EEDR
CP R30,R24
BREQ __EEPROMWRB0
OUT EEDR,R30
SBI EECR,EEMWE
SBI EECR,EEWE
__EEPROMWRB0:
OUT SREG,R25
...
# _Артём_ 2014-03-25 09:37
Quoting JoJo:
В CodeVision прерывания запрещаются на время всей процедуры записи.
Зачем они это делают? Достаточно запрещать на время установки EEMWE и EEWE.
Правда окончания записи CV не ожидает (судя по листингу) и на том спасибо.
# JoJo 2014-03-25 10:41
Окончание записи в начале кода проверяется, я не весь код привел.
Code:
__EEPROMWRB:
0000a5 9be1 SBIS EECR,EEWE
0000a6 c002 RJMP __EEPROMWRB1
0000a7 95a8 WDR
0000a8 cffc RJMP __EEPROMWRB
__EEPROMWRB1:
0000a9 b79f IN R25,SREG
0000aa 94f8 CLI
0000ab bbae OUT EEARL,R26
0000ac bbbf OUT EEARH,R27
0000ad 9ae0 SBI EECR,EERE
0000ae b38d IN R24,EEDR
0000af 17e8 CP R30,R24
0000b0 f019 BREQ __EEPROMWRB0
0000b1 bbed OUT EEDR,R30
0000b2 9ae2 SBI EECR,EEMWE
0000b3 9ae1 SBI EECR,EEWE
__EEPROMWRB0:
0000b4 bf9f OUT SREG,R25
0000b5 9508 RET
# _Артём_ 2014-03-25 10:58
Quoting JoJo:
Окончание записи в начале кода проверяется, я не весь код привел.

Понятно...В принципе код ничего: прерывания запрещаются ненамного дольше чем надо.
Code: WDR
Сброс собаки CV сам добавил? Это они зря...
# Виктор 2014-03-26 19:36
Как по мне не зря. А если собака вот-вот залает? Вы же не знаете в какой момент это произойдет. Как по мне можно отложить все дела на пару милисек и дать спокойно запистаь данные. Этож вам не почти секунда при бращении к DS1820, там библиотечная функция стопорит камень пока не получит значение температуры. Там есть смысл городить задержку по таймеру, и т.д. , а сдесь можно и подождать не морочась с флагами
# _Артём_ 2014-03-26 20:07
Quoting Виктор:
Как по мне не зря. А если собака вот-вот залает?

Кормите собаку вовремя, тогда не залает. :)

Quoting Виктор:

Вы же не знаете в какой момент это произойдет.

Почему не знаете? Если понимаете как ваша программа работает, то проблем нет.

Quoting Виктор:

Как по мне можно отложить все дела на пару милисек и дать спокойно запистаь данные.

Quoting Виктор:

а сдесь можно и подождать не морочась с флагами

Случаи бывают разные: иногда можно отложить, но чаще всего нельзя (у меня так, к примеру).

Quoting Виктор:

Этож вам не почти секунда при бращении к DS1820, там библиотечная функция стопорит камень пока не получит значение температуры.

Жечь такие библиотеки вместе с книгами.
Quoting Виктор:

Там есть смысл городить задержку по таймеру, и т.д. ,

Вот это смысл имеет.
# Виктор 2014-03-26 19:45
П.С. зря народ со скепсисом относится к Вижену. Мол это для тех кто не профи, а вот ИАР это для настоящих мужиков. Например в вижине есть встроенный тип данных BIT чем не многие могут похвастать. РОНы использует по полной, ништяк.
# _Артём_ 2014-03-26 20:08
Quoting Виктор:

П.С. зря народ со скепсисом относится к Вижену. Мол это для тех кто не профи, а вот ИАР это для настоящих мужиков.

Не зря. GCC тоже хорош, и к тому же бесплатен. И плюсы поддерживает.
Quoting Виктор:

Например в вижине есть встроенный тип данных BIT чем не многие могут похвастать.

Этим надо хвастать? Тип bit - отсебятина нестандартная.
Ладно ещё на 51-ых - там аппаратно биты поддержаны.
А на АВР они зачем? На xmega ещё ладно бы, а на mega они ни к чему.

Quoting Виктор:

РОНы использует по полной, ништяк.

Думаете IAR/GCC не по полной используют?
# Виктор 2014-03-27 10:56
-не спорю, хорош.
-этим не нужно хвастать.
-что вы, не в коем случае не думаю. каждый выбирает то что ему подходит. низкий поклон специалистам.
# Tantrum 2014-08-07 10:07
Очень не хватает примера.
# Антон М 2014-10-24 08:55
Дайте, пожалуйста, доходчивый пример для AVR Studio 6.2 ?
# Антон М 2014-10-24 11:01
Ну закинул я такое в память:
Code:uint8_t index_0[] EEMEM = "Commander";
А как назад получить? AStudio ругается, мол:
Code:Error 1 'index_0' undeclared (first use in this function)
# Владимир84 2015-10-10 21:19
CodevisionAVR

1. Не понятно, как работает инициализация. Запись вида __eeprom unsigned int countdown_eepro m = 1; нужна только для создания .eep файла, и программа игнорирует эту строку при последующих запусках, если отработала строка countdown_eepro m = 2 ?

2. Если перезаписать переменную, объявленную, как __eeprom, с тем же значением, будет ли проверка на необходимость перезаписи, учитывая ограниченный лайф цикл самого eeprom.

3. При перезапуске все равно пишет 0:
unsigned int countdown = 0;
__eeprom unsigned int countdown_eepro m = 0;
void main(void)
{
countdown = countdown_eepro m;
countdown_eepro m++;
}
# ignat 2016-08-22 15:54
#include


unsigned char my_var;

uint8_t ee_var EEMEM = 1;


int main(void){ //Главная функция

my_var = eeprom_read_byt e(&ee_var); // считали значение из EEPROM

DDRC = 0xff;

while (1) {
PORTC = my_var;

}
}

что не так сделал? почему при чтении из памяти число один, я не могу получить его на выходе?

У вас недостаточно прав для комментирования.