Страница 1 из 2
Atmega8 запись / чтение переменной eeprom
Добавлено: Вс авг 30, 2009 08:18:47
-==MAXCELERON==-
Ребят, подскажите, как осуществить запись / чтение переменной в eeprom. На простом примере :
#include <util>
#define F_CPU 1000000UL
char x ;
int main(void){
DDRC = 0xff; // порт С - выход
while { //бесконечный цикл
PORTC = x; // на порту C то, что в переменной х
x++; // прибавляем к х единицу
_delay_ms (1000); // задержка на 1 сек
} // конец цикла
Вопрос: как сделать так, чтобы при нажатии на кнопку, подключенную к м.к., значение переменной х сохранилось в eeprom,
и при следующем включении м.к. счет начался не от нуля, а от сохраненного значения??

Добавлено: Вс авг 30, 2009 08:39:27
demson
Работа с eeprom в CodeVision сделана прозрачной, вот так:
eeprom int ee_test = 1;
int test;
test = ee_test; // чтение из ЕЕПРОМ
ee_test = test; // запись в ЕЕПРОМ
http://radiokot.ru/forum/viewtopic.php? ... 50d67d9c45
Добавлено: Вс авг 30, 2009 09:00:21
-==MAXCELERON==-
Да, но вся проблема в том, что eeprom int ee_test = 1; в winawr - не известная команда, выдает ошибку error: expected '=', ',', ';', 'asm' or '__attribute__' before 'int'
Добавлено: Вс авг 30, 2009 09:25:55
demson
с winavr работал мало но вроде это то что нужно...
http://electronix.ru/forum/index.php?s= ... opic=32125
В WinAVR есть готовые функции для работы с eeprom.
смотрите eeprom.h.
Предпочтительнее использовать их, т.к. они работают вне зависимости от уровня оптимизации.
#include <avr>
eeprom_write_byte
eeprom_read_byte
Добавлено: Вс авг 30, 2009 09:30:03
Meteor
Вот код из даташита на запись
void EEPROM_write(unsigned int uiAddress, unsigned char ucData)
{
/* Wait for completion of previous write */
while(EECR & (1<<EEWE))
;
/* Set up address and data registers */
EEAR = uiAddress;
EEDR = ucData;
/* Write logical one to EEMWE */
EECR |= (1<<EEMWE);
/* Start eeprom write by setting EEWE */
EECR |= (1<<EEWE);
}
А вот на чтение:
unsigned char EEPROM_read(unsigned int uiAddress)
{
/* Wait for completion of previous write */
while(EECR & (1<<EEWE))
;
/* Set up address register */
EEAR = uiAddress;
/* Start eeprom read by writing EERE */
EECR |= (1<<EERE);
/* Return data from data register */
return EEDR;
}
Добавлено: Вс авг 30, 2009 09:32:02
-==MAXCELERON==-
Сейчас проверю

Добавлено: Вс авг 30, 2009 09:34:18
Meteor
Советую всегда сначала рыть на даташитах. Там ничего секретного нет. Или нет самого даташита?
Добавлено: Вс авг 30, 2009 09:35:33
-==MAXCELERON==-
Та все на буржуйском, и найти конкретно под winavr сложно

Добавлено: Вс авг 30, 2009 09:45:50
Meteor
-==MAXCELERON==- писал(а):Та все на буржуйском, и найти конкретно под winavr сложно

!
1)Раз уж захотели баловаться с МК - учите буржуйский (вдруг пригодится еще в институт поступать)
2)В DS есть примеры на асме и С. Думаю наверняка они ориентированы на АВРСтудию с ВинАВРом...
3) Я например в школе немецкий изучал, но приходиться читать только англицкий...
Добавлено: Вс авг 30, 2009 10:16:18
Meteor
Если уж совсем "влом" то могу рекомендовать книгу А.В.Евстифеева "Микроконтроллеры AVR семейств Tiny и Mega фирмы ATMEL" Издательство Додека. Там почти все по нашенски напсано. НО, недостаток - книгу издали раза 2, а контроллеры то исчезают то новые появляются... Но для старта более чем достаточно.
Успехов!!!
Добавлено: Вс авг 30, 2009 10:19:20
-==MAXCELERON==-
Meteor
Благодарю, щас качну

Добавлено: Вс авг 30, 2009 10:29:01
ARV
В
WinAVR делается так:
eeprom_read_xxxx(uint8_t *adr) - считывает из EEPROM переменную, расположенную по адресу
adr. вместо
xxxx пишите
byte,
word или
dword соответственно для чтения переменной размером в байт, 2 байта или 4 байта.
аналогично для записи имеются функции
eeprom_write_xxxx(uint8_t *adr, ....) - вместо многоточия тип соответствующей переменной.
таким образом, получается примерно следующее:
Код: Выделить всё
#include (avr/eeprom.h) // скобки, естественно, угловые - форум не дает вставить нормальные
EEMEM uint8_t ee_var; // переменная размером в байт в EEPROM
int main(void){
uint8_t my_var;
my_var = eeprom_read_byte(&ee_var); // считали значение из EEPROM
...
eeprom_write_byte(&ee_var, my_var); // сохранили значение в EEPROM
}
Добавлено: Вс авг 30, 2009 10:48:12
-==MAXCELERON==-
ARV
СПАСИБО ОГРОМНОЕ!! Работает!!

Добавлено: Вс авг 30, 2009 11:10:37
-==MAXCELERON==-
Выкладываю полностью рабочий код, может комунибудь пригодится:
В данном случае при отключении питания, значение регистра PORTC сохраняется
Код: Выделить всё
#include (avr/eeprom.h) // скобки угловые
#include (util/delay.h)
#define F_CPU 1000000UL
unsigned char my_var;
EEMEM uint8_t ee_var; /* переменная размером в байт в EEPROM, (если надо 2 байта пишем uint16_t, если 4 - uint32_t */
int main(void){ //Главная функция
my_var = eeprom_read_byte(&ee_var); // считали значение из EEPROM
DDRC = 0xff;
while (1) {
PORTC = my_var;
_delay_ms(1000);
my_var++;
eeprom_write_byte(&ee_var, my_var); //сохранили значение в EEPROM
}
}
В реальном контроллере так делать не рекомендуется - eeprom'а от частых перезаписей при таком цикле хватит на чуть больше суток

Re: Atmega8 запись / чтение переменной eeprom
Добавлено: Вс фев 13, 2011 18:52:45
TICLIR
Доброе время. Дело вот в чём, все примеры опробовал всё работат, смущает одно,переменная
EEMEM uint8_t ee_var, при работе программы, даже когда EEPROM не участвует, изменяет своё значени от 0 до 255. Проследить что её меняет не удалось. Это косяк, или нет.
Re: Atmega8 запись / чтение переменной eeprom
Добавлено: Пн сен 26, 2011 13:53:24
slavokhire5
суть проблемы: пишу программу под одометр. необходимо чтение информации о пройденом расстоянии из еепром, вывод на дисплей, при увеличении расстояния на 1 км - инкримент, запись в еепром и опять вывод. начальный пробег авто - 430000 км. я думаю обьявить его глобальной переменной как EEMEM long int km = 430000; но ведь при новом запуске программы все пройденные километры будут затираться значением 430к. или я не прав? подскажите пожалуйста, как сделать по-человечески)
Re: Atmega8 запись / чтение переменной eeprom
Добавлено: Пн сен 26, 2011 14:09:44
Мастер Ломастер
slavokhire5 писал(а):я думаю обьявить его глобальной переменной как EEMEM long int km = 430000; но ведь при новом запуске программы все пройденные километры будут затираться значением 430к. или я не прав? подскажите пожалуйста, как сделать по-человечески)
не будут. компилятор генерирует файл с начальным содержимым EEPROM, если вы этот файл сами не прошьете - никакой инициализации переменных в EEPROM при запуске программы не будет
Re: Atmega8 запись / чтение переменной eeprom
Добавлено: Пн сен 26, 2011 14:15:13
Flasher
Надеюсь, поделие выкинут раньше, чем набежит 100 000 км пробега
Re: Atmega8 запись / чтение переменной eeprom
Добавлено: Пн сен 26, 2011 14:32:17
slavokhire5
Flasher писал(а):Надеюсь, поделие выкинут раньше, чем набежит 100 000 км пробега
скорее, выкинут машину) 500к пробега это уже серьезно) кроме того, 100к это минимальное количество циклов перезаписи. по форумам пишут, что еепром держит раза в 2-3 дольше
спасибо за быстрый ответ
Re: Atmega8 запись / чтение переменной eeprom
Добавлено: Пт апр 12, 2013 06:47:39
tom1101
Если почитать, там еще всплывает функция
eeprom_update_xxxx(uint8_t *adr) - читает, сравнивает, и, если не равно, записывает. Т.е. записывает только в том случае, если текущее значение отличается от записанного. чтобы не тратить зря и так небольшой ресурс памяти.
А также
eeprom_is_ready() - возвращает 1, если память готова для чтения/записи. и 0 в ином случае.
И
eeprom_busy_wait() - приостанавливает программу до тех пор, пока память не будет готова.
Их листинга я не накопал, ну, кроме eeprom_busy_wait - там просто цикл ожидания.
Насколько известно, перед записью необходимо убедится, что это делать можно, что память не занята другой операцией. Для этого и служит функция ready. Но вот проверяют ли память функции write и update? Тут бы и помог листинг. Ну да ладно, сейчас все сами проверим:
Спойлер
Описания регистров eeprom можно почитать здесь(потыкайте след/пред. страница), а заодно в файле описания контроллера (для меня это include\avr\iotn13.h)
А описание ассемблерных команд - здесь
Также полезно заглянуть в файл описания eeprom (include\avr\eeprom.h)
Код
Код: Выделить всё
//Заполняем eeprom
EEMEM uint8_t uint8_InEeprom_Rezhim = 0b111; //Режим работы (0..255)
//Задаем переменные
uint8_t uint8_Rezhim;
//Загружаем переменные из eeprom
uint8_Rezhim = eeprom_read_byte(&uint8_InEeprom_Rezhim);
На tiny14 превращается вКод: Выделить всё
//Загружаем переменные из eeprom
uint8_Rezhim = eeprom_read_byte(&uint8_InEeprom_Rezhim);
64: 80 e0 ldi r24, 0x00 ; 0 - загружаем младший сегмент адреса ячейки eeprom uint8_InEeprom_Rezhim в двойной регистр
66: 90 e0 ldi r25, 0x00 ; 0 - загружаем старший сегмент
68: 90 d0 rcall .+288 ; 0x18a <__eerd_byte_tn13> - прыгаем к процедуре eeprom_read_byte
6a: 18 2f mov r17, r24 это нам не нужно - компилятор копирует прочитанные данные, видимо для дальнейшего использования
6c: 80 93 60 00 sts 0x0060, r24 копируем прочитанные данные в ОЗУ, в переменную Rezhim
0000018a <__eerd_byte_tn13>:
18a: e1 99 sbic 0x1c, 1 ; 28 Проверяем EECR.EEPE
18c: fe cf rjmp .-4 ; 0x18a <__eerd_byte_tn13> - если EEPE=1 - прыгаем
Короче говоря, здесь программа зацикливается до тех пор. пока не будет сброшен EEPE
Т.е. эти две строчки выше полностью аналогичны процедуре eeprom_busy_wait
18e: 1f ba out 0x1f, r1 ; 31 - записываем в старший сегмент адреса (EEARH) нули
190: 8e bb out 0x1e, r24 ; 30 - записываем в младший сегмент адрес ячейки eeprom
192: e0 9a sbi 0x1c, 0 ; 28 - устанавливаем бит EECR.EERE, просим считать байт из eeprom в регистр EEDR
194: 99 27 eor r25, r25 Это опять компилятор лишнего понапихал - очищает регистр. Он его мог вообще не инициализировать, т.к. заменил выше регистром r1, в котором всегда нуль.
196: 8d b3 in r24, 0x1d ; 29 - читаем буфер EEDR, собственно здесь мы забираем прочитанные данные и помещаем их во временный регистр
198: 08 95 ret прыгаем обратно
Из любопытного - принудительный цикл ожидания готовности памяти перед чтением, может сильно помешать в некоторых случаях.
****************************************************************************
Так, теперь проверим eeprom_write_byteКод: Выделить всё
//Сохраняем в eeprom
eeprom_write_byte(&uint8_InEeprom_Rezhim, temp);
d8: 80 e0 ldi r24, 0x00 ; 0 - загружаем младший сегмент адреса ячейки eeprom uint8_InEeprom_Rezhim в двойной регистр
da: 90 e0 ldi r25, 0x00 ; 0 - загружаем старший сегмент
dc: 61 2f mov r22, r17 копируем переменную temp в регистр r22 для передачи в процедуру
de: c0 d0 rcall .+384 ; 0x260 <__eewr_byte_tn13> - прыгаем к процедуре записи
00000260 <__eewr_byte_tn13>:
260: 26 2f mov r18, r22 помещаем записываемые данные в свой регистр
00000262 <__eewr_r18_tn13>:
262: e1 99 sbic 0x1c, 1 ; 28 - проверяем EECR.EEPE, не занята ли память
264: fe cf rjmp .-4 ; 0x262 <__eewr_r18_tn13> - если занята, ждем в цикле
266: 1c ba out 0x1c, r1 ; 28 - обнуляем EECR
268: 1f ba out 0x1f, r1 ; 31 - записываем в старший сегмент адреса (EEARH) нули
26a: 8e bb out 0x1e, r24 ; 30 - записываем в младший сегмент адрес ячейки eeprom uint8_InEeprom_Rezhim
26c: 2d bb out 0x1d, r18 ; 29 - помещаем в буфер EEDR записываемые данные
26e: 0f b6 in r0, 0x3f ; 63 - сохраняем регистр состояния SREG
270: f8 94 cli запрещаем прерывания
272: e2 9a sbi 0x1c, 2 ; 28 - устанавливаем EEMWE(EEMPE), взводим предохранитель на 4 такта
274: e1 9a sbi 0x1c, 1 ; 28 - устанавливаем EEWE(EEPE), запрашиваем железо произвести запись буфера в eeprom
276: 0f be out 0x3f, r0 ; 63 - восстанавливаем SREG, а заодно разрешаем прерывания (бит I хранился в SREG, и теперь он тоже восстановлен)
278: 01 96 adiw r24, 0x01 ; 1 - инкрементируем адрес в двойном регистре (это на случай потоковой записи, чтобы не задавать каждый раз адрес следующего байта вручную)
27a: 08 95 ret прыгаем в программу
Из любопытного - тот же цикл ожидания, автоматическое соблюдение атомарности (перед записью самостоятельно запрещает прерывания), автоинкремент адреса.
Автоинкремент - если писать последовательные переменные, либо писать блок данных, не нужно увеличивать адрес.
Это меня даже удивило, потому что эта особенность вроде явно не описывается, а попробуешь сам инкрементировать, встроенный автоинкремент только все испортит, поскольку о нем даже не подозреваешь
****************************************************************************
Так, теперь посмотрим на eeprom_update_xxxx(uint8_t *adr)
Код: Выделить всё
//Сохраняем в eeprom
eeprom_update_byte(&uint8_InEeprom_Rezhim, temp);
d8: 80 e0 ldi r24, 0x00 ; 0 - загружаем младший сегмент адреса ячейки eeprom uint8_InEeprom_Rezhim в двойной регистр
da: 90 e0 ldi r25, 0x00 ; 0 - загружаем старший сегмент
dc: 61 2f mov r22, r17 копируем переменную temp в регистр r22 для передачи в процедуру
de: c0 d0 rcall .+384 ; 0x260 <__eeupd_byte_tn13> - прыгаем к процедуре записи
00000260 <__eeupd_byte_tn13>:
260: 26 2f mov r18, r22 помещаем записываемые данные в свой регистр
00000262 <__eeupd_r18_tn13>:
262: e1 99 sbic 0x1c, 1 ; 28 - проверяем EECR.EEPE, не занята ли память
264: fe cf rjmp .-4 ; 0x262 <__eeupd_r18_tn13> - если занята, ждем в цикле
266: 1f ba out 0x1f, r1 ; 31 - записываем в старший сегмент адреса (EEARH) нули
268: 8e bb out 0x1e, r24 ; 30 - записываем в младший сегмент адрес ячейки eeprom uint8_InEeprom_Rezhim
26a: e0 9a sbi 0x1c, 0 ; 28 - устанавливаем бит EECR.EERE, просим считать байт из eeprom в регистр-буфер EEDR
26c: 81 50 subi r24, 0x01 ; 1 - декрементируем(!) адрес в двойном регистре
26e: 0d b2 in r0, 0x1d ; 29 - читаем буфер EEDR, собственно забираем прочитанные данные
270: 02 16 cp r0, r18 сравниваем считанные данные с переменной temp
272: 39 f0 breq .+14 ; 0x282 <__eeupd_r18_tn13+0x20> - если одинаковы, прыгаем на выход
274: 1c ba out 0x1c, r1 ; 28 - если не равны, обнуляем EECR
276: 2d bb out 0x1d, r18 ; 29 - помещаем в буфер EEDR записываемые данные
278: 0f b6 in r0, 0x3f ; 63 - сохраняем регистр состояния SREG
27a: f8 94 cli запрещаем прерывания
27c: e2 9a sbi 0x1c, 2 ; 28 - устанавливаем EEMWE(EEMPE), взводим предохранитель на 4 такта
27e: e1 9a sbi 0x1c, 1 ; 28 - устанавливаем EEWE(EEPE), запрашиваем железо произвести запись буфера в eeprom
280: 0f be out 0x3f, r0 ; 63 - восстанавливаем SREG, а заодно разрешаем прерывания (бит I хранился в SREG, и теперь он тоже восстановлен)
282: 08 95 ret прыгаем назад в программу
Из любопытного - тот же цикл ожидания, автоматическое соблюдение атомарности (перед записью самостоятельно запрещает прерывания), автодекремент адреса.
Непонятко: если автоинкремент понятно зачем, то зачем автодекремент непонятно вообще.
****************************************************************************
Ага. Видно, что eeprom_read_byte(addr) превратилась в последовательность
Код: Выделить всё
uint8_t eeprom_read_byte(uint16_t addr)
{
eeprom_busy_wait();
return = eeprom_read_byte(addr); //Собственно полезная процедура чтения
};
Команда eeprom_write превратилась в последовательность
Код: Выделить всё
void eeprom_write_byte(uint16_t addr, uint8_t var)
{
uint8_t temp;
eeprom_busy_wait();
temp = SREG;
cli();
eeprom_write_byte(addr, var); //Собственно полезная процедура записи
SREG = temp;
addr++;
};
А команда eeprom_update(addr, var) в последовательность
Код: Выделить всё
void eeprom_update_byte(uint16_t addr, uint8_t val)
{
uint8_t temp;
eeprom_busy_wait();
temp = eeprom_read_byte(addr); //Собственно полезная процедура чтения
if (temp != var) //Собственно полезная процедура сравнения
{
temp = SREG;
cli();
eeprom_write_byte(addr, var); //Собственно полезная процедура записи
SREG = temp;
addr--;
};
};
Многовато мусора. Но именно поэтому ни у кого из вас не возникало проблем с записью - оно ждет, пока память не будет свободна.
Но хорошо это или плохо? Плохо. И вот почему: если вдруг память не готова, наша программа зависнет в цикле, пусть и на чуть-чуть.
Если программа не должна прерываться на цикл ожидания, лучше заранее проверять память eeprom_is_ready(), и, если память занята, отменять операции до следующего цикла - может тогда память освободится. а может и нет, но главное - что мы не зависли, а работаем дальше.
Особенно это касается записи - записываемые переменные у нас и так продублированы в оперативке, так что ничто не мешает нам работать дальше, запишем тогда, когда сможем.
Поэтому читать из eeprom можно один раз, при включении, и далее работать с оперативкой, а записывать лишь при явном изменении параметров (например при выходе из меню), и только в том случае, если они изменились: чтобы не расходовать зря ресурс памяти по записям - для этого у нас есть eeprom_update_xxxx(uint8_t *adr)
Автоинкремент там явно лишний, лучше уж его делать вручную, зато наверняка. Накладных расходов это все-равно не повлечет, зато сейчас лишняя команда. Тем более он все-равно не будет работать - при каждом вызове процедуры адрес загружается в регистры заново, затирая результаты работы инкремента/декремента.
Автодекремент - это вообще не знаю зачем.
Ну и на основании этих листингов, мы можем набросать себе простенькие процедуры чтения и записи, проверять готовность памяти вручную (eeprom_is_ready), не ожидать в цикле (а если нужен цикл, всегда есть eeprom_busy_wait), проверять переменную перед записью непосредственно в памяти (а не считывать старое значение из eeprom).
Сама команда чтения нам стоит 4 такта (на время которых CPU будет заморожен), плюс 3 такта передача адреса, плюс такт на выгрузку значения из буфера, итого 9 тактов.
Команда сравнения в памяти стоит 5 тактов (по 2 такта на доступ к памяти) или 1, если переменные в регистрах.
Команда записи стоит 3,4мс, т.е. порядка 3400 тактов на мегагерц частоты. При 8МГц потеряем 27200 тактов, при 16МГц соответственно 54400.
Много? Именно столько придется провести в цикле, ожидая готовности памяти, в стандартных процедурах eeprom_read_byte, eeprom_write_byte, eeprom_update_byte. Именно поэтому имеет смысл использовать свои, или хотя бы проверять память перед операцией с помощью eeprom_is_ready.