RXCIE в регистре UCR). Возникновение этого прерывания означает, что в регистре данных udr имеется принятый байт. Процедуру обработки этого прерывания иллюстрирует листинг 16.1.
Листинг 16.1
UART_RXC:
in temp,UDR ;принятый байт — в переменной temp
cbi UCR,RXCIE ;запрещаем прерывание «прием закончен»
…
<анализируем команду, если это не та команда — опять разрешаем прерывание «прием закончен» и выходим из процедуры
sbi UCR,RXCIE
reti
В противном случае готовим данные, самый первый посылаемый байт должен быть в переменной temp>
…
sbi UCR,UDRIE ;разрешение прерывания «регистр данных пуст»
reti
Далее у нас почти немедленно возникает прерывание «регистр данных пуст». Обработчик этого прерывания состоит в том, что мы посылаем байт, содержащийся в переменной temp, и готовим данные для следующей посылки (листинг 16.2).
Листинг 16.2
UART_DRE:
out UDR,temp ;посылаем байт
cbi UCR,UDRIE ;запрещаем прерывание «регистр данных пуст»
…
<готовим данные, следующий байт — в temp. Если же был отправлен последний нужный байт, то опять разрешаем прерывание «прием закончен» и далее выходим из процедуры, иначе выполняем следующий оператор:>
sbi UCR,UDRIE ;разрешаем прерывание «регистр данных пуст»
reti
Для семейства Mega (USART) вместо UCR в текст примеров надо подставить UCSRB. Обратим внимание на то, что после обработки первого прерывания переменная temp здесь может содержать подготовленный для отправки байт, и не должна в промежутках между прерываниями использоваться еще где-то. В противном случае ее надо сохранять, например, в стеке, или все же отвести для этого дела специальный регистр. Как видите, все довольно сложно.
Однако, как и в случае записи в EEPROM (см. главу 15), поскольку эти события (прием и передача) происходят относительно редко, на практике мы не будем использовать прерывания, и процедуры резко упростятся (на примере USART — листинг 16.3).
Листинг 16.3
Out_com: ;посылка байта из temp с ожиданием готовности
sbis UCSRA,UDRE ;ждем готовности буфера передатчика
rjmp out_com
out UDR,temp ;собственно посылка байта
ret ;возврат из процедуры Out_com
In_com: ;прием байта в temp с ожиданием готовности
sbis UCSRA,RXC ;ждем готовности буфера приемника
rjmp in_com
in temp,UDR ;собственно прием байта
ret ;возврат из процедуры In_com
Для семейства Classic надо заменить все UCSRA на USR. Для сформулированной ранее задачи непрерывного ожидания внешних команд обращение к процедуре In_com при этом вставляется в пустой цикл в конце программы:
Cykle:
rcall In_com
…
<анализируем полученный в temp байт, и что-то с ним делаем, например, посылаем ответ через процедуру Out_com>
…
rjmp Cykle ;зацикливаем программу
При таком способе контроллер большую часть времени ожидает приема, непрерывно выполняя проверку бита RXC (в процедуре In_com), этот процесс прерывается только на время выполнения «настоящих» прерываний. Прерывания все равно должны выполняться много быстрее, чем байт, в случае, если он пришел, успевает в UDR смениться следующим (пауза составляет около 1 мс при скорости 9600, и за это время успеет выполниться порядка нескольких тысяч команд), так что мы ничего не потеряем. А процедура посылки Out_com сама по себе может выполняться долго (как и в случае с записью EEPROM, кроме самого первого обращения: задержка будет, если посылать несколько байт подряд). Но для программиста процедура также в основном будет заключаться в том, что контроллер будет ожидать очистки UDR, и т. к. это не прерывание, то ожидание в любой момент может быть прервано реальным прерыванием, и мы ничего не теряем (даже если длительность прерывания превысит время посылки байта, то это лишь вызовет небольшую паузу в передаче).
Но чтобы ничего действительно не потерять, при таком способе следует быть внимательным: так, нужно следить за использованием temp внутри возникшего прерывания, а лучше на момент посылки данных вообще прерывания запретить. Правда, если мы будем применять процедуру Out_com внутри процедуры прерывания, куда другое прерывание «влезть» не может, то temp меняться заведомо не будет, но тогда при посылке нескольких байтов контроллер будет терять значительное время на ожидание, и это может нарушить работу других прерываний. Если это критично, то следует перейти к более сложной процедуре с использованием прерываний UART.
В общем и целом все эти нюансы следует иметь в виду, но на практике они почти не доставляют сложностей, за исключением одного момента, который мы еще обсудим в главе 17: если вам необходим переход в режим энергосбережения, то его объявление останавливает МК немедленно, как только встретится соответствующая команда. Если при этом в регистре данных UART оставался недоотправленный байт, то он так и не будет отправлен. Простыми задержками (например, выполнением пустого цикла) перед остановкой МК с этим явлением бороться неудобно (как мы говорили, нужно выполнить несколько тысяч команд). Лучше всего в таких случаях дождаться момента, когда регистр передатчика вновь окажется пуст (выполнением того же цикла непрерывной проверки UDRE, как в процедуре Out_com), и только тогда переходить к объявлению режима энергосбережения.
Отладка программ с помощью UART
Эти процедуры помогут мне выполнить давнее обещание и рассказать о том, как любую схему превратить в отладочный стенд. Вы просто временно расставляете в нужных местах программы контрольные точки в виде пар операторов:
move temp,RegX
rcall Out_com
Здесь RegX — регистр, значение которого хочется отследить в реальном времени. Если это регистр ввода/вывода, то вместо move надо использовать инструкцию in. Подсоединив схему к компьютеру (см. главу 18), вы будете получать на ПК значения требуемого регистра при каждом прохождении программой этой контрольной точки. Иногда это может нарушить нормальную работу программы, как мы говорили ранее, но даже с учетом этого обстоятельства такой способ много нагляднее, быстрее и дешевле, чем использование дорогих отладочных модулей в совокупности с AVR Studio.
Если вы исследуете программу, в которой работа с UART не предусмотрена, то ничего не стоит вставить его инициализацию туда временно, и также временно вывести проводочками выводы RxD и TxD на небольшой отладочный стендик, состоящий из одного-единственного преобразователя уровней UART/RS-232. Единственное неудобство — при перестановке контрольных точек программу придется каждый раз перекомпилировать и заново записывать ее в МК, но это все равно потребуется при ее правке. По этим причинам я стараюсь иметь компьютеры с двумя COM-портами: к одному из них подключается программатор, к другому — выход схемы. Если у вас есть редактор текста, позволяющий запускать компиляцию прямо из него, как описывалось в главе 13, то процесс отладки микропрограммы становится не сложнее, чем работа в среде Turbo Pascal, Delphi или Visual Basic. Правда, многое еще зависит от удобства программы, которая принимает данные в ПК. Этот вопрос мы также обсудим в главе 18.
Научившись таким образом принимать и передавать данные через UART, мы можем внести изменения в нашу программу измерителя с тем, чтобы загружать коэффициенты в EEPROM без перепрограммирования системы. Для начала придется изменить схему самого измерителя, добавив к ней модуль преобразователя UART/RS-232, который будет подсоединяться к выводам 14 (RxD) и 15 (TxD) контроллера ATmega8535 (см. рис. 15.2). Саму схему мы рисовать пока не будем, различные варианты ее построения мы подробно обсудим в главе 18.
Инициализацию UART удобно производить в той же секции начальной загрузки, например, сразу после инициализации таймеров. Вставьте туда фрагмент задания скорости и режима, приведенный ранее (естественно, в варианте для семейства Mega, но с коэффициентом 25, а не 103, как в примере, т. к. у нас кварц 4 МГц), потом процедуры Out_com и In_com (например, в начало текста, сразу после векторов прерываний), а затем вместо пустого цикла в конце программы впишите следующий код:
Gcykle:
cpi temp,0xE0 ;записать коэффициенты + 8 байт
breq ргос_Е0
cpi temp,0хЕ2 ;читать коэффициенты 8 байт
breq ргос_Е2
rjmp Gcykle
Работает этот кусок программы так, как описано ранее: программа в основном непрерывно опрашивает бит RXC, «отвлекаясь» только на выполнение настоящего прерывания (Timer 0 в данном случае, см. главу 15). И только если через UART был принят какой-то байт (он окажется в temp), программа переходит к его последовательной проверке на одно из заданных значений.
Заметки на полях
В оформлении процедуры заложен один потенциальный баг: между выполнением приема байта (процедура in_com) и его анализом (cpi temp…) может «вклиниться» прерывание, и содержимое temp будет, скорее всего, испорчено. Чтобы избежать этой ошибки, можно поступить двояко: либо запретить на время все прерывания (по крайней мере, пока идет анализ), либо каждый обработчик прерывания начинать с команды push temp