ния. Это небольшая микросхема (как правило, трехвыводная), которая при снижении питания ниже допустимого закорачивает свой выход на «землю». Если питание в пределах нормы, то выход находится в состоянии «разрыва» и никак не влияет на работу схемы. Присоединив этот выход к выводу Reset, мы получаем надежный предохранитель (рис. 15.5).
Рис. 15.5. Подсоединение внешнего монитора питания МС34064 к МК (схема из руководства фирмы Motorola)
Для напряжений питания 5 В один из самых популярных мониторов питания — микросхема МС34064, которая имеет встроенный порог срабатывания 4,6 В, выпускается в корпусе ТО-92 с гибкими выводами и обладает достаточно малым собственным потреблением (менее 0,5 мА). Время срабатывания у нее составляет при плавном снижении напряжения порядка 200 не, что достаточно для предотвращения выполнения «вредных» команд.
Если у вас питание автономное (от батарей), то к выбору монитора питания следует подходить довольно тщательно — так, чтобы не приводить схему в неработоспособное состояние тогда, когда батареи еще не исчерпали свой ресурс. При напряжении питания схемы 3,3 В пригодны приборы DS1816-10, MAX809S, при напряжении питания 3,0 В — DS1816-20 или MAX803R, а также некоторые другие.
Отметим, что рекомендуемый в фирменных описаниях способ защиты EEPROM вводом МК в состояние пониженного энергопотребления (см. главу 17) довольно сложно осуществить на практике. Если же вы все же умудритесь его задействовать, то следует учитывать, особенно в случае батарейного питания, что при резком снижении потребления напряжение источника немедленно повысится, что при относительно малом значении гистерезиса монитора питания (для МС34064 — 20 мВ) обязательно вызовет «дребезг» схемы. Увеличить гистерезис можно включением еще нескольких резисторов, но лучше обойтись вводом в режим сброса, как более простым и надежным способом.
Запись и чтение EEPROM
Запись и чтение данных в EEPROM осуществляется через специальные регистры ввода/вывода: регистр данных EEDR, регистр адреса EEAR (если объем EEPROM более 256 байт, то он делится на два — EEARH и EEARL) и регистр управления EECR. Основная особенность этого процесса — медленность процедуры записи, которая для разных моделей AVR может длиться от 2 до 9 мс, в тысячи раз дольше, чем выполнение обычных команд (обратим внимание, что в отличие от записи чтение осуществляется всего за один машинный цикл, даже быстрее, чем из обычной SRAM).
Для удобства проведения всех подобных процедур, которые могут длиться достаточно долго, в AVR предусмотрено соответствующее прерывание. В данном случае прерывание EEPROM может генерироваться по окончании очередного цикла записи, когда память свободна. Использовать его удобно, если нам требуется производить запись достаточно больших массивов: например, для 100 байтов запись может длиться почти секунду, и тормозить МК на весь этот период было бы неразумно. Тогда основная схема действий будет такой: разрешить прерывание EEPROM, и «внутри него» произвести запись очередного байта. Когда массив заканчивается, прерывания EEPROM запрещаются.
Такой метод можно назвать «правильным», но он заметно сложнее простого «лобового» метода, рекомендуемого, кстати, и в фирменном описании. Простой метод состоит в том, что мы запускаем бесконечный цикл ожидания, пока EEPROM освободится, и только тогда выполняем запись (или чтение) данных. В этом случае, если нам нужно записать всего один байт, МК вообще не будет затормаживаться (перед первой записью память свободна), и лишь при записи нескольких байтов подряд будет возникать упомянутая задержка. Факт задержки стоит учесть на будущее, когда нам придется стыковать запись в EEPROM с процедурами приема данных из последовательного порта, а во всех остальных ситуациях это практически не играет никакого значения: как вы увидите, в простейшем случае запись в EEPROM в процессе эксплуатации нам вообще не потребуется.
Процедура записи в EEPROM, которую мы будем использовать (листинг 15.7), ничем не отличается от приводимой в фирменных описаниях контроллеров, и я ее привожу в удобных для нас обозначениях регистров.
Листинг 15.7
WriteEEP: ;в ZH:ZL — адрес EEPROM куда писать
;в temp — записываемый байт
sbic EECR,EEWE ;ждем очистки бита
rjmp WriteEEP ;разрешения записи EEWE
out EEARH,ZH ;старший адреса
out EEARL,ZL ;младший адреса
out EEDR,temp ;данные
sbi EECR,EEMWE ;установить флаг разрешения записи
sbi EECR,EEWE ;установить бит разрешения
ret ;(конец WriteEEP)
Установленный нами бит разрешения EEWE в регистре управления сбросится автоматически, когда запись закончится — этого сброса мы и ожидаем в начале процедуры. Естественно, в самый первый раз никакого ожидания на самом деле не потребуется. На всякий случай то же самое рекомендуется делать и при чтении, но практически всегда (если только мы не читаем непосредственно после записи), это не будет задерживать программу дольше, чем на время выполнения команды sbic, т. е. на два машинных цикла. Так как при чтении не требуется устанавливать никаких флагов, то процедура получается несколько короче (листинг 15.8).
Листинг 15.8
ReadEEP: ;в ZH: ZL — адрес откуда читать
;возврат temp — прочтенный байт
sbic EECR,EEWE ;ожидание очистки флага записи
rjmp ReadEEP
out EEARH,ZH ;старший адреса
out EEARL,ZL ;младший адреса
sbi EECR,EERE ;бит чтения
in temp,EEDR ;чтение
ret ;конец ReadEEP
В этих процедурах регистр Z не играет никакой выделенной роли, а просто выбран в качестве удобной пары регистров, и может быть заменен на любую другую пару. Отметим еще, что на время записи следует запрещать прерывания, однако в наших программах далее это будет обеспечиваться автоматически.
Первичная запись констант в EEPROM
В принципе можно избежать процедуры записи вообще, если просто записать в EEPROM необходимые константы в процессе программирования. Это нужно делать отдельно от записи программы во Flash, с помощью специально подготовленного hex-файла. Но это ничем не будет отличаться от ситуации, когда константы хранятся в тексте программы, только программировать МК придется значительно дольше, особенно при отладке. Гораздо грамотнее будет не пожалеть труда и составить программу так, чтобы она сама записывала нужные константы «по умолчанию». Как это правильно сделать?
Разумеется, это следует сделать при запуске МК, в процедуре Reset. Но записывать константы каждый раз при включении питания не только не имеет смысла (тогда проще их опять же хранить в тексте), но и еще более неудобно для пользователя, чем установка часов, о которой шла речь в главе 14 — в дальнейшем мы научимся отдельно от программы записывать коэффициенты, не меняя текст программы, и хочется, чтобы это не требовалось делать после каждого сбоя питания. Тогда при удаче (если схема спроектирована верно и EEPROM надежно защищена от сбоев) автоматическая запись будет производиться один-единственный раз: при первом запуске контроллера, сразу после загрузки в его память программы, которую мы сейчас создадим.
Для этого нам потребуется как-то узнавать, есть ли уже в EEPROM какие-то данные, или нет, и правильно ли они записаны. Можно учесть тот факт, что в пустой EEPROM всегда записаны одни единицы (любой считанный байт будет равен $FF), но в общем случае это ненадежно. Наиболее универсальный способ — выделить для этого один какой-то байт в EEPROM, и всегда придавать ему определенное значение, а при загрузке МК его проверять. Это не гарантирует 100 %-ной надежности при сбоях (т. к. данные в незащищенной EEPROM могут меняться произвольно, в том числе и с сохранением значения отдельных байтов), но мы будем считать, что от сбоев защищены «двойной броней» (из внешнего монитора питания и встроенной схемы BOD), и нам важно только распознать ситуацию, когда требуется первичная запись в еще не заполненную память. Приборы, которые я проектировал таким образом, работали, не выключаясь годами, без единого сбоя загруженных констант.
Итак, общая схема алгоритма такая: читаем контрольный байт из EEPROM, если он равен заданной величине (обычно я выбираю чередование единиц и нулей: $АА), то это значит, что коэффициенты уже записаны. Если же нет, то записывает значения «по умолчанию», в том числе и значение этого контрольного байта. Далее в любом случае переходим к процедуре чтения из EEPROM и перегрузки записанных констант в SRAM, откуда они при необходимости извлекаются точно так же, как ранее в процедурах расчета физических величин. Так мы сможем ничего не менять в основной программе, описанной ранее в этой главе, а лишь дописать некий текст в секции начальной загрузки.
Пусть значения коэффициентов записываются в EEPROM с самого начала (с адреса 0:0, в том же порядке, в котором они расположены в SRAM), а по адресу $10 записывается контрольный байт, равный $АА. Тогда в программе, приведенной в Приложении 5, в конце процедуры начальной загрузки по метке reset вместо всего фрагмента, начинающегося с заголовка «запись коэффициентов» до команды sei (обязательно перед ней, а не после) добавляется текст листинга 15.9.
Листинг 15.9
;чтение коэффициентов из EEPROM =====
clr ZH ;ст. адрес =0
ldi ZL,$10 ;адрес контрольного байта
rcall ReadEEP
cpi temp,$AA ;если он равен $AA
breq mm_RK ;то на чтение в ОЗУ
rcall ZapisK ;иначе запись значений по умолчанию
mm_RK: ;извлечение коэфф. из EEPROM в SRAM
clr ZL