Atmega8 запись / чтение переменной eeprom

Обсуждаем контроллеры компании Atmel.
Ответить
Встал на лапы
Аватара пользователя
Сообщения: 96
Зарегистрирован: Сб авг 15, 2009 07:31:56
Откуда: г. Кемерово

Сообщение -==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,
и при следующем включении м.к. счет начался не от нуля, а от сохраненного значения?? :idea:
Реклама
IT KT
Аватара пользователя
Сообщения: 1309
Зарегистрирован: Пн авг 25, 2008 08:49:33

Сообщение demson »

Работа с eeprom в CodeVision сделана прозрачной, вот так:

eeprom int ee_test = 1;
int test;

test = ee_test; // чтение из ЕЕПРОМ
ee_test = test; // запись в ЕЕПРОМ
http://radiokot.ru/forum/viewtopic.php? ... 50d67d9c45
в спорах рождается ИСТИНА [AND] flud.
Контактная информация:
Реклама
Встал на лапы
Аватара пользователя
Сообщения: 96
Зарегистрирован: Сб авг 15, 2009 07:31:56
Откуда: г. Кемерово

Сообщение -==MAXCELERON==- »

Да, но вся проблема в том, что eeprom int ee_test = 1; в winawr - не известная команда, выдает ошибку error: expected '=', ',', ';', 'asm' or '__attribute__' before 'int'
IT KT
Аватара пользователя
Сообщения: 1309
Зарегистрирован: Пн авг 25, 2008 08:49:33

Сообщение demson »

с winavr работал мало но вроде это то что нужно...
http://electronix.ru/forum/index.php?s= ... opic=32125
8)
В WinAVR есть готовые функции для работы с eeprom.
смотрите eeprom.h.
Предпочтительнее использовать их, т.к. они работают вне зависимости от уровня оптимизации.

#include <avr>

eeprom_write_byte
eeprom_read_byte
в спорах рождается ИСТИНА [AND] flud.
Контактная информация:
Реклама
Эиком - электронные компоненты и радиодетали
Друг Кота
Аватара пользователя
Сообщения: 3961
Зарегистрирован: Пн июл 13, 2009 14:37:39
Откуда: Московская область, наукоград.....

Сообщение 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;
}
Контактная информация:
Реклама
Встал на лапы
Аватара пользователя
Сообщения: 96
Зарегистрирован: Сб авг 15, 2009 07:31:56
Откуда: г. Кемерово

Сообщение -==MAXCELERON==- »

Сейчас проверю :)
Реклама
Друг Кота
Аватара пользователя
Сообщения: 3961
Зарегистрирован: Пн июл 13, 2009 14:37:39
Откуда: Московская область, наукоград.....

Сообщение Meteor »

Советую всегда сначала рыть на даташитах. Там ничего секретного нет. Или нет самого даташита?
Контактная информация:
Встал на лапы
Аватара пользователя
Сообщения: 96
Зарегистрирован: Сб авг 15, 2009 07:31:56
Откуда: г. Кемерово

Сообщение -==MAXCELERON==- »

Та все на буржуйском, и найти конкретно под winavr сложно :(
Друг Кота
Аватара пользователя
Сообщения: 3961
Зарегистрирован: Пн июл 13, 2009 14:37:39
Откуда: Московская область, наукоград.....

Сообщение Meteor »

-==MAXCELERON==- писал(а):Та все на буржуйском, и найти конкретно под winavr сложно :(
!
1)Раз уж захотели баловаться с МК - учите буржуйский (вдруг пригодится еще в институт поступать) :)
2)В DS есть примеры на асме и С. Думаю наверняка они ориентированы на АВРСтудию с ВинАВРом...
3) Я например в школе немецкий изучал, но приходиться читать только англицкий...
Контактная информация:
Друг Кота
Аватара пользователя
Сообщения: 3961
Зарегистрирован: Пн июл 13, 2009 14:37:39
Откуда: Московская область, наукоград.....

Сообщение Meteor »

Если уж совсем "влом" то могу рекомендовать книгу А.В.Евстифеева "Микроконтроллеры AVR семейств Tiny и Mega фирмы ATMEL" Издательство Додека. Там почти все по нашенски напсано. НО, недостаток - книгу издали раза 2, а контроллеры то исчезают то новые появляются... Но для старта более чем достаточно.
Успехов!!!
Контактная информация:
Встал на лапы
Аватара пользователя
Сообщения: 96
Зарегистрирован: Сб авг 15, 2009 07:31:56
Откуда: г. Кемерово

