- читает, сравнивает, и, если не равно, записывает. Т.е. записывает только в том случае, если текущее значение отличается от записанного. чтобы не тратить зря и так небольшой ресурс памяти.
- возвращает 1, если память готова для чтения/записи. и 0 в ином случае.
- приостанавливает программу до тех пор, пока память не будет готова.
Их листинга я не накопал, ну, кроме 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.