Введение
При программировании микроконтроллеров 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
__eeprom __no_init float koef1;
float koef2;
При записи
if (koef2>1)
koef1=koef2;
При выполнении условия коэфициент записывается. Но при сбросе питания koef1 не сохраняет предыдущего значения. В чем моя ошибка?
Не понял чем вам __no_init помогает. На генерируемый код программы он не влияет - размер программы и данных получается один и тот же, содержимое не проверял, но думаю отличий нет.
Шить eeprom всё равно ведь нужно, программатором или ещё как - не важно. Иначе eeprom будет содержать FF-ы или другой мусор вместо осмысленных данных.
Или вы как-то по-другому делаете?
Quoting nickx:
Если eeprom не используется, то работать будет, иначе - от программы зависит.
Quoting nickx:
Ну да, правильно.
Почитайте даташит на любую мегу (п. "Preventing EEPROM Corruption"):
Quote:
Quoting nickx:
И что блок питание обеспечивает мгновенное нарастание VDD до +5 В при включении и мгновенное падение напряжения до 0 В при выключении? И мега не выполняет код низком VDD?
я имею в виду то что запись в еепром занимает время. так вот эти макросы ждут завершения записи или нет?
Quote: Скорее всего ждут - иначе как они будут записывать очередной байт, если предъидущий ещё не записался? Можно конечно организовать буфер записи и по прерыванию готовности eeprom запускать запись следующего байта. Но врядли так делается в большинстве проектов...
Тогда встречный вопрос:
в макросе присутствует временное запрещение прерываний? чтобы цикл был атомарным.
Не могу понять почему, подскажите кто знает.
Для использования uint8_t - подключите stdint.h.
Но возможно ошибка не в этом - может забыли символ ")" или ";" - мало данных.
Допустим,
нижний порог: -10
шаг температуры: 1 градус.
Тогда 0(число хранимое в eeprom) может соответствовать минус 10 градусам.
Если нужна бОльшая точность, то можно взято тип uint16_t - для температуры этого хватит.
То есть eeprom-переменн ые можно линейно преобразовать по формуле:
Tx=k*t_eeprom+eepro_offset и наоборот.
Цитирую Pashgan:
Да, ждут в цикле.
Тогда встречный вопрос:
в макросе присутствует временное запрещение прерываний? чтобы цикл был атомарным.
Code:
CLI
SBI EECR,EEMWE
SBI EECR,EEWE
OUT SREG,T0
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);
}
disable_interrupt();
enable_interrupt();
но это от куда?
Я спрашиваю что компилятор CVAVR подставит вместо вот такого выражения :
переменная_ЕЕпром=переменная_СРАМ;
В листинге посмотрите или в симуляторе.
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
...
Правда окончания записи CV не ожидает (судя по листингу) и на том спасибо.
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
Понятно...В принципе код ничего: прерывания запрещаются ненамного дольше чем надо.
Code:
WDR
Сброс собаки CV сам добавил? Это они зря...
Кормите собаку вовремя, тогда не залает. :)
Quoting Виктор:
Почему не знаете? Если понимаете как ваша программа работает, то проблем нет.
Quoting Виктор:
Quoting Виктор:
Случаи бывают разные: иногда можно отложить, но чаще всего нельзя (у меня так, к примеру).
Quoting Виктор:
Жечь такие библиотеки вместе с книгами.
Quoting Виктор:
Вот это смысл имеет.
Не зря. GCC тоже хорош, и к тому же бесплатен. И плюсы поддерживает.
Quoting Виктор:
Этим надо хвастать? Тип bit - отсебятина нестандартная.
Ладно ещё на 51-ых - там аппаратно биты поддержаны.
А на АВР они зачем? На xmega ещё ладно бы, а на mega они ни к чему.
Quoting Виктор:
Думаете IAR/GCC не по полной используют?
-этим не нужно хвастать.
-что вы, не в коем случае не думаю. каждый выбирает то что ему подходит. низкий поклон специалистам.
Code:
uint8_t index_0[] EEMEM = "Commander";
А как назад получить? AStudio ругается, мол:
Code:
Error 1 'index_0' undeclared (first use in this function)
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++;
}
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;
}
}
что не так сделал? почему при чтении из памяти число один, я не могу получить его на выходе?
RSS feed for comments to this post