Сообщение -==MAXCELERON==- »

Meteor
Благодарю, щас качну :)
ARV
Ум, честь и совесть. И скромность.
Аватара пользователя
Сообщения: 18785
Зарегистрирован: Чт дек 28, 2006 08:19:56
Откуда: Новочеркасск

Сообщение 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
}
если рассматривать человека снизу, покажется, что мозг у него глубоко в жопе
при взгляде на многих сверху ничего не меняется...

Мой уютный бложик... заходите!
Контактная информация:
Встал на лапы
Аватара пользователя
Сообщения: 96
Зарегистрирован: Сб авг 15, 2009 07:31:56
Откуда: г. Кемерово

Сообщение -==MAXCELERON==- »

ARV

СПАСИБО ОГРОМНОЕ!! Работает!! :)))
Встал на лапы
Аватара пользователя
Сообщения: 96
Зарегистрирован: Сб авг 15, 2009 07:31:56
Откуда: г. Кемерово

Сообщение -==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'а от частых перезаписей при таком цикле хватит на чуть больше суток :twisted:
Встал на лапы
Сообщения: 134
Зарегистрирован: Вс авг 01, 2010 16:05:25

Сообщение TICLIR »

Доброе время. Дело вот в чём, все примеры опробовал всё работат, смущает одно,переменная
EEMEM uint8_t ee_var, при работе программы, даже когда EEPROM не участвует, изменяет своё значени от 0 до 255. Проследить что её меняет не удалось. Это косяк, или нет.
Прорезались зубы
Аватара пользователя
Сообщения: 202
Зарегистрирован: Пн сен 26, 2011 13:48:25
Откуда: Харьков

Сообщение slavokhire5 »

суть проблемы: пишу программу под одометр. необходимо чтение информации о пройденом расстоянии из еепром, вывод на дисплей, при увеличении расстояния на 1 км - инкримент, запись в еепром и опять вывод. начальный пробег авто - 430000 км. я думаю обьявить его глобальной переменной как EEMEM long int km = 430000; но ведь при новом запуске программы все пройденные километры будут затираться значением 430к. или я не прав? подскажите пожалуйста, как сделать по-человечески)
Осилит дорогу идущий
--------------------------
Пишу на Си за еду
Поставщик валерьянки для Кота
Сообщения: 1995
Зарегистрирован: Ср май 11, 2011 21:37:45
Откуда: Цветочный город

Сообщение Мастер Ломастер »

slavokhire5 писал(а):я думаю обьявить его глобальной переменной как EEMEM long int km = 430000; но ведь при новом запуске программы все пройденные километры будут затираться значением 430к. или я не прав? подскажите пожалуйста, как сделать по-человечески)
не будут. компилятор генерирует файл с начальным содержимым EEPROM, если вы этот файл сами не прошьете - никакой инициализации переменных в EEPROM при запуске программы не будет
битва с дураками проиграна, победители торжествуют. слава победителям!
Контактная информация:
Мудрый кот
Аватара пользователя
Сообщения: 1802
Зарегистрирован: Ср сен 07, 2011 21:40:30

Сообщение Flasher »

Надеюсь, поделие выкинут раньше, чем набежит 100 000 км пробега
Прорезались зубы
Аватара пользователя
Сообщения: 202
Зарегистрирован: Пн сен 26, 2011 13:48:25
Откуда: Харьков

Сообщение slavokhire5 »

Flasher писал(а):Надеюсь, поделие выкинут раньше, чем набежит 100 000 км пробега
скорее, выкинут машину) 500к пробега это уже серьезно) кроме того, 100к это минимальное количество циклов перезаписи. по форумам пишут, что еепром держит раза в 2-3 дольше

спасибо за быстрый ответ
Осилит дорогу идущий
--------------------------
Пишу на Си за еду
Вымогатель припоя
Аватара пользователя
Сообщения: 581
Зарегистрирован: Чт янв 15, 2009 18:57:12

Сообщение 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.
[img]http://ub.imgfiles.ru/2.png[/img]
Ответить

Вернуться в «AVR»