PIC-микроконтроллеры. Все, что вам необходимо знать

Катцен Сид

Часть III

ОКРУЖАЮЩИЙ МИР

 

 

Не считая краткого обсуждения гарвардской архитектуры, имевшего место в 3-й главе книги, мы до сих пор ограничивались рассмотрением внутренней структуры микроконтроллеров PIC среднего уровня и их программного обеспечения. Только вскользь упоминались параллельные порты ввода/вывода. Последняя же часть книги посвящена взаимодействию ядра микроконтроллера со средой, физически находящейся за пределами корпуса микроконтроллера. То есть в данной части мы изучим вопросы использования портов ввода/вывода и встроенных периферийных устройств микроконтроллера. В самом конце мы разберем учебный пример создания законченного автономного встраиваемого контроллера.

На Рис. 4.1, приведенном на стр. 89, изображена внутренняя структура и цоколевка микроконтроллера PIC16F84. У этой 18-выводной модели имеется два параллельных порта ввода/вывода, причем 4-й вывод порта А совмещен со счетным входом 8-битного таймера, а 0-й вывод порта В — с входом внешнего прерывания. Помимо этого, в составе микроконтроллера имеется EEPROM-память данных размером 64 байта и сторожевой таймер.

Микроконтроллер PIC16F84 был одним из первых представителей семейства среднего уровня, в которых наряду с параллельными портами и таймерами, унаследованными от более старого базового семейства, имелась поддержка прерываний и модуль EEPROM-памяти. По мере появления новых моделей расширялся и набор периферийных устройств. В этой части книги на примере 8-выводных моделей PIC12F629/75, 18-выводных моделей PIC16F627/28/48 и 28/40-выводных моделей PIC16F873/74/76/77 мы рассмотрим наиболее часто используемые периферийные устройства. Вообще говоря, функционирование любого модуля практически не зависит от модели устройства, в которой он реализован, однако модули в более новых моделях могут иметь расширенные функциональные возможности. По мере прочтения, вы:

• Изучите сопутствующие вопросы, такие как выбор источника питания, выбор источника тактового сигнала, управление энергопотреблением микроконтроллера и конфигурирование устройства.

• Познакомитесь с параллельным и последовательным вводом/выводом цифровых данных.

• Разберетесь с подсистемами счетного и сторожевого таймеров.

• Узнаете, каким образом микроконтроллер обрабатывает аналоговые сигналы.

• Самостоятельно разработаете встраиваемый таймер со звуковой индикацией.

• Узнаете, каким образом можно протестировать и отладить созданную систему.

Микроконтроллеры РIС в различных корпусах

 

Глава 10

Реальное окружение

До этого момента мы главным образом изучали взаимодействие программы с внутренними регистрами процессора и памятью данных. Но прежде чем перейти к изучению встроенных периферийных устройств микроконтроллера, посредством которых он может отслеживать состояние и управлять окружающей его средой, т. е. реальным миром вне его выводов, нам необходимо рассмотреть требования, предъявляемые к источнику питания, а также вопросы тактирования и сброса микроконтроллеров PIC.

Прочитав эту главу, вы:

• Ознакомитесь с рекомендуемыми величинами напряжения питания, а также допустимыми уровнями входных и выходных напряжений.

• Поймете, в чем разница между статической и динамической рассеиваемой мощностью, и узнаете, что величина последней прямо пропорциональна тактовой частоте микроконтроллера и квадрату напряжения питания.

• Научитесь переключать микроконтроллер в режим пониженного энергопотребления и выводить его из этого режима, а также узнаете, как этот режим влияет на процессор.

• Познакомитесь со встроенным тактовым генератором.

• Узнаете, как можно во время программирования микроконтроллера задавать его конфигурацию.

• Разберетесь в нюансах различных вариантов сброса.

В качестве своеобразной прелюдии к нашему разговору предлагаю взглянуть на Рис. 10.1, где показана структура микроконтроллеров PIC16F874 и PIC16F877, на примере которых мы в основном и будем далее изучать микроконтроллеры PIC. Эти модели полностью идентичны, за исключением большего объема памяти программ, данных и EEPROM в последней. Поэтому основное внимание мы уделим микроконтроллеру PIC16F877. Микроконтроллеры PIC16F873/6 являются 28-выводными вариантами тех же микроконтроллеров и соответственно имеют урезанный набор периферии. В дальнейшем для ссылки на эти четыре модели мы будем использовать обозначение PIC16F87X.

Рис. 10.1. Архитектура микроконтроллеров PICI6F874/77A

За исключением объемов различных областей памяти, ядра этих процессоров очень похожи на ядра остальных моделей среднего уровня, поскольку поддерживают набор из 33 команд, описанный в главе 5. Если сравнить структуру микроконтроллера PIC16F84, приведенную на Рис. 4.1 (стр. 89), со структурой микроконтроллеров, изображенной на Рис. 10.1, то можно заметить, что у последних имеется больше периферийных модулей. Разумеется, даже при наличии 40 выводов невозможно предоставить каждому периферийному устройству отдельные линии ввода/вывода для общения с внешним миром. Поэтому большинство выводов является разделяемым ресурсом. Например, вывод RA3 является 3-м битом порта А, но также может использоваться в качестве 3-го аналогового входа AN3 или даже как вход для подключения внешнего источника опорного напряжения Vref+ для модуля аналого-цифрового преобразователя. В более миниатюрных микроконтроллерах, таких как 18-выводной PIC16F627 и 8-выводной PIC12F675, цоколевка которых приведена на Рис. 10.2, тоже имеются различные периферийные модули. Только в этом случае один и тот же вывод может использоваться несколькими модулями, что накладывает более серьезные ограничения на одновременное применение различных модулей в конкретном приложении. В моделях с малым числом выводов разработчик обычно может использовать внутренний тактовый генератор, а также исключить вход внешнего сброса для экономии драгоценных ресурсов (см. Табл. 10.2).

Рис. 10.2. Цоколевка некоторых моделей микроконтроллеров PIC

Все микроконтроллеры РIС обычно имеют номинальное напряжение питания VDD = 5 В. Типичный представитель семейства PIC16F87X может работать на частотах до 20 МГц при напряжении питания 5 ±0.5 В. Если тактовая частота не превышает 16 МГц, то напряжение может быть снижено до 4 В. Многие представители семейства также имеют низковольтные исполнения. Например, микроконтроллер PIC16LF87X может работать на частотах до 10 МГц при напряжении 3…5.5 В, а при снижении частоты до 4 МГц напряжение питания может быть уменьшено до 2 В. А вот модели PIC12F629/675 даже в обычном исполнении могут работать при напряжении 2…5 В.

С точки зрения элементов схемы микроконтроллеры PIC являются обычными цифровыми микросхемами. Напряжение НИЗКОГО уровня на выводе, сконфигурированном как выход, не превышает значения VDD = 0.6 В при втекающем токе до 8.5 мА в диапазоне температур -40…+85 °C. Вывод, установленный микроконтроллером в состояние ВЫСОКОГО уровня, может отдавать ток до 3 мА, при этом напряжение на нем будет не менее VDD — 0.7 В, т. е. при VDD = 5 В напряжение ВЫСОКОГО уровня VОН = 4.3 В.

Для вывода, сконфигурированного как вход, напряжение величиной, составляющей менее 15 % (для входов с триггером Шмитта — менее 20 %) от напряжения питания, будет восприниматься как напряжение НИЗКОГО уровня. Таким образом, при VDD = 5 В входное напряжение НИЗКОГО уровня FIL составит 0.75 В. За небольшим исключением, все выводы, функционирующие как входы, воспринимают напряжение величиной более 25 % (для входов с триггером Шмитта — более 80 %) от напряжения питания плюс 0.8 В как напряжение ВЫСОКОГО уровня, т. е. VIH = 2 В при FDD = 5 В.

В Табл. 10.1 приведены значения тока потребления рассматриваемых моделей микроконтроллеров при различных режимах работы.

Многие микроконтроллерные устройства питаются от батарей, и в этих случаях величина тока потребления микроконтроллера является одним из его важнейших параметров. Для облегчения разработки таких устройств компания Microchip выпускает семейство nanoWatt, к которому, в частности, относятся модели PIC12F629/675. Представители этого семейства имеют очень маленькое потребление и способны работать в широком диапазоне питающих напряжений. Из документации на микроконтроллеры можно увидеть, что максимальный и минимальный токи потребления могут отличаться в десятки миллионов раз. Поэтому крайне необходимо четко понимать, какие факторы влияют на суммарное потребление микроконтроллера.

Типичная зависимость тока потребления микроконтроллеров PIC от их тактовой частоты приведена на Рис. 10.3. Ясно видно, что рассеиваемая мощность VDD х /DD прямо пропорциональна рабочей частоте. Так, при тактовой частоте 10 МГц ток потребления микроконтроллера в 100 раз больше, нежели при частоте 100 кГц.

Рис. 10.3. Типичная зависимость тока потребления от тактовой частоты

Чтобы понять, почему так происходит, представьте себе ключ, заряжающий и разряжающий емкостную нагрузку С, как показано на Рис. 10.4. В качестве такого ключа выступает транзистор, а нагрузкой является паразитная емкость связи со следующим полевым транзистором и его затвором. RS представляет собой сопротивление открытого транзистора.

Рис. 10.4. Эквивалентная схема выходного каскада (конденсатор С представляет собой как внутреннюю емкость, так и емкость нагрузки)

При разомкнутом ключе (транзистор закрыт) емкость заряжается по экспоненциальному закону до уровня V вольт с постоянной времени τ =CRL. В устойчивом состоянии в конденсаторе хранится энергия, равная 1/2 СV2 Дж. Кроме того, при протекании этого тока заряда конденсатора через сопротивление нагрузки в последнем рассеивается энергия, которая вычисляется следующим образом:

Итак, 1/2 СV2 Дж рассеивается на сопротивлении нагрузки (независимо от величины этого сопротивления RL!) и еще столько же накапливается в электрическом поле конденсатора. При разряде конденсатора эта накопленная энергия рассеивается на сопротивлении, образованном параллельным соединением резисторов RS и RL (и опять же значение рассеиваемой энергии не зависит от значений этих сопротивлений). Таким образом, при каждом переключении транзистора рассеивается энергия, равная CV2 Дж. Суммарная рассеиваемая мощность равна произведению этого значения на число циклов переключения в секунду (CV2f) плюс потери в статическом режиме из-за токов утечки.

Из предыдущего соотношения (CV2f) видно, что рассеиваемая мощность прямо пропорциональна частоте при любом заданном напряжении питания. Более того, она пропорциональна квадрату напряжения питания, так что, снизив напряжение питания в 2 раза (скажем, с 5 до 2.5 В), мы уменьшим рассеиваемую мощность (VDD х IDD) в 4 раза.

Значение динамической рассеиваемой мощности, вычисленной выше, следует сложить с мощностью, рассеиваемой в статическом режиме (при тактовой частоте устройства, равной нулю). Из нижней строки Табл. 10.1 видно, что величина этого базового тока (или тока в режиме Power Down), который в документации обозначается как IРD, обычно не превышает 1 мкА. При этом подразумевается, что периферийные модули, имеющие собственные тактовые генераторы (такие, как сторожевой таймер и схема сброса по снижению питания), отключены.

Разумеется, оставить процессор без тактового сигнала будет слишком непродуктивно, поскольку в этом случае он ничего не сможет делать! Однако во многих встраиваемых системах необходимость в вычислениях возникает спорадически, поэтому было бы полезно иметь возможность перевода процессора в режим ожидания в те периоды, когда от него не требуется никаких действий. Возьмем, к примеру, микроконтроллерный датчик системы радиотелеметрии, находящийся на дне озера. Он может проводить измерение температуры всего один раз в час, при этом время его работы от одной батареи должно составлять не менее года.

Для поддержки ситуаций, подобных описанной, все микроконтроллеры PIC имеют «спящий» режим, в котором внутренний тактовый генератор отключается. Переход в этот режим осуществляется по команде sleep. При нахождении в «спящем» режиме содержимое памяти данных не изменяется при условии, что напряжение питания больше 1.5 В (параметр VDR в документации). Микроконтроллеры PIC могут выходить из спящего режима при сбросе устройства (см. стр. 316), при появлении запроса разрешенного прерывания или при переполнении сторожевого таймера.

Когда процессор исполняет команду sleep, он сбрасывает бит  в регистре STATUS (см. Рис. 4.6 на стр. 95) и выключает внутренний тактовый генератор. Если сторожевой таймер (см. стр. 453) включен, то при выполнении этой команды он вместе со своим предделителем сбрасывается, но продолжает работать, поскольку имеет собственный внутренний генератор. При этом устанавливается флаг , индицирующий отсутствие тайм-аута. Содержимое всех регистров, включая различные установки портов, остается неизменным.

Для пробуждения микроконтроллера по прерыванию флаг этого прерывания должен быть сброшен, а соответствующий бит маски прерывания — установлен, чтобы разрешить запрос прерывания от этого источника. Если флаг общего разрешения прерываний GIE (см. Рис. 7.3 на стр. 213) установлен, то после выполнения команды, следующей за командой sleep, процессор перейдет к процедуре обработки прерывания, как и при обычном отклике на запрос прерывания. Однако если бит GIE сброшен, запрещая реакцию на прерывания, то процессор не будет передавать управление в обработчик прерывания, а просто выполнит команду, следующую за sleep, и продолжит работу в обычном режиме. Обратите внимание, что в этом случае программист должен будет после выхода из спящего режима сбросить соответствующий флаг прерывания.

Если при сброшенном бите GIE разрешенное прерывание произойдет до выполнения команды sleep (на что указывает установленный флаг прерывания), то команда sleep будет выполнена как команда nop (нет операции). При этом бит  сброшен не будет, так что программа сможет, при необходимости, определить, действительно ли микроконтроллер находился в «спящем» режиме после выполнения команды sleep. Программа также может определить ситуацию пробуждения по тайм-ауту сторожевого таймера, контролируя состояние бита ТО регистра STATUS (в этом случае он должен быть сброшен). При использовании в программе сторожевого таймера хорошим тоном считается помещение перед командой sleep команды сброса сторожевого таймера clrwdt. Проверяя состояние флага соответствующего прерывания в регистре INTCON, можно будет определить, вышел ли микроконтроллер в результате прерывания из спящего режима или нет.

Независимо от причины, вызвавшей выход микроконтроллера из «спящего» режима, перед выполнением команды, следующей за sleep, формируется задержка длительностью 1024 такта fOSC. Эта задержка необходима для запуска тактового генератора и установления его сигнала. Данная задержка, показанная на Рис. 10.10, не формируется, если микроконтроллер использует тактовый генератор на RС-цепочке (см. Рис. 10.5, б и Табл. 10.3).

При выключенном сторожевом таймере ток ждущего режима IРD намного меньше. К примеру, в модели PIC16F87XA-I типичные значения тока IРD равны 1.5 мк (максимальное — 16 мкА) и 10.5 мкА (максимальное — 42 мкА) при выключенном и включенном сторожевом таймере соответственно. Указанные значения справедливы для VDD = 4 В, при этом все линии ввода/вывода микроконтроллера сконфигурированы как входы и подтянуты либо к VSS (как правило), либо к VDD.

Во всех микроконтроллерах PIC имеется встроенный тактовый генератор, который в совокупности с узлами схемы синхронизации формирует внутренние синхросигналы, показанные на Рис. 4.4 (стр. 92). Все модели могут работать в одном из четырех стандартных режимов, перечисленных в Табл. 10.2. В трех из этих режимов в качестве времязадающего элемента используется кварцевый или керамический резонатор, подключаемый между выводами OSC1 и OSC2 микроконтроллера. Для бюджетных приложений, не предъявляющих повышенных требований к стабильности частоты тактового сигнала, можно использовать режим, в котором частота тактового сигнала задается RС-цепочкой.

Таблица 10.2. Режимы работы тактового генератора

Режимы, стандартные для всех моделей

LP ∙ Экономичный режим для резонаторов с частотой до 200 кГц

XT ∙ Обычный режим для резонаторов с частотами от 200 кГц до 4 МГц

HS ∙ Высокопроизводительный режим для резонаторов с частотами от 4 до 20 МГц

RC/EXTRC ∙ Генератор с внешней RС-цепочкой. На вывод CLKOUT выдается тактовый сигнал

Дополнительные режимы для PIC12F629/675

INTOSC1 ∙ Внутренний RС-генератор 4 МГц. Вывод CLKOUT используется в качестве линии ввода/вывода

INTOSC2 ∙ Внутренний RС-генератор 4 МГц. На вывод CLKOUT выдается тактовый сигнал

ЕС ∙ Внешний генератор. Вывод CLKOUT используется в качестве линии ввода/вывода

RC2 ∙ Генератор с внешней RС-цепочкой. Вывод CLKOUT используется в качестве линии ввода/вывода

Из Рис. 10.5, а видно, что в режимах с резонатором для формирования сигнала используется инвертирующий усилитель, который отключается вместе с подключенными к нему узлами по команде sleep. Единственное различие между режимами заключается в коэффициенте усиления этого инвертирующего усилителя. В режиме LP коэффициент усиления имеет минимальное значение, за счет чего уменьшается потребление генератора. Режим HS используется при высоких частотах резонатора, при этом потребление генератора максимально. Вообще говоря, следует выбирать режим с минимально возможным коэффициентом усиления. В документации на конкретное устройство подробно указаны диапазоны рабочих частот и номиналы внешних компонентов. Максимальная тактовая частота моделей среднего уровня составляет 20 МГц, однако некоторые модели имеют пониженное быстродействие — их тактовая частота, как правило, не может превышать 4 МГц.

Рис. 10.5. Типовые конфигурации тактового генератора

В типичном устройстве с тактовой частотой 10 МГц используется 10-МГц кристалл с АТ-срезом в режиме HS, при этом С1 = 22 пФ и С2 = 33 пФ. В режиме LP используется 32-кГц резонатор с С1 = 68 пФ и С2 = 100 пФ. Несмотря на то что оба конденсатора могут иметь одинаковые номиналы, большее значение С2 улучшает пусковые характеристики после сброса и выхода из спящего режима. Некоторые резонаторы в режиме HS могут потребовать подключения к выводу OSC2 последовательного резистора. Более подробно это расписано в документе AN588 «Р1C16/17 Oscillator Design». Керамические резонаторы менее дороги, чем кварцевые, однако они имеют худшую точность установки частоты (порядка 0.5 %) и меньшую температурную стабильность. Некоторые керамические резонаторы могут уже иметь встроенные конденсаторы, что позволяет уменьшить количество компонентов схемы. В документе AN588 приводится сравнение керамических и кварцевых резонаторов, используемых в таких приложениях.

Четвертый из стандартных режимов позволяет использовать в качестве времязадающего элемента внешнюю ЛС-цепочку. В этом режиме, как показано на Рис. 10.5, б, вывод OSC2 играет роль буферизированного выхода тактового сигнала и может использоваться для синхронизации внешних цифровых схем, в том числе и других микроконтроллеров PIC. Такой режим в основном применяется в бюджетных приложениях, которые не предъявляют жестких требований к точности установки тактовой частоты и ее стабильности. Частота зависит от сопротивления R1, емкости конденсатора C1 и напряжения питания VDD. Как правило, в документации на конкретную модель приводятся таблицы и графики, показывающие типичные зависимости частоты от этих параметров. Так, в микроконтроллере PIC16F87X усредненное значение частоты будет равно 1.7 МГц ±10 % при VDD = 5 В, R1 = 3.3 кОм и C1 = 100 пФ (при температуре +25 °C). Разумеется, необходимо принимать во внимание точность параметров используемых компонентов, а также их температурные характеристики.

Кроме того, микроконтроллеры PIC можно тактировать от внешнего генератора. Эта возможность может быть полезна при работе нескольких устройств от одного тактового сигнала. В этом случае внешний генератор подключается к выводу OSC1 микроконтроллера, а вывод OSC2 оставляют неподключенным или заземляют через резистор, чтобы уменьшить уровень помех. Сигнал генератора должен удовлетворять следующим требованиям: напряжение НИЗКОГО уровня V1L < 0.3 VDD, а напряжение ВЫСОКОГО уровня V1H > 0.7 VDD. При использовании внешнего генератора микроконтроллеры PIC должны работать в соответствующем по частоте режиме, рассчитанном на подключение резонатора.

Использование целых двух выводов для подключения времязадающих элементов слишком расточительно в случае 8-выводных устройств, особенно если учесть, что два вывода в любом случае используются для подачи питания! По этой причине в моделях с малым числом выводов обычно имеются дополнительные режимы схемы тактирования, предназначенные для высвобождения одного или даже обоих выводов под нужды ввода/вывода. В Табл. 10.2 для примера приведены режимы работы микроконтроллеров PIC12F629/675. В этих режимах используется генератор с внутренней RС-цепочкой, формирующий сигнал частотой 4 МГц. Как и в случае внешней RС-цепочки, реальное значение частоты можно определить только с известной долей приближения, однако для каждого конкретного экземпляра микроконтроллера программист может подстроить частоту, изменяя значение четырех младших битов регистра OSCAL (Oscillator CALibrate — калибровка генератора). За счет этого можно получить 16 слегка отличных друг от друга значений. Наилучшее из этих значений заносится при изготовлении микроконтроллера в последнюю ячейку памяти программ в виде команды retlw n. Таким образом, программист может в блоке инициализации программы выполнить эту команду с последующим копированием значения, возвращенного в W, в регистр OSCAL.

В режиме INTOSC2 на вывод OSC2 выдается тактовый сигнал для управления внешними узлами, как показано на Рис. 10.5, б. Остальные три режима высвобождают вывод OSC2 под нужды ввода/вывода. В частности, режим RC2 идентичен стандартному режиму RC (иногда называемому EXTRC), но не формирует выходного сигнала. Режим External Clock позволяет использовать внешний генератор, но, в отличие от обычного режима кварцевого генератора, также не препятствует использованию вывода OSC2 в качестве линии порта ввода/вывода.

Режим работы тактового генератора является всего лишь одной из опций, которые можно задавать в момент записи программы в память программ микроконтроллера. В принципе если вы не собираетесь разрабатывать собственный программатор, то вам будет совершенно неважно, каким образом осуществляется собственно процесс программирования. Но скорее всего вы будете использовать коммерческий программатор, например PICSTART®, выпускаемый компанией Microchip. Окно ИСР MPLAB при работе совместно с этим программатором показано на Рис. 17.4 (стр. 616).

Тем не менее для полноты изложения на Рис. 10.6, а приведена схема подключения микроконтроллера при так называемом высоковольтном программировании (High-Voltage Programming — HVP). Это специальное состояние микроконтроллера, используемое для его программирования и верификации, инициируется подачей на вход сброса  напряжения +13 В при НИЗКОМ уровне на выводах RB6 и RB7. После этого требуемые данные можно будет пересылать в микроконтроллер через вывод RB6 синхронно с внешним тактовым сигналом, подаваемым на вывод RB7. Такими данными могут быть команды режима программирования или же машинный код. Аналогично, можно будет считать содержимое незащищенных областей памяти программ и сравнить с оригинальным кодом.

Большинство новых микроконтроллеров PIC поддерживают также режим низковольтного программирования (Low-Voltage Programming — LVP), не требующий использования источника питания высокого напряжения. Этот режим особенно полезен для осуществления внутрисхемного последовательного программирования (ICSP™), при котором перепрограммирование микроконтроллера осуществляется непосредственно на печатной плате устройства. Для входа в данное состояние необходимо во время сброса удерживать на выводе RB3 НИЗКИЙ уровень. Затем, после подачи на него напряжения ВЫСОКОГО уровня, можно выполнять программирование через выводы RB7 и RB6. К сожалению, после разрешения этого режима программирования вывод RB3 нельзя будет использовать в качестве вывода порта.

Рис. 10.6. Конфигурирование некоторых моделей микроконтроллеров PIC

Пока микроконтроллер находится в режиме программирования, программатор имеет доступ к памяти программ и может загружать в нее код программы. Кроме того, программатор может обращаться к некоторым «секретным» участкам памяти программ (скрытая область памяти), которые не видны прикладной программе при работе микроконтроллера в нормальном режиме. В микроконтроллерах среднего уровня таким участком является ячейка памяти программ с адресом h’2007’, зарезервированная под слово конфигурации.

Различные биты этого слова (биты конфигурации) используются для задания конфигурации тактового генератора и других узлов микроконтроллера при его работе в нормальном режиме. В качестве примера на Рис. 10.6, б показан формат конфигурационного слова модели PIC16F84, посредством которого можно задавать значения четырех конфигурируемых параметров. Эти параметры являются базовыми, т. е. имеются во всех микроконтроллерах:

• Биты FOSC1:() определяют режим работы тактового генератора (Табл. 10.2).

• Бит WDTE используется для включения и выключения сторожевого таймера, показанного на Рис. 13.1 (стр. 451).

• Бит  используется для управления таймером включения питания, который рассматривается на стр. 318.

• Биты СР предназначены для защиты памяти программ от считывания. Как уже говорилось, в режиме программирования содержимое памяти программ доступно для считывания. Это сделано для того, чтобы программатор мог осуществлять верификацию кода, загруженного в память программ (см. Рис. 17.4 на стр. 616). Если сбросить все биты СР, то данная возможность будет заблокирована. Это позволяет предотвратить копирование прошивки микроконтроллера. После программирования биты СР нельзя стереть даже в исполнениях с кварцевым окошком или памятью программ EEPROM-типа. По этой причине Microchip не рекомендует использовать эту опцию во время разработки и отладки устройств.

Более новые модели семейства имеют дополнительные опции. Например, в микроконтроллере PIC16F87X, формат слова конфигурации которого показан на Рис. 10.6, в, можно защищать всю память программ, первую половину или вообще только 256 первых ячеек. Также в этих моделях имеются следующие опции:

• Бит BODEN, установленный в 1, разрешает работу схемы сброса по снижению напряжения питания (Brown-Out Reset — BOR). Эта схема позволяет перезапускать микроконтроллер при провалах напряжения питания ниже заданного уровня, как показано на Рис. 10.11.

• Установка бита LVP в 1 позволяет использовать режим низковольтного программирования.

• Бит CPD позволяет защитить содержимое EEPROM-памяти данных (см. главу 15).

• В этих моделях имеется возможность изменения содержимого незащищенных областей памяти программ непосредственно из самой программы, как показано на Рис. 15.4 (стр. 551). Сброс бита WRT запрещает это.

• В данных моделях имеется специальный режим отладки, который отключается при установке бита DEBUG в 1.

Рис. 10.7. Задание конфигурации микроконтроллера в среде MPLAB версии 7.x при использовании программатора PICSTART

Программное обеспечение большинства программаторов позволяет пользователю «вручную» задать значение требуемых битов слова конфигурации (как, скажем, на Рис. 10.7) перед собственно записью программы в память микроконтроллера. Однако наилучшим решением будет указание требуемого значения слова конфигурации непосредственно в программе для автоматического задания конфигурации микроконтроллера при его программировании. В качестве примера возьмем микроконтроллер PIC16F87X, который должен иметь следующую конфигурацию:

Режим генератора — XT

Биты 1:0 = 01

Сторожевой таймер — выключен

Бит 2 = 0

Таймер включения питания — включен

Бит 3 = 0

Защита памяти программ — отключена

Биты 5:4 и 13:12= 11

Схема сброса по снижению питания — включена

Бит 6 = 1

Низковольтное программирование — запрещено

Бит 7 = 0

Тогда наличие в исходном ассемблерном файле директивы

__config b’11111101110001’ ; или h’3F71’

приведет к формированию следующего машинного кода:

:02 400Е 00 713F 00

(формат этой строки был описан на стр. 250). При программировании микроконтроллера требуемое значение будет записано в ячейку с адресом h’2007’. По умолчанию все биты слова конфигурации установлены в 1, поэтому без указания этой директивы микроконтроллер PIC16F87X будет иметь следующую конфигурацию:

• Используется тактовый генератор с внешней RС-цепочкой.

• Защита кода отсутствует.

• Работа таймера включения питания, сторожевого таймера, а также схемы сброса по напряжению питания разрешена.

• Низковольтное программирование и запись в память программ разрешены.

Во включаемых файлах, предоставляемых компанией Microchip для всех моделей микроконтроллеров (см. Листинг 8.1 на стр. 241), определены константы для всех опций, доступных в конкретном устройстве. Для формирования значения слова конфигурации необходимо объединить эти константы по И. Например, строка:

__config _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _BODEN_ON & _LVP_OFF

формирует тот же самый машинный код, но более понятна программисту, что уменьшает вероятность возникновения ошибок. Кроме того, такой код имеет большую переносимость, поскольку при замене процессора на другой (который может иметь совершенно иное расположение битов в слове конфигурации достаточно будет поменять только название включаемого файла. Обращаю ваше внимание на то, что при включении некорректного заголовочного файла могут быть запрограммированы неправильные биты.

В компиляторах Си тоже имеется похожий механизм для задания значений битов слова конфигурации. Так, в компиляторе CCS для этого предусмотрена директива #fuses, размещаемая в начале файла. В нашем случае строка с данной директивой будет выглядеть следующим образом:

#fuses XT, NOWDT, PUT, NOPROTECT, BROWNOUT, NOLVP

Все последовательностные цифровые системы должны выходить из выключенного или какого-либо неисправного состояния строго определенным образом. Сброс микроконтроллеров PIC может быть осуществлен несколькими способами:

• Вручную, с помощью кнопки, подключенной к выводу , как показано на Рис. 10.8, а.

• При подаче питания на микроконтроллер, как показано на Рис. 10.10.

• При снижении напряжения питания ниже определенного значения во время работы микроконтроллера (см. Рис. 10.11).

• При наступлении тайм-аута сторожевого таймера (см. Рис. 13.1 на стр. 451), который может возникнуть из-за ошибки в программе или даже из-за выброса напряжения питания.

Рис. 10.8. Внешний сброс микроконтроллеров PIC

Мы рассмотрим все четыре механизма, а по традиции начнем с внешнего сброса. У всех микроконтроллеров PIC имеется, хотя бы как опция, вывод внешнего сброса . К этому выводу можно подключить кнопку или другую схему, как это показано на Рис. 10.8, а, посредством которых можно будет осуществлять перезапуск устройства, в результате чего программа снова начнет выполняться с команды, расположенной по адресу вектора сброса. До тех пор пока напряжение на выводе  не превышает 0.2 VDD, устройство остается в состоянии сброса (фаза Q1 внутреннего цикла, см. Рис. 4.4 на стр. 92). Для предотвращения ложных срабатываний схемы сброса НИЗКИЙ уровень на выводе  должен удерживаться в течение не менее 2 мкс (см. Пример 10.2). Максимальное рекомендуемое сопротивление подтягивающего резистора составляет 33 кОм. Такое значение выбрано для того, чтобы ток утечки вывода при разомкнутой кнопке не вызвал появления на выводе напряжения ниже 0.8 VDD. Максимальное значение тока утечки IIL вывода  составляет ±5 мкА при напряжении на входе VSS =<  =< VDD. Резистор сопротивлением 100 Ом предназначен для ограничения тока при появлении на выводе отрицательного выброса напряжения, в результате которого один из защитных диодов откроется.

Когда на выводе  появляется лог. 1 (т. е. напряжение, больше или равное 0–8 VDD), процессор начинает работать в нормальном режиме, при этом счетчик команд и регистр PCLATH обнуляются, указывая таким образом на команду с адресом h’000’ (вектор сброса). Биты выбора банка ОЗУ IRP, RP0 и RP1 регистра STATUS также обнуляются, так что при запуске процессор работает с 0-м банком. Если вывод  использовался для «пробуждения» микроконтроллера, то бит  будет установлен в 1 (не было тайм-аута сторожевого таймера), а бит  будет сброшен в 0 (процессор находился в «спящем» режиме), в противном случае состояние этих битов не изменяется. В любом случае состояние флагов арифметических операций регистра STATUS не изменится. Состояния всех РСН после сброса приведены в Приложении Б.

Помимо сброса, инициируемого подачей сигнала на вход , во всех микроконтроллерах PIC предусмотрен сброс по включению питания (Power-On Reset — POR). Эта внутренняя схема автоматически определяет готовность процессора к работе после подачи питания на микроконтроллер.

При сбросе по включению питания сбрасывается бит  регистра PCON, формат которого приведен на Рис. 10.9. Благодаря этому программа может определить наступление данного события (см. Табл. 10.4). При чтении бита  он не устанавливается автоматически в 1, это необходимо делать самостоятельно.

Рис. 10.9. Формат регистра PCON микроконтроллеров PIC16XXXX

Чтобы проиллюстрировать работу схемы сброса по включению питания, рассмотрим некоторую идеализированную ситуацию (см. Рис. 10.10). В момент времени t0 был включен источник питания, напряжение которого (VDD) стало возрастать по экспоненциальному закону до номинального значения +5 В. Если скорость нарастания этого напряжения превышает 0.05 В/мс, то при достижении уровня 1.5…2.1 В будет сформирован внутренний сигнал сброса. По этому сигналу происходит следующее:

1. С помощью 10-битного счетчика, тактируемого встроенным генератором, формируется задержка ТРWRT фиксированной длительностью 72 мс. Формирование этой задержки может быть отключено установкой бита  слова конфигурации в 1 (см. Рис. 10.6).

Рис. 10.10. Процесс запуска 5-В устройства при подаче напряжения питания

2. Если используется один из режимов с резонатором, то после задержки ТРWRT формируется еще одна задержка TOST длительностью 1024 такта системного тактового сигнала. Эта задержка формируется с помощью 10-битного счетчика, тактируемого системным генератором. Таким образом, гарантируется, что к моменту старта программы основной генератор уже запущен и работает в нормальном режиме. Понятно, что длительность задержки TOST зависит от частоты резонатора. Например, при частоте резонатора 32 кГц длительность задержки будет не менее 32 мс, тогда как при резонаторе частотой 10 МГц длительность задержки будет равна 102 мкс. Если по окончании этой задержки генератор все еще не запустится, то будет сформирована дополнительная задержка неопределенной длительности. При использовании RС-генератора эта задержка не формируется. Также задержка TOST формируется при выходе микроконтроллера из «спящего» режима и опять с той же целью — чтобы обеспечить выход тактового генератора на нормальный режим к моменту запуска программы.

3. Как и в случае внешнего сброса, исполнение кода начинается с адреса вектора сброса (h’000’). Однако в отличие от внешнего сброса, который не влияет на биты  и , при сбросе по включению питания оба бита устанавливаются в неактивное состояние.

Все задержки, формируемые в том или ином случае, указаны в Табл. 10.3.

Если в конечном устройстве ручной сброс не требуется, вывод  можно подключить непосредственно к линии VDD через токоограничивающий резистор. В моделях с небольшим числом выводов, таких как 8-выводной PIC12F629/675, этот вывод можно использовать в качестве линии порта ввода/вывода, если сбросить бит MCLRE слова конфигурации в 0.

Возможна такая ситуация, когда напряжение на выходе источника питания микроконтроллера возрастает настолько медленно, что внутренний импульс сброса по питанию не генерируется, или же напряжение не достигает требуемого значения даже по истечении задержек ТPWRT и TOST. Как правило, это значение составляет 4.5 В для 5-В моделей с генератором, работающим в режиме HS, и 4 В — в других режимах. В этом случае микроконтроллер может начать выполнять программу некорректно или вообще не запуститься. Когда надежность встроенной схемы сброса по включению питания вызывает сомнение, можно использовать дополнительные узлы, предназначенные для удерживания НИЗКОГО уровня на выводе  в течение достаточно длительного времени, необходимого для достижения напряжением VDD своего рабочего значения. Указанные функции выполняет схема, показанная на Рис. 10.8, б. Емкость конденсатора следует выбирать таким образом, чтобы постоянная времени RC в несколько раз превышала время нарастания напряжения питания до рабочего значения. При заданном значении сопротивления и емкости конденсатора 2.2 мкФ постоянная времени будет равна примерно 100 мс. Более подробно об этом можно прочитать в документах AN522 «Power-up Consideration» и AN607 «Power-up Trouble Shooting».

Нормально работающий микроконтроллер может начать работать неправильно, если напряжение питания упадет ниже рабочего значения. Это может произойти из-за кратковременной просадки напряжения на линии VDD при переключении мощной нагрузки или из-за разряда батареи. В обоих случаях микроконтроллер PIC может начать работать неправильно из-за этого снижения напряжения (brownout). Указанное явление может привести к серьезным последствиям; например, нагревательный элемент посудомоечной машины может включиться при отсутствии воды в резервуаре!

Из Рис. 10.11 видно, что при включенном таймере BOR (т. е. при установленном бите BODEN в слове конфигурации) внутренний сигнал сброса будет сформирован при снижении напряжения питания VDD ниже порогового значения BVDD. В устройствах, работающих при номинальном напряжении питания 5 В, этот порог составляет 4 ±0.3 В. Для устройств, способных работать в более широком диапазоне питающих напряжений, таких как PIC 12F629/675, это пороговое значение может быть снижено вплоть до 2 В.

Рис. 10.11. Сброс из-за провала (снижения) напряжения питания

Из рисунка видно, что напряжение питания через некоторое время превысило пороговое значение. Если время, в течение которого напряжение находилось ниже порогового значения, составило более 100 мкс, то, прежде чем процессор выйдет из состояния сброса, таймер включения питания (если его работа разрешена) сформирует задержку длительностью 72 мс, как показано на Рис. 10.10. По окончании этой задержки процессор приступит к исполнению команды, расположенной по адресу вектора сброса h’000’. В одних моделях, таких как PIC16F627, состояние бита  изменяется автоматически при включении схемы BOD, чтобы гарантировать наличие задержки при снижении (провале) напряжения питания, тогда как в других, например PIC12F675, это остается на совести программиста, так что читайте документацию внимательно! Работающий таймер включения питания уменьшает вероятность многократного формирования сигнала сброса из-за помех на шине питания при медленном нарастании напряжения питания VDD.

После сброса по снижению напряжения питания в РСН заносятся те же значения, что и после сброса по включению питания, за исключением того, что сбрасывается флаг  в регистре PCON. Благодаря этому программа может определить причину, по которой произошел сброс. При включении микроконтроллера состояние флага  не определено, поэтому он должен быть установлен в самом начале программы.

Одной из проблем, возникающих при включении схемы сброса по снижению напряжения питания, является повышение тока потребления микроконтроллера. К примеру, для моделей линейки PIC16F87X это приращение ΔIBOR составляет обычно 85 мкА (максимум до 200 мкА). Указанное значение следует прибавить к базовому значению тока, приведенному в Табл. 10.1. Более того, ток, потребляемый этой схемой, сравним с током потребления микроконтроллера в «спящем» режиме. С другой стороны, в моделях PIC12F629/675 семейства nanoWatt этот ток составляет всего 0.3/1.5 мкА (typ/max) при VDD = 2 В.

Помимо того, микроконтроллеры PIC могут сбрасываться по тайм-ауту сторожевого таймера. При этом процессор немедленно начинает исполнение программы с адреса вектора сброса, а также сбрасывает флаг  и устанавливает флаг  регистра STATUS. Если тайм-аут сторожевого таймера наступит во время нахождения процессора в «спящем» режиме, то это приведет к продолжению выполнения программы, начиная с команды, расположенной после команды sleep (при работе от кварцевого генератора — после задержки TOST). При этом оба флага  и  регистра STATUS будут сброшены.

Различные варианты сброса микроконтроллера сведены в Табл. 10.4. В этой же таблице указана реакция на выход из «спящего» режима по прерыванию. При сбросе по включению питания устанавливаются оба флага —  и , тогда как при внешнем сбросе состояние этих битов не изменяется. Флаг  сбрасывается по тайм-ауту сторожевого таймера и устанавливается после выполнения команд clrwdt или sleep. Команда clrwdt также устанавливает флаг , который сбрасывается при выполнении команды sleep. Оба этих флага доступны только для чтения, т. е. их нельзя явно изменить, скажем, командой bsf.

При сбросе микроконтроллера обнуляется счетчик команд и различные биты, отвечающие за переключение банков памяти данных, такие как RP0. Состояние битов статуса арифметических операций Z, С и DC после сброса по включению питания не определено, а при других видах сброса оно остается неизменным.

Примеры

Пример 10.1

Схема, приведенная на Рис. 10.12, похожа на схему с Рис. 10.5, б, но содержит два последовательно соединенных резистора. По задумке разработчика с помощью такой схемы программа сможет самостоятельно изменять скорость своего выполнения. Докажите его правоту, учитывая, что при заданной емкости конденсатора тактовая частота прямо пропорциональна сопротивлению, подключенному к выводу OSC1.

Можете ли вы придумать применение для такой возможности?

Рис. 10.12. Тактовый генератор с изменяемой частотой

Решение

Точка соединения резисторов подключена к выводу порта микроконтроллера. Любой вывод микроконтроллера может быть сконфигурирован как вход или как выход (это будет описано в следующей главе). При сбросе любого вида все выводы портов конфигурируются как входы. В данном случае, если не учитывать мизерный ток утечки, вывод RA0 не будет оказывать влияние на подключенные к нему резисторы. Таким образом, общее сопротивление цепочки будет немного больше 100 кОм. При емкости конденсатора 100 пФ и напряжении питания VDD = 5 В тактовая частота микроконтроллера будет равна примерно 100 кГц. В результате частота выполнения команд составит около 25 000 команд/с (на самом деле немного меньше, поскольку команды, вызывающие сброс конвейера, выполняются за два цикла).

Если программа переключит вывод RA0 на выход, установив при этом бит 0 регистра PORTA, то точка соединения резисторов окажется подключенной к линии VDD. При этом к выводу OSC1 реально окажется подключенным только один резистор сопротивлением 3.3 кОм. При таком значении сопротивления RC-цепочки частота генератора будет составлять 1.7 МГц, что в 17 раз увеличит частоту исполнения команд (до 450 000 команд/с). Таким образом, программа сможет при необходимости самостоятельно увеличивать свою скорость выполнения и замедляться в остальных случаях для экономии энергии источника питания.

Пример 10.2

В документации на микроконтроллер PIC16F84 сказано, что минимальная длительность сигнала НИЗКОГО уровня на выводе , распознаваемого как сигнал внешнего сброса, составляет 100 не. Можете ли вы предположить, какие проблемы могут возникнуть в связи с наличием такого условия?

Решение

В зашумленной среде микроконтроллер может работать неустойчиво из-за кратковременных импульсов, случайным образом сбрасывающих устройство. В таких случаях сигнал, подаваемый на вывод , следует пропускать через фильтр нижних частот (ФНЧ). Обычно хватает высокочастотного конденсатора емкостью 1 нФ, подключенного к выводу  вместе с подтягивающим резистором сопротивлением 10 кОм. Кроме того, необходимо предусмотреть развязку по питанию, элементы которой должны располагаться в непосредственной близости от выводов питания микроконтроллера. Из-за проблем такого рода в более современных моделях, даже в PIC16F84A, минимальная длительность импульса сброса увеличена до 2 мкс. Тем не менее все указанные методики справедливы и для них, особенно при работе устройства в среде с высоким уровнем помех.

Вопросы для самопроверки

10.1. Что произойдет, если из-за программной ошибки на выходе RA0 микроконтроллера, включенного в соответствии с Рис. 10.12, будет присутствовать лог. 0? Такая ситуация может возникнуть, если вывод будет сконфигурирован как выход до установки 0-го бита регистра PORTA в 1.

10.2. Каким образом схема, приведенная на Рис. 10.13, будет влиять на частоту тактового генератора?

Рис. 10.13. Альтернативная схема управления частотой тактового генератора

10.3. Пытаясь уменьшить потребление схемы при нахождении микроконтроллера в состоянии сброса, один студент поставил в цепь ручного сброса (Рис. 10.8) резистор сопротивлением 10 МОм. Почему микроконтроллер перестал выходить из состояния сброса?

10.4. Потребление микроконтроллера PIC, работающего на частоте 4 МГц и при напряжении 5 В, составило 550 мкА при отсутствии нагрузки на портах ввода/вывода. Каким будет потребляемый ток, если устройство будет работать на частоте 100 кГц и при напряжении питания 4 В?

 

Глава 11

Ничего, кроме байтов

Способность программы изменять или отслеживать состояние выводов, подключенных к внешним цепям, является наиболее важной среди разнообразных возможностей по приему и передаче данных, присущих микропроцессору или микроконтроллеру. Эти выводы обычно объединяются в группы, при этом число выводов в группе может достигать числа разрядов внутренней шины данных. В микроконтроллерах PIC такие параллельные порты дают возможность ядру процессора считывать или передавать вовне до восьми битов данных побайтно. Суммарное количество таких линий ввода/вывода, имеющихся в каждой конкретной модели семейства, зависит от типа корпуса и от того, сколько имеется используемых разделяемых ресурсов. Количество этих линий ввода/вывода может варьироваться от четырех в 6/8-выводных PIC10FXXX до 52 в 64-выводном PIC16C924.

Прочитав эту главу, вы:

• Разберетесь в назначении параллельных портов ввода/вывода.

• Научитесь конфигурировать линии портов ввода/вывода.

• Познакомитесь со схемотехникой портов ввода/вывода и поймете различие между активной и пассивной подтяжкой.

• Узнаете, каким образом с параллельными портами ввода/вывода взаимодействуют команды типа «чтение/модификация/запись».

• Познакомитесь с электрическими и нагрузочными характеристиками портов ввода/вывода.

• Узнаете, как включать встроенные подтягивающие резисторы на линиях портов.

• Поймете, как работает функция генерации прерывания по изменению уровня сигналов на выводах порта В.

• Узнаете, как с помощью дополнительных микросхем можно увеличить количество линий ввода/вывода.

Вообще говоря, параллельный порт ввода/вывода может рассматриваться как обычный регистр, содержимое которого доступно остальным элементам схемы. Именно такое несколько упрощенное представление показано на Рис. 11.1. На рисунке изображена небольшая область памяти данных микроконтроллера PIC16F84, структура которой полностью была показана на Рис. 4.7 (стр. 97). В микроконтроллерах PIC16XXXX среднего уровня в обязательном порядке имеется, как минимум, 13 линий ввода/вывода. В микроконтроллерах группы PIC16F87X есть дополнительный вывод RA5 (портА), в то время как в микроконтроллерах группы PIC16F62X имеется уже три дополнительных линии, показанные на Рис. 11.1 пунктиром (если пожертвовать выводами OSC1, OSC2 и . В «миниатюрных» микроконтроллерах PIC10FXXX/12XXX присутствует только один параллельный порт ввода/вывода общего назначения (General-Purpose parallel I/O — GPIO), в котором сочетаются характеристики портов А и В прочих микроконтроллеров и который имеет не более 6 линий ввода/вывода.

Рис. 11.1. Упрощенное представление параллельных портов А и В микроконтроллеров линейки PIC16XXXX

В моделях среднего уровня, имеющих 28 выводов и более, реализованы дополнительные порты ввода/вывода, как указано в Табл. 11.1. В то же время эти модели имеют более богатый набор встроенных периферийных устройств, использующих линии ввода/вывода, так что увеличение емкости параллельных портов ввода/вывода может оказаться не более чем иллюзией. К примеру, в модели PIC16F87X пять линий порта A (RA5, RA[3:0]) и 3-битный порт Е используются в качестве аналоговых входов 8-канального АЦП.

И все же, несмотря на возможность такого упрощенного представления (как на Рис. 11.1), поведение порты ввода/вывода несколько отличается от поведения остальных регистров микроконтроллера. Так, необходимо иметь возможность конфигурирования портов либо на считывание сигналов с соответствующих выводов микроконтроллера (вход), либо на выдачу сигналов на эти выводы (выход). Помимо этого, нам нужно определить, каким образом та или иная конфигурация порта будет влиять на результат операций изменения или чтения состояния порта.

Из Рис. 11.1 видно, что каждому регистру параллельного порта в 0-м банке соответствует регистр TRIS в 1-м банке. В Приложении Б можно увидеть, что это справедливо для любого порта. С каждым битом n параллельного порта связан бит n соответствующего регистра TRIS, который предназначен для задания конфигурации вывода: вход (TRIS[n] = 1) или выход (TRIS[n] = 0). В большинстве микроконтроллеров других производителей такие регистры называются регистрами направления передачи данных (Data Direction Register — DDR), однако компания Microchip использует аббревиатуру TRIS, образованную от словосочетания TRI-State (тристабильный). Причину, по — которой регистры называются именно так, вы узнаете чуть позже в данной главе.

В качестве примера рассмотрим ситуацию, при которой вывод RA0 и выводы RB[7:0] являются выходами, а остальные выводы порта А — входами. Следующий фрагмент кода, как правило, размещается в самом начале основной процедуры (см. Программу 11.1, а):

bsf STATUS f RP0 ; Переключаемся на 1-й банк

    movlw b’1111110’ ; Вывод RA0 — выход

    movwf TRISA ; Остальные выводы — входы

    clrf TRISB ; Все выводы порта В — выходы

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

Разумеется, на языке Си тоже можно написать код, выполняющий аналогичные действия. К примеру, в используемом нами компиляторе CCS этот код будет выглядеть следующим образом:

#bit BANK_SWITCH =3.5 /* Бит RP0 регистра STATUS */

#byte TRISA = 0x85 /* Регистр направления передачи данных TRISA */

#byte TRISB = 0x86 /* Регистр направления передачи данных TRISB */

main()

{

     BANK_SWITCH =1 ; /* Переключаемся на 1-й банк */

     TRISA = 0xFE; /* Вывод RA0 — выход, остальные выводы — входы */

     TRISB =0; /* Все выводы порта В — выходы */

     BANK_SWITCH =0 ; /* Возвращаемся в 0-й банк */

Однако в отдельных компиляторах могут иметься встроенные функции для поддержки операций инициализации и обращения к портам. Так, в компиляторе CCS для каждого порта X имеется своя функция set_tris_x () для установки соответствующего регистра TRISX:

main()

{

       /* В начале идут описания разных переменных */

       set_tris_a(0xFE); /* Вывод RA0 — выход, остальные выводы — входы */

       set_tris_b(0); /* Все выводы порта В — выходы */

При любом сбросе все биты регистров TRIS устанавливаются в 1, т. е. после сброса все выводы микроконтроллера работают как входы. Такой выбор не случаен, так как если бы вывод переключался на выход до задания ему программой начального значения, то напряжение, появляющееся на этом выводе после выхода из состояния сброса, было бы непредсказуемым. А это, в свою очередь, может привести к нежелательной активизации управляемых цепей. Например, при управлении нагревательным элементом стиральной машины нагреватель мог бы включиться до заполнения емкости водой. Если существует вероятность возникновения подобной ситуации, то начальное состояние соответствующих битов порта необходимо задавать до конфигурирования регистра TRIS.

После того как задано направление передачи данных через выводы порта, программа может считывать данные из порта или записывать их в него, как в обычный регистр, и таким образом взаимодействовать с окружающим миром. А именно:

• Для отслеживания состояния любого вывода, сконфигурированного как вход, можно использовать команды btfsc и btfss. Так, команда btfss PORTA, 1 пропустит следующую команду, если на выводе RA1 присутствует ВЫСОКИЙ уровень (т. е. если 1-й бит регистра PORTA установлен в 1). Можно одновременно считать состояние нескольких битов, копируя содержимое всего регистра порта в рабочий регистр, например командой movf PORTA, w. При необходимости это значение можно будет затем переписать в какой-нибудь РОН для дальнейшей обработки.

• Для изменения состояния любого вывода, сконфигурированного как выход, можно использовать команды bcf или bsf. Так, команда bcf PORTA, 0 установит на выводе RA0 НИЗКИЙ уровень (т. е. 0-й бит регистра PORTA сбросится в 0). Можно одновременно изменять несколько битов, копируя содержимое рабочего регистра в регистр данных порта. К примеру, если все выводы порта В являются выходами, то для выдачи на выводы RB[7:6] ВЫСОКОГО уровня, а на выводы RB[5:0] — НИЗКОГО, можно воспользоваться следующими командами:

movlw b’11000000’

movwf PORTB

Наверняка у вас возник вопрос: а что произойдет, если будет считано состояние вывода, сконфигурированного как выход? Ответ на этот и некоторые другие вопросы вы узнаете чуть позже, когда мы приступим к изучению электрических характеристик портов ввода/вывода. А пока давайте рассмотрим использование портов на реальном примере.

Представим ситуацию, изображенную на Рис. 11.2, в которой внешнее периферийное устройство (скажем, принтер) собирается через порт В считывать по запросу содержимое регистра h’20’, подавая на вывод RA1 микроконтроллера напряжение НИЗКОГО уровня. Обозначим этот сигнал от периферийного устройства как  ( — готовность к приему данных). При обнаружении этого запроса микроконтроллер копирует требуемый байт данных в порт В, а затем формирует отрицательный импульс на выводе RA0, информируя периферийное устройство о готовности запрошенных данных. Назовем этот сигнал  ( — готовность данных). При сбросе по питанию на всех выводах порта В должен устанавливаться НИЗКИЙ уровень, а на выводе RA0 — ВЫСОКИЙ.

Рис. 11.2. Передача данных через порт В с использованием квитирования

Такой принцип обмена с использованием семафоров называется обменом с квитированием (handshaking). Квитирование позволяет осуществлять обмен между несинхронизированными устройствами без потери данных.

В Программе 11.1, а приведен пример реализации обмена с квитированием на языке ассемблера. Обратите внимание, что начальное состояние портов в программе задается перед их конфигурированием. Символические имена PORTA и PORTB определены во включаемом файле (для регистров h’05’ и h’06’ соответственно). После обратного переключения в 0-й банк памяти выводы портов, сконфигурированных как выходы, устанавливаются в начальное состояние: вывод RA0 — ВЫСОКИЙ уровень (бит 0 регистра PORTA = 1), а выводы RB[7:0] — НИЗКИЙ уровень (соответствующие биты регистра PORTB = 0).

Программа 11.1. Реализация параллельного обмена с квитированием

а) Ассемблер (17 команд)

           include "p16f627a.inc"

           __config _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _LVP_OFF

DATUM equ h’20’

; Инициализируем порты и задаем начальные состояния выводов —

MAIN clrf PORTB ; Начальное состояние порта В — 0

         bsf PORTA,0 ; Начальное состояние сигнала DAV — 1

         bsf STATUS,RP0 ; Сначала переключаемся на 1-й банк

         movlw b’11111110’ ; Вывод RA0 — выход

         movwf TRISA ; Остальные выводы — входы

         clrf TRISB ; Все выводы порта В — выходы

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

; Ожидаем появления НИЗКОГО уровня на выводе RA1 —

RFD_YES btfsc PORTA,1 ; 1-й бит порта А равен 0?

                 goto RFD_YES ; ЕСЛИ нет, ТО считываем снова

; Копируем запрошенные данные в порт В —

         movf DATUM,w ;Копируем в W

         movwf PORTB ; и вовне

; Теперь формируем отрицательный импульс DAY на выводе RA0 —

         bcf PORTA,0 ; DAV (вывод RA0) — НИЗКИЙ уровень

         nop ; на короткое время,

         bsf PORTA,0 ; а затем ВЫСОКИЙ уровень

; Теперь ждем появления ВЫСОКОГО уровня на линии RFD —

RFD_NO btfss PORTA,1 ;Пропускаем, если на RA1 ВЫСОКИЙ уровень

               goto RFD_NO ;ЕСЛИ нет, ТО считываем снова

              goto RFD_YES ;И так до бесконечности

               end

б) Язык Си (26 команд)

#include <16f627a.h>

#byte PORTB =6 /* Порт В — регистр 0x06 */

#byte DATUM = 0x20 /* В регистре 0x20 хранится байт данных */

#bit DAV =5.0 /* Вывод RA0 — линия DAV */

Ibic RFD =5.1 /* Вывод RA1 — линия RFD */

void main(void)

{

      DAV = 1; /* Неактивный уровень на линии DAV — 1 */

      PORTB = 0 ; /* Начальное состояние порта В — 0 */

      set_tris_a(0xFE); /* Вывод RAO (DAV) — выход */

      set_tris_b(0); /* Все выводы порта В — выходы */

      while(TRUE) /* БЕСКОНЕЧНЫЙ ЦИКЛ */

        {

              while(RFD) {;} /* Ждем, пока результат чтения RFD не станет FALSE */

/* (НИЗКИЙ уровень) */

              PORTB = DATUM; /* Копируем байт данных в порт В */

              DAV = 0; /* Выставляем на DAV (вывод RA0) НИЗКИЙ уровень */

              delay_cycles(1); /* Немного подождем, */

              DAV = 1; /* а затем снова выставляем ВЫСОКИЙ уровень */

              while(!RFD) {;} /* Ждем, пока результат чтения RFD не станет TRUE */

/* (ВЫСОКИЙ уровень) */

        }

}

После инициализации портов микроконтроллер ожидает появления НИЗКОГО уровня на выводе RA1 — этому состоянию соответствует 0 в 1-м бите регистра PORTA. Когда это происходит, содержимое регистра h’20’ копируется через рабочий регистр в регистр PORTB и на вывод RA0 выставляется НИЗКИЙ уровень. Перед повторным переводом RA0 в состояние ВЫСОКОГО уровня вставляется одна команда nop, формирующая задержку длительностью в один машинный цикл. Здесь мы не оговаривали длительность импульса DAV, но в реальном устройстве команда nop будет заменена вызовом подпрограммы формирования задержки.

В конце процедуры микроконтроллер снова проверяет состояние вывода RA1 (отслеживая значение 1-го бита регистра PORTA), ожидая появления на линии  сигнала ВЫСОКОГО уровня, свидетельствующего о завершении транзакции (этот сигнал выставляется периферийным устройством). Разумеется, случается и так, что периферийное устройство не сможет ответить, в результате чего микроконтроллер просто зависнет. Поэтому для надежности в таких случаях следует предусматривать некоторый тайм-аут — скажем, переходить к процедуре обработки ошибок, если после 65 536 обращений к порту не было обнаружено требуемого отклика.

В Программе 11.1,б приведена эквивалентная реализация такого обмена на языке Си. Эта программа имеет похожую структуру; обратите только внимание на то, как осуществляется проверка состояния входа. Для этого используется конструкция вида while (RFD) {;}, которая ничего не делает ({;} является пустым оператором) до тех пор, пока при считывании вывода, названного RFD, не возвращается ИСТИНА, т. е. ненулевое значение. Когда RFD становится равным 0, т. е. на выводе RA1 появляется НИЗКИЙ уровень, цикл завершается и управление передается на следующий оператор программы. В конце программы имеется аналогичная конструкция while(!RFD) {;}, в которой используется оператор языка Си «!» (NOT). В данном случае выход из цикла произойдет при установке RFD в 1 (операция! RFD вернет нулевое значение).

Встроенная функция delay_cycles () формирует дополнительную задержку заданной длительности (в машинных циклах) и при необходимости может быть заменена любой подходящей функцией задержки.

Может показаться, что эти программы бесполезны, поскольку содержимое регистра h’20’ нигде не задается и не изменяется. Однако в реальной жизни данное значение могло бы изменяться в каком-либо прерывании, скажем, по сигналу от внешнего или внутреннего таймера. Также в этот регистр по прерыванию от модуля АЦП мог бы заноситься результат преобразования. Все эти модули мы будем рассматривать в последующих главах книги.

Наша программа рассчитана на микроконтроллер PIC16F84A. Разумеется, она может выполняться и на других моделях семейства, следует только быть более внимательными при использовании моделей, имеющих аналоговые модули. В этих моделях выводы портов, используемые также и аналоговыми модулями, после сброса работают как аналоговые входы. Так сделано потому, что аналоговое напряжение, формируемое внешней схемой, может повредить входные транзисторы, рассчитанные на работу с логическими сигналами. Поэтому если программист собирается использовать указанные выводы для ввода/вывода цифровых сигналов, то ему необходимо задать такую конфигурацию аналогового модуля, при которой требуемые выводы подключены не к модулю, а к цифровому порту. Обычно эта операция выполняется во время процедуры инициализации вместе с установкой значений регистров TRIS. К примеру, в моделях PIC16F87X входы модуля АЦП выведены на линии порта А. Чтобы выводы RA[3:0] и RA5 работали как цифровые, необходимо загрузить число Ь’00000110’ в 1-й регистр управления АЦП ADCON1 (см. Рис. 14.12 на стр. 512). Поскольку этот регистр находится в 1-м банке, то к командам

movlw b’00000110’ ; Все выводы — цифровые

movwf ADCON1

следует добавить команды, осуществляющие переключение банков памяти. В компиляторе CCS для этих целей предусмотрена специальная функция setup_adc_ports (), вызов которой с параметром setup_adc_ports (NO_ANLOGS) выполняет те же самые операции.

Чтобы хорошо понимать характеристики портов ввода/вывода, необходимо знать, как они устроены. Несколько упрощенная схема одного канала порта ввода/вывода показана на Рис. 11.3. Основными элементами этой схемы являются D-триггер данных и тристабилъный (с тремя состояниями) буфер данных.

Рис. 11.3. Упрощенная схема одной линии порта ввода/вывода

• Запись в порт вызывает переключение D-триггера данных, в результате чего в него будет записано значение, находящееся на линии шины данных. Эти данные будут храниться до тех пор, пока на микроконтроллер подается напряжение питания (см. Рис. 2.16, в и г на стр. 46). К примеру, в результате выполнения команд

movlw b’11111111’ ; Все биты рабочего регистра установлены в 1

movwf h’06’ ; Копируем его в порт В (регистр h’06’)

во все восемь D-триггеров, составляющих регистр порта В (PORTB), будет записана лог. 1.

Установка битов порта будет происходить независимо от того, как сконфигурированы его выводы (на вход или на выход). Однако для выдачи состояния триггера на вывод микроконтроллера должен быть включен буфер TRIS (тристабильный). В этом случае, как показано на Рис. 11.4, б, триггер данных оказывается напрямую подключенным к остальным узлам схемы.

• При чтении этого регистра данных порта включается буфер данных, в результате чего состояние защелки выдается на линию шины данных микроконтроллера. Если чтения порта не происходит, то эта D-защелка прозрачна и состояние ее выхода соответствует состоянию вывода микроконтроллера (см. Рис. 2.16, а и б на стр. 46). При чтении порта на входе разрешения D-защелки устанавливается ВЫСОКИЙ уровень, в результате чего значение на выходе тристабильного буфера данных «замораживается», оставаясь неизменным в процессе операции чтения. Для увеличения помехоустойчивости вход защелки данных отделен от вывода микроконтроллера буфером с гистерезисом (триггером Шмитта). К примеру, чтобы считать состояние порта В, достаточно выполнить следующую команду:

movf h’06’,w ; Считываем состояние всех восьми линий порта В в W

Рис. 11.4. Различные варианты чтения/записи одного бита порта при различных конфигурациях соответствующего вывода

Микроконтроллеры младшего уровня первого поколения PIC12C5XXX с 12-битным ядром не имеют отдельных регистров TRIS, т. е. триггеры TRIS не отображены на адресное пространство памяти данных. Вместо этого в указанных микроконтроллерах используется команда tris, копирующая содержимое рабочего регистра во внутренний управляющий регистр, не отображенный на память данных. Так, в нашем случае:

movlw b’00001111’; Старшие 4 линии — выходы, остальные — входы

tris h’06’; Конфигурируем порт В

Даже когда появились первые представители 14-битных микроконтроллеров среднего уровня с регистрами TRIS, команда tris была сохранена в системе команд. Причем компания Microchip не гарантирует, что эта команда будет реализована в будущих устройствах. Тем не менее, эту команду до сих пор используют многие программисты, а также некоторые компиляторы языка Си, такие как CCS.

Из Рис. 11.3 видно, что бит TRIS доступен не только для записи, но и для чтения. На первый взгляд эта возможность может показаться бесполезной. Однако предположим, что программист хочет переключить вывод RB7 в режим выхода (см. Пример 11.4):

bcf h’86’,7 ; Сбросить бит 7 регистра TRISB

Команда bcf относится к командам типа «чтение — модификация — запись» (см. стр. 138), т. е. содержимое регистра TRISB считывается процессором, модифицируется и записывается обратно в регистр. Для выполнения указанной операции процессор должен иметь возможность как читать из регистра, так и записывать в него.

Поскольку параллельный порт не только может быть сконфигурирован как входной или выходной, но также может содержать линии различных направлений, необходимо знать ограничения, имеющие место при чтении или изменении состояния таких не совсем обычных регистров. Например, что произойдет, если программа прочитает бит порта, который был сконфигурирован как выход? Всего возможно четыре ситуации, которые представлены на Рис. 11.4.

а) Чтение вывода, сконфигурированного как вход (TRIS = 1)

В данном случае буфер TRIS отключен, и состояние триггера данных остается неизменным. Например, команда movf h’06’,w считает состояние выводов порта В в рабочий регистр.

б) Запись в вывод порта, сконфигурированного как выход (TRIS = 0)

В данном случае буфер TRIS включен, а состояние триггера данных изменяется процессором, выполняющим запись в порт. Значение, сохраненное в этом триггере, появляется на выводе микроконтроллера. К примеру, если все выводы порта В сконфигурированы как выходы, то в результате выполнения команд

movlw b’10101010’

movwf h’06’

выводы порта В будут установлены в состояние HLHLHLHL (Н — ВЫСОКИЙ уровень, L — НИЗКИЙ уровень).

в) Чтение вывода, сконфигурированного как выход (TRIS = 0)

В данном случае буфер TRIS включен, поэтому вывод микроконтроллера будет подключен к выходу соответствующего триггера данных. В большинстве случаев в результате чтения вывода порта, сконфигурированного как выход, будет считано состояние триггера данных и соответствующего вывода. Однако так происходит не всегда. Если ток, отбираемый подключенным к выводу устройством, достаточно велик, то напряжение на выводе может довольно сильно отличаться от значений, соответствующих нормальным логическим уровням. Так, подключение биполярного транзистора непосредственно к выводу порта (как на Рис. 11.5, а) приведет к потреблению значительного тока от буфера TRIS, что вызовет снижение напряжения на выводе до уровня 0.7 В (падение напряжения на переходе база-эмиттер типового транзистора).

Рис. 11.5. Втекающий и вытекающий ток

Ситуация, изображенная на Рис. 11.5, б, аналогична предыдущей, только в этом случае ток втекает в порт микроконтроллера через светодиод (СИД), в результате чего напряжение на входе буфера TRIS возрастет до 3 В (учитывая, что падение напряжения на открытом СИД составляет около 2 В). В таких ситуациях результат чтения состояния вывода порта, являющегося выходом, очень часто не соответствует состоянию триггера данных из-за некорректных значений напряжения на выводе. Например, при исполнении команды btfsc PORTB, 7 может быть ошибочно пропущена следующая за ней команда, если с вывода RB7 отбирается или в него втекает слишком большой ток.

г) Запись в вывод порта, сконфигурированного как вход (TRIS = 1)

В этом случае будет изменено соответствующим образом состояние триггера данных. Однако, поскольку буфер TRIS отключен, это изменение никоим образом не отразится на состоянии соответствующего вывода микроконтроллера до тех пор, пока он не будет переведен в режим выхода. Эту возможность установки состояния портов «незаметно» для внешних цепей мы использовали в Программе 11.2 при инициализации параллельных портов после сброса. Напоминаю вам, что после сброса все порты работают как входы, другими словами, во всех регистрах TRIS находится значение Ь’11111111’.

Большинство выводов, сконфигурированных как входы, имеют буферы с триггером Шмитта. Особенностью этих триггеров является то, что при изменении уровня входного сигнала с НИЗКОГО на ВЫСОКИЙ они переключаются при напряжении на входе, составляющем более 80 % от напряжения питания, а при изменении сигнала в обратном направлении — менее 20 % от напряжения питания. Такой гистерезис значительно увеличивает надежность при считывании логических состояний в средах с повышенным уровнем помех. Порт GPIO в 8-битных устройствах (исключая линию GP3), порт В, а в некоторых старых моделях, таких как PIC16F84, еще и порт А (кроме RA4) имеют обычные буферы без гистерезиса, с порогами переключения VIL =< 0.5 В и VIH >= 2 В.

Любой вывод, сконфигурированный как выход, должен быть способен давать ток, требуемый для управления нагрузкой. В большинстве случаев выходы микроконтроллера нагружаются током величиной всего несколько миллиампер. И тем не менее очень важно иметь представление об ограничениях нагрузочной способности выходов портов.

В документации на устройства обычно указываются два параметра:

1. Ток, потребляемый выводом (IOL) при наличии на выходе напряжения НИЗКОГО уровня, не должен превышать 8.5 мА, если напряжение НИЗКОГО уровня VOL не превышает 0.6 В.

2. Ток, отдаваемый выводом (IOH) ПРИ наличии на выходе напряжения ВЫСОКОГО уровня, не должен превышать —3 мА, если напряжение ВЫСОКОГО уровня падает не более чем на 0.7 В ниже VDD. Отрицательное значение тока соответствует источнику тока, т. е. ток вытекает из устройства.

Если допускается изменение логических уровней относительно их номинальных значений, то устройство может потреблять или отдавать большие токи (как показано на Рис. 11.5). При этом следует учитывать, что имеется еще одно ограничение — ток через любой вывод порта не должен превышать ±25 мА во избежание повреждения микроконтроллера. Если для управления используется более одного вывода, то необходимо учитывать ограничения, накладываемые на суммарный ток порта, В 8-выводных устройствах суммарный ток их единственного порта ввода/вывода (который является комбинацией портов А и В более старших собратьев) должен находиться в пределах ±125 мА. В моделях с большим числом выводов суммарный ток портов А, В и, при его наличии, С ограничивается на уровне ±200 мА. Аналогично, суммарный ток портов D и Е тоже должен находиться в пределах ±200 мА.

Каждый вывод, отдающий или потребляющий ток, будет рассеивать мощность, что проявляется в виде нагрева корпуса. Из упрощенной модели, изображенной на Рис. 11.6, можно заметить, что рассеивание мощности происходит на трех компонентах (на рисунке они изображены в виде резисторов):

1. С линии питания VDD мы потребляем ток IDD. Однако ток через сопротивление R1, представляющее собой сопротивление всего микроконтроллера в целом, будет меньше на величину токов, вытекающих через выводы портов. Таким образом, рассеиваемая мощность (определяемая, как известно, выражением V x I) равна VDD х (IDD — ΣIOH).

Рис. 11.6. Модель для расчета рассеиваемой мощности

2. Падение напряжения на эквивалентном сопротивлении R2 между выходными контактами и выводом питания составляет ΔV = VDD — VOH. Соответственно рассеиваемая мощность равна ΔV x ΣIOH.

3. Ток, протекающий от выходов к общему проводу (через вывод VSS), рассеивает на резисторе R3 мощность VOL х ΣIOL.

Сложив эти компоненты, получим выражение, которое приводится в документации:

В данном выражении учитывается тот факт, что выходные напряжения на каждом из выводов будут отличаться, поскольку через эти выводы протекают токи разной величины. Для моделей в маленьких корпусах PDIS составляет 800 мВт, а для моделей в 40-выводных корпусах — 1 Вт. В любом случае максимальный ток, отбираемый выводом VDD, не должен превышать 250 мА, а через вывод VSS не должен вытекать ток более 300 мА. В действительности эквивалентное сопротивление R2 имеет нелинейный характер и изменяется достаточно сложным образом (см. Рис. 11.13). То есть изменение напряжения VOH не прямо пропорционально току. В документации приводятся графики этой зависимости напряжения от тока (см., например, Рис. 11.13 и Рис. 11.17). Однако в наихудшем варианте при больших значениях токов можно считать, что напряжение VOH  падает до нуля, a VOL  возрастает до напряжения питания VDD. В этом случае разница между током IDD и суммарным током ΣIOH, потребляемым ЦПУ и другими периферийными модулями, будет минимальной и ей можно будет пренебречь. Тогда суммарная рассеиваемая мощность будет определяться выражением

ΣIOH.

Структурная схема, приведенная на Рис. 11.3, представляет собой типовую схему одной линии параллельного порта ввода/вывода. Отдельные порты (в частности, порты А и Е) могут иметь другую структуру, что кардинальным образом влияет на их электрические характеристики. В большинстве портов буферы TRIS построены по схеме, приведенной на Рис. 11.7, а, т. е. имеют двухтактный выход на двух последовательно соединенных полевых транзисторах с каналами n- и р-типа. Этот буфер работает следующим образом:

• Когда в триггере TRIS записана 1, на выходе нижнего элемента И присутствует лог. О, а на выходе верхнего элемента ИЛИ — лог. 1. В этом случае оба транзистора закрыты, и выход триггера отключен от вывода микроконтроллера. При этом вывод порта работает как вход.

• Когда в триггере TRIS записан 0, инвертированное значение с выхода триггера данных подается на затворы обоих транзисторов. Если на выходе триггера данных присутствует НИЗКИЙ уровень, то n-канальный транзистор открывается, а p-канальный закрывается, в результате чего на выводе микроконтроллера появляется НИЗКИЙ уровень. Если же на выходе триггера данных присутствует ВЫСОКИЙ уровень, то p-канальный транзистор открывается, а n-канальный закрывается. В результате на выводе микроконтроллера появляется ВЫСОКИЙ уровень. В данном случае напряжение на выводе микроконтроллера соответствует состоянию триггера данных, при этом ток вытекает или втекает через относительно малое сопротивление соответствующего открытого транзистора.

Для примера представим, что нам необходимо управлять электромагнитным реле, для включения которого требуется ток 200 мА и напряжение 12 В. При работе с такими большими напряжениями и токами мы должны использовать внешние буферы. На Рис. 11.7, в в качестве такого внешнего ключа используется биполярный транзистор. Если минимальный коэффициент усиления транзистора равен 100, то при сопротивлении резистора 1.8 кОм базовый ток будет равен 2 мА (считаем, что на открытом переходе база-эмиттер падает 0.7 В, а напряжение ВЫСОКОГО уровня на выходе микроконтроллера составляет не менее 4.3 В).

Выходной каскад линии RA4/GP3, схема которого приведена на Рис. 11.7, б, отличается тем, что в нем присутствует только транзистор нижнего плеча. В отличие от схемы с тремя состояниями (Рис. 11.7, а) данная схема имеет только два состояния — лог. 0 и разомкнутая цепь. Выходы такого типа называются выходами с открытым стоком или с открытым коллектором (см. Рис. 2.3 на стр. 33). Этот выход работает следующим образом:

• Когда в триггере TRIS записана 1 (состояние по умолчанию), то на выходе элемента И присутствует НИЗКИЙ уровень, транзистор закрыт, а выход микроконтроллера находится в состоянии с высоким входным сопротивлением. При этом вывод RA4/GP3 работает как вход.

• Когда в триггере TRIS записан 0, то, при наличии лог. 0 в триггере данных, выходной транзистор находится в открытом состоянии, формируя на выходе микроконтроллера НИЗКИЙ уровень. Когда же в триггере данных находится лог. 1, транзистор закрыт и выход микроконтроллера «висит» в воздухе.

Рис. 11.7. Структурные схемы выходных каскадов

Выход с открытым стоком не может быть источником тока — необходимо либо подключить саму нагрузку между выходом и шиной питания, либо использовать в качестве нагрузки внешний подтягивающий резистор. В частности, второй вариант изображен на Рис. 11.7, г — при выключенном выходе RA4/GP3 ток базы внешнего транзистора формируется подтягивающим резистором сопротивлением 1.8 кОм.

Если вывод RA4 используется в качестве входа Таймера 0, то он обычно конфигурируется как вход. Если он будет сконфигурирован как выход, то в соответствующий бит порта необходимо записать лог. 1, которая будет удерживать выходной транзистор в закрытом состоянии и предотвратит его воздействие на вход внешнего тактового сигнала таймера.

Во многих приложениях приходится считывать состояние групп переключателей (кнопок). Вместо того чтобы использовать для формирования двух логических уровней однополюсные переключатели на два направления (Single-Pole Double-Throw — SPDT), такие как приведены на Рис. 11.8, а, в большинстве случаев (см., к примеру, Рис. 11.10) используют более дешевые однополюсные (Single-Pole Single-Throw — SPST). В этом случае для формирования напряжения ВЫСОКОГО уровня при разомкнутых контактах переключателя требуется внешний подтягивающий резистор, как показано на Рис. 11.8, б. Аналогичная ситуация возникает при считывании состояния устройства, имеющего выход с открытым стоком/коллектором, например фототранзистора. Сопротивление подтягивающего резистора не должно быть слишком маленьким, так как в этом случае через закрытый ключ будет течь большой ток. Вместе с тем сопротивление не должно быть и слишком большим, иначе устройство станет чувствительным к электромагнитным помехам, наводимым от внешних источников. Хорошим компромиссом будет сопротивление из диапазона 10…100 кОм.

Рис. 11.8. Подключение переключателей к выводу порта

Чтобы упростить подключение таких устройств, входы порта В уже имеют внутренние подтягивающие резисторы. Эти резисторы называются слабой подтяжкой (weak pull-up), поскольку их эквивалентное сопротивление (около 20 кОм) достаточно велико, чтобы они не оказывали влияния на операции чтения устройств, имеющих «нормальные» логические выходы.

Из Рис. 11.9 видно, что внутренние подтягивающие резисторы (являющиеся в действительности полевым транзистором с p-каналом) включаются только в том случае, если бит  регистра OPTION_REG сброшен в 0. Несмотря на то что этот бит управляет всеми восемью подтягивающими резисторами, на тех линиях, которые сконфигурированы как выходы (TRIS[n] = 0), этот резистор будет отключен. После сброса бит  устанавливается в 1, так что по умолчанию внутренние подтягивающие резисторы отключены.

Рис. 11.9. «Слабая» подтяжка линий порта В управляется битом RBPU регистра OPTION_REG

Порт ввода/вывода устройств в 8-выводных корпусах (он в них один-единственный) имеет похожую схему. При этом в моделях PIC16F629/75 внутренние подтягивающие резисторы (вывод GP3 не имеет такового) можно включать или отключать в индивидуальном порядке с помощью регистра специального назначения WPU (Weak Pull-Up), который, в свою очередь, управляется 7-м битом регистра OPTION_REG, названным .

В качестве типичного примера использования внутренней подтяжки рассмотрим задачу определения состояния клавиатуры, например, такой как показана на Рис. 11.10, а. В данном случае клавиатура состоит из 12 кнопок. В принципе ничто не мешает нам использовать столько же линий ввода/вывода. Однако более эффективным решением будет организация этих кнопок в виде матрицы 4x3, как показано на Рис. 11.10, б. При таком подключении количество требуемых выводов уменьшается до 7. В случае клавиатур большего размера эта экономия будет еще больше. Так, для клавиатуры на 64 кнопки (8 х 8) потребуется всего 16 линий ввода/вывода.

Хотя организация матриц может быть различной, на рисунке представлена наиболее типичная. Сигналы трех столбцов считываются с выводов RB[7:5] с включенными внутренними подтягивающими резисторами. Поочередный выбор каждой из строк (сканирование матрицы), подключенных к выводам RB[3:0], осуществляется выдачей на соответствующий вывод НИЗКОГО уровня, как показано на Рис. 11.10, в. Кнопки имеют нормально-разомкнутые контакты, поэтому если кнопка не нажата, то из-за подключенных подтягивающих резисторов считывается лог. 1. Однако стоит замкнуть кнопку, подключенную к строке, на которую подан НИЗКИЙ уровень, как на соответствующей линии столбца тоже появится НИЗКИЙ уровень. Таким образом, нажатую кнопку можно определить по пересечению строки и столбца. Резисторы сопротивлением 330 Ом ограничивают ток через контакты кнопок, если из-за ошибки в программе на каком-либо выводе RB[7:5] случайно появится ВЫСОКИЙ уровень.

Возьмем на вооружение оба принципа и напишем подпрограмму опроса клавиатуры, которая будет возвращать либо номер нажатой кнопки (первой из нажатых, если одновременно нажали несколько кнопок), либо если ни одна из кнопок не нажата, то -1 (т. е. h’FF’). Прежде чем перейти к собственно программированию, условимся, что порт В уже соответствующим образом сконфигурирован, а бит  регистра OPTION_REG сброшен. Скажем, так:

      include "p16f627.inc"

MAIN bsf STATUS,RP0 ; Переключаемся на 1-й банк памяти, в котором расположены

        movlw b’11110000’ ; регистры TRISB и OPTION_REG

        movwf TRISB ; RB[7:4] — входы, RB[3:0] — выходы

        bcf OPTION_REG,NOT_RBPU ; Включаем внутреннюю подтяжку

        bсf STATUS,RP0 ; Возвращаемся в 0-й банк

Рис. 11.10. Подключение клавиатуры

Код, приведенный в Программе 11.2, соответствует следующему алгоритму:

1. Установить KEY_COUNT = 1.

2. Для i = 0…3

• Выбрать строку i.

• Для j = 0…2:

— Проверить столбец j.

— Если ноль, то перейти к шагу 4.

— Иначе инкрементировать KEY_COUNT.

3. Установить KEY_COUNT равным -1 (нажатых клавиш не обнаружено).

4. Вернуть KEY_COUNT.

Программа 11.2. Сканирование клавиатуры

; *****************************

; * ФУНКЦИЯ: Сканирует клавиатуру 4 х 3 и возвращает номер клавиши *

; * ВХОД: Нет

; * ВЫХОД: Номер клавиши в W ([MEM]=10, [0]=11, [SET]=12) *

; * ВЫХОД: Возвращает -1 (h’FF’) если не нажато ни одной клавиши *

; * ОКРУЖЕНИЕ: Переменные KEY, PATTERN *

; *****************************

          cblock ;Две глобальные переменные

            KEY_COUNT:1, PATTERN:1

          endc

SCAN_IT clrf KEY_COUNT ; Первая клавиша — "1"

              incf KEY_COUNT,f

              movlw b’111111101’ ; Начальное значение шаблона

              movwf PATTERN

SLOOP movf PATTERN,w ; Считываем шаблон из памяти

           movwf PORTB ; Выдаем на строку НИЗКИЙ уровень

; Теперь проверяем каждый столбец на наличие нуля —

           btfss PORTB,5 ; Проверяем 1-й столбец

              goto GOT_IT ; ЕСЛИ ноль, ТО клавиша обнаружена!

           incf KEY_COUNT,f ; ИНАЧЕ инкрементируем счетчик

           btfss PORTB,6 ; Проверяем 2-й столбец

              goto GOT_IT ; ЕСЛИ ноль, TO клавиша обнаружена!

           incf KEY_COUNT,f ; ИНАЧЕ инкрементируем счетчик

           btfss PORTB,7 ; Проверяем 3-й столбец

               goto GOT_IT ; ЕСЛИ ноль, TO клавиша обнаружена!

           incf KEY_COUNT,f ; ИНАЧЕ инкрементируем счетчик

; Сюда попадаем, если нет нажатых клавиш —

           rlf PATTERN,f ; Сдвигаем шаблон

           btfsc PATTERN,4 ; Появился ли 0 в 4-м бите?

              goto SLOOP ; ЕСЛИ нет, TO переходим к следующей строке

; ИНАЧЕ на клавиатуре нет нажатых клавиш —

           movlw -1 ; Возвращаем -1

               goto S_EXIT

GOT_IT movf KEY_COUNT,w ; Копируем значение счетчика в W

S_EXIT return ; и выходим

Отсчет начинается с кнопки № 1, при этом на 0-ю строку выставляется напряжение НИЗКОГО уровня. По мере проверки каждого столбца на ноль содержимое рабочего регистра, выполняющего роль счетчика, инкрементируется. При отсутствии замыкания (нулевого значения) осуществляется переход к следующей строке, для чего значение шаблона PATTERN сдвигается на одну позицию влево.

При этом справа вдвигается бит переноса, но поскольку предыдущие строки мы уже проверили, то его значение безразлично.

Выход из цикла сканирования происходит в двух случаях:

Если в процессе сканирования будет обнаружено нулевое значение. В переменной key_COUNT при этом будет находиться номер нажатой клавиши, который перегружается в рабочий регистр перед возвратом из подпрограммы.

• Если в результате сдвига активный бит (0) шаблона окажется в 4-м бите регистра. В этом случае в рабочий регистр заносится число h’FF’, означающее, что нажатых клавиш не обнаружено.

• На практике подобные подпрограммы часто возвращают всякую ерунду из-за дребезга контактов, а также различных наводок в проводах, соединяющих клавиатуру и электронные узлы. Один из возможных вариантов решения этой проблемы приведен в Программе 11.3. В этой программе для опроса клавиатуры используется подпрограмма SCAN_IT, код которой был приведен в Программе 11.2. Сохраняя в памяти значение, полученное при предыдущем опросе, можно отслеживать любые изменения состояния клавиатуры. Подпрограмма GET_IT возвращает код кнопки только в том случае, если состояние клавиатуры не менялось на протяжении 256 последовательных опросов. В зависимости от качества клавиатуры, уровня помех и частоты процессора надежность считывания можно увеличить (при этом возрастет время отклика), добавив в тело цикла короткую задержку или увеличив размер счетчика до двух байтов.

Программа 11.3. Сканирование клавиатуры с защитой от дребезга

; ************************************

; * ФУНКЦИЯ: Сканирует клавиатуру 4 х 3 и возвращает номер клавиши *

; * ФУНКЦИЯ: (имеется защита от дребезга) *

; * ВХОД: Нет *

; * ВЫХОД: Номер клавиши в W ([МЕМ]=10, [0]=11, [SET]=12) *

; * ВЫХОД: Возвращает -1 (h’FF’), если не нажато ни одной клавиши *

; * ОКРУЖЕНИЕ: Переменные COUNT, NEW_KEY, OLD_KEY *

; * ОКРУЖЕНИЕ: Подпрограмма SCAN_IT *

; *************************************

           cblock  ; Три глобальные переменные

             COUNT 1, NEW_KEY:1, OLD_KEY:1

           endc

GET_IT clrf COUNT ; Обнуляем счетчик

GLOOP call SCAN_IT ; «Сырое» значение находится в W

            movwf NEW_KEY ; Сохраняем новое значение

            subwf OLD_KEY,w ; Отличается от предыдущего?

            btf sc STATUS,Z

               goto EQUAL ; ЕСЛИ одинаковы, ТО переходим к EQUAL

;Результат отличается от предыдущего, поэтому:

            movf NEW_KEY,w ; Переписываем предыдущее значение новым

            movwf OLD KEY

               goto GET_IT ; и начинаем цикл опроса сначала

; ЕСЛИ значения одинаковы, ТО —

EQUAL incfsz COUNT,f ; Инкрементируем счетчик. ЕСЛИ нет

               goto GLOOP ; переполнения, считываем новое значение

            movf OLD_KEY,w ; ИНАЧЕ возвращаем требуемое значение!

            return

Программа 11.4 . Подпрограмма сканирования клавиатуры на Си

unsigned int scan_it(void)

{

      unsigned int key, pattern ;

      key=1; pattern = 0xFE ; /* Начальное значение маски b’11111110’ */

      while(key<13) /* У нас 12 клавиш */

      {

          PORT_B = pattern ; /* Выбираем строку */

          if(!COL1) {break;} /* Считываем состояние каждого столбца, */

          key++; /* выходя из цикла при нулевом значении */

          if(!COL2) {break;} /* ИНАЧЕ инкрементируем счетчик цикла */

          kеу++ ;

          if(!COL3) {break;}

          kеу++;

          pattern = pattern << 1 ; /* Сдвигаем маску на один бит влево */

       }

       if(key==13) {key = 0xFF;} /* Если в счетчике число 13, нажатые клавиши */

       return key ; /* отсутствуют */

}

В Программе 11.4 приведен текст Си-программы для компилятора CCS, которая выполняет те же действия, что и код в Программе 11.2. При этом предполагается, что порт В уже сконфигурирован следующим образом:

#include <16f627.h>

#use fast_io(b)

#byte PORT_B = 6

#bit COL1 = PORT_B.5 /* Столбец 1 — RB5 */

#bit COL2 = PORT_B.6 /* Столбец 2 — RB6 */

#bit COL3 = PORT_B.7 /* Столбец 3 — RB7 */

unsigned int scan_it(void);

int main()

{

      set_tris_b(0xF0);

       port_b_pullups(TRUE);

В компиляторе CCS предусмотрены различные средства поддержки параллельного ввода/вывода. Так, выражение #use fast_io (b), использованное нами в предыдущем фрагменте кода, предоставляет программисту возможность явно задавать конфигурацию регистров TRIS. Альтернативная директива #use standard_io (b) позволяет программисту не обращать внимание на установки этих регистров, но тогда компилятор будет конфигурировать порт при каждом обращении к нему, даже если его конфигурация и не изменялась с момента последнего использования. Ну, а функция port_b_pullups (true) предназначена для установки бита  регистра OPTION_REG.

Логика программы практически не отличается от логики ассемблерной программы, написанной нами ранее. Единственное отличие заключается в том, что количество проходов цикла задается заранее, а не определяется моментом сброса 4-го бита маски. Это делает процесс вычислений более прозрачным, хотя и менее эффективным.

Реализация интерфейса с клавиатурой является настолько частой задачей, что в большинстве микроконтроллеров PIC имеется возможность определять изменения состояний входов порта В. Логика работы этой схемы показана на Рис. 11.11. Старшие четыре линии порта имеют вторую D-защелку, подключенную параллельно основной, но работающую с ней в противофазе. При чтении порта В состояние входа, как обычно, запоминается в защелке Capture. Однако в то же время защелка Change становится прозрачной. По завершении операции чтения защелка Change фиксируется, в результате чего в ней сохраняется состояние вывода, которое было в момент считывания. Выходы обеих защелок объединены посредством элемента Исключающее ИЛИ (XOR). Как вы уже знаете (см. стр. 28), логический элемент XOR фиксирует различие между двумя входами.

Рис. 11.11. Логика работы схемы, формирующей прерывание по изменению состояния порта В

Поскольку защелка Capture в это время прозрачна, любое последующее изменение сигнала на входе приведет к появлению лог. 1 на выходе соответствующего элемента XOR. По такой схеме построены линии RB[7:4] порта В. Выходы всех четырех элементов XOR объединены с помощью 4-входового элемента ИЛИ, сигнал с выхода которого используется для установки флага прерывания RBIF регистра INTCON (см. Рис. 7.3 на стр. 213) в 1. Если бит RBIE (разрешение прерывания от порта В) также установлен в 1, то это событие позволяет выводить микроконтроллер PIC из «спящего» режима. А если установлен бит глобального разрешения прерываний GIE, то изменение состояния четырех старших линий порта В приведет еще и к генерации прерывания. Обратите внимание, что сигнал от каждого из элементов XOR проходит через двухвходовый элемент И, второй вход которого подключен к соответствующему биту регистра TRIS. Благодаря этому в формировании итогового сигнала участвуют только те линии, которые сконфигурированы как входы.

В нашем конкретном случае (см. клавиатуру на Рис. 11.10) если на линиях всех строк выставить НИЗКИЙ уровень, то при нажатии любой клавиши изменится состояние линии столбца. Если бит RBIE будет при этом установлен, то одновременно с установкой флага RBIF будет сгенерировано прерывание. После этого в обработчике прерывания можно будет выполнить сканирование клавиатуры для определения нажатой клавиши. Порт ввода/вывода GPIO моделей среднего уровня, выпускающихся в 8-выводных корпусах, имеет схожую функциональность. Причем для большей гибкости реакция на изменение состояния может быть разрешена или запрещена индивидуально для каждого из выводов порта.

При использовании этой возможности необходимо быть очень аккуратным. Например, при изменении младших битов порта В (скажем, командой bcf PORTB, 0) во все защелки будут записаны новые значения с выводов микроконтроллера, что может повлиять на работу этой функции. В более старых устройствах также существует вероятность пропустить изменение состояния вывода, если оно произойдет в момент чтения порта. Однако ни один из этих недостатков не является сколько-нибудь существенным, если нажатие на клавиатуру используется для «пробуждения» процессора.

После отклика микроконтроллера на данное прерывание необходимо повторным чтением порта В снять внутренний сигнал, вызвавший установку бита RBIF. При этом в обоих D-защелках окажется одинаковое значение. И только затем можно будет сбросить флаг RBIF. Если этого не сделать, то сразу же после сброса флаг прерывания снова установится.

Приведем пример использования клавиатуры для вывода микроконтроллера из «спящего» режима (предполагается, что бит GIE сброшен, т. е. прерывания в программе не используются):

movf PORTB,w ; Считываем порт В, чтобы сбросить защелки

bcf INTCON,RBIF ; Сбрасываем флаг прерывания по изменению порта

bsf INTCON,RBIE ; Разрешаем генерацию прерывания

sleep ; Переходим в «спящий» режим

; ссс-п-и-и-и-и-м…

call DELAY ; После «пробуждения» ждем некоторое время

movwf PORTB,w ; перед сбросом защелок

bcf INTCON,RBIF ; Сбрасываем флаг прерывания

bcf INTCON,RBIE ; Запрещаем генерацию прерывания

В большинстве микроконтроллеров PIC имеется относительно мало линий портов ввода/вывода (см. Табл. 11.1). Даже в развитых моделях (например, PIC16F877), имеющих в общей сложности 33 линии ввода/вывода, этих ресурсов может в ряде случаев оказаться недостаточно, особенно если часть выводов будет задействована встроенными периферийными устройствами.

В качестве примера рассмотрим многофункциональную систему охранной сигнализации, которая может контролировать до восьми зон — скажем, этажей многоэтажного здания. В каждой зоне может быть расположено до восьми датчиков движения. Дисплей в виде восьми лампочек, расположенный на посту охраны, используется для индикации зоны, в которой было обнаружено проникновение.

Исходя из условий задачи, нам потребуется 72 линии ввода/вывода (64 входа и 8 выходов). Вместо того чтобы устанавливать в каждой зоне по микроконтроллеру, каждый из которых отсылал бы информацию центральному контроллеру, было принято решение расширить возможности ввода/вывода одного-единственного микроконтроллера PIC16F627.

Один из возможных вариантов решения этой задачи изображен на Рис. 11.12. В данном случае порт В используется для реализации внешней шины данных, подключенной к восьми микросхемам параллельных регистров (буферов) с тремя состояниями (по одной для каждой зоны) и одному регистру индикации. Датчики каждой из зон подключаются к локальной шине через эти 8-битные буферы с тремя состояниями. Разрешение одного из восьми буферов осуществляется с использованием дешифратора 3 на 8, подключенного к порту А. К примеру, если RA[2:0] = b’111’ и RA3 = 0, то будет разрешен буферный регистр 7-й зоны, в результате чего с выводов порта В можно будет считать состояния восьми датчиков этой зоны.

Рис. 11.12. Многозонная система охранной сигнализации

Для управления лампочками бит RA3 необходимо установить в 1, а порт В сконфигурировать как выход. После этого содержимое порта можно будет занести в регистр, выставив на вывод RA0 напряжение НИЗКОГО, а затем ВЫСОКОГО уровня, формируя тем самым нарастающий фронт.

Количество выходных портов в этой системе можно увеличить до восьми, добавив в схему второй декодер 3 на 8, посредством которого будет осуществляться выбор требуемого порта при RA3 = 1. Правда, один или два дополнительных выходных порта можно добавить еще проще, подключив тактовые входы новых регистров к линиям RA1 и RA2. Один из этих портов, к примеру, может использоваться для индикации активного датчика раздела, а для управления звуковым излучателем, включающимся при обнаружении вторжения в любую из зон, можно использовать вывод RA4.

Чтобы продемонстрировать управление такой схемой, рассмотрим подпро грамму, код которой приведен в Программе 11.5. Эта подпрограмма считывает со стояние N-й зоны и, если возвращаемое значение отлично от нуля, включает N-ю лампу (N — число от 0 до 7, загружаемое перед вызовом подпрограммы в регистр ZONE). Предполагается, что на выходе сработавшего датчика формируется лог. 1 а индикаторная лампа загорается при лог. 0.

Программа 11.5. Управление системой охранной сигнализации

; ************************************

; * ФУНКЦИЯ: Считывает состояние N-й зоны и включает N-ю лампу *

; * ВХОД: N передается в регистре ZONE в виде b’00000nnn’ *

; * ВЫХОД: Включается N-я лампа, если состояние N-й зоны отлично от 0*

; * ВЫХОД: Регистр ZONE обнуляется, регистр TEMP не изменяется *

; ************************************

ZONE_N bsf STATUS,RP0 ; Переключаемся в 1-й банк

             movlw h’FF’ ; Конфигурируем порт В как вход

             movwf TRISB

             clrf TRISA; Конфигурируем порт А как выход

             bcf STATUS,RP0 ; Возвращаемся в 0-й банк

; -------------------------------------

             movf ZONE,w ; Считываем N, используемое

             movwf PORTA ; для выбора буферов N-й зоны

             nop ; Формируем задержку для установления сигнала

             nop ; в случае длинных соединительных линий

             movf PORTB,w ; Теперь считываем данные с порта В

             btfsc STATUS,Z ; ЕСЛИ не 0, ТО проникновение!

                goto LAMP_OFF ; ИНАЧЕ выключаем все лампы

; Обнаружено проникновение, включаем сигнальную лампу ------

             bsf STATUS,RP0 ; Переключаемся в 1-й банк

             clrf TRISB ; Теперь конфигурируем порт В как выход

             bcf STATUS,RP0 ; Возвращаемся в 0-й банк

; Преобразуем двоичное число в унарный эквивалент для включения соотв. лампы ---

             movlw h’FF’ ; Все биты переменной TEMP

             movwf TEMP ; установлены в 1

             bcf STATUS,С ; Обнуляем бит переноса

             incf ZONE,f ; Транслируем номер зоны в диапазон 1…8

Z_LOOP rlf TEMP,f ; Сдвигаем маску влево

             bsf STATUS,С ; Устанавливаем бит переноса

             decfsz ZONE,f ; Декрементируем номер зоны

                goto Z_LOOP ; и повторяем N раз

; В TEMP теперь находится маска для включения требуемой лампы ---

             movf TEMP,w ; Сохраняем ее в рабочем регистре

LAMP_OUT bsf PORTA,3 ; Разрешаем выходной порт

             movwf PORTB ; Выставляем маску

             bsf PORTA,0 ; Формируем тактовый импульс

             bcf PORTA,0

             return ; Все сделано

; Сюда переходим при отсутствии проникновения (нужно выключить все лампы) ---

LAMP_OFF bsf STATUS,RP0 ; Переключаемся в 1-й банк

             clrf TRISB ; Теперь конфигурируем порт В как выход

             bcf STATUS,RP0 ; Возвращаемся в 0-й банк

             movlw h’FF’ ; Выключаем все лампы

             goto LAMP_OUT

Для проверки состояния датчиков N-й зоны номер зоны из переменной ZONE копируется в порт А, все линии которого работают как выходы. При НИЗКОМ уровне на RA3 дешифратор разрешает буфер соответствующей зоны. После короткой задержки, введенной для установления сигналов на выходе буфера, состояние датчиков выбранной зоны считывается с порта В. В реальных системах (при больших расстояниях между буферными регистрами зон) для обеспечения надежного считывания данных могут потребоваться задержки порядка нескольких сот миллисекунд, а также операции цифровой фильтрации, подобные реализованной в Программе 11.3. Все это связано с тем, что буферные регистры зон могут располагаться далеко друг от друга.

Управление восемью лампами более мудреное. Для этой операции порт В необходимо сконфигурировать как выход. Включение требуемых ламп осуществляется копированием соответствующей маски в порт В, установкой RA3 в состояние ВЫСОКОГО уровня для отключения дешифратора буферов зон и последующего формирования импульса на линии RA0. В Программе 11.5 эти операции выполняет подпрограмма LAMP_OUT. При отсутствии проникновения, т. е. когда выходы всех датчиков зоны сброшены в 0, байт маски, управляющий свечением ламп, равен h’FF’ (все лампы выключены).

При обнаружении проникновения необходимо включить N-ю лампу. Для этого двоичный код зоны, хранящийся в переменной ZONE, необходимо преобразовать в соответствующий унарный (один из n) код. Так, число Ь’00000010’ (2-я зона) преобразуется в число Ь’11111011’, число b’00000011’ (3-я зона) преобразуется в число Ь’11110111’ и т. д.

В программе унарный код формируется в переменной TEMP, которой первоначально присваивается значение b’11111111’. Сбрасывая флаг переноса перед входом в цикл Z_LOOP, но устанавливая его в теле цикла, можно выполнить сдвиг сброшенного бита влево с помощью команды rlf TEMP,f. В результате содержимое регистра будет изменяться следующим образом: Ь’11111111’ <- Ь’11111110’ <- Ь’11111101’ <-…<- b’01111111’. В процессе сдвига переменная ZONE (приведенная к диапазону 1…8, так что выполняется, по крайней мере, один сдвиг) декрементируется, а выход из цикла производится, когда она становится равной нулю. Таким образом, позиция единственного нулевого бита (начальное значение С = 0) соответствует исходному номеру зоны. Этот унарный код затем выдается в порт в секции LAMP_OUT.

Примеры

Пример 11.1

Для управления обмоткой возбуждения небольшого шагового двигателя используется биполярный n-р-n транзистор 2N3055. Принимая во внимание минимальный коэффициент усиления этого транзистора в диапазоне температур —40…+85 °C, было принято решение, что ток базы должен быть не менее 10 мА. Транзистор подключен к линии ввода/вывода микроконтроллера, причем полагается, что падение напряжения на переходе база-эмиттер не превышает 0.7 В при VDD = 5 В. Какое максимальное сопротивление может иметь базовый резистор RB и чему в самом худшем случае будет равен максимальный ток базы при таком резисторе?

Решение

Для таких величин токов можно предположить, что напряжение на выводе будет меньше 5 В. В документации приводится минимальная величина выходного напряжения при IOH = -3 мА, которая равна 4.3 В (на 0.7 В ниже напряжения питания), но для больших значений токов нам придется воспользоваться графиками.

На Рис. 11.13 приведены графики зависимости выходного тока IOH от напряжения ВЫСОКОГО уровня VOH при граничных значениях температуры (-40 °C и +80 °C).

Напряжение VOH зависит от сопротивления базового резистора в соответствии с уравнением VOH = 0.7 + IOH х RB.Прямая линия, выражающая это соотношение (называемая нагрузочной линией), проведена на рисунке через точку (0,0.7) и точку, соответствующую минимальному напряжению при токе —10 мА. Эта точка является единственной, удовлетворяющей обоим соотношениям ток-напряжение.

Рис. 11.13. Зависимость выходного напряжения ВЫСОКОГО уровня оттока

Крутизна нагрузочной линии ΔV/ΔI представляет собой сопротивление в кОм (так как ток выражается в мА) и получается равной 280 Ом. Обратите внимание, что напряжение ВЫСОКОГО уровня при таком токе снижается до 4 В (-10,4.0).

Продолжив линию, мы можем определить максимальный ток как координату X точки пересечения линии с верхней кривой. Этот ток равен примерно 11.5 мА, что не слишком отличается от предыдущего значения. Если бы нам требовалось получить бóльший ток, то вы бы увидели, что его величина очень сильно зависит от температуры. Например, чтобы получить минимальный базовый ток, равный 20 мА, нам потребуется резистор сопротивлением около 120 Ом (учитывая, что напряжение базы равно 0.8 В). Максимальный базовый ток в этом случае будет равен уже 28 мА.

Пример 11.2

Микроконтроллер PIC среднего уровня используется в качестве цифрового компаратора, который сравнивает 8-битное значение Р, считываемое с выводов порта, с байтом, хранящимся в регистре TRIP. Компаратор формирует три сигнала — «меньше, чем», «равно» и «больше, чем» и должен иметь гистерезис ±1 бит. То есть если в результате сравнения будет получено Р < TRIP, то уровень переключения для сигнала «равно» увеличится до значения TRIP + 1. Аналогично, при обратном соотношении уровень переключения для сигнала «равно» становится равным TRIP — 1.

Значение Р считывается из порта В, все линии которого сконфигурированы как входы, а для вывода результатов сравнения используются три младших вывода порта А — RA2 («меньше»), RA1 («равно») и RA0 («больше») с ВЫСОКИМ активным уровнем.

Решение

Напишем алгоритм, удовлетворяющий заданию:

1. Вычесть Р из LEVEL.

2. ЕСЛИ Р= LEVEL (Z = 1), то активизировать выход «Равно».

3. ИНАЧЕ, ЕСЛИ Р > LEVEL (С = 0), то активизировать выход «Больше» и присвоить LEVEL = TRIP — 1.

4. ИНАЧЕ, ЕСЛИ Р < LEVEL (С = 1), то активизировать выход «Меньше» и присвоить LEVEL = TRIP + 1.

Подпрограмма, текст которой приведен в Программе 11.6, а, написана с расчетом на то, что порты уже настроены соответствующим образом, а в переменную TRIP уже занесено фиксированное значение. Сначала значение регистра LEVEL принимается равным TRIP, но впоследствии оно изменяется в пределах ±1 по указанному выше алгоритму, формируя гистерезис.

Программа 11.6 . Цифровой компаратор с гистерезисом

а) Подпрограмма на ассемблере

СОМР movf PORTB,w ; Берем входное значение P

          subwf LEVEL,w ; LEVEL — P

          btfss STATUS,Z ; Пропускаем, если равно

             goto CONTINUE ; ИНАЧЕ проверяем остальные варианты

; Сюда попадаем при равенстве

          movlw b’11111010’ ; Выставляем на вывод «==» лог. 1

          movwf PORTA ; На остальных выходах — лог. 0

              goto COMP_END ; и выходим

CONTINUE btfsc STATUS,С ; Пропускаем, если заем (Р > LEVEL)

              goto LO ; ИНАЧЕ Р < LEVEL

; Сюда попадаем при P > LEVEL

HI       movlw b’11111001’ ; Выставляем на вывод «>» лог. 1

          movwf PORTA ; На остальных выходах — лог. 0

          decf TRIP,w ; Копируем TRIP-1 в W

          movwf LEVEL ; Новое значение порога

              goto COMP_END ; и выходим

; Сюда попадаем при P < LEVEL

LO      movlw b’11111100’ ; Выставляем на вывод «<» лог. 1

          movwf PORTA ; На остальных выходах — лог. 0

          incf TRIP,w ; Копируем TRIP+1 в W

          movwf LEVEL ; Новое значение порога

COMP_END return

б) Функция на Си (компилятор CCS)

void compare(unsigned int trip)

{

       EQ = HI = LO = 0;

       if(PORTB == LEVEL) {EQ =1;}

       else if(PORTB > LEVEL) {HI = 1; LEVEL = trip — 1;}

                 else {LO = 1; LEVEL = trip +1;}

}

Преимущество программной реализации функций, традиционно реализуемых аппаратно (таких как функция сравнения), заключается в большей гибкости, хотя и за счет снижения пропускной способности. Использование недорогих «вычислителей», таких как микроконтроллеры PIC, означает, что относительно простые функции, традиционно реализуемые специальными микросхемами, могут выполняться с помощью встраиваемых процессоров.

В данном случае гибкость состоит в том, что вместо фиксированного уровня можно легко начать использовать произвольное значение, считываемое, скажем, с порта С (см. Вопрос для самопроверки 11.5). В Примере 12.1 на стр. 435 показано, как можно считывать внешние данные последовательно. Также один или оба уровня могут быть сформированы из аналоговых сигналов с использованием встроенного модуля АЦП (см. главу 14). Во всех этих случаях гистерезис может задаваться в виде доли от порогового значения, например ±1/32, а не как фиксированное значение ± 1 бит.

В Си-варианте подпрограммы, код которой приведен в Программе 11.6, б, используются символические имена — EQ, YI и LO, которые определены в основной программе как соответствующие биты порта А. В данном случае пороговое значение trip передается в подпрограмму в качестве переменной. Сама функция только сравнивает значения и управляет соответствующими выводами. При необходимости также изменяется глобальная переменная LEVEL для изменения уровня переключения компаратора. Если значение trip фиксированно, то его не требуется передавать в функцию, и оно может задаваться константой.

Пример 11.3

На Рис. 11.14 изображен принцип работы шагового двигателя. В двигателе имеется четыре обмотки, обозначенные буквами А, В, С и D, которые могут возбуждаться по одиночке или попарно для формирования магнитного поля в одном из восьми направлений с шагом 45°. Так, обмотка А формирует поле в направлении север, А + В — в направлении северо-восток, В — восток и т. д. Соответственно ротор вращается вслед за изменением направления магнитного поля, при условии, что конструкция двигателя обеспечивает стабилизацию положения ротора при разгоне и торможении.

Рис. 11.14. Принцип работы шагового двигателя

Напишите подпрограмму, размещаемую по адресу h’050’ памяти программ, которая будет управлять перемещением ротора. В подпрограмму будет передаваться количество шагов от 1 до 256. Предполагается, что выводы порта A RA[3:0] подключены к обмоткам А, В, С, D соответственно. Скорость вращения должна быть равна 100 шагам в секунду, что обеспечивается 10-мс задержкой. Подпрограмма формирования этой задержки должна быть написана таким образом, чтобы в минимальной степени зависеть от тактовой частоты микроконтроллера. Последняя указывается программистом в виде константы FREQ, являющейся множителем 100 кГц, т. е. для 4-МГц резонатора FREQ = d’40’.

Решение

Прежде всего, нам потребуется составить таблицу, содержащую коды управления обмотками шагового двигателя для всех восьми возможных направлений магнитного поля (см. Табл. 11.2).

Код, приведенный в Программе 11.7, состоит из трех подпрограмм.

MOTOR

Это основная подпрограмма, которая просто инкрементирует по модулю 8 переменную, хранящую номер вектора направления магнитного поля. Чтобы после числа 7 счет снова начинался с 0, результат обычного инкрементирования логически умножается (AND) на константу Ь’00000111’. Затем номер вектора преобразуется в соответствующий код, который после 10-мс задержки выдается на выводы управления двигателем. Процесс повторяется до тех пор, пока декрементируемый регистр STEP не станет равным 0; если он изначально был нулевым, то будет сделано 256 шагов.

PATTERN

Эта подпрограмма возвращает один из восьми кодов в соответствии с Табл. 11.2. Принцип реализации подобных таблиц был описан в Программе 6.6 (стр. 184). Поскольку подпрограммы располагаются в памяти, начиная с адреса h’050’, операция 8-битного сложения номера шаблона со счетчиком команд не вызовет перехода через границу страницы памяти программ.

Программа 11.7 . Управление шаговым двигателем

       #define FREQ d’401; Задается программистом как множитель 100 кГц

       org h’50’ ; Код начинается с адреса h’50’

; ************************************************

; * ФУНКЦИЯ: Поворот ротора на заданный угол (1…256 шагов) *

; * ВХОД: Число шагов в STEP *

; * ВХОД: Номер текущего вектора магнитного поля в POSITION *

; * ВЫХОД: POSITION обновляется, STEP = -1, W изменяется *

; * РЕСУРСЫ: Подпрограммы PATTERN, DELAY_10MS *

; *************************************************

MOTOR incf POSITION,w ; Берем следующий вектор

            andlw b’00000111’ ; Делим по модулю 8

            movwf POSITION ; Корректируем

            call PATTERN ; Получаем управляющий код

            movwf PORTA ; Выдаем на шаговый двигатель

            call DELAY_10MS ; Ждем 10 мс

            decfsz STEP,f ; Декрементируем число шагов,

                goto MOTOR ;пока не станет равно 0

             return

; *************************************************

; * ФУНКЦИЯ: Преобразует целое число 0… 7 в управляющий код *

; * ВХОД: Целое число от 0 до 7 в W *

; * ВЫХОД: Код для управления обмотками ШД в W *

; *************************************************

PATTERN addwf PCL,f ; Изменяем счетчик программ

               retlw b’1000’ ; Север

               retlw b’1100’ ; Северо-восток

               retlw b’0100’ ; Восток

               retlw b’0110’ ; Юго-восток

               retlw b’0010’ ; Юг

               retlw b’0011’ ; Юго-запад

               retlw b’0001’ ; Запад

               retlw b’1001’ ; Северо-запад

; *************************************************

; * ФУНКЦИЯ: Формирует 10-мс задержку, не зависящую от тактовой частоты *

; * ВХОД: Значение тактовой частоты, деленное на 100 кГц, в TEMP *

; * ВЫХОД: 10-мс задержка; DELAY обнуляется, W изменяется *

; *************************************************

DELAY_10MS

             movlw FREQ ; Тактовая частота указывается

             movwf TEMP ; программистом

; Цикл 10-мс задержки при тактовой частоте 100 кГц (1 цикл = 40 мкс)

DLOOP1 movlw d’62’ ; Счетчик цикла

             movwf DELAY

DLOOP2 decf DELAY,f ; 62*40 мкс

             btfss STATUS,Z ; 62*40 мкс

                goto DLOOP2 ; 62*80 мкс

             decfsz TEMP,f ; Декрементируем параметр и повторяем,

                 goto DLOOP1 ; пока он не станет равным нулю

             return

DELAY_10MS

Эта подпрограмма формирует задержку длительностью 10 мс, независимую от частоты резонатора. Значение частоты задается программистом константой FREQ посредством директивы #define. Данная константа представляет собой множитель, равный значению тактовой частоты, деленной на 100 кГц. К примеру, для 8-МГц резонатора константа FREQ будет равна 80.

В основе подпрограммы лежит цикл, формирующий задержку длительностью 10 мс при тактовой частоте 100 кГц, т. е. при длительности машинного цикла, равной 40 мкс. Этот цикл повторяется FREQ раз. Так, в нашем примере с 8-МГц резонатором длительность базового цикла будет равна 10/80 мс, однако этот цикл будет повторен 80 раз, что и даст нам искомые 10 мс.

Пример 11.4

Доработайте функцию дешифратора клавиатуры из Программы 11.4, добавив к нему процедуру подавления дребезга подобно тому, как это было сделано в Программе 11.3.

Решение

Функция get_it (), текст которой приведен в Программе 11.8, накапливает в переменной count число вызовов функции scan_it (), каждый раз сравнивая возвращаемое значение, которое присваивается переменной new_key, с предыдущим значением, хранящимся в переменной old_key. Если эти значения не совпадают, то счетчик обнуляется. Выход из цикла происходит только после 254 идентичных считываний, в результате чего в вызывающую программу возвращается стабильное значение.

Программа 11.8. Подавление дребезга для драйвера клавиатуры

unsigned int get_it(void)

{

        unsigned int count, old_key, new_key;

        count = 0;

        while(count<255)

        {

             new_key = scan_it();

             if(new_key == old_key)

             { count++;}

             else

             {

                 old_key = new_key;

                 count = 0;

              }

        }

        return (old_key);

}

Пример 11.5

Несмотря на все более широкое распространение жидкокристаллических алфавитно-цифровых матричных дисплеев, для отображения многоразрядных чисел до сих пор очень часто используются дискретные 7-сегментные светодиодные индикаторы. Применение таких индикаторов особенно эффективно при низкой освещенности и при необходимости использования больших дисплеев.

Если учесть, что для управления каждым индикатором требуется восемь линий (семь сегментов плюс десятичная точка), то получается, что для управления «-разрядным дисплеем нам потребуется 8хn параллельных линий. Типичное решение этой проблемы представлено на Рис. 11.15. В этой схеме 3-разрядный дисплей управляется тремя параллельными регистрами, подключенными к локальной шине, наподобие той, что была использована в схеме на Рис. 11.12. Этот же принцип можно использовать и с дисплеями большей разрядности, взяв соответствующее число регистров.

Индикаторы, показанные на схеме, выполнены по схеме с общим катодом, поэтому каждый сегмент включается тогда, когда на соответствующем выходе регистра присутствует ВЫСОКИЙ уровень. Резисторы, включенные последовательно с сегментами, служат для ограничения тока. На практике некоторые логические схемы могут отдавать больший выходной ток в состоянии НИЗКОГО уровня, поэтому чаще используются индикаторы с общим анодом, в которых включение сегментов осуществляется подачей НИЗКОГО уровня. В крупногабаритных дисплеях, например высотой 5 см (2 дюйма), каждый сегмент может состоять из нескольких СИД, включенных последовательно и/или параллельно. В этом случае для управления индикатором может потребоваться большее напряжение и/или ток, для обеспечения которых необходимо подключить к выходам регистра подходящие драйверы.

Рис. 11.15. Расширение порта для управления 7-сегментными индикаторами

Альтернативное решение, показанное на Рис. 11.16, часто применяется для дисплеев на базе светодиодных индикаторов. Вместо использования отдельного регистра для каждого разряда, все индикаторы подключены параллельно к одному из портов микроконтроллера. Каждый индикатор поочередно включается на короткий промежуток времени, отображая соответствующие данные с выходного порта. Если обновлять изображение чаще чем 50 раз в секунду (а еще лучше — чаще 100 раз в секунду), то из-за инерционности системы зрения будет казаться, что индикаторы не мерцают. Разумеется, ток, протекающий через каждый сегмент, следует увеличить, чтобы скомпенсировать снижение яркости, вызванное импульсным режимом работы. А поскольку в таком режиме СИД работают более эффективно, зависимость между сопротивлением последовательных резисторов и коэффициентом заполнения управляющего сигнала будет не прямо пропорциональной.

Рис. 11.16. Динамическое управление тремя 7-сегментными индикаторами

Прикиньте все плюсы и минусы обоих решений, учитывая как аппаратные затраты, так и затраты на программирование. Для иллюстрации своего ответа напишите программу, отображающую число, хранящееся в регистре h’20’. Например, если в этом регистре (назовем его BINARY) находится число h’FF’, то на дисплее должно отображаться .

Решение

Что касается программного обеспечения, то тут можно выделить две основные функции. Сначала код, находящийся в регистре BINARY, необходимо преобразовать в три BCD-разряда (HUNDREDS, TENS и UNITS). После этого значение каждого из разрядов (0…9) следует преобразовать в 7-сегментный код, чтобы включить соответствующие сегменты индикаторов, отображая требуемую цифру. У нас уже есть подпрограммы для реализации 1-го (см. Программу 6.11 на стр. 196) и последнего (см. Программу 6.6 на стр. 184) этапов. С учетом этих программ, можно составить алгоритм управления схемой, приведенной на Рис. 11.15:

1. Преобразовать двоичное однобайтное число в BCD-число.

2. ВЫПОЛНЯТЬ:

а) Скопировать содержимое HUNDREDS в W и преобразовать его в 7-сегментный код.

б) Вывести полученный код в порт В.

в) Сформировать импульс  на выходе RA2.

3. ВЫПОЛНЯТЬ:

а) Скопировать содержимое TENS в W и преобразовать его в 7-сегментный код.

б) Вывести полученный код в порт В.

в) Сформировать импульс  на выходе RA1.

4. ВЫПОЛНЯТЬ:

а) Скопировать содержимое UNITS в W и преобразовать его в 7-сегментный код.

б) Вывести полученный код в порт В.

в) Сформировать импульс  на выходе RA0.

Код, реализующий этот алгоритм, приведен в Программе 11.9.

Программа 11.9. Отображение трехразрядного десятичного числа (статическая индикация)

; Задача 1 ----------

DISPLAY movf BINARY,w ; Берем двоичное значение

             call BIN_2_BCD ; Преобразуем его в три BCD-разряда

; Задача 2 ----------

             movf HUNDREDS,w ; Берем число сотен

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf PORTB ; Высылаем в порт В

             bsf PORTA,2 ; Заносим в регистр

             bcf PORTA,2

; Задача 3 ----------

             movf TENS,w ; Берем число десятков

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf PORTB ; Высылаем в порт В

             bsf PORTA,1 ; Заносим в регистр

             bcf PORTA,1

; Задача 4 ----------

             movf UNITS,w ; Берем число единиц

             call SVN_SEG ; Преобразуем в 7-сегментный

             movwf PORTB ; Высылаем в порт В

             bsf PORTA,0 ; Заносим в регистр

             bcf PORTA,0

Управление схемой, показанной на Рис. 11.16, несколько сложнее, поскольку в ней отсутствуют регистры, хранящие данные! Поэтому данные необходимо непрерывно выдавать друг за другом одновременно с включением соответствующего индикатора. Если мы собираемся обновлять изображение 100 раз в секунду, то перед переходом к следующему знакоместу эти данные должны удерживаться в течение 10 мс. Таким образом, мы получаем новый алгоритм:

1. Преобразовать двоичное однобайтное число в BCD-формат.

2. ВЫПОЛНЯТЬ бесконечно:

а)

• Скопировать содержимое HUNDREDS в W и преобразовать его в 7-сегментный код.

• Выдать полученный код в порт В.

• Выставить на RA2 НИЗКИЙ уровень .

• Подождать 10 мс.

• Выставить на RA2 ВЫСОКИЙ уровень .

б)

• Скопировать содержимое TENS в W и преобразовать его в 7-сегментный код.

• Выдать полученный код в порт В.

• Выставить на RA1 НИЗКИЙ уровень .

• Подождать 10 мс.

• Выставить на RA1 ВЫСОКИЙ уровень .

в)

• Скопировать содержимое UNITS в W и преобразовать его в 7-сегментный код.

• Выдать полученный код в порт В.

• Выставить на RA0 НИЗКИЙ уровень .

• Подождать 10 мс.

• Выставить на RA0 ВЫСОКИЙ уровень .

В коде, приведенном в Программе 11.10, используется подпрограмма формирования 10-мс задержки, которую мы использовали в Программе 11.7 для задания скорости сканирования. За исключением длительности импульса разрешения, основная часть программы идентична предыдущей. Однако чтобы цифры на дисплее светились постоянно, код программы должен выполняться непрерывно. В этом и заключается компромисс между затратами на аппаратную и программную части. Действительно, как уже было показано, все ресурсы микроконтроллера PIC уйдут на обслуживание индикатора! На самом деле ситуацию может спасти прерывание микроконтроллера с периодом 10 мс, что позволит избежать использования подпрограмм формирования задержки. В листинге на стр. 475 показано, как это можно реализовать. Разумеется, в этом случае таймер нельзя будет использовать для других задач. Также можно воспользоваться внешним генератором с частотой 100 Гц, однако при этом схема не будет столь эффективной с аппаратной точки зрения. При длительности свечения одного знакоместа, равной 10 мс, можно без использования дополнительных интерфейсных схем обслуживать до десяти разрядов и все равно изображение будет обновляться чаще 100 раз в секунду.

Программа 11.10. Отображение трехразрядного десятичного числа (динамическая индикация)

; Задача 1 ------------

DISPLAY movf BINARY,w ; Берем двоичное значение

             call BIN_2_BCD ; Преобразуем его в 3 BCD-разряда

; Задача 2, a ---------

LOOP     movf HUNDREDS,w ; Берем число сотен

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf PORTB ; Высылаем в порт В

             bcf PORTA,2 ; Включаем индикатор сотен

             call DELAY_10MS ; на 10 мс

             bsf PORTA,2 ; и выключаем его

; Задача 2, б --------

             movf TENS,w ; Берем число десятков

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf PORTB ; Высылаем в порт В

             bcf PORTA,1 ; Включаем индикатор десятков

             call DELAY_10MS ; на 10 мс

             bsf PORTA,1 ; и выключаем его

; Задача 2, в --------

             movf UNITS,w ; Берем число единиц

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf PORTB ; Высылаем в порт В

             bcf PORTA,0 ; Включаем индикатор единиц

             call DELAY_10MS ; на 10 мс

             bsf PORTA,0 ; и выключаем его

                goto LOOP ;Образуем бесконечный цикл

Другим моментом, который следует учитывать при использовании динамической индикации, являются электромагнитные помехи, вызываемые периодическими импульсами относительно большого тока. При наличии в устройстве аналоговых цепей эти помехи могут представлять серьезную проблему, которая в какой-то степени может быть решена за счет хорошей развязки источника питания.

Вопросы для самопроверки

11.1. Одним из недостатков схемы охранной сигнализации, приведенной на Рис. 11.12, является необходимость использования многожильного кабеля для соединения зон (8 линий плюс по одной на зону). В качестве альтернативы можно было бы заменить тристабильный буфер каждой зоны микроконтроллером PIC. При этом связь базового микроконтроллера с микроконтроллерами зон осуществлялась бы по 4-проводной общей шине. Одну из линий шины можно было бы использовать для передачи сигнала квитирования, извещающего базовый контроллер об обнаружении проникновения в зоне, номер которой присутствует на остальных трех линиях шины.

Покажите, как можно было бы сконфигурировать микроконтроллер PIC16F84 для использования в качестве локального контроллера зоны, обращая особое внимание на то, что линия квитирования должна совместно использоваться контроллерами всех зон.

Можно ли уменьшить число линий до трех? Как можно добавить в схему локальные дисплеи, отображающие сработавший датчик?

11.2. К порту С микроконтроллера PIC, работающего на частоте 20 МГц, подключена группа СИД. При этом каждый вывод порта подключен к линии питания через резистор сопротивлением 1 кОм и к общему проводу через конденсатор емкостью 300 пФ. Все светодиоды выключены, и программист пытается включить 7-й и 0-й светодиоды следующим образом:

bcf PORTC,7 ; Включить 7-й СИД

bcf PORTC,0 ; Включить 0-й СИД

Однако в действительности включается только 0-й СИД. Почему так происходит?

11.3. Выводы RC[1:0] должны быть сконфигурированы как выходы, на которых после сброса по питанию присутствует лог. 0. Приведенный ниже фрагмент предполагалось использовать для сброса обоих триггеров перед переключением линий порта на выход. При проверке оказалось, что результат для RC0 обратен ожидаемому. Почему так происходит, и можете ли вы исправить код, чтобы он выполнялся правильно?

bcf PORTC,0 ; Сбрасываем триггер бита 0 (см. Рис. 11.3, г)

bcf PORTC,1 ; Сбрасываем триггер бита 1

bcf STATUS,RP0 ; Переключаемся на 1-й банк

movlw b’11111100’ ; Делаем RC[1:0] выходами

movwf TRISC

bcf STATUS,RP0 ; Переключаемся обратно в 0-й банк

11.4. В системе необходимо управлять восемью СИД и считывать состояние восьми кнопок с нормально разомкнутыми контактами. В принципе для обеих целей можно использовать один порт В, который в первом случае конфигурируется как выход, а во втором — как вход. Можете ли вы нарисовать соответствующую схему?

11.5. Доработайте цифровой компаратор из Примера 11.2 так, чтобы он сравнивал два однобайтных числа, поступающих извне в 28-выводной микроконтроллер PIC, причем число Р подается на порт В, а число Q — на порт С.

11.6. В беспроводной системе сбора данных с низким потреблением перевод микроконтроллера в «спящий» режим не влияет на потребление радиопередатчика. Было предложено использовать для питания передатчика один из выводов порта. Таким образом, можно будет включать и выключать вспомогательные узлы по мере необходимости. Подумайте над этим.

11.7. Зависимость выходного напряжения лог. 0 VOL от тока IOL для двух крайних значений коммерческого температурного диапазона показана на Рис. 11.17. Используя графический способ, определите максимальное значение сопротивления резистора, включенного последовательно с СИД, которое обеспечит ток в цепи не менее 20 мА во всем диапазоне температур. Чему будет равен ток при —40 °C? Предполагается, что падение напряжения на светодиоде равно 2 В.

Рис. 11.17. Зависимость выходного напряжения низкого уровня от тока

 

Глава 12

Ох уж эти биты!

Параллельная передача данных может осуществляться с высокой скоростью и требует минимальных программных затрат для реализации. Однако имеется множество приложений, в которых параллельная передача данных неприменима либо из-за удорожания аппаратной части (см., например, Рис. 11.12 на стр. 350), либо, что встречается гораздо чаще, по причине значительного удаления узлов друг от друга. В последнем случае организация множества коммуникационных каналов вместе с соответствующим интерфейсным оборудованием невозможна в принципе или же требует неоправданных затрат. В таких случаях на помощь может прийти последовательная передача данных, при которой данные пересылаются побитно (по одному биту за раз) и объединяются в приемном устройстве в исходные байты. Здесь можно провести сравнение с параллельным портом персонального компьютера, обычно используемого для подключения локальных периферийных устройств (например, принтера), и последовательным или USB-портом, которые часто используются совместно с модемом для выхода через телефонную линию в сеть Интернет.

В данной главе мы познакомимся с различными средствами, использующимися для последовательной передачи данных (в основе всех этих средств лежат специализированные сдвиговые регистры), а также с серийно выпускаемыми микросхемами, поддерживающими стандартные протоколы обмена. После прочтения этой главы вы:

• Осознаете необходимость в последовательной передаче данных.

• Сможете разрабатывать последовательные порты и сопутствующее программное обеспечение для обмена со стандартными параллельными периферийными устройствами.

• Научитесь работать с последовательными периферийными устройствами, поддерживающими протоколы SPI™ и I2С.

• Поймете необходимость асинхронной последовательной передачи данных и сможете писать программные драйверы, поддерживающие этот протокол.

• Научитесь использовать интегрированный модуль универсального синхронно-асинхронного приемопередатчика (USART) в асинхронном режиме.

• Поймете причины, по которым возникает необходимость буферизации в устройствах, передающих данные на большие расстояния.

В качестве примера последовательной передачи данных рассмотрим смарт-карту, которая наверняка имеется в вашем кошельке. В каждую такую карту встроен микроконтроллер, обычно 8-битный, который, собственно, и делает карту «умной» (smart). Себестоимость изготовления этих карт не должна превышать 1 долл., причем большая часть этих денег уходит на коррозионно-стойкие контакты с золотым покрытием, через которые на микроконтроллер подается питание и тактовый сигнал, когда карта устанавливается в считывающее устройство. Чтобы снизить требования к точности изготовления механических узлов считывателя и, соответственно, увеличить его надежность, количество контактов необходимо свести к минимуму, а их размеры, наоборот, должны быть максимально возможными.

Стандартная цоколевка такого микроконтроллера показана на Рис. 12.1. Как видно из рисунка, два контакта используются для подачи напряжения питания, еще два контакта предназначены для подачи тактового сигнала и сигнала сброса, и всего лишь один контакт используется для побитовой передачи данных в обоих направлениях. Несмотря на то что такой обмен происходит достаточно медленно, на фоне скорости системы «человек — машина» он незаметен. Кроме того, связь между считывателем/банкоматом и центральным компьютером, который может быть расположен на расстоянии нескольких тысяч миль/километров, обычно осуществляется по одной телефонной линии.

Рис. 12.1. Смарт-карта

Вернемся к схеме параллельного интерфейса с 3-разрядным 7-сегментным дисплеем, приведенной на Рис. 11.15 (стр. 361), в которой используются оба параллельных порта А и В. Хотя это вполне рабочая схема, в ней задействована большая часть выводов 18-выводных микроконтроллеров. Поскольку в данном случае скорость не очень критична, можно использовать более медленный способ передачи данных.

Взгляните на схему с использованием последовательного интерфейса, приведенную на Рис. 12.2. В этой схеме используются только два вывода порта. Один, называемый SDO (Serial Data Out — выход последовательных данных), используется для побитовой передачи данных. Второй, названный SCK (Serial ClocK — последовательный тактовый сигнал), используется для одновременного тактирования трех сдвиговых регистров, осуществляя, таким образом, побитовый сдвиг данных вправо аналогично тому, как это было изображено на Рис. 3.8 (стр. 78).

Рис. 12.2. Последовательный интерфейс с 3-разрядным 7-сегментным дисплеем

Каждый индикатор дисплея подключен к своему 8-битному сдвиговому регистру 74НСТ164 (см. Рис. 2.22 на стр. 51). Эта микросхема имеет тактовый вход С1 (активный фронт — нарастающий) и два входа данных, объединенных по И. Один из этих входов может использоваться для стробирования второго, но в нашем примере они объединены, образуя один вход данных. Также имеется вход сброса с активным НИЗКИМ уровнем для очистки содержимого регистра (в нашей схеме на него подается ВЫСОКИЙ уровень). При необходимости для управления этим входом можно задействовать еще один вывод микроконтроллера.

Чтобы сменить изображение на дисплее, необходимо задвинуть в указанную цепочку регистров 24 бита данных. Чтобы разобраться, каким образом это можно сделать, обратимся снова к процедуре управления 7-сегментным индикатором из Программы 11.9 (стр. 363), в которой осуществляется преобразование двоичного числа в набор BCD-разрядов, хранящихся в регистрах HUNDREDS, TENS и UNITS. Эти значения преобразовывались в 7-сегментный код, который затем выставлялся на 8-битную шину.

Чтобы преобразовать этот процесс к последовательному виду, нам потребуется написать подпрограмму, которая будет по очереди выдавать все биты регистра, скажем DATA_OUT, на вывод RAO/SDO, начиная с самого левого (старшего) бита. Одновременно на выводе RA1/SCK будут формироваться тактовые импульсы для загрузки этих битов в регистры. Алгоритм работы данной подпрограммы будет следующим:

1. Выставить на SCK НИЗКИЙ уровень.

2. COUNT = 8.

3. ПОКА COUNT > 0, ВЫПОЛНЯТЬ:

а) Копировать старший бит DATA_OUT в SDO.

б) Сдвинуть DATA_OUT на один бит влево.

в) Сформировать импульс  на SCK.

г) Декрементировать COUNT.

В Программе 12.1 содержится две подпрограммы. Первая из них, названная DISPLAY, очень похожа на ту, что была написана нами в Программе 11.9, поскольку точно так же вызывает подпрограмму BIN_2_BCD и копирует значения кодов 7-сегментного индикатора в интерфейсный регистр. В данном случае сначала загружается значение единиц (поскольку этот байт в конце концов будет задвинут в самый последний регистр цепочки), затем десятков и, наконец, сотен.

Программа 12.1. Отображение трехразрядного десятичного числа с использованием последовательной передачи данных

include "p16f627а. inc"

SDO equ 0

SCK equ 1

DISPLAY bcf PORTA,SCK ; Инициализируем линию SCK

             movf BINARY,w ; Берем двоичное значение

             call BIN_2_BCD ; Преобразуем его в три BCD разряда

             movf UNITS,w ; Берем число единиц

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf DATA_OUT ; Копируем в регистр последовательной передачи

             call SPI_WRITE ; Выдвигаем его

             movf TENS,W ; Берем число десятков

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf DATA_OUT ; Копируем в регистр последовательной передачи

             call SPI_WRITE ; Выдвигаем его

             movf HUNDREDS,w ; Берем число сотен

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf DATA_OUT ; Копируем в регистр последовательной передачи

             call SPI_WRITE ; Выдвигаем его

             return

; ********************

; * ФУНКЦИЯ: Побитно передает байт данных, начиная со старшего бита *

; * ВХОД: Байт данных в DATA_OUT *

; * ВЫХОД: DATA_OUT обнуляется *

; ********************

; Задача 1

SPI_WRITE

            bcf PORTA,SCK ; В режиме ожидания на линии SCK — НИЗКИЙ

; Задача 2

            movlw 8 ; Инициализируем счетчик цикла

            movwf COUNT

; Задача 3, a и 3, б

LOOP bcf PORTA,SDO ; Выставляем на линию данных 0

         btfsc DATA_OUT,7 ; Пропускаем, ЕСЛИ старший бит = 0

         bsf PORTA,SDO ; ИНАЧЕ выставляем на линию данных 1

         rlf DATA_OUT,f ; Сдвигаем байт данных на один бит влево

; Задача 3, в

         bsf PORTA,SCK ; Формируем тактовый импульс

         bcf PORTA,SCK

;Задача 3,г

         decfsz COUNT,f ; Декрементируем счетчик

             goto LOOP ; и повторяем, пока он не станет равным 0

         return

Собственно последовательная передача данных осуществляется подпрограммой SPI_WRITE, работающей по приведенному выше алгоритму. В подпрограмме проверяется 7-й бит содержимого регистра DATA_OUT и в соответствии с его значением на вывод RA0 выставляется ВЫСОКИЙ или НИЗКИЙ уровень. Затем на выводе RA1 формируется положительный импульс  для загрузки очередного бита в цепочку сдвиговых регистров, после чего байт данных сдвигается влево. Этот процесс повторяется 8 раз. На все это требуется не более 87 машинных циклов (конкретная цифра слегка зависит от значения байта данных). Таким образом, на полное обновление изображения 3-разрядного дисплея уйдет около 120 мкс при частоте процессора 8 МГц (не учитывая время, затраченное на преобразование данных).

В Программе 12.2 приведена одна из возможных реализаций данной подпрограммы на языке Си. Функция spi_write () 8 раз выдает 7-й бит переданного ей байта данных на вывод SDO и сдвигает значение этого байта влево. Предполагается, что оба вывода последовательного интерфейса SPI уже определены как соответствующие линии порта ввода/вывода микроконтроллера.

Программа 12.2. Реализация подпрограммы SPI_write на Си

void spi_write(int datum)

{

      int k;

      for(k=0;k<8;k++)

      {

      if((datum & 0x80)) {SDO = 1;} /* Проверяем 7-й бит и ЕСЛИ TRUE, выставляем 1 */

      else {SDO = 0;} /* ИНАЧЕ выставляем 0 */

      SCK = 1 ; /* Загружаем бит в цепочку регистров */

      SCK = 0;

      datum = datum <<1 ; /* Сдвигаем байт данных влево и повторяем 8 раз */

      }

}

При работе с длинной цепочкой сдвиговых регистров скорость загрузки данных можно немного увеличить, если выделить каждому регистру собственные линии данных, при этом тактовые входы всех регистров будут подключены к выводу SCK. Также можно подключить входы данных всех регистров к одной-единственной линии данных и управлять регистрами с помощью отдельных сигналов разрешения. Последний способ использован в схеме, показанной на Рис. 12.7.

У нашей схемы, использующей сдвиговые регистры, есть один недостаток: значения, появляющиеся на выходах регистров во время их загрузки (в нашем случае в течение 23 тактовых импульсов), некорректны. Разумеется, в данном конкретном случае из-за инерционности зрения мы просто не заметим эти кратковременные изменения в свечении индикаторов. Однако иногда такое поведение схемы оказывается неприемлемым, поэтому к выходам сдвиговых регистров необходимо подключить буферы на D-триггерах или защелках. Загрузка содержимого регистров в эти буферы будет осуществляться после завершения передачи данных, в результате чего изображение на дисплее будет меняться только единожды.

Чтобы не использовать отдельные буферные регистры, в большинстве устройств, рассчитанных на последовательную передачу данных, имеется встроенный регистр с параллельным входом/выходом. В качестве примера можно привести микросхему 74НСТ595, изображенную на Рис. 12.3. Эта микросхема представляет собой стробируемый сдвиговый регистр, на выходе которого имеется встроенный 8-битный PIPO-регистр. По нарастающему фронту сигнала на выводе RCK (Register ClocK) содержимое сдвигового регистра выставляется на параллельные выходы. В микросхеме также выведен выход последнего триггера сдвигового регистра, что позволяет объединять эти регистры в цепочку любой длины. В этом случае на все выводы RCK может подаваться один и тот же стробирующий импульс для одновременного обновления выходов всех микросхем.

Примером ситуации, когда пульсации данных могут быть нежелательными, является преобразование цифровых данных в аналоговый сигнал. В схеме на Рис. 12.3 преобразование осуществляется с помощью микросхемы ЦАП DAC0800 фирмы National Semiconductor. Выходное налоговое напряжение является линейной функцией от 8-битного цифрового входа и изменяется от —9.96 В для входного значения Ь’00000000’ до +9.96 В для значения Ь’11111111’ (см. Рис. 14.17 на стр. 527).

Рис. 12.3. Последовательный интерфейс с микросхемой ЦАП, реализованный посредством сдвигового регистра 74НСТ595

Благодаря наличию регистра 74НСТ595 состояние входа ЦАП не меняется до тех пор, пока не будет загружен новый байт и микроконтроллер не сформирует на выводе RCK положительный импульс. Таким образом, обеспечивается отсутствие шумов в выходном аналоговом сигнале.

Аналогичным образом можно реализовать прием данных в последовательном режиме, используя сдвиговые регистры с параллельным входом и последовательным выходом (PISO-регистры). Схема, приведенная на Рис. 12.4, представляет собой последовательный вариант системы охранной сигнализации с Рис. 11.12 (стр. 350), использующий только три линии для подключения всех восьми групп датчиков. Это гораздо меньше 16 линий, задействованных нами в исходной схеме.

Каждая группа датчиков подключена к 8-битному сдвиговому PISO-регистру 74НСТ165. При этом последовательный выход каждого регистра подключен к последовательному входу следующего регистра. После загрузки данных в регистр их можно будет побитно передать на вывод RA1 порта A (SDI). В данном конкретном случае многозонной системы охранной сигнализации после каждого восьмого сдвига полученный байт можно проверять на ненулевое значение и осуществлять соответствующие действия.

Рис. 12.4. Вариант многозонной системы охранной сигнализации с последовательным интерфейсом

Для управления отображением активной зоны в схеме на Рис. 12.4 тоже используется один выход микроконтроллера. Поскольку и входной (SDI), и выходной (SDO) каналы используют один и тот же тактовый сигнал SCK, то одновременно с приемом данных будет осуществляться и их передача. И наоборот, передача данных через выходной порт приведет к побитному приему данных из регистров зон. В данном случае это не страшно, поскольку микросекундные изменения в свечении ламп совершенно незаметны, и после загрузки в регистр индикаторов требуемого значения все нормализуется. Когда же такое взаимное влияние операций приема и передачи нежелательно, то либо во время считывания данных с вывода SDI на выводе SDO должны всегда присутствовать требуемые данные, либо необходимо использовать стробируемый регистр, например 74НСТ595, для одновременного вывода всех битов данных. В качестве альтернативного решения можно также задействовать отдельные линии тактовых сигналов.

Подпрограмма для работы с последовательным интерфейсом SPI_READ является точной противоположностью подпрограммы SPI_WRITE, код которой был приведен в Программе 12.1, и реализует следующий алгоритм:

1. Выставить на SCK НИЗКИЙ уровень.

2. COUNT = 8.

3. ПОКА COUNT > 0, ВЫПОЛНЯТЬ:

а) Сформировать на линии SCK импульс

б) Сдвинуть содержимое регистра DATA_IN на один бит влево.

в) Скопировать значение с вывода SDI в старший бит регистра DATA_IN.

г) Декрементировать COUNT.

Этот алгоритм похож на приведенный ранее (см. стр. 371), за исключением того, что регистр DATA_IN сдвигается влево, а состояние вывода SDI становится значением 0-го бита. После восьмого цикла «такт — сдвиг — проверка» содержимое регистра DATA_IN представляет собой байт данных, считанный с последовательного порта. При этом первый принятый бит окажется старшим битом итогового значения.

Подпрограмма SPI_READ, текст которой приведен в Программе 12.3, похожа на подпрограмму вывода SPI_WRITE из Программы 12.1. И их действительно можно объединить для того, чтобы одновременно передавать и принимать данные. Такой тип обмена называется полнодуплексным (или просто дуплексным) в отличие от полудуплексного, при котором передача информации в каждый момент времени осуществляется только в одном направлении. Последовательный обмен, при котором поток данных может передаваться только в одном фиксированном направлении, называется симплексным.

Программа 12.3. Подпрограмма приема байта по последовательному каналу

; ***********************

; * ФУНКЦИЯ: Побитно принимает байт данных, начиная со старшего бита *

; * ВХОД: Нет *

; * ВЫХОД: Принятый байт в DATA_IN; COUNT = 0 *

; ***********************

; Задача 1: Выставляем на SCK НИЗКИЙ уровень

SPI_READ

          bcf PORTA,SCK ; В режиме ожидания на линии SCK — НИЗКИЙ уровень

; Задача 2: COUNT=8

          movlw 8 ; Инициализируем счетчик цикла

          movwf COUNT

; Задача 3: ПОКА COUNT>0, ВЫПОЛНЯТЬ:

; Задача 3, а : Формируем импульс SCK

SER_IN_LOOP

          bsf PORTA,SCK

          bcf PORTA,SCK

; Задача 3, б : Сдвигаем байт данных влево

          bcf STATUS,С ; Обнуляем флаг переноса

          rlf DATA_IN,f ; Сдвигаем байт влево

; Задача 3, в : ЕСЛИ SDI = 1, ТО устанавливаем 0-й бит (самый правый)

          btf sc PORTA,SDI ; Пропускаем, ЕСЛИ SDI == 0

          bsf DATA_IN,0 ; ИНАЧЕ заносим в 0-й бит 1

; Задача 3, г : Декрементируем COUNT и повторяем задачу 3, пока COUNT > 0

          decfsz COUNT,f; Декрементируем счетчик

             goto SER_IN_LOOP; и повторяем, пока он не станет равным 0

          return

Си-вариант подпрограммы, приведенный в Программе 12.4, использует тот же алгоритм, что и его ассемблерный предшественник. Обратите внимание, как для установки 0-го бита переменной DATA_IN используется Си-оператор ИЛИ (|) с константой Ь’00000001’. Аналогично, операция И с константой b’11111110’ сбрасывает 0-й бит. В компиляторе CCS имеются специальные нестандартные функции bset (DATA__IN, 0) и bclr (DATA_IN, 0), которые можно использовать для установки или сброса любого бита переменной и которые при необходимости изменения единственного бита часто более эффективны, чем использование логических операторов.

Программа 12.4. Реализация подпрограммы SPI_READ на Си

unsigned int spi_read()

{

     int k;

     for(k=0;k<8;k++) /* Повторяем 8 раз */

     {

        SCK =1; /* Формируем тактовый импульс, по которому */

        SCK =0; /* ведомый передает бит на SDI */

        DATA_IN = DATA_IN <<1; /* Сдвигаем на один бит влево */

         if(SDI)

             {DATA_IN = DATA_IN I 0x01;} /* Сбрасываем бит, если SDI =0 */

        else

             {DATA_IN = DATA_IN & 0xFE;} /* ИНАЧЕ устанавливаем его */

      }

      return DATA_IN /* Возвращаем принятый байт */

}

Последовательный протокол, как две капли воды похожий на только что рассмотренный нами, известен как последовательный периферийный интерфейс (SPI™). Существует еще один похожий последовательный протокол — Microwire, но он несколько отличается от рассмотренного.

Интерфейс SPI имеется в большинстве микроконтроллеров, и он в достаточной степени стандартизован, чтобы производители могли выпускать широкий ассортимент микросхем, предназначенных для непосредственного (без использования дополнительных сдвиговых регистров) подключения к этой шине.

Возьмем в качестве примера микросхему сдвоенного цифро-аналогового преобразователя (ЦАП) МАХ549А, показанную на Рис. 12.5, которая работает при напряжении питания от +2.5 до +5.5 В. Типичный ток потребления в рабочем режиме составляет около 150 мкА/канал при напряжении питания 5 В. Кроме того, один или оба модуля ЦАП можно отключить для уменьшения тока потребления до уровня менее 1 мкА. Максимальная частота шины SPI составляет 12.5 МГц. И все эти возможности заключены в крошечном 8-выводном корпусе — сравните с 20-выводной микросхемой МАХ506, изображенной на Рис. 14.16 (стр. 526), которая рассчитана на подключение к параллельному порту микроконтроллера.

Из упрощенной функциональной схемы МАХ549А, приведенной на Рис. 12.5, видно, что в микросхеме имеется встроенный 16-битный сдвиговый регистр, тактовый вход которого соединен с выводом SCLK микросхемы, а вход данных — с выводом DIN. Поэтому данные в этот регистр могут загружаться в соответствии с обычным протоколом SPI. Дополнительные восемь разрядов регистра используются для хранения четырех управляющих битов, выполняющих следующие функции:

∙ А0

Разрешает работу входного PIPO-регистра канала А, тактирование которого осуществляется по нарастающему фронту сигнала на выводе .

∙ А1

Разрешает работу входного PIPO-регистра канала В, тактирование которого осуществляется по нарастающему фронту сигнала на выводе .

∙ С1

Управляет работой обоих регистров ЦАП; при установленном бите содержимое этих регистров одновременно обновляется по нарастающему фронту  сигнала на выводе .

Рис. 12.5. Микросхема сдвоенного ЦАП с интерфейсом SPI МАХ549А (Maxim)

∙ С2

Установка этого бита переводит ЦАП, определяемый битами А0 и/или А1, в «спящий режим». При этом источник опорного напряжения Vref отключается от резистивной цепи выбранного ЦАП (см. Рис. 14.14 на стр. 516), в результате чего ток потребления ЦАП уменьшается до значения менее 1 мкА (содержимое внутренних регистров ЦАП остается неизменным).

Между каждым из модулей ЦАП и сдвиговым регистром имеется 2-уровневый регистровый конвейер. На первом уровне расположены регистры INREGx, разрешение которых осуществляется установкой бита А0 (канал А) или А1 (канал В) в 1. При установленном бите данные, находящиеся в 1-м байте сдвигового регистра, можно загрузить в регистр, подав на вывод  отрицательный импульс. Однако это значение не появится на параллельном входе ЦАП до тех пор, пока тактовый импульс не будет подан на регистры второго уровня — DACREGx. Разрешение этих регистров осуществляется установкой бита С1 в 1, а тактирование — подачей отрицательного импульса на вывод  микросхемы. То есть мы можем загрузить один байт, скажем, в ЦАП А, а другой — в ЦАП В. Содержимое же обоих регистров DACREGx обновляется одновременно, что приводит к одновременному изменению сигналов на выходах VoutA и VoutB (см. Программу 12.5). Это можно сделать даже в то время, когда МАХ549А находится в режиме пониженного потребления, поскольку при переходе в данный режим содержимое регистров не изменяется. Из всего сказанного становится понятно, что каждая транзакция между микроконтроллером и микросхемой ЦАП состоит из пересылки двух 8-битных значений , сопровождающихся подачей нарастающего фронта  на вывод .

Для примера, перешлем содержимое регистра h’20’ в ЦАП А, а содержимое регистра h’21’ — в ЦАП В. Затем перегрузим переданные значения в регистры ЦАП, в результате чего на выводах VoutA и VoutB одновременно появятся напряжения, эквивалентные содержимому регистров h’20’ и h’21’ соответственно.

В общей сложности для выполнения указанной задачи нам придется переслать четыре байта:

1. Управляющий байт 1: Ь’ХХХ00Х01’

Рабочий режим, обновить канал А, не формировать аналоговый сигнал.

2. Байт данных 1:

Содержимое регистра h’20’.

3. Подаем импульс  на вывод .

4. Управляющий байт 2: Ь’ХХХ01X10’

Рабочий режим, обновить канал В, формировать аналоговый сигнал на обоих каналах.

5. Байт данных 2:

Содержимое регистра h’21’.

6. Подаем импульс  на вывод .

Код, осуществляющий указанные операции, приведен в Программе 12.5. Для передачи каждого из четырех байтов используется подпрограмма SPI_WRITE, после передачи каждой пары байтов  формируется импульс  на выводе . В управляющем байте второй пары бит С1 устанавливается в 1, в результате чего одновременно с загрузкой входного регистра канала В оба байта данных перегружаются в регистры ЦАП.

Программа 12.5. Работа с двухканальным ЦАП МАХ549

СЕ equ 2

; ******************

; * ФУНКЦИЯ: Загружает новые данные в каналы А и В ЦАП МАХ549А *

; *                  и осуществляет одновременное обновление выходов *

; * РЕСУРСЫ: Подпрограмма SPI_WRITE *

; * ВХОД: Значение канала А — регистр h’20’, канала В — h’21’ *

; * ВЫХОД: Изменяется состояние обоих аналоговых выходов *

; ******************

MAX549A movlw b’00000001’ ; 1-й управляющий байт

               movwf DATA_OUT ; Помещаем в требуемый регистр

               call SPI_WRITE ; и пересылаем в МАХ549А

               movf CHANNEL_A,W ; Берем значение канала А

               movwf DATA_OUT ; Помещаем в требуемый регистр

               call SPI_WRITE ; и пересылаем в МАХ549А

               bsf PORTA,CE ; Формируем импульс на СЕ

               bcf PORTA,CE

               movlw b’00001010’ ; 2-й управляющий байт

               movwf DATA_OUT ; Помещаем в требуемый регистр

               call SPI_WRITE ; и пересылаем в МАХ549А

               movf CHANNEL_B,w ; Берем значение канала В

               movwf DATA_OUT ; Помещаем в требуемый регистр

               call SPI_WRITE ; и пересылаем в МАХ549А

               bsf PORTA,CE ; Формируем импульс на СЕ

               bcf PORTA,CE

               return

Если мы снимем осциллограммы с трех выводов микросхемы МАХ549А, то увидим сигналы, похожие на те, что изображены на Рис. 12.6 (на рисунке показана передача 1-й пары байтов . Во время передачи на выводе  удерживается НИЗКИЙ уровень, а данные побитно загружаются во внутренний сдвиговый регистр. После 2-го байта, т. е. после 16-го тактового импульса, подача на вывод  напряжения ВЫСОКОГО уровня активирует регистры ЦАП, заданные в управляющем байте.

Из Рис. 12.6 видно, что изменение состояния линии DIN, управляемой выводом SDO микроконтроллера, происходит перед формированием активного нарастающего фронта на выводе SCK. Очевидно, что состояние должно измениться за определенное время до появления фронта и удерживаться в течение короткого интервала времени после него. В документации на МАХ549А сказано, что минимальное время установки fDS составляет 30 нс, а время удержания fDH — 10 нс. Наша схема будет удовлетворять этим требованиям в любом случае, поскольку даже при тактовой частоте 20 МГц длительность машинного цикла микроконтроллера PIC будет равна 200 нс.

Рис. 12.6. Передача данных в МАХ549А по шине SPI

Благодаря наличию у микросхем входа СЕ к линиям SCK/SDO можно подключить несколько ЦАП — последовательные данные будут заноситься только в ту микросхему, на выводе  которой будет присутствовать НИЗКИЙ уровень. На Рис. 12.7 изображены две микросхемы МАХ549А, подключенные к одной шине SPI и формирующие 4 аналоговых выхода. А, подключив к выводам RA[3:2] дешифратор 2–4, мы сможем управлять четырьмя МАХ549А, используя для этого всего четыре вывода порта.

Рис. 12.7. Подключение нескольких МАХ549А к одной шине SPI

Большинство микроконтроллеров среднего уровня и все микроконтроллеры старшего уровня имеют в своем составе модуль синхронного последовательного порта (SSP), который реализует, помимо всего прочего, протокол SPI. В зависимости от функциональной насыщенности конкретной модели микроконтроллера существует три очень похожих исполнения этого модуля. Первое из этих исполнений называется базовым SSP (BSSP), из которого позже «вырос» обычный SSP. В самых последних моделях появился модуль MSSP (ведущий синхронный последовательный порт). В этом модуле вводится несколько дополнительных опций формирования тактового сигнала SPI, однако гораздо большее значение имеет тот факт, что данный модуль может использоваться в качестве ведущего шины I2С (отсюда и название).

Несколько упрощенная структурная схема модуля MSSP, сконфигурированного для работы по протоколу SPI, приведена на Рис. 12.8. Основным узлом модуля MSSP является регистр специального назначения SSPBUF, расположенный по адресу h’13’. Байт данных, загруженный в этот РСН, автоматически перегружается в сдвиговый регистр модуля SSPSR и побитно выдается на вывод RC5/SDO микроконтроллера. Одновременно с этим восемь битов данных считываются с вывода RC4/SDI. После завершения указанных операций принятый байт автоматически пересылается в регистр SSPBUF, откуда его можно считать. Это индицируется установкой флага BF (Buffer Full) в регистре состояния SSPSTAT, формат которого приведен на Рис. 12.9. После чтения регистра SSPBUF флаг BF автоматически сбрасывается.

Рис. 12.8. Модуль MSSP, сконфгурированный для работы по протоколу SPI. Выводы модуля задействуют линии параллельного порта С

В отличие от параллельных портов ввода/вывода, конфигурирование и контроль состояния интерфейсных модулей микроконтроллеров, как правило, осуществляются с помощью ряда соответствующих регистров управления и состояния. Помимо этого используются биты масок и флагов прерываний, расположенные либо в регистре INTCON, либо в одном или двух регистрах разрешения прерываний от периферийных устройств и регистрах флагов прерываний от периферийных устройств, аналогичных показанным на Рис. 7.6 (стр. 224). Конфигурирование регистров управления, состояния и прерываний периферийных устройств обычно выполняется в той же части программы, в которой производится конфигурирование параллельных портов ввода/вывода. Так как интерфейсные модули в обязательном порядке задействуют выводы параллельных портов, то эти выводы часто необходимо конфигурировать даже в том случае, если порты ввода/вывода в программе не используются. Конфигурация линий ввода/вывода переопределяется автоматически при включении периферийного модуля или же должна явно задаваться самим программистом. К сожалению, ответ на этот вопрос далеко не всегда очевиден, поэтому для получения точной информации необходимо обращаться к документации на конкретный микроконтроллер.

Но вернемся к нашему модулю MSSP. На Рис. 12.9 показаны установки регистров SSPCON и SSPSTAT, используемые при работе в режиме SPI. Ну, а для подключения к внешним устройствам в этом режиме в общей сложности задействуется четыре вывода.

∙ RC5/SDO

Бит TRISC[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
должен быть сброшен для переключения этого вывода в режим выхода.

∙ RC4/SDI

Этот бит конфигурируется модулем MSSP как вход независимо от состояния соответствующего бита TRISC[4]Здесь имеется в виду не размер данных, которыми оперирует микроконтроллер, а число битов, использующихся для записи слова команды. — Примеч. пер.
,

∙ RC3/SCK

При работе в качестве ведущего бит TRSC[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
должен быть сброшен, поскольку на этом выводе формируется тактовый сигнал. И, наоборот, при работе в режиме ведомого бит TRISC[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
должен быть установлен, чтобы принимать тактовый сигнал от ведущего.

В режиме ведомого Ь’0100’ этот вывод должен быть сконфигурирован как вход (TRISA[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
= 1), чтобы другой ведущий мог выбрать данное устройство.

При любом сбросе оба регистра SSPCON и SSPSTAT очищаются, а внутренний счетчик битов обнуляется. При этом модуль отключен, и если программист собирается использовать MSSP, то он должен задать соответствующие значения различных управляющих битов.

∙ SSPEN

Установка бита SSPCON[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
в 1 разрешает работу модуля последовательного синхронного порта. Если модуль выключен, то соответствующие выводы могут использоваться в качестве линий-обычных параллельных портов ввода/вывода.

∙ SSPM[3:0]

Четыре бита выбора режима работы модуля, расположенные в SSPCON[3:0], используются для выбора протокола обмена, а также различных опций работы ведущего/ведомого. На Рис. 12.9 указано шесть комбинаций этих битов, относящихся к протоколу SPI.

Рис. 12.9. Регистры управления (SSPCON) и состояния (SSPSTAT) модуля MSSP при его работе в режиме SPI

Режимы ведущего (а их четыре) отличаются друг от друга только значением частоты тактового сигнала и ее источниками. В трех из этих режимов тактовый сигнал формируется из тактового сигнала микроконтроллера. Например, при использовании резонатора с частотой 20 МГц частота сигналов на выводе SCK может быть равна 5 МГц, 1.25 МГц и 312.5 кГц (период 200 не, 800 не и 3.2 мкc соответственно). В последнем же из режимов частота тактового сигнала шины SPI равна половине частоты сигнала, формируемого при переполнении Таймера 2 (см. Рис. 13.8 на стр. 474). Этот режим используется, когда требуется очень низкая скорость передачи данных.

При работе модуля в качестве ведомого тактовый сигнал поступает на вывод SCK извне от внешнего ведущего устройства. Кроме того, ведущий может управлять выводом SS для выбора одного из нескольких ведомых (см. Рис. 12.12).

∙ SSPOV

При работе модуля в режиме ведомого единица в этом бите указывает на то, что новый байт был принят раньше, чем считан предыдущий, т. е. то, что произошла потеря одного или нескольких байтов. Этот бит не сбрасывается автоматически при чтении регистра SSPBUF и должен быть сброшен самостоятельно, из программы. В режиме ведущего данный бит не используется.

∙ WCOL

Если программа попытается записать данные в регистр SSPBUF до завершения передачи предыдущего байта, то операция записи будет прервана, а бит конфликта записи WCOL установится в 1. В случае обнаружения такой ситуации необходимо сбросить этот бит программно и повторить операцию записи.

∙ СКР, СКЕ, SMP

Эти три бита предназначены для задания моментов формирования фронтов тактового сигнала, по которым будет осуществляться считывание битов с линии данных и выставление их на линию.

Чтобы проиллюстрировать использование указанных битов, рассмотрим ситуацию, когда модуль MSSP работает в режиме ведущего. Как ведущий, модуль полностью управляет тактовым сигналом на линии SCK, который используется для тактирования сдвигового регистра как передатчика, так и приемника. Для ведомого-приемника активный фронт тактового сигнала должен формироваться в тот момент, когда значение бита данных, выставленного ведущим на вывод SDO, станет стабильным. А сдвиговый регистр ведомого-передатчика должен тактироваться таким образом, чтобы выставляемые им значения битов данных были стабильны в то время, когда ведущий считывает их с вывода SDI. Такая схема изображена на Рис. 12.12, где ведущее устройство на базе микроконтроллера PIC может взаимодействовать с одним из двух ведомых устройств, каждое из которых может одновременно принимать и передавать данные. Этими ведомыми устройствами могут быть другие микроконтроллеры PIC (как показано на рисунке) или любые SPI-совместимые устройства.

Процесс передачи каждого байта состоит из восьми фаз, как показано на Рис. 12.11. В любом случае очередной бит данных Dn выставляется на вывод SDO вскоре (как правило, не позже чем через 50 нc) после начала соответствующей фазы тактового сигнала (см. верхнюю часть рисунка). Удаленный ведомый-приемник должен «защелкнуть» это значение в середине фазы. Подобным образом удаленный ведомый-передатчик должен своевременно выдавать на вывод SDI ведущего очередной бит своих данных dn, чтобы ведущий мог его считать.

На Рис. 12.10 изображены две часто встречающиеся ситуации. Диаграммы сигнала SCK в верхней части рисунка соответствуют случаям, когда передатчики и приемники используют различные активные фронты тактового сигнала. Поскольку тактирование передатчика осуществляется в начале каждой фазы, его данные необходимо считывать в середине фазы, для чего бит SMP должен быть установлен в 1.

Рис. 12.10. Прием и передача данных удаленными ведомыми устройствами

∙ СКЕ: СКР = 0:0

Когда тактирование удаленного передатчика осуществляется по нарастающему фронту  сигнала SCK, ведущий должен считывать его данные с вывода SDI в середине фазы. Эти данные должны присутствовать на выводе как минимум за 100 не до наступления указанного момента и удерживаться в течение, как минимум, 100 не после. Удаленный ведомый-приемник считывает переданные ему данные с вывода SDO по спадающему фронту сигнала  SCK также в середине фазы. В спецификации протокола SPI такой режим работы называется режимом 0,1 (или просто режим 1).

∙ СКЕ: СКР = 0:1

Режим 1,1 (режим 3) похож на предыдущий, за исключением того, что тактирование передатчика осуществляется по спадающему фронту , а приемника — по нарастающему .

Диаграммы в нижней части Рис. 12.10 соответствуют ситуации, когда тактирование сдвиговых регистров удаленных передатчиков и приемников осуществляется по одному и тому же активному фронту. Поскольку передатчик выдает данные в середине фазы, то данные от него, присутствующие на выводе SDI, должны считываться в конце фазы, для чего бит SMP должен быть сброшен в 0.

∙ СКЕ: СКР = 1:0

Режим 0,0 (режим 0) используется тогда, когда тактирование ведомых-передатчиков и ведомых-приемников производится одновременно в середине фазы по нарастающему фронту . К этому моменту значение бита данных от ведущего Dn станет стабильным, что позволит ведомому устройству считать его. Соответственно, данные от ведомого устройства должны быть готовы для считывания к концу фазы.

∙ СКЕ: СКР = 1:1

В режиме 1,0 (режим 2) тактирование ведомых-передатчиков и ведомых-приемников осуществляется в середине фазы по спадающему фронту  тактового сигнала.

Когда модуль MSSP работает в качестве ведомого, тактовый сигнал на него поступает от внешнего устройства. Как и прежде, любые данные, предварительно загруженные в регистр SSPBUF, будут в начале каждой фазы тактового сигнала выставляться на вывод SDO. При этом биты СКЕ и СКР все равно необходимо устанавливать в зависимости от того, по какому фронту тактового сигнала удаленный передатчик выставляет свои данные и какой фронт для удаленного приемника является активным. Установки указанных битов зависят также от того, когда ведущий выставляет первый бит своих данных Dn — до первого тактового импульса или после. В любом случае модуль MSSP, сконфигурированный как ведомый, должен считывать значения этих битов со своего вывода SDI в конце каждой фазы, т. е. бит SMP должен быть сброшен в 0. Чтобы посмотреть подробные временные диаграммы, советую вам обратиться к фирменной документации на используемый микроконтроллер.

Когда микроконтроллер PIC работает в качестве ведомого SPI-устройства, то для его выбора удаленным ведущим используется вывод выбора ведомого . При появлении на выводе  напряжения ВЫСОКОГО уровня, даже в середине транзакции, внутренний счетчик битов сбрасывается в 0. Кроме того, вывод SDO переключается в режим выхода с открытым стоком, чтобы дать возможность другому устройству захватить линию.

∙ BF, SSPIF

После считывания микроконтроллером полного фрейма из восьми битов и пересылки его в буферный регистр SSPBUF бит BF устанавливается в 1, извещая о приеме нового байта. При этом также устанавливается флаг прерывания SSPIF в регистре PIR1 (см. Рис. 7.6 на стр. 224), и, если установлен соответствующий бит маски прерывания SSPIE в регистре PIE1, генерируется прерывание. Если модуль MSSP работает в качестве ведомого, а микроконтроллер находится в «спящем» режиме, то данное прерывание можно использовать для «пробуждения» микроконтроллера. Это возможно благодаря тому, что тактовые импульсы на выводе SCK формируются внешним ведущим устройством, и поэтому микроконтроллеру не обязательно находиться в активном режиме, т. е. системный генератор может быть выключен.

При считывании нового байта из регистра SSPBUF бит BF автоматически сбрасывается. Если же новое значение своевременно не считать, то принятый байт будет потерян и, сигнализируя об этом, будет установлен флаг SSPOV. Флаг прерывания SSPIF необходимо сбрасывать самостоятельно в процедуре обработки прерывания.

Вооружившись информацией, приведенной на Рис. 12.8 и Рис. 12.9, мы теперь легко можем перечислить операции, которые необходимо выполнить для осуществления передачи байта и/или приема нового байта:

1. Сконфигурировать модуль SSP;

• Сделать SCK, RC5/SDO выходами, a RC4/SDI и, при необходимости, RA5/ — входами.

• Выбрать режим работы модуля (ведущий/ведомый) и источник тактового сигнала.

• Задать активные фронты с помощью битов СКР, СКЕ и SMP.

• Разрешить работу модуля SSP установкой бита SSPEN.

2. Загрузить байт в регистр SSPBUF для инициирования передачи.

3. Если бит WCOL = 1, то сбросить его и перейти к п. 2.

4. Ждать установки бита BF.

5. Скопировать полученный байт данных из SSPBUF, при этом бит BF будет сброшен автоматически.

Для иллюстрации описанного процесса рассмотрим подпрограмму SPI_IN_OUT, которая объединяет в себе функции SPI_READ и SPI_WRITE, т. е. передает содержимое регистра DATA_OUT и возвращает полученный байт в регистре DATA_IN. Предполагается, что сдвиговые регистры удаленного устройства «защелкиваются» по нарастающему фронту, т. е. используется режим SPI 0,0.

Реализация этой подпрограммы зависит от установок модуля MSSP, заданных во время инициализации основной программы. В следующем фрагменте кода мы переводим модуль MSSP в режим ведущего и задаем тактовую частоту шины SPI равной fOSC/4:

     include "p16f877.inc’

MAIN bsf STATUS,RP0 ; Переключаемся в 1-й банк

         movlw b’11010111’ ; RC5/SD0 и RC3/SCK — выходы

         movwf TRISC ; RC4/SDI — вход

         movwf b’11000000’ ; Устанавливаем биты SMP и СКЕ

         movwf SSPSTAT;

... ...

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

         movlw b’00100000’ ; Включаем SSP, пассивный уровень — НИЗКИЙ

         movwf SSPCON ; Режим ведущего SPI, Fosc/4

Код, приведенный в Программе 12.6, в точности соответствует вышеприведенному списку операций. Байт данных, который необходимо передать, копируется из указанного регистра в регистр SSPBUF, после чего проверяется бит состояния WCOL, чтобы удостовериться, что новое значение действительно было загружено в буфер. Если в этот момент осуществлялась передача предыдущего байта, то новый байт не будет загружен в регистр SSPBUF, а бит WCOL будет установлен в 1. Если обращение к модулю SSP осуществляется только из указанной подпрограммы, то возникновение такой ситуации маловероятно, и в большинстве случаев эта проверка может быть исключена. Тем не менее наличие такой проверки увеличивает надежность системы.

Программа 12.6. Использование модуля SSP для приема и передачи данных по шине SPI

;************************

; * ФУНКЦИЯ: Передает и одновременно принимает один байт *

; * ФУНКЦИЯ: данных по шине SPI с использованием модуля SSP *

; * ВХОД: Передаваемый байт — в DATA_OUT *

; * ВЫХОД: Принятый байт — в DATA_IN *

;************************

SPI_IN_OUT

       movf DATA_OUT,w ; Берем байт для передачи

       movwf SSPBUF ; Загружаем его в SSPBUF

SSP_IN_OUT_LOOP

       btfss SSPCON,WCOL ; Он загрузился?

          goto SPI_IN_OUT_CON7 ; ЕСЛИ да, TO продолжим

       bcf SSPCON,WCOL ; ИНАЧЕ сбросим WCOL и

          goto SSP_IN_OUT_LOOP ; попытаемся снова

SPI_IN_OUT_CONT

        bsf STATUS,RPO ; Переключаемся в 1-й банк

        btfss SSPSTAT,BF ; Проверяем состояние буфера

           goto SPl_IN_OUT_CONT ; ЕСЛИ не полон, проверяем снова

        bcf STATUS,RPO ; Возвращаемся в 0-й банк

        movf SSPBUF,w ; ИНАЧЕ считываем принятый байт

        movwf DATA_IN ; и помещаем его в требуемый POН

        return

После загрузки передаваемого байта в буфер сразу же начинается процесс передачи, изображенный на Рис. 12.11. По окончании передачи устанавливается флаг BF, и принятый байт может быть скопирован из регистра SSPBUF в требуемый РОН. Флаг BF при этом сбросится автоматически.

Рис. 12.11. Временные диаграммы, соответствующие работе модуля SSP в режиме ведущего SPI

Помимо небольшого уменьшения размера кода, преимуществом использования аппаратного модуля является увеличение скорости передачи. Одна транзакция приема/передачи состоит из восьми тактов SCK, которые в нашем случае становятся равными восьми машинным циклам. При fOSC = 20 МГц частота сигнала SCK равна 5 МГц (т. е. скорость передачи составляет 5 миллионов битов в секунду; обычно это записывается как 5 Мбит/с). Таким образом, для передачи одного бита требуется всего 1.6 мкс.

На Рис. 12.11 показаны временные диаграммы работы модуля в режиме SPI,который используется в нашей подпрограмме. Поскольку мы сбросили бит СКР и установили бит СКЕ, то в режиме ожидания на линии SCK будет присутствовать НИЗКИЙ уровень. Сразу же после загрузки байта в регистр SSPBUF на вывод SDO выдается старший бит передаваемого байта. Это значение будет занесено в сдвиговый регистр удаленного приемника по нарастающему фронту тактового сигнала в середине фазы.

Поскольку удаленный приемник также тактируется в середине фазы, у него имеется достаточно времени для выдачи очередного бита данных на вход SDI микроконтроллера. Этот бит считывается микроконтроллером в конце каждой фазы.

* * *

Одним из применений последовательной передачи данных является объединение нескольких устройств в одну многопроцессорную систему. Например, каждый сустав манипулятора робота может управляться своим микроконтроллером, обменивающимся данными с основным процессором. Простая многоабонентская система из одного ведущего и двух ведомых процессоров показана на Рис. 12.12.

Рис. 12.12. Многоабонентская сеть на базе шины SPI

В этой схеме ведущий PIC-микроконтроллер управляет выводами SCK обоих ведомых, определяя, таким образом, периодичность и скорость обмена данными по сети. Оба ведомых устройства работают в режиме Ь’0100’, в котором разрешена работа входов SS. Таким образом, если ведущий собирается прочитать данные из ведомого № 2, то он подает на вход SS последнего НИЗКИЙ уровень и считывает восемь битов из регистров SSPBUF/SSPSR 2-го ведомого в свои регистры SSPBUF/SSPSR. Одновременно с этим ведомый принимает любые данные, передаваемые ведущим.

Си-процедуры для работы с шиной SPI могут быть написаны либо по аналогии с ассемблерными процедурами (выполняя установку/чтение соответствующих регистров специального назначения), либо с использованием специальных встроенных функций компилятора. Основными функциями компилятора CCS, управляющими модулем SSP в режиме SPI, являются:

setup_spi(spi_master I spi_h_to_1 I spi_clk_div_4);

Функция setup_spi с указанными параметрами переводит модуль SSP в режим ведущего SPI с активным нарастающим фронтом сигнала и частотой шины SPI, равной 1/4 частоты основного тактового генератора. Эти константы, как и многие другие, скажем spi_slave, spi_sample_at_end и spi_xmit_1_to_h, определены в стандартных заголовочных файлах, таких как 16f877a.h. Данная функция также конфигурирует соответствующие выводы портов А и С.

∙ spi_write(value);

Эта функция используется для передачи байта по шине SPI. Возврат из функции осуществляется после установки флага BF.

∙ spi_read();

Эта функция практически идентична функции spi_write (), за исключением того, что она возвращает значение байта, принятого модулем SSP. Если в данную функцию будет передано значение, то оно будет передано по шине.

∙ spi_data_is_in();

Эта функция возвращает ненулевое значение, если по шине SPI были получены данные, т. е. если флаг BF установлен.

Чтобы проиллюстрировать использование указанных функций, напишем процедуру для взаимодействия с микросхемой МАХ549А (см. Программу 12.5). Прежде всего, нам необходимо сконфигурировать модуль SSP. Это можно сделать следующим образом:

#include <16f877a.h>

#bit СЕ = 5.2 /* 2-й бит порта А — сигнал СЕ для МАХ549А */

void МАХ549А (unsigned int channel_A, unsigned int channel_B);

void main(void)

{

set_tris_a(0xFB); /* CE = RA2 — выход */

setup_adc(NO_ANALOGS); /* Все входы портов А и E — цифровые */

setup_spi (spi_master I spi_1_to_h| spi_clk_div_4);

В приведенном выше фрагменте предполагается, что вывод СЕ микросхемы МАХ549А подключен к выводу RA2 порта А, как показано на Рис. 12.7.

В подпрограмме (см. Программу 12.7) четыре раза вызывается функция spi_write (), причем после передачи каждой пары значений  на вывод  микросхемы подается положительный импульс.

Программа 12.7. Управление ЦАП МАХ549А на Си

void МАХ549А(unsigned int channel_A, unsigned int channel_B)

{

       spi_write(0x01); /* Передаем 1-й управляющий байт */

       spi_write(channel_A); /* Передаем байт данных */

       CE=0; /* Формируем импульс */

       CE=1;

       spi_write(0х0А); /* Передаем 2-й управляющий байт */

       spi_write(channel_B); /* Передаем байт данных */

       CE=0; /* Формируем импульс */

       CE=1;

}

Несмотря на то что протокол SPI достаточно быстрый, для его реализации требуется как минимум три линии плюс по одной линии для выбора каждого ведомого устройства. Даже если не принимать во внимание ценовой фактор, добавление в законченную схему нового устройства потребует модификации аппаратной части изделия. Однако, увеличив степень «интеллектуальности» ведомых устройств, мы сможем использовать один-единственный последовательный поток для передачи управляющей информации, адреса и данных. Именно эта концепция легла в основу протокола I2C™ (Inter-Integrated Circuit — протокол межсоединения интегральных схем), разработанного компанией Philips/Signetics Corporation в начале 1980-х годов. В интерфейсе I2С количество линий связи уменьшено до двух за счет использования двунаправленной передачи данных (см. Рис. 12.13).

∙ SCL

Это линия тактового сигнала, используемая для синхронизации передачи данных и выполняющая те же функции, что и линия SCK в протоколе SPI. Однако линия SCL является двунаправленной — это позволяет различным ведущим устройствам захватывать управление ею в разные моменты времени.

Изначально в спецификации I2С максимальная скорость передачи была ограничена на уровне 100 Кбит/с (частота сигнала SCL — 100 кГц), однако в 1993 году спецификация была дополнена высокоскоростным режимом Fast, обеспечивающим скорость передачи до 400 Кбит/с, который в настоящее время стал стандартом де-факто. В 1998 году был введен режим High-Speed, обеспечивающий скорость передачи до 3.4 Мбит/с.

∙ SDA

Это линия данных, позволяющая передавать данные в обоих направлениях — как от ведущего к ведомому (ведущий-передатчик), так и от ведомого к ведущему (ведущий-приемник). Более того, наличие двунаправленной линии позволяет приемнику отсылать свое состояние передатчику после передачи каждого байта.

Рис. 12.13. Передача данных по шине I 2 С

Протокол I2С достаточно сложен, и полную его спецификацию можно найти на сайте компании Philips Corporation. Прежде чем перейти к изучению основ этого протокола, рассмотрим подробнее работу линий SDA и SCL. В отсутствие передаваемых данных на обеих линиях присутствует ВЫСОКИЙ уровень (состояние ожидания). Устройство, собирающееся захватить управление шиной, находящейся в состоянии ожидания, должно выставить на вывод SDA НИЗКИЙ уровень. Такое состояние шины называется состоянием СТАРТ. Чтобы любое устройство, желающее стать ведущим, могло выставить на эту линию НИЗКИЙ уровень, выводы SDA всех остальных устройств, подключенных к шине, должны быть «отключены» от линии, а сама линия шины должна быть подтянута внешним резистором до напряжения ВЫСОКОГО уровня (см. Рис. 12.14, а). Для этого выходы SDA (а также SCL) всех I2С-совместимых устройств выполняются по схеме с открытым коллектором или открытым стоком (см. Рис. 2.2, б на стр. 32). Это означает, что любое устройство, подключенное к шине, может перевести ее линию в состояние НИЗКОГО уровня, выставив на свой выход лог. 0.

Помимо формирования состояния СТАРТ, ведущий должен генерировать тактовый сигнал, а также передавать значение адреса другим устройствам на шине для установления соединения с одним или более ведущими устройствами. Один из битов в байте адреса используется для того, чтобы сообщить ведомому, какая посылка ожидается далее: от ведущего к ведомому (ведущий-передатчик) или от ведомого к ведущему (ведущий-приемник).

Каждый пакет, передаваемый по шине, состоит из девяти битов. Восемь из них представляют собой байт данных, синхронизируемый тактовым сигналом. Состояние линии SDA должно изменяться только при НИЗКОМ уровне на линии SCL. Данные считываются приемником по следующему спадающему фронту сигнала SCL. Эти байты могут являться адресом или управляющей информацией, посылаемой ведущим, или же данными, передаваемыми как ведущим, так и ведомым. В протоколе I2С предусмотрен механизм квитирования (см. Рис. 11.2 на стр. 329). Во время девятого тактового импульса передающее устройство высвобождает линию SDA, и принимающее устройство подтверждает (acknowledges) данные, посланные передатчиком. Если данные были успешно приняты, то линия SDA удерживается приемником в состоянии НИЗКОГО уровня — это состояние называется АСК (подтверждение) (см. Рис. 12.15). Если же при приеме возникли некие проблемы или приемник не хочет больше принимать данные, то он оставляет на линии ВЫСОКИЙ уровень — такое состояние называется NACK (нет подтверждения). В последнем случае передатчик, как правило, пытается повторить передачу еще несколько раз, прежде чем принять решение о завершении обмена.

В качестве менее радикального способа ведомое устройство может удерживать линию тактового сигнала в состоянии НИЗКОГО уровня. Растягивание тактового сигнала (clock stretching) полезно в тех случаях, когда ведомое устройство не может обрабатывать поступающие данные с требуемой скоростью. После высвобождения ведомым линии SCL ведущий продолжит формирование тактовых импульсов.

В любом случае только ведущий может прекратить обмен, выставляя на линии SDA ВЫСОКИЙ уровень при ВЫСОКОМ уровне на линии SCL; такое состояние называется состоянием СТОП. При необходимости ведущий может начать обмен с другим ведомым, повторно сформировав на шине состояние СТАРТ. Кроме того, ведущий может формировать состояние СТАРТ, не генерируя перед этим состояние СТОП; в таком случае это состояние называется повторный СТАРТ или просто ПОВСТАРТ. К примеру, ведущий собирается передать (ведущий-передатчик) внутренний адрес ячейки в I2С-совместимую микросхему памяти (см. Пример 12.3), а затем прочитать (ведущий-приемник) данные из этой ячейки. Для выполнения этой операции потребуется сменить направление передачи данных, что осуществляется повторным формированием состояния СТАРТ и посылкой нового адресного пакета с требуемым значением бита направления (см. Рис. 12.27). Различие между использованием состояния ПОВСТАРТ и СТОП заключается в том, что последнее сигнализирует другим устройствам на шине, что ведущий освободил шину и что другое устройство может стать ведущим.

При программной реализации интерфейса I2С в микроконтроллерах PIC возникает проблема, заключающаяся в том, что выходы портов не являются выходами с открытым стоком, т. е. состояние лог. 1 не формируется разомкнутой цепью, как того требует спецификация (см. Рис. 12.14, а). Однако состояние с высоким входным сопротивлением можно эмулировать путем переключения линии порта с выхода на вход. Так, если мы хотим использовать для управления линией SCL вывод RA2, то для формирования на этой линии отрицательного импульса мы должны сделать так:

bcf PORTA,2 ; При конфигурировании RA2

... ...

STATUS,RP0 ; Переключаемся в 1-й банк

bcf TRISA,2; На выходе RA2 — лог. 0

nор ; Короткая задержка

bsf TRISA,2 ; Переводим RA2 в третье состояние, делая его входом

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

При этом ВЫСОКИЙ уровень на линии формируется совместным влиянием внешнего подтягивающего резистора и высокого входного сопротивления, как показано на Рис. 12.14, б (справа).

Рис. 12.14. Совместное использование линий SCL и SDA несколькими устройствами

Собственно процесс обмена между ведущим и ведомым устройствами состоит из передачи различных пакетов (один пакет — 8 бит данных плюс бит квитирования), передаваемых между состояниями СТАРТ и СТОП. Содержимое этих пакетов может слегка изменяться в зависимости от требований ведомого устройства, однако последовательность передачи пакетов всегда остается неизменной (см. Рис. 12.15): сначала передается адрес ведомого, затем — управляющий байт или команда, а потом передается один или несколько байтов данных.

Рис. 12.15. Порядок передачи пакетов по шине I 2 С

В соответствии со спецификацией I2С каждое ведомое устройство должно иметь уникальный адрес. Этот адрес выдается производителю I2С-совместимых микросхем и заносится в них на этапе изготовления. Чтобы на одной шине можно было использовать несколько однотипных микросхем, большинство I2С-совместимых устройств позволяют разработчику задавать до 4 битов адреса самостоятельно, обычно путем подачи на выводы адреса соответствующих логических уровней. После обнаружения состояния СТАРТ все ведомые устройства на шине проверяют первые семь битов на совпадение со своим адресом. Если совпадения не обнаружено, то до появления нового состояния СТАРТ это устройство игнорирует все изменения на шине. Восьмой бит пакета адреса (R/W¯) определяет направление передачи данных. Этот бит равен 0, если ведущее устройство намеревается передавать данные, т. е. писать (Write) в ведомое устройство, и 1 — если ведущий собирается считывать (Read) данные из ведомого устройства.

Не все 7-битные адреса являются допустимыми. В частности, все адреса формата Ь’0000ХХХ’ или b’1111ХХХ’ зарезервированы для специальных применений; таким образом, в распоряжении пользователей остается 224 возможных адреса.

Например, адрес Ь’0000000’ обозначает широковещательный пакет, посылаемый всем ведомым на шине, а не какому-то конкретному устройству. Одновременно с введением режима Fast в протоколе I2С появилась возможность использования 10-битных адресов. На использование расширенной адресации указывает передача зарезервированного адреса Ь’111110ХХХ’, три младших бита которого будут объединены с содержимым последующего 7-битного пакета адреса.

Байт, передаваемый вслед за байтом (или байтами) адреса, обычно интерпретируется адресованным ведомым как команда или управляющий/байт, посредством которого передается конфигурационная информация. Например, для I2C-совместимых микросхем памяти может потребоваться передача внутреннего адреса, начиная с которого будут заноситься данные (см. Пример 12.3). Все последующие байты обычно являются простыми данными или же данными, перемежающимися управляющими байтами.

Для иллюстрации рассмотрим микросхему ЦАП МАХ518 компании Maxim, блок-схема которой приведена на Рис. 12.16. Эта микросхема аналогична SPI-совместимой МАХ459, имеет двухуровневый регистровый конвейер, два канала и режим пониженного потребления.

Микросхема МАХ518 имеет 7-битный адрес вида 01011AD1AD0, где значения битов AD1 и ADO определяются логическими уровнями на соответствующих выводах микросхемы. Если предположить, что оба вывода подключены к общему проводу, то для адресации микросхемы ведущий должен будет послать следующий пакет: . Бит R/W¯ равен 0, поскольку эта микросхема может только принимать данные.

Байт команды имеет формат 000 RST PD XX А0 и содержит три управляющих бита.

∙ А0

Этот бит используется для разрешения входного PIPO-регистра 0-го (А0 = 0) и 1-го (А0 = 1) каналов.

∙ PD

Когда этот бит равен 1, оба канала АЦП переключаются в режим пониженного потребления, при этом суммарный ток потребления микросхемы не превышает 4 мкА. Содержимое внутренних регистров остается неизменным, кроме того, в этом состоянии можно загружать данные и обновлять содержимое регистров. Изменение режима работы происходит только после формирования ведущим состояния СТОП, т. е. учитывается только последнее переданное значение бита.

∙ RST

Если этот бит равен 1, то все внутренние регистры сбрасываются независимо от значения последующего байта данных. После формирования на шине состояния СТОП напряжение на выходах обоих каналов становится равным нулю.

В любом случае изменение сигнала на аналоговом выходе в соответствии со значением управляющего байта и байта данных происходит только после формирования на шине состояния СТОП. Если с момента формирования последнего состояния СТОП было послано несколько пар байтов, то на состояние устройства окажут влияние только последние значения.

Рис. 12.16. Микросхема сдвоенного ЦАП с интерфейсом I 2 C МАХ518 (Maxim)

Для работы с ЦАП МАХ518 нам потребуется написать подпрограмму, формирующую состояния СТАРТ, СТОП и передающую по шине байты данных. Для написания подобного драйвера устройства необходимо более внимательно рассмотреть временные соотношения между тактовым сигналом и сигналом данных, на которые в большинстве своем накладываются более жесткие ограничения, чем в случае интерфейса SPI.

Микросхема МАХ518 и большинство современных I2С-совместимых устройств поддерживают режим Fast, поэтому диаграммы, приведенные на Рис. 12.17, соответствуют тактовой частоте для этого режима (400 кГц). В частности, для корректного формирования состояния СТАРТ требуется, чтобы ВЫСОКИЙ уровень на линии SCL удерживался в течение не менее 0.6 мкс (tHD;STA) после появления активного фронта  на линии SDA. Аналогично, для корректного формирования состояния СТОП требуется, чтобы ВЫСОКИЙ уровень на линии SCL был выставлен не позже чем за 0.6 мкс (tSU;STO) до появления активного фронта  на линии SDA. А время нахождения шины в незанятом состоянии (tBUF) между формированием состояния СТОП и последующего состояния СТАРТ должно быть не менее 1.3 мкс. Указанные значения позволяют всем ведомым устройствам однозначно обнаруживать эти специальные состояния шины.

Рис. 12.17. Минимальные значения временных параметров для режима Fast

Во время передачи байта данных тактовый сигнал должен удовлетворять следующим условиям: длительность интервала НИЗКОГО уровня (tLOW) — не менее 1.3 мкс, длительность интервала ВЫСОКОГО уровня (tHIGH) — не менее 0.6 мкс и, следовательно, полный период сигнала — не менее 2.5 мкс, что соответствует частоте 400 кГц. Изменение состояния линии данных может происходить только при НИЗКОМ уровне на линии тактового сигнала, причем все изменения должны быть прекращены не позже чем за 100 не (tSU;DAT) до появления нарастающего фронта тактового сигнала.

На Рис. 12.17 не указаны максимальные значения времени нарастания и спада, которые не должны превышать 300 не при наибольшей емкости шины, равной 400 пФ. Чтобы удовлетворить этому требованию, при такой емкости шины сопротивление подтягивающих резисторов, изображенных на Рис. 12.14, не должно превышать 1.8 кОм. При небольшой длине шины и малом количестве подключенных к ней устройств сопротивление подтягивающих резисторов можно увеличить в десятки раз для уменьшения потребления.

Если мы воспользуемся микроконтроллером PIC, работающим на частоте более 3.2 МГц (время исполнения команды менее 1.25 мкс), то для формирования временных интервалов, удовлетворяющих спецификации I2С, может потребоваться вставка коротких задержек между отдельными операциями. Например, если мы используем резонатор с частотой 20 МГц, то при выполнении следующих строк:

     bcf TRISA,SCL

; Выставить на линию НИЗКИЙ уровень, записав в порт 0

     bsf TRISA,SCL

; Сформировать на линии ВЫСОКИЙ уровень, переключив вывод на вход

длительности интервалов ВЫСОКОГО и НИЗКОГО уровней тактового сигнала будут составлять всего 0.2 мкс. Обычно короткие задержки формируются командами nop, каждая из которых выполняется за один машинный цикл (fOSC/4). Соответственно, для формирования тактового сигнала частотой 400 кГц при 20-МГц резонаторе можно написать:

bcf PORTA,SCL ; Выставляем НИЗКИЙ уровень

nop ; 0.2 мкс

nop ; 0.4 мкс

nop ; 0.6 мкс

nop ; 0.8 мкс

nop ; 1.0 мкс

nop ; 1.2 мкс

bsf PORTA,SCL ; Выставляем ВЫСОКИЙ уровень

nop ; 1.6 мкс

nop ; 1.8 мкс

nop ; 2.0 мкс

nop ; 2.2 мкс

nop ; 2.4 мкс

nop ; 2.6 мкс

Разумеется, меньшее значение тактовой частоты потребует меньше операций nop. Вместо того чтобы корректировать наши подпрограммы в соответствии с используемым в каждом конкретном случае резонатором, мы воспользуемся макрокомандой Delay_600, код которой приведен в Программе 12.8. Эта макрокоманда вставляет в программу столько операций nop, сколько требуется для формирования задержки длительностью 600 нc (0.6 мкс), в зависимости от значения константы XTAL, заданной программистом. Например, чтобы запустить Программу 12.9 на микроконтроллере с 12-МГц резонатором, необходимо просто заменить строку

#define XTAL 20

на

#define XTAL 12

и перекомпилировать программу.

Программа 12.8. Макрокоманда формирования короткой задержки, независимой от частоты резонатора

Delay_600 macro ; Формирует задержку длительностью 0.6 мкс

          if (XTAL <= 6)

          nop ; Одна команда пор, если частота резонатора < 6 МГц

          endif

          if ((XTAL > 6) && (XTAL <= 13))

             nop ; Две команды nор, если частота резонатора

             nop ; от 6 до 13 МГц

          endif

          if (XTAL > 13)

             nop ; Три команды пор, если частота резонатора

             nop ; выше 13 КГц

             nop

             endif

         endm

В Программе 12.8 используются ассемблерные директивы условной компиляции if — endif. Директива if похожа на условный оператор языка Си (см. стр. 293) тем, что вставляет в программу все команды, расположенные между ней и последующей директивой endif, если аргумент директивы if имеет значение ИСТИНА. Например, выражение if ((XTAL>6&& (XTAL<=13)) означает, что если значение константы больше 6 и меньше или равно 13, то в программу будет вставлено две команды пор. При частоте 13 МГц время их выполнения будет равно примерно 600 нc. На практике различные команды, управляющие состоянием линий шины и выполняющие вспомогательные задачи, будут вносить дополнительные задержки, поэтому если необходимо достичь максимальной скорости передачи, то длительности задержек придется подбирать более точно.

Используя макрокоманду из Программы 12.8 и учитывая приведенный ниже инициализационный код (в котором из соображений удобства обращение к регистру направления порта А осуществляется с использованием косвенной адресации):

      include "p16f877a.inc"

        #define XTAL 20

SCL   equ 0

SDA   equ 1

MAIN movlw h’85’ ; Инициализируем регистр FSR,

         movwf FSR ; чтобы он указывал на TRISA (регистр h’85’)

         bcf PORTA,SCL ; Сбрасываем биты порта в 0, чтобы впоследствии

         bcf PORTA,SDA ; можно было заставлять на линии НИЗКИЙ уровень

         bsf INDF,0 ; Формируем на линии тактового сигнала (TRISA[0])

         bsf INDF,1 ; и линии данных (TRISA[1]) ВЫСОКИЙ уровень

мы можем написать три подпрограммы для работы с I2С-совместимой микросхемой МАХ518 (предполагается, что под линии SCL и SDA задействованы 0-й и 1-й выводы порта А микроконтроллера PIC16F84, работающего на частоте 20 МГц). Код этих подпрограмм приведен в Программе 12.9.

∙ START

Эта подпрограмма сначала высвобождает линии SCL и SDA, на которых в результате формируется ВЫСОКИЙ уровень на время не менее 1.3 мкс (fBUF). Затем путем выдачи на вывод SDA НИЗКОГО уровня на шине формируется состояние СТАРТ, после чего формируется задержка длительностью 0.6 мкс для выдерживания интервала tHD;STA (см. Рис. 12.17). После возврата из подпрограммы на обеих линиях будет присутствовать НИЗКИЙ уровень.

∙ STOP

Для формирования состояния СТОП на обе линии сначала выставляется НИЗКИЙ уровень (в принципе шина и так должна находиться в таком состоянии после передачи сброшенного бита квитирования). Затем высвобождается линия SCL, в результате чего на ней появляется ВЫСОКИЙ уровень. А после задержки длительностью 0.6 мкс (tSU;STO) высвобождается линия SDA, формируя тем самым состояние СТОП. После возврата из подпрограммы на обеих линиях шины будет присутствовать ВЫСОКИЙ уровень, т. е. шина будет находиться в состоянии ожидания, готовая к формированию следующего состояния СТАРТ.

∙ I 2 C_OUT

Эта подпрограмма передает по шине восемь битов содержимого регистра DATA_OUT, начиная со старшего бита, и контролирует подтверждение приема ведомым.

Первая операция реализуется путем многократного сдвига содержимого регистра с проверкой флага переноса, значение которого выдается на линию SDA. После выдачи каждого бита данных на линии SCL формируется тактовый импульс, параметры которого tLOW и tHIGH удовлетворяют значениям, указанным на Рис. 12.17.

Программа 12.9. Подпрограммы низкого уровня для управления шиной I 2 С

; **************

; * ФУНКЦИЯ: Формирует на дине состояние СТАРТ *

; * ВХОД: FSR указывает на регистр TRIS порта, *

; * подключенного к шине I2C *

; * ВЫХОД: Формируется состояние СТАРТ, SCL и SDA — НИЗКИЙ уровень *

; **************

START bsf INDF,SDA ; Гарантируем, что перед состоянием СТАРТ

           bsf INDF,SCL ; линии тактового сигнала и данных находились

           Delay_600 ; в режиме ожидания (ВЫСОКИЙ уровень)

           Delay_600 ; в течение не менее 1.3 мкс

           bcf INDF,SDA ; Формируем спадающий фронт на линии данных

           Delay_600 ; Ждем, чтобы ведомый мог его обнаружить

           bcf INDF,SCL ; Выходим, при этом на SCL — НИЗКИЙ уровень

           return

; ****************

; * ФУНКЦИЯ: Формирует на дине состояние СТОП *

; * ВХОД: FSR указывает на регистр TRIS порта, подключенного к шине I2C *

; * ВЫХОД: Формируется состояние СТОП, SCL и SDA — ВЫСОКИЙ уровень *

; ****************

STOP bcf INDF,SCL ; Гарантируем наличие НИЗКОГО уровня на

         bcf INDF,SDA ; линии тактового сигнала и данных

         bsf INDF, SCL ; Формируем на линии тактового сигнала

         Delay_600 ; ВЫСОКИЙ уровень на время не менее 0.6 мкс

         bsf INDF,SDA ; Формируем нарастающий фронт на линии данных

         return

; ***************

; * ФУНКЦИЯ: Передает байт ведомому и проверяет подтверждение *

; * ВХОД: 8 бит передаваемых данных в DATA_OUT *

; * РЕСУРСЫ: Подпрограммы START и STOP *

; * ВЫХОД: Байт передан. Если не было подтверждения, ERR = 1 *

; * ВЫХОД: ИНАЧЕ ERR = 00. SCL — НИЗКИЕ уровень *

; ***************

I2C_OUT bcf TNDF,SCL ; На линии такт, сигнала — НИЗКИЙ уровень

              clrf ERR ; Сбрасываем признак ошибки

              movlw 8 ; Инициализируем счетчик цикла

              movwf COUNT

I2C_OUT_LOOP

              bcf INDF,SDA ; Попробуем выдать 0 на линию данных

              rlf DATA_OUT,f ; Сдвигаем исходный байт влево

              btfsc STATUS,С ; С = 0 или 1?

                 bsf INDF,SDA ; ЕСЛИ последнее, TO выдаем на линию 1

              Delay_600 ; Формируем требуемую задержку

              Delay_600

              bsf TNDF,SCL ; Выдаем на линию такт, сигнала ВЫСОКИЙ

              Delay_600 ; уровень на время не менее 0.6 мкс

              bcf INDF,SCL ; Выдаем ка линию такт, сигнала НИЗКИЙ уровень

              decfsz COUNT,f ; Декрементируем счетчик цикла

                 goto I2C_OUT_LOOP ; и повторяем восемь раз

; Теперь проверим наличие подтверждения от ведомого

              bsf INDF,SDA ; Высвобождаем линию данных

              Delay_600 ; Сохраняем на линии такт, сигнала НИЗКИЙ уровень

              Delay_600 ; на время, достаточное для ответа

              bsf INDF,SCL ; Выдаем на линию такт, сигнала ВЫСОКИЙ уровень

              bcf sc INDF, SDA ; Проверяем наличие НИЗКОГО уровня на линии данных

                incf ERR,f ; ЕСЛИ нет, TO ERR = 1

              bcf INDF,SCL ; Переводим линию такт, сигнала в состояние НИЗКОГО уровня

              return

После выхода из цикла линия данных высвобождается, а на линии SCL в течение tLOW удерживается НИЗКИЙ уровень. Затем линия SCL высвобождается (на ней появляется ВЫСОКИЙ уровень) и проверяется состояние линии SDA, на которую ведомый должен был выставить НИЗКИЙ уровень. Если это не так, значит, подтверждения не было (NACK) и в регистр ERR заносится число h’01’; в противном случае в этом регистре возвращается 0.

Нашу программу нельзя считать полностью рабочей, поскольку в ней отсутствует обработка ошибок. А ошибки могут возникать, например, если какое-либо устройство будет удерживать на любой из линий НИЗКИЙ уровень, т. е. если шина будет занята.

Мы не стали реализовывать в подпрограмме I2C_OUT блок ведущего-приемника, поскольку в микросхеме МАХ518 не предусмотрена передача данных ведомому. Однако функция приема данных по шине 12С реализована в Программе 12.18 (подпрограмма I2C_IN).

В качестве примера перешлем содержимое регистра h’40’ в 0-й канал, а содержимое регистра h’41’ — в 1-й канал. После этого обновим оба регистра ЦАП и, следовательно, одновременно сформируем напряжения на выводах Vout1 и Vout2, эквивалентные содержимому регистров h’40’ и h’41’ соответственно. При этом предполагается, что оба вывода AD0 и AD1 подключены к общему проводу.

Для выполнения указанных операций нам потребуется переслать по шине пять пакетов:

1. СТАРТ.

2. Пакет адреса: Ь’01011000’.

Адрес ведомого Ь’01011(00), запись.

3. Управляющий байт 1: Ь’00000ХХ0’.

Нет сброса, активный режим, канал 0.

4. Байт данных 1:

Содержимое регистра h’40’.

5. Управляющий байт 2:

Нет сброса, активный режим, канал 1.

6. Байт данных 2:

Содержимое регистра h’41’.

7. СТОП; содержимое обоих регистров ЦАП обновляется.

Ход выполнения Программы 12.10 в точности повторяет указанную последовательность операций. После каждого возврата из подпрограммы I2C_OUT регистр ERR проверяется на нулевое значение. Если он не равен нулю, то последовательность повторяется с самого начала — повторное формирование состояний СТАРТ допускается протоколом I2С. Однако если произошел аппаратный сбой самой шины или ведомого устройства, то этот процесс может продолжаться бесконечно. Поэтому для увеличения надежности и предотвращения зависания системы необходимо предусмотреть механизм тайм-аута.

Программа 12.10 . Работа с I 2 С-совместимой микросхемой двух канального ЦАП МАХ518

ANALOG call START ; Начинаем передачу

; Байт адреса ---------------------

          movlw b’01011000’ ; Адрес ведомого, режим — ведущий-передатчик

          movwf DATA_OUT ; Копируем в промежуточный регистр

          call I2C_OUT ; Передаем

          movf ERR,f ; Проверяем ка наличие ошибок

          btfsc STATUS,Z ; ЕСЛИ ноль, TO продолжаем

             goto ANALOG ; ИНАЧЕ пробуем снова

; Управляющий байт 1 -----------

          movlw b’00000000’ ; Нет сброса, активный режим, канал 1

          movwf DATA_OUT ; Копируем в промежуточный регистр

          call I2C_OUT ; Передаем

          movf ERR, f ; Проверяем на наличие ошибок

          btfsc STATUS,Z ; ЕСЛИ ноль, TO продолжаем

             goto ANALOG ; ИНАЧЕ пробуем снова

; Байт данных 1

          movf 20h,w ; Считываем значение канала 0 из памяти

          movwf DATA_OUT ; Копируем в промежуточный регистр

          call I2C_OUT ; Передаем

          movf ERR,f ; Проверяем на наличие ошибок

          btfsc STATUS,Z ; ЕСЛИ ноль, TO продолжаем

             goto ANALOG ; ИНАЧЕ пробуем снова

; Управляющий байт 2

           moviw b’00000001’ ; Нет сброса, активный режим, канал 1

           movwf DATA_OUT ; Копируем в промежуточный регистр

           call I2C_OUT ; Передаем

           movf ERR,f ; Проверяем на наличие ошибок

           btfsc STATUS,Z ; ЕСЛИ ноль, TO продолжаем

              goto ANALOG ; ИНАЧЕ пробуем снова

; Байт данных 2

          movf 21h,w ; Считываем значение канала 1 из памяти

          movwf DATA_OUT ; Копируем в промежуточный регистр

          call I2C_OUT ; Передаем

          movf ERR,f ; Проверяем на наличие ошибок

          bcfsc STATUS,Z ; ЕСЛИ ноль, TO продолжаем

             goto ANALOG ; ИНАЧЕ пробуем скова

          call STOP

Любые варианты модулей SSP микроконтроллеров PIC поддерживают работу в режиме I2С. Ранние версии модулей позволяли использовать микроконтроллер только в качестве ведомого устройства, тогда как модуль ведущего синхронного последовательного порта MSSP автоматически обеспечивает работу микроконтроллера в качестве ведущего устройства при наличии на шине нескольких ведущих, на что, собственно, и указывает его наименование. Спецификация шины I2С разрешает наличие нескольких ведущих устройств, но, разумеется, не одновременно. Предотвращение конфликтов на шине является довольно сложной задачей, поэтому использование модуля MSSP в качестве ведущего шины I2С в данной книге рассматриваться не будет. Использование модуля MSSP в этом качестве подробно описано в документе AN7578 «Use of the SSP Module in the I 2 C MultiMaster Environment ». Мы же ограничимся изучением работы модуля MSSP в качестве ведомого устройства I2С.

На Рис. 12.18 приведена структурная схема модуля SSP, сконфигурированного для работы в качестве ведомого I2С-устройства. Для подключения к двунаправленной линии данных SDA используется вывод RC4, а к линии SCL — вывод RC3. При работе модуля в режиме I2С оба вывода должны быть сконфигурированы как входы.

Собственно ввод и вывод данных производится посредством сдвигового регистра SSPSR, который используется и для приема данных, и для их передачи.

Рис. 12.18. Структурная схема модуля MSSP, сконфигурированного для работы в качестве ведомого I 2 С-устройства

Передача

При посылке ведомым устройством данных удаленному ведущему, который при этом находится в режиме ведущий-приемник, байт данных, помещенный в буферный регистр SSPBUF, автоматически пересылается в регистр SSPSR (если тот пуст), из которого затем побитно выдается на линию SDA. Если регистр SSPSR полон, то данные на линию не выдаются, и устанавливается флаг конфликта записи.

Прием

Если ведомое устройство ожидает пакет от удаленного ведущего, то данные побитно вдвигаются через вывод SDA и после приема всех восьми битов полученный байт пересылается в регистр SSPBUF. Если ошибки переполнения не было, то модуль MSSP автоматически формирует подтверждение (АСК) во время 9-го тактовою импульса. Эта ошибка возникает в том случае, если ранее принятый байт не был в свое время считан из регистра SSPBUF.

После обнаружения состояния СТАРТ все ведомые устройства на шине принимают первый пакет от ведущего и сравнивают его содержимое со значением, записанным в регистре адреса SSPADD. При совпадении старших семи битов (0-й бит — бит направления передачи) соответствующее устройство формирует подтверждение (АСК) и готовится к обмену данными с ведущим. При этом устанавливаются оба флага — BF и SSPIF, сигнализирующие о событии на шине. Как мы уже видели на Рис. 12.15, 8-й бит первого пакета адреса указывает ведомому устройству, принимать или передавать данные до появления на шине следующего состояния СТАРТ или СТОП.

Как и в SPI-режиме, для конфигурирования модуля MSSP нам необходимо записать определенные значения в регистры управления и состояния. Формат регистров, приведенный на Рис. 12.19, соответствует четырем возможным режимам работы модуля SSP в качестве ведомого устройства I2С. В этом режиме используются те же регистры SSPSTAT и SSPCON. К сожалению, названия некоторых битов, используемых в этом режиме, например СКЕ, остаются прежними, хотя их назначение кардинальным образом меняется. По сравнению с более старыми модулями SSP модуль MSSP имеет второй регистр управления SSPCON2. За исключением 0-го и 7-го битов, этот регистр используется исключительно при работе модуля в качестве ведущего I2С.

Рис. 12.19. Регистры управления и состояния модуля MSSP при его работе в режиме ведомого I 2 С

∙ SSPEN

Установка бита SSPCON[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
разрешает работу синхронного последовательного порта. После любого сброса модуль MSSP отключен, а выводы RC3 и RC4 могут использоваться в качестве линий порта С.

∙ SSPM[3:0]

К нашему обсуждению относятся четыре комбинации этих битов выбора режима работы модуля SSP. Для простоты мы будем считать, что используется режим 7-битной адресации. При использовании 10-битной адресации сначала необходимо загружать в регистр SSPADD старший байт адреса b’1110А9А8’, а после его совпадения с принятым значением заменять на младший байт адреса Ь’А7А6А5А4А3А2А1’. Режимы Ь’0110’ и Ь’1110’ отличаются только тем, что в последнем при обнаружении состояний СТАРТ и СТОП устанавливается флаг прерывания SSP1F.

∙ BF, SSPIF

Установленный флаг BF свидетельствует о том, что с данными в регистре SSPBUF что-то произошло. Флаг SSPIF является флагом прерывания от модуля MSSP и устанавливается при любом событии на шине I2С.

Ведомый-приемник

При приеме кадра от ведущего и записи его содержимого в регистр SSPBUF устанавливается флаг BF, показывая тем самым, что новые данные доступны для обработки, а во время 9-го тактового импульса передается подтверждение (АСК). Также при этом устанавливается флаг SSPIF (PIR[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
), который может использоваться для генерации прерывания. При считывании полученного байта из буфера бит BF автоматически сбрасывается (этот флаг доступен только для чтения), однако флаг SSPIF необходимо сбрасывать вручную, как и остальные флаги прерываний.

В случае приема нового байта до считывания предыдущего значения, т. е. при установленном бите BF, он не пересылается в буфер SSPBUF. Вместо этого устанавливается флаг SSPOV, извещающий о возникновении переполнения. В этом случае подтверждение не посылается (NACK).

Ведомый-передатчик

В течение всего времени пересылки байта ведущему флаг BF остается установленным, показывая, что идет передача. Если в этот момент попытаться записать в регистр SSBUF новый байт, то вместо его пересылки в сдвиговый регистр SSPSR будет установлен флаг WCOL, извещающий о возникновении конфликта записи.

∙ SSPOV

При работе в режиме ведомый-приемник попытка считывания регистра SSPBUF до приема нового байта индицируется установкой данного флага. При этом ведущему передается NACK. Ведомый может намеренно передавать NACK, чтобы информировать ведущего о том, что тот может повторить попытку передачи позже. Это состояние сбрасывается при считывании регистра SSPBUF (при этом сбрасывается флаг BF) и ручном сбросе флага SSPOV.

∙ WCOL

Этот флаг устанавливается при попытке записи в регистр SSPBUF во время передачи, извещая о возникновении конфликта записи. Данный флаг должен быть сброшен вручную.

∙ S,P

Эти флаги индицируют обнаружение на шине состояний СТАРТ и СТОП соответственно. Обычно они имеют противоположные значения. Исключением из данного правила является их состояние после любого сброса микроконтроллера или при разрешении модуля (SSPEN — > 1) — в эти моменты оба флага сброшены.

Установленный флаг Р показывает, что шина свободна. Эта информация может потребоваться следящему за состоянием шины устройству, которое намеревается начать работать в качестве ведущего.

∙ D/A¯, R/W¯, UA

Эти флаги относятся к пакету (пакетам), передаваемым по шине после формирования состояния СТАРТ и содержащим информацию об адресе ведомого устройства и направлении передачи последующих пакетов.

Бит D/А¯ показывает, какой именно байт находится в регистре SSPBUF — данные (D) или адрес (А¯).

Бит R/W¯ информирует программу о том, в каком направлении будут передаваться последующие пакеты данных — к ведущему (R/W¯ = 1) или от ведущего (R/W¯= 0). Вообще говоря, значение данного бита соответствует значению 0-го бита (первого) пакета адреса.

Бит UA используется только в режимах с 10-битной адресацией. В этих режимах сначала сравниваются семь старших битов первого байта адреса b’11110A9A80’. Младший бит является битом направления передачи и информирует о том, что следующий пакет адреса будет передаваться ведущим (R/W¯ = 0). После приема 1-го байта адреса флаг UA автоматически устанавливается в 1, извещая программу о том, что в регистр SPPADD можно загружать младший байт адреса. После выполнения записи флаг UA автоматически сбрасывается.

∙ GCEN

Если бит разрешения общего вызова GCEN равен 1, то флаг прерывания SSPIF будет устанавливаться при приеме адреса общего вызова Ь’0000000’ независимо от значения, находящегося в регистре адреса модуля. Прием этого адреса свидетельствует о том, что ведущий собирается приступить к широковещательной рассылке всем ведомым устройствам. В более ранних вариантах модуля SSP данная функция отсутствует.

∙ CKP, SEN

При сброшенном бите СКР ведомое устройство удерживает линию SCL в состоянии НИЗКОГО уровня, запрещая, таким образом, ведущему генерацию тактовых импульсов. После установки бита СКР в 1, ведомый освобождает линию SCL, позволяя ведущему формировать тактовые импульсы для нового пакета. Хотя бит СКР может быть изменен программно в любой момент времени (т. е. вручную), растягивание тактового сигнала может выполняться автоматически.

SEN = 0

При сброшенном бите SEN (состояние по умолчанию), а также в модулях SSP более ранних версий бит СКР сбрасывается автоматически в конце каждого пакета, отсылаемого модулем ведущему. Ведомый должен устанавливать бит СКР каждый раз после загрузки содержимого нового пакета в регистр SSPBUF для высвобождения линии тактового сигнала и разрешения передачи следующего пакета. Растягивание тактового сигнала в таких ситуациях осуществляется всегда, независимо от состояния бита SEN. Точно так же работают модули, в которых этот бит отсутствует.

SEN = 1

В последних версиях модуля MSSP автоматическое растягивание тактового сигнала при установленном бите SEN разрешается как при передаче от ведомого устройства, так и при приеме. Последнее полезно в том случае, если ведущий посылает пакеты быстрее, чем ведомый успевает их обрабатывать.

∙ СКЕ

Этот бит имеется только в модулях MSSP, и при его установке электрические параметры сигналов на линиях SDA и SCL будут соответствовать спецификации шины SMBus.

Работа модуля MSSP в качестве ведомого представляет собой многоэтапный процесс, требующий от программы реакции на всевозможные события, возникающие на шине I2С. Хотя это можно реализовать с помощью простого опроса флага SSPIF регистра PIR1, в наших примерах мы будем использовать прерывания.

Прежде чем перейти к рассмотрению этих событий и знакомству с учебными программами, необходимо разобраться с инициализацией модуля MSSP. Типичный инициализационный код для микроконтроллера PIC16F877A, который должен работать в качестве ведомого с адресом h’06’, при частоте шины 100 кГц выглядит следующим образом:

          include "p16f877a.inc"

SETUP movlw b’00110110’ ; Включаем MSSP, такт, линия свободна

          movwf SSPCON ; Режим ведомого с 7-битной адресацией (0110)

          bsf STATUS,RP0 ; переключаемся в 1-й банк

          bsf SSPSTAT,SMP ; Скорость нарастания соответствует частоте 100 кГц

          bsf SSPCON2,SEN ; SEN = 1 для автоматического удержания линии тактового сигнала после приема данных

          movlw h’0C’ ; Адрес h’06’ сдвигаем влево на один бит,

          movwf SSPADD ; чтобы значение соответствовало содержимому пакета

          bsf PIE1,SSPIE ; Разрешаем прерывание от модуля SSP

          bsf INTCON,PEIE ; Разрешаем прерывания от периферийных устройств

          bsf INTCON,GIE ; Разрешаем прерывания

          bcf STATUS,RP0 ; Возвращаемся в 0-й банк

В этом фрагменте:

1. В регистры управления и состояния модуля MSSP заносятся значения, соответствующие заданию.

2. Адрес ведомого (число h’06’, сдвинутое влево для соответствия семи старшим битам пакета адреса) заносится в регистр SSPAD.

3. Установкой бита маски SSPIE совместно с битами PEIE и GIE разрешается прерывание от модуля MSSP (см. Рис. 7.5 на стр. 223).

Теперь, когда у нас есть код для инициализации модуля MSSP и системы прерываний, можно приступать к написанию процедуры обработки прерывания, распознающей различные события, происходящие на шине I2С. Любое допустимое событие приведет к передаче управления из фоновой программы в обработчик прерывания. Даже если микроконтроллер будет находиться в «спящем» режиме, это событие вызовет установку бита SSPIF и «пробуждение» микроконтроллера.

Вот эти события:

1. Ведущий-передатчик: принятый пакет был пакетом адреса

S = 1 Последним состоянием на шине было состояние СТАРТ.

R/W¯ = 0 Ожидается передача данных от ведущего.

D/А¯ = 0 Это пакет адреса.

BF = 1 Буфер полон.

Для сброса флага BF необходимо прочитать регистр SSPBUF, даже если пакет адреса, присланный ведущим, будет проигнорирован. Если этого не сделать, следующий байт, посланный ведущим, вызовет переполнение буфера (SSPOV —> 1) и модуль отошлет NACK.

Если бит SEN установлен в 1, то бит СКР будет автоматически сброшен и на линию SCL будет выставлен НИЗКИЙ уровень. Когда это станет возможным, необходимо будет установить бит СКР в 1 для разрешения работы ведущего.

2. Ведущий-передатчик: принятый пакет был пакетом данных

После передачи адресного пакета ведущий посылает один или более пакетов данных. Чтобы избежать возникновения переполнения и гарантировать отсылку подтверждения АСК, ведомое устройство должно считывать каждый из этих пакетов. Также при установленном бите SEN необходимо манипулировать битом СКР, как и при обработке предыдущего события. Содержимое регистра STATUS для этого события отличается от предыдущего только значением бита D/A¯.

S = 1 Последним состоянием на шине было состояние СТАРТ.

R/W¯ = 0 Ожидается передача данных от ведущего.

D/A¯ = 1 Это пакет данных.

BF = 1 Буфер полон.

3. Ведущий-приемник: принятый пакет был пакетом адреса

Обмен на шине начинается с посылки ведущим пакета адреса с установленным битом R/W¯, извещающим ведомого, что от него ожидается передача пакетов данных ведущему. После распознавания ведомым своего адреса или адреса общего вызова при установленном бите GCEN, состояния битов регистра SSPSTAT будут следующими:

S = 1 Последним состоянием на шине было состояние СТАРТ.

R/W¯ = 1 Ожидается передача данных к ведущему.

D/А¯ = 0 Это пакет адреса.

BF = 0 Буфер свободен для передачи.

Заметьте, что в данной ситуации бит BF сброшен. Дело в том, что при работе модуля в режиме ведущего-приемника флаг BF используется для того, чтобы сообщать программе о готовности регистра SSPBUF к загрузке байта данных, посылаемого ведущему. Поэтому, в отличие от 1-го события, нам нет необходимости считывать содержимое регистра SSPBUF.

После распознавания адреса ведомый может послать первый байт данных ведущему, загружая его в регистр SSPBUF и устанавливая бит СКР для высвобождения вывода SCL. Расширение тактового сигнала производится автоматически при получении пакета от ведущего-приемника, независимо от состояния бита SEN.

4. ведущий-приемник: принятый пакет был пакетом данных

Это событие похоже на предыдущее, и бит СКР в этом случае действует аналогичным образом. Новый байт нельзя загружать в регистр SSPBUF до тех пор, пока не будет сброшен бит BF, в противном случае установится бит WCOL.

Значения битов регистра SSPSTAT идентичны значениям для предыдущего события, за исключением установленного бита D/А, показывающего, что последним принятым пакетом был пакет данных:

S = 1 Последним состоянием на шине было состояние СТАРТ.

R/W¯ = 1 Ожидается передача данных к ведущему.

D/A¯ = 1 Это пакет данных.

BF = 0 Буфер свободен для передачи.

5. Ведущий-приемник: ведущий послал NACK

Эта ситуация обычно возникает, когда ведущий по какой-либо причине не хочет больше принимать данные от ведомого. Сигнал NACK указывает на завершение обмена, и при его приеме ведомый модуль сбрасывает логику I2С.

S = 1 Последним событием на шине было состояние СТАРТ.

R/W¯ = 0 Бит R/W¯ сбрасывается ведомым.

D/A¯ = 1 Это пакет данных.

BF = 0 Буфер свободен для передачи.

Появление NACK определяется по сброшенному биту BF при нулевом бите R/W¯. Это конфликтное состояние, поскольку такое сочетание битов говорит о том, что был принят пакет данных от ведущего, но буфер при этом остался пустым!

В качестве примера давайте представим, что ведомый микроконтроллер PIC16F877A с адресом h’06’ контролирует восемь температурных датчиков, подключенных к входам встроенного модуля АЦП (см. главу 14). Если ведущий хочет считать значение одного из этих оцифрованных каналов, то он сначала посылает ведомому номер канала (ведущий-передатчик) N, а затем, перейдя в режим ведущего-приемника, принимает от ведомого запрошенные данные.

Предположим, что подпрограмма GET_ANALOG (Программа 14.1, стр. 516) уже написана.

Последовательность операций может быть следующей:

1. Ведущий формирует на шине состояние СТАРТ, после чего адресует ведомого с адресом h’06’, приказывая ему принять следующий пакет данных (ведущий-передатчик).

2. Ведущий посылает пакет данных, содержащий номер канала 0…7.

3. Ведущий формирует состояние ПОВТСТАРТ для ведомого с адресом h’06’, требуя на этот раз, чтобы последний передал ему следующий пакет данных (ведущий-приемник).

4. Ведомый удерживает линию тактового сигнала, дожидаясь завершения преобразования по выбранному каналу.

5. Ведомый посылает требуемые значения.

6. Ведущий отвечает NACK, сообщая о завершении обмена.

Для ясности разобьем нашу программу на две отдельные процедуры. Кроме того, предположим, что для сохранения контекста используются ячейки, отображенные на все банки памяти. В случае микроконтроллера PIC16F877A эти ячейки располагаются по адресам h’60’…h’7F’. Переменные, используемые в программе, располагаются в 0-м банке.

В Программе 12.11 приведен код процедуры обработки прерывания, сохранение и восстановление контекста в которой осуществляются в соответствии с принципами, обсуждавшимися ранее (см. стр. 216). Перед восстановлением контекста бит СКР устанавливается в 1 для разрешения формирования тактового сигнала, а флаг SSPIF сбрасывается.

Программа 12.11. Процедура обработки прерывания I 2 С-совместимого регистратора температуры

; ***********

; * ФУНКЦИЯ: Обработчик для передачи значения N-ro канала по шине I 2 С *

; * ВХОД: Произошло событие на шине *

; * ОКРУЖЕНИЕ: Использует п/п GET_ANALOG, I2C_HANDLER *

; ***********

; Сначала сохраним контекст ------------

ISR movwf _work ; Сохраняем W

     swapf STATUS,w ; и регистр STATUS

     movwf _status

; Проверяем, установлен ли флаг SSPIF? —

      bcf STATUS,RP0 ; Переключаемся ка 0-й банк

      bcf STATUS,RP1

      btfss PIR1,SSPIF ; Это прерывание от MSSP?

         goto ISR_EXIT ; ЕСЛИ нет, TO выходим

      bsf STATUS,RP1 ; ИНАЧЕ переключаемся на 1-й банк

      movf SSPSTAT,w ; Считываем состояние из SSPSTAT

      bcf STATUS,RP1 ; и возвращаемся в 0-й банк

      andlw b’00101101’ ; Обнуляем все биты, кроме S, D/A, R/W и BF,

      movwf I2C_STATUS ; и копируем полученное значение во временный регистр

      clrf I2C_ERROR ; Обнуляем признак ошибки

      call I2C_HANDLER ; Теперь обработаем событие на шине I2C

; Восстановим контекст —

ISR_SXIT bcf PIR1,SSPIF ; Сбрасываем флаг прерывания

       bsf SSPCON,CKP ; Освобождаем линию такт. сигнала

       swapf _status,w ; Восстанавливаем исходное значение STATUS

       swapf _work,f ; Восстанавливаем исходное значение W,

       swapf _work,w ; не изменяя битов регистра STATUS,

       retfie ; и возвращаемся в фоновую программу

Собственно, обработчик просто проверяет состояние флага SSPIF и, если тот не установлен, процедура завершается. В реальной жизни прерывания могут генерироваться несколькими источниками, что повлечет за собой переделку этого фрагмента программы для проверки флагов всех используемых прерываний.

Если флаг SSPIF установлен, то содержимое регистра состояния SSPSTAT копируется из 1-го банка в регистр I2C_STATUS, расположенный в более удобном 0-м банке (перед этим сбрасываются не интересующие нас биты). Регистр I2C_ERROR также обнуляется. При обнаружении ошибочной ситуации в этот регистр будет занесено ненулевое значение для информирования фоновой программы.

После инициализации управление передается в подпрограмму, которая, собственно, и анализирует события, происходящие на шине I2С. Код данной подпрограммы приведен в Программе 12.12. Эта программа состоит из пяти блоков, каждый из которых соответствует одному из состояний шины I2С, перечисленных на стр. 412. Выбор требуемого блока осуществляется с использованием команды xorlw (см. стр. 146), с помощью которой проверяется равенство копии регистра SSPSTAT и константы, соответствующей тому или иному состоянию. При обнаружении равенства выполняются действия в соответствии с нашим алгоритмом или же просто вспомогательные операции, которые позволят модулю MSSP продолжить работу с корректного состояния. Так, нам ничего не нужно делать при возникновении 5-го состояния, при котором ведущий отсылает ведомому NACK, поскольку модуль MSSP будет сброшен автоматически. Если соответствий не обнаружено, декрементируется переменная I2C_ERROR, сигнализируя об ошибке.

Программа 12.12. Обработчик событий шины I 2 С регистратора температуры

; ************

; * ФУНКЦИЯ: Анализирует события вины I 2 С и реагирует требуемым образом *

; * ВХОД: Копия SSPCON в I2C_STATUS *

; * ВЫХОД: Выполняются требуемые действия I2C_ERROR = -1, ЕСЛИ событие не было распознано *

; ************

I2C_HANDLER ; 1-е событие? (пакет адреса, ведущий-передатчик) --------

            movf I2C_STATUS,w ; Берем копию содержимого SSPSTAT

            xorlw b’00001001’ ; Проверяем наличие S=1, D/A=0, R/W=0, BF=1

            btfss STATUS,Z ; Равно?

              goto STATE2 ; ЕСЛИ нет, TO проверяем 2-е событие

            movf SSPBUF,w ; ИНАЧЕ читаем буфер для сброса флага BF

; 2-е событие? (пакет данных, ведущий-передатчик) ---------

STATE2 movf I2C_STATUS,w ; Берем копию содержимого SSPSTAT

            xorlw b’00101001’ ; Проверяем наличие S=1, D/A=1, R/W=0, BF=1

            btfss STATUS,Z ; Равно?

               goto STATE3 ; ЕСЛИ нет, TO проверяем 3-е событие

            movf SSPBUF,w ; ИНАЧЕ считываем номер канала

            call GET_ANALOG ; Оцифровываем сигнал N-го канала

            movwf TEMP ; и сохраняем результат в регистре ТЕMР

; 3-е событие? (пакет данных, ведущий-передатчик) ----------

STATE3 movf I2C_STATUS,w ; Берем копию содержимого SSPSTAT

            xorlw b’00001100’ ; Проверяем наличие S=1, D/A=0, R/W=1, BF=0

            btfss STATUS,Z ; Равно?

               goto STATE4 ; ЕСЛИ нет, ТО проверяем 4-е событие

            movf TEMP,w ; ИНАЧЕ берем значение температуры

            movwf SSPBUF ; и помещаем в буферный регистр для передачи

; 4-е событие? (пакет данных, ведущий-передатчик) -----------

STATE4 movf I2C_STATUS,w ; Берем копию содержимого SSPSTAT

            xorlw b’00101100’ ; Проверяем наличие S=1, D/A=1, R/W=1, BF=0

            btfss STATUS,Z ; Равно?

               goto STATES ; ЕСЛИ нет, TO проверяем 5-е событие

; Ничего не делаем!!!

; 5-е событие? (ведущий отослал ведомому NACK) ------------

STATE5 movf I2C_STATUS,w ; Берем копию содержимого SSPSTAT

            xorlw b’00101000’ ; Проверяем наличие S=1, D/A=1, R/W=0, BF=0

             btfss STATUS,Z ; Равно?

               decf I2C_ERROR,f ; ЕСЛИ нет, TO сообщаем об ошибке

             return

Еще один пример использования модуля SSP приведен в документе AN734 «Using the PIC Microcontroller SSP for Slave I 2 C Communications ».

Как и в случае с протоколом SPI, большинство Си-компиляторов для микроконтроллеров PIC имеют встроенные функции для реализации протокола I2С, что позволяет отказаться от написания собственных функций, манипулирующих различными битами регистров специального назначения.

В качестве примера рассмотрим Программу 12.13, написанную для компилятора CCS и выполняющую действия, аналогичные ассемблерной программе, фрагменты которой были приведены в Программах 12.9 и 12.10.

Программа 12.13. Взаимодействие с ЦАП MAX518 на Си

#include <16F84.h>

/* 0-й бит порта А — SCL, 1-й бит порта А — SDA, режим ведущего, протокол Fast */

#use i2c(master, scl=PIN_A0, sda=PIN_A1, fast)

#byte DATA_X = 0x20

#byte DATA_Y = 0x21

void MAX518 (unsigned int channel_0, unsigned int channel_1);

void irain(void)

{

/* Различный код * /

МАХ518(DATA_X, DATA_Y); /* Передаем два байта данных * /

/* Остальной ход * /

}

void MAX518(unsigned int channel_0, unsigned int channel_1)

{

i2c_start(); /* Формируем состояние СТАРТ * /

i2c_write(0x58); /* Передаем адрес ведомого * /

i2c_write(0); /* Посылаем 1-й управляющий байт * /

i2c_write(channel_0); /* Посылаем данные 0-го канала * /

i2c_write(0x01); /* Посылаем 2-й управляющий байт * /

i2c_write(channel_1); /* Посылаем данные 1-го канала * /

/* Обновляем оба канала * /

i2c_stop(); /* Формируем состояние СТОП * /

}

В этой программе используются следующие встроенные функции компилятора CCS:

∙ i2c_start();

Формирует состояние СТАРТ.

∙ i2c_stop();

Формирует состояние СТОП.

∙ i2c_read();

Считывает один байт с шины. Если необязательный параметр равен 0, то в ответ на принятые данные будет возвращен NACK. При работе в режиме ведущего также генерирует тактовый сигнал.

∙ i2c_write(value);

Передает по шине один байт. При работе в режиме ведущего также генерирует тактовый сигнал.

∙ #use i2c(master, scl=PIN_A0, sda=PIN_A1, fast)

С помощью этой директивы программист сообщает компилятору о том, какие выводы будут использоваться для подключения к линиям шины I2С, тип используемого протокола (стандартный или высокоскоростной), а также режим работы модуля (ведущий или ведомый). К моменту написания книги компилятор не поддерживал возможности модуля MSSP по работе в качестве ведущего, поэтому такие функции реализованы программно. Функции ведомого могут быть реализованы аппаратно модулем MSSP, если в директиве #use i2c() указать опцию FORCE_HW.

* * *

Отличительной особенностью всех последовательных протоколов, рассмотренных нами до настоящего момента, является то, что ведущее устройство генерирует тактовый сигнал, благодаря которому ведомое устройство может принимать и передавать данные в синхронном режиме. Альтернативный подход к передаче данных базируется на допущении, что передатчик и приемник работают примерно с одной и той же скоростью. Такой асинхронный протокол использовался в системах передачи данных на протяжении более ста лет для передачи алфавитно-цифровых символов по телеграфу, телефону и радиоканалу.

Одной из особенностей ранних компьютеров, создававшихся в 40-х и 50-х годах, было интенсивное заимствование существовавших к тому времени технологий. А одним из основных элементов любой машинно-ориентированной системы является терминал данных. В то время в области телекоммуникаций широко использовались телетайпы (TTY). Последовательные данные преобразовывались в параллельный формат самим терминалом, который также выполнял функции клавиатуры и печатающего устройства.

До начала 80-х годов телетайпы были исключительно электромеханическими устройствами, управляющимися синхронными электродвигателями. То есть синхронность работы удаленных терминалов гарантировалась только в течение короткого интервала времени. Для устранения этой проблемы каждому передаваемому слову предшествовал старт-бит, а после него передавался один или более стоп-битов. Типичный пример показан на Рис. 12.20. В свободном состоянии линии на ней присутствует лог. 1 (обрыв). Появление сигнала лог. 0 сигнализирует о начале слова. Завершает передачу слова сигнал лог. 1. Электромеханические терминалы обычно печатали со скоростью десять символов в секунду и требовали не менее двух стоп-битов. Для 8-битных слов данных это соответствует скорости передачи, равной 110 бит в секунду или 110 бод.

Рис. 12.20. Передача строки сообщения «РIС» по асинхронному последовательному каналу с проверкой четности и, как минимум, одним стоп-битом

Первый полностью электронный терминал требовал наличия всего одного стоп-бита и мог печатать со скоростью 300 символов в секунду, обеспечивая скорость передачи 300 бод. По традиции, для каналов передачи данных используются скорости, кратные 300, например 1200, 2400, 4800, 9600 и т. д. Последовательный порт ПК может работать на скоростях до 115 200 бод. Однако придерживаться этих значений, кратных 300, вовсе не обязательно — главное, чтобы приемник и передатчик работали с одинаковой номинальной скоростью.

Обычно приемник при обнаружении входящих данных пытается прочитать значение каждого бита примерно в середине интервала его передачи. То есть на интервале передачи 10 бит будет допустимым уход частоты в пределах ±0.5 бита. Соответственно частоты приемника и передатчика должны отличаться друг от друга не более чем на ±5 %, а их ресинхронизация будет производиться в начале каждого слова данных.

Несмотря на не самую большую эффективность, описанный асинхронный протокол имеет огромное преимущество, заключающееся в том, что он является международным стандартом. Существует несколько его вариантов, к примеру, размер слова может варьироваться от 5 до 9 бит. В нашем примере длина слова равна 8 бит, причем восьмой бит используется для контроля ошибок. Значения случае малых скоростей в тело цикла может потребоваться добавить дополнительные команды nop.

При использовании этой макрокоманды формирования задержки базовые подпрограммы ввода/вывода, код которых приведен в Программе 12.14, похожи на аналогичные подпрограммы для протокола SP1. Подпрограмма PUTCHAR просто выдает на вывод ТХ сигнал НИЗКОГО уровня в течение двух периодов Baud_delay, а затем изменяет состояние вывода восемь раз в соответствии с содержимым регистра DATA_OUT, начиная с младшего бита, т. е. в обратном порядке по сравнению с протоколами SPI/I2C. В конце на вывод ТХ выдается сигнал ВЫСОКОГО уровня для формирования сигнала СТОП.

Программа 12.14. Подпрограммы асинхронного приема и передачи данных

; **************

; * ФУНКЦИЯ: Передает 8-битное значение в асинхронном режиме *

; * ФУНКЦИЯ: Скорость передачи: 1200…9600 для XTAL 1…20 МГц *

; * РЕСУРСЫ: Макрокоманда BAUD_DELAY, формирующая задержку 0.5 битового интервала; COUNT *

; * ВХОД: 8-битное слово данных в DATA_OUT, предопределенные константы XTAL и BAUD *

; * ВЫХОД: Содержимое DATA_OUT обнуляется, байт передан *

; **************

PUTCHAR movlw 8 ; Восемь битов данных

               movwf COUNT

               bcf PORTA,ТХ ; Старт-бит

               Baud_delay ; Задержка 2x0.5 бита

               Baud_delay

; Теперь выдвигаем байт данных, начиная с младшего бита

PUTCHAR_LOOP rrf DATA_OUT,f ; Сдвигаем вправо через перенос

                btfss STATUS,С ; Проверяем флаг переноса

                  goto ITS_A_0 ; ЕСЛИ 0, ТО передаем 0

                bsf PORTA,TX ; ИНАЧЕ передаем 1

                  goto PUTCHAR_NEXT ; и продолжаем

ITS_A_0 bcf PORTA,TX ; Выдаем 0

PUTCHAR_NEXT

                Baud_delay ; Задержка на 1 бит

                Baud_delay

                decfsz COUNT,f ; Повторяем восемь раз

                   goto PUTCHAR_LOOP

                bsf PORTA,ТХ; Стоп-бит

                Baud_delay

                Baud_delay

                return

; **************

; * ФУНКЦИЯ: Принимает 8-битное значение в асинхронном режиме *

; *               : Скорость передачи: 1200…9600 для XTAL 1…20 МГц *

; * РЕСУРСЫ: Макрокоманда BAUD_DELAY, формирующая задержку 0.5 битового интервала; COUNT *

; * ВЫХОД: Принят байт в DATA_IN *

; * ВЫХОД: Если нет ошибки кадрирования, ERR = 0, ИНАЧЕ ERR = -1*

; **************

GETCHAR movlw 8 ; Восемь битов данных

               movwf COUNT

               clrf ERR ; Обнуляем байт признака ошибки

GETC HAR_START

               btfsc PORTA,RX ; Ожидаем появления 0

                 goto GETCHAR_START

               Baud_delay ; Ждем в течение 0.5 бита

               btfsc PORTA,RX ; Все еще 0?

                 goto GETCHAR_START

               Baud_delay ; ЕСЛИ да, TO ждем в течение 1 бита

               Baud_delay

GETCHAR_LOOP bcf STATUS,С ;Сбрасываем флаг переноса

                rrf DATA_IN,f ; Вдвиг аем 0 в байт данных

                btfsc PORTA,RX ; На входе ВЫСОКИЙ уровень?

                bsf DATA_IN,7 ; ЕСЛИ да, ТО устанавливаем бит

                Baud_delay

                Baud_delay

                decfsz COUNT,f ; Повторяем восемь раз

                   goto GETCHAR_LOOP

                btfss PORTA,RX; Проверяем приход стоп-бита (1)

                  decf ERR,f; ЕСЛИ 0 , ТО сообщаем об ошибке

                return

Подпрограмма приема GETCHAR более сложна. Появление на выводе RX НИЗКОГО уровня расценивается как приход старт-бита. Однако если осуществлять выборку значений последующего потока данных с периодичностью, равной длительности битового интервала (два включения макроса Baud_delay), то, поскольку этот момент может соответствовать моменту окончания битового интервала, уход любой из двух частот может привести к появлению ошибок. Чтобы избежать этого, состояние вывода RX считывается повторно после задержки, равной половине битового интервала, чтобы еще раз убедиться в наличии старт-бита. Если это окажется так, то последующие выборки осуществляются с периодом, равным двум битовым интервалам. При этом моменты выборок будут приходиться примерно на середину интервала. Лучших результатов можно достичь, считывая состояние вывода с большей частотой (передискретизация) и принимая мажоритарное решение на основании считанных значений.

После приема восьми битов данных стоп-бит проверяется на равенство лог. 1. Если стоп-бит равен 0, значит, произошла ошибка кадрирования. Эта ситуация сигнализируется возвратом —1 в ERR. При использовании других, более развитых схем могут возвращаться сообщения об ошибках разных типов. Например, при использовании контроля четности может быть возвращена ошибка четности.

В качестве примера рассмотрим фрагмент кода, осуществляющий передачу 3-символьного сообщения «РIС». К счастью, ассемблер предоставляет программисту возможность вместо ASCII-кодов символов записывать сами символы в одинарных кавычках, как описано на стр. 267.

; Передадим строку "PIC"

        movlw ’P’ ; Аналогично movlw h’50’ (ASCII-код символа «Р»)

        movwf DATA_OUT ; Помещаем в память данных

        call PUTCHAR ; Передаем

        movlw ’I’ ; Аналогично movlw h’49’ (ASCII-код символа «I»)

        movwf DATA_OUT ; Помещаем в память данных

        call PUTCHAR ; Передаем

        movlw ’C’ ; Аналогично movlw h’43’ (ASCII-код символа «С»)

        movwf DATA_OUT ; Помещаем в память данных

        call PUTCHAR ; Передаем

На самом деле такая реализация асинхронного обмена по последовательному каналу годится только в самых простых случаях. Например, если не отслеживать непрерывно состояние вывода RX, можно пропустить передачу или потерять синхронизацию. Кроме того, при таком подходе трудно реализовать дуплексную связь, не говоря уже о том, что большая часть вычислительной мощности микроконтроллера в данном случае тратится на выполнение циклов задержки. Эту ситуацию можно несколько улучшить, используя для формирования задержек внутренний таймер и работая по прерываниям. Однако в большинстве микроконтроллеров PIC, выпускающихся в «многовыводных» корпусах (более 28 контактов), для реализации асинхронного обмена данными имеется встроенный коммуникационный порт.

Одним из первых применений новых технологий производства БИС, появившихся в конце 60-х годов, было создание отдельной микросхемы асинхронного последовательного порта, называемого универсальным асинхронным приемопередатчиком (Universal Asynchronous Receiver Transmitter — UART). К тому времени, когда начались разработки процессоров, эта микросхема UART уже производилась вовсю. В большинстве ПК, даже выпущенных в 70-х годах, имелся последовательный порт на базе микросхемы UART, также как и в большинстве современных систем. Помимо узлов, отвечающих за побитовую передачу данных, контроль ошибок и обработку прерываний, большинство микросхем имели встроенный контроллер скорости передачи, который можно было конфигурировать программно для задания требуемой скорости.

Базовая структура микросхемы UART показана на Рис. 12.21. В любой подобной микросхеме можно выделить три основные части. Сдвиговый регистр передатчика преобразует исходные данные из параллельных в последовательные для выдачи через вывод ТХ, обрамляя их старт- и стоп-битами. С этим регистром связан буферный регистр, хранящий данные для последующей передачи. Регистр состояния содержит флаг (TBUF на рисунке), показывающий, что буфер пуст и готов для записи новых данных.

Рис. 12.21. Основные элементы модуля UART

Сдвиговый регистр приемника удаляет из принятой посылки старт- и стоп-биты, перегружая принятые данные в тот или иной буферный регистр. Одновременно с этим устанавливается флаг (RBUF в нашем случае), благодаря чему программа может определить наличие новых данных. Эти данные необходимо прочитать из буферного регистра до сборки следующего пакета, в противном случае возникнет переполнение, и данные будут потеряны.

Прием и передача кадра не взаимосвязаны, т. е. могут перекрываться, однако скорости обмена обычно делаются одинаковыми.

Реальные приемопередатчики UART являются более сложными устройствами, позволяющими, например, передавать данные различной разрядности, а также обеспечивающие обнаружение различных ошибок. При этом, разумеется, усложняется структура соответствующих регистров управления и состояния. Тем не менее в основе модуля последовательного коммуникационного интерфейса (SCI), реализованного в микроконтроллерах PIC и более известного под названием USART (Universal Synchronous-Asynchronous Receiver Transmitter — универсальный синхронно-асинхронный приемопередатчик) (см. Рис. 12.22), лежит все та же архитектура UART. Этот модуль поддерживает два вида последовательного обмена: описанный выше асинхронный, осуществляемый при сброшенном бите SYNC регистра TXSTA[4]Здесь имеется в виду не размер данных, которыми оперирует микроконтроллер, а число битов, использующихся для записи слова команды. — Примеч. пер.
(состояние по умолчанию после сброса), и синхронный (SYNC = 1), при котором старт- и стоп-биты не используются. В последнем случае под линию тактового сигнала задействуется дополнительный вывод RC6/CK — выход при передаче данных и вход при приеме. Вывод RC7/DT используется в качестве линии ввода/вывода данных. При работе в синхронном режиме данные могут посылаться либо побайтно, либо сплошным потоком. Именно из-за возможности работать в синхронном режиме этот модуль и называется USART, а не UART. Сейчас мы все свое внимание уделим асинхронному режиму, поэтому на Рис. 12.22, где показан формат обоих регистров состояния, обозначены только биты, соответствующие этому режиму работы.

Рис. 12.22. Модуль синхронного последовательного интерфейса SCI, сконфигурированный для работы в асинхронном режиме.

Основными элементами модуля USART являются сдвиговые регистры приема и передачи, а также связанные с ними буферные регистры и регистры состояния, Для разрешения работы всего модуля USART необходимо установить бит SPEN регистра состояния приемника RCSTA (RCSTA[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
) в 1. Оба вывода RC6 и RC7, использующиеся соответственно для передачи и приема данных, должны быть сконфигурированы как входы.

Передача

Работа передатчика разрешается установкой бита TXEN регистра состояния передатчика TXSTA (TXSTA[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
). Для передачи слова данных необходимо записать его в регистр передатчика TXREG, откуда он будет перегружен в сдвиговый регистр и побитно передан с вывода ТХ. Если требуется работать с 9-битными данными, то битТХ9 (TXSTA[6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
) должен быть установлен в 1, а девятый бит данных необходимо записать в 0-й бит того же регистра перед загрузкой младших восьми битов в регистр TXREG. Если сдвиговый регистр передачи не пуст, т. е. передача предыдущего слова еще не закончена, то новое значение останется в буфере TXREG и будет перегружено в сдвиговый регистр только после завершения передачи.

Первый бит регистра состояния TXSTA отображает состояние сдвигового регистра передатчика, тогда как флаг прерывания TXIF, расположенный в регистре PIR1, автоматически устанавливается в 1 при опустошении буфера TXREG (при его готовности к загрузке новых данных). Если это прерывание требуется в программе, необходимо установить соответствующий бит маски TXIE регистра PIE1 (PIE1 [4]Здесь имеется в виду не размер данных, которыми оперирует микроконтроллер, а число битов, использующихся для записи слова команды. — Примеч. пер.
); см Рис. 7.5 на стр. 223. Флаг ТХIF автоматически сбрасывается при записи в регистр TXREG, поэтому нет необходимости вручную обнулять его в процедуре опроса или в обработчике прерывания.

Прием

После обнаружения на выводе RX старт-бита последующие восемь или девять битов задвигаются в сдвиговый регистр приемника, откуда после завершения приема перегружаются в 2-уровневый буферный регистр RCREG, независимо от того, что в этот момент происходит в секции передатчика. Причем принятые данные сохраняются в регистре верхнего уровня, а содержимое последнего автоматически перегружается в регистр нижнего уровня при условии, что в нем отсутствуют данные, ожидающие считывания. При появлении в этом регистре данных устанавливается флаг прерывания от приемника RCIF, который может использоваться для генерации прерывания при установленном бите маски RCIE регистра PIE1 (а также установленных битах GIE и PEIE). При чтении регистра флаг RCIF автоматически сбрасывается. Если при этом в регистре верхнего уровня находились очередные данные, они перегружаются в регистр младшего уровня, и флаг RCIF устанавливается снова.

Если в момент приема очередного слова данных 2-уровневый буфер приемника окажется полон, то устанавливается флаг ошибки переполнения OERR (RCSTA[1]Этот сайт посвящен оригинальному изданию книги на английском языке, и все перечисленные ниже материалы представлены также на английском. — Примеч. ред.
), а принятое значение теряется. При этом оба слова, находящиеся в буфере, по-прежнему доступны для чтения. Однако для сброса флага OERR необходимо сбросить логику приемника, сбросив бит CREN (RCSTA[4]Здесь имеется в виду не размер данных, которыми оперирует микроконтроллер, а число битов, использующихся для записи слова команды. — Примеч. пер.
), а затем установив его снова.

Флаг ошибки кадрирования FERR в RCSTA[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
устанавливается в 1, если после приема битов данных не было обнаружено стоп-бита. Флаг FERR (как и девятый бит принятых данных) буферизуется вместе с принимаемыми данными. Поэтому значение указанного флага необходимо проверять перед считыванием содержимого RCREG, поскольку во время этой операции изменяется состояние буфера и, соответственно, значение указанных битов.

Все версии модуля USART позволяют работать с 8- или 9-битными данными независимо при передаче и при приеме. Для последнего необходимо сбросить бит RX9 регистра RCSTA (RCSTA[6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
). Обычно этот дополнительный бит используется для контроля четности. Другим применением 9-битных данных является реализация сети из асинхронных устройств. В этом случае девятый бит используется в качестве селектора, показывающего содержимое пакета — данные или адрес устройства. Примитивная сеть, в которой используется этот принцип, изображена на Рис. 12.23. При 8-битном адресе можно адресовать до 255 ведомых устройств (один адрес при этом резервируется для широковещательных вызовов).

Для облегчения работы в подобной сети последние версии модуля US ART можно сконфигурировать таким образом, чтобы при приеме пакета с установленным девятым битом автоматически устанавливался флаг RCIF. Эта функция включается установкой в 1 бита ADDEN (RCSTA[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
). При одновременно установленных битах ADDEN и RX9 любой кадр со сброшенным старшим битом будет игнорироваться, а принимаемые данные не будут загружаться в буфер приемника. Если же старший бит окажется равным 1, то принятый байт будет скопирован из сдвигового регистра приема в буфер приемника с одновременной установкой флага RCIF. Ведомое устройство может прочитать этот адрес из RCREG, при этом флаг RCIF будет сброшен. Если адрес верен, то ведомый может сбросить бит ADDEN и принимать все последующие кадры данных обычным образом. При этом ведомый может продолжать контролировать значение девятого бита в RX9D, прекращая прием при обнаружении кадра с установленным 9-м битом.

Рис. 12.23. Локальная сеть, использующая асинхронный последовательный протокол

Контроллер скорости обмена SPBRG

Этот узел представляет собой программируемый 8-битный счетчик, на выходе которого имеется отключаемый делитель на 4. Конфигурация данного счетчика может задаваться пользователем для получения частот выборки и сдвига, соответствующих желаемой скорости обмена. Отталкиваясь от значения частоты кварцевого генератора микроконтроллера, получаем:

Скорость обмена = (XTAL x 106)/(64 x (X + 1)) — низкоскоростной режим (BRGH = 0),

Скорость обмена = (XTAL x 106)/(16 х (X + 1) — высокоскоростной режим (BRGH = 1),

где X — 8-битное число, находящееся в регистре SPBRG. При использовании низкоскоростного режима значение X определяется из выражения

X = ((XTAL x 106)/64 x (BAUD)) — 1. Так, если при частоте резонатора 20 МГц нам потребуется скорость обмена, равная 9600 бод, то при Х = 31 действительное значение скорости будет равно 9766, т. е. ошибка составит ±1.73 %. При частоте резонатора, равной 20 МГц, максимальная скорость обмена составляет 312 500 бод, а минимальная — 1221 бод. Скорость передачи, равную 1.25 Мбод, можно получить с резонатором частотой 20 МГц, используя высокоскоростной режим модуля при Х = 1.

В действительности контроллер скорости обмена формирует тактовый сигнал с частотой в 16 раз больше заданной скорости, чтобы модуль мог за время передачи бита сделать три выборки в окрестностях середины битового интервала и принять мажоритарное решение о значении этого бита. Такая схема увеличивает надежность передачи данных в системах с высоким уровнем помех.

Чтобы проиллюстрировать использование модуля USART, перепишем наши подпрограммы GETCHAR и PUTCHAR таким образом, чтобы они использовали аппаратные возможности модуля. Первым делом (в основной программе) мы должны сконфигурировать контроллер скорости обмена, а также регистры управления и состояния приемника и передатчика. Предполагая, что константы XTAL и BAUD уже определены программистом, возложим вычисление числа X, которое мы впоследствии запишем в регистр SPBRG, на ассемблер. С учетом всего сказанного инициализационный код будет выглядеть следующим образом:

           include "p16f877а. inc"

           #define BAUD d’4800’ ; Скорость обмена 4800 бод

           #define XTAL d’8’ ; 8-МГц резонатор

           #define X ((XTAL*d’1000000’)/(d’64’*BAUD))-1

START bsf STATUS,RP0 ; Переключаемся в 1-й банк

           movlw X ; Загружаем X в контроллер скорости обмена

           movwf SPBRG

           movlw b’00100000’ ; 8 битов данных, передатчик включен,

           movwf TXSTA ; низкоскоростной режим

           bcf STATUS,RP0 ; Возвращаемся обратно в 0-й банк

           movlw b’10010000’ ; USART включен, 8-битные данные

           movwf RCSTA ; Приемник включен

Заметьте, что для корректной работы модуля USART в микроконтроллерах линейки PIC16F87X требуется, чтобы оба вывода RX и ТХ были сконфигурированы как входы. Поскольку после сброса микроконтроллера выводы находятся в этом состоянии по умолчанию, в приведенном фрагменте отсутствуют команды для конфигурирования регистра TRISC. Как было отмечено в примечании на стр. 426, другие члены семейства могут потребовать других настроек для выводов RX и ТХ.

Код самих подпрограмм приведен в Программе 12.15. Подпрограмма PUTCHAR просто опрашивает флаг TXIF, ожидая его установки, а затем копирует байт данных в регистр передачи TXREG.

Подпрограмма приема символа GETCHAR будет немного сложнее из-за наличия контроля ошибок. Подпрограмма постоянно опрашивает состояние флага RCIF, который устанавливается при наличии доступных для чтения данных. При отсутствии каких-либо проблем в переменной ERR возвращается число h’00’, при возникновении ошибки кадрирования возвращается -1, при переполнении —2, а при одновременном обнаружении обеих ошибок —3. В последних двух случаях осуществляется сброс бита OERR посредством сброса логики приемника. После проверки на наличие ошибок данные считываются из буфера приемника RCXREG. Контроль ошибок всегда выполняется перед считыванием данных, чтобы избежать непреднамеренного изменения этих флагов регистра состояния приемника.

Программа 12.15 . Подпрограммы ввода/вывода с использованием модуля USART

; *****************

* ФУНКЦИЯ: Передает 8-битное значение по асинхронному каналу *

* РЕСУРСЫ: Модуль USART *

* ВХОД: 8-битное значение в DATA_OUT *

* ВЫХОД: Содержимое DATA_OUT не изменяется, байт передан *

; ******************

PUTCHAR btfss PIR1,TXIF ;Проверим, полон ли буфер передатчика?

                  goto PUTCHAR ;ЕСЛИ нет, ТО проверим снова

                movf DATA_OUT,w ;ИНАЧЕ считываем значение

                movwf TXREG ;и копируем его в регистр передатчика

                return

; ******************

; * ФУНКЦИЯ: Принимает 8-битное значение по асинхронному каналу *

; * РЕСУРСЫ: Модуль USART *

; * ВХОД: Нет *

; * ВЫХОД: Принятый байт — в DATA_IN. *

; * ВЫХОД: ERR = 00, если не было ошибок. При ошибке *

; *              кадрирования ERR = -1, при переполнении ERR = -2, *

; *              при наличии обеих ошибок ERR = -3 *

; *******************

GETCHAR clrf ERR ; Обнуляем признак ошибки

         btfss PIR1,RCIF ; Проверим, есть ли символ?

           goto GETCHAR ; ЕСЛИ нет, ТО проверим снова

; Обработка ошибок

         btfss RCSTA,FERR ; Была ошибка кадрирования?

           goto CHECK_OERR ; ЕСЛИ нет, ТО проверим на переполнение

         movlw -1 ; ИНАЧЕ фиксируем ошибку

CHECK_OERR

         btfss RCSTA,OERR ; Было переполнение?

            goco GET_EXIT ; ЕСЛИ нет, TO завершаем обработку ошибок

         decf ERR,f ; Иначе фиксируем ошибку

         decf ERR,f

         bcf RCSTA,CREN ; и сбрасываем логику приемника

         bcf RCSTA,CREN

GET_EXIT

         movf RCREG,w ; Читаем байт данных

         movwf DATA_IN ; и помещаем во временный регистр

         return

         end

В некоторых системах нельзя позволить процессору тратить машинное время на ожидание символа, который придет неизвестно когда. Специально для таких случаев можно было бы написать альтернативную подпрограмму приема, назвав ее, скажем, getch. Эта подпрограмма будет возвращать ERR = +1 при отсутствии данных в буфере. И все же наилучшим выходом из ситуации будет генерация прерывания при обнаружении входящего символа, а не простой опрос флага этого прерывания.

В языке Си каналы асинхронного обмена могут использоваться в качестве стандартных потоков ввода/вывода. Что же касается конкретно компилятора CCS, то в нем имеется директива #use rs232 (), посредством которой можно сообщить компилятору, какие выводы будут использоваться для приема и передачи данных, а также какой должна быть скорость обмена. Стандартные Си-функции ввода/вывода, такие как printf (), используют эти выводы для связи со стандартным каналом. С помощью данного компилятора можно реализовать множество не связанных между собой асинхронных каналов.

В качестве примера, в Программе 12.16 приведена реализация на языке Си асинхронной дуплексной связи с терминалом (см. Рис. 12.25), работающим на скорости 9600 бод. К выводу RB0 подключена кнопка, и, когда оператор посылает микроконтроллеру символ ‘G’, тот начинает опрашивать состояние этой кнопки. При ее замыкании (появлении на выводе сигнала НИЗКОГО уровня) терминал извещает оператора строкой «Кнопка 1 замкнута». Для ввода и вывода данных воспользуемся стандартными функциями printf () >) и getch () >).

Программа 12.16. Использование дуплексного асинхронного канала в языке Си

#include <16f877a.h>

#use delay (clock = 20000000) /* Сообщаем компилятору частоту резонатора (20 МГц) */

/* Сообщаем компилятору о требуемой скорости обмена и используемых выводах */

#use rs232(baud=9600, xmit=PIN_A1, rcv=PIN_A2)

#bit SWITCH1 =6.0 /* Кнопка подключена к RB0 */

void main(void)

{

      while(TRUE)

       {

              if(getch() == ’G’)

              {

                  while (SWITCH1) {;} /* Пока кнопка разомкнута (1), ничего не делаем */

                  printf("Кнопка 1 замкнута \n");

               }

        }

}

Поскольку в качестве выводов приемника и передатчика используются выводы RA1 и RA2, компилятор сгенерирует код программно-реализованного UART, подобный использованному нами в Программе 12.14. Именно по этой причине компилятору необходима информация о частоте кварцевого резонатора микроконтроллера — для формирования требуемых задержек. Если же мы укажем выводы RC6 и RC7, то для реализации последовательного интерфейса компилятор автоматически воспользуется встроенным модулем USART. В нашем примере для реализации программного UART потребовалось 146 команд, тогда как при использовании модуля UART размер программы составил всего 74 команды.

Однако для реализации полноценного соединения недостаточно одного только выбора подходящего протокола. При работе микроконтроллеров PIC используются напряжения нормальных логических уровней и токи, которые предназначены для организации соединений на расстояниях не более 30 см (1 фут). Хотя при соблюдении определенных правил это расстояние можно значительно увеличить, при относительно больших скоростях обмена должны использоваться принципиально другие методы формирования сигналов.

В эпоху электромеханических терминалов широко использовался интерфейс «Токовая петля 20 мА», ставший стандартом де-факто. В этом интерфейсе для обозначения состояний лог. 0 и лог. 1 использовались разные значения тока: 0 мА и 20 мА соответственно. Привязка к току, а не напряжению позволяла избежать влияния потерь в линии (поскольку вытекающий ток должен быть равен втекающему), и, кроме того, тока такой величины было достаточно для непосредственного управления электромагнитным реле приемного устройства.

Источники тока реализуются посредством источников высокого напряжения, последовательно с которыми включается большое сопротивление. Именно из-за последнего величины постоянных времени получаются настолько большими, что хотя они и удовлетворяли требованиям эпохи скоростей в 110 бод, но для использования в электронных терминалах, UART и модемах не годятся. В качестве стандартного интерфейса для подключения терминального оборудования (Data Terminal Equipment — DTE) к устройствам передачи данных (Data Circuit Equipment — DCE), как правило к модемам, в 1969 году был предложен интерфейс RS-232. В спецификации этого интерфейса были определены не только различные уровни сигналов, как показано на Рис. 12.24, а, но и различные линии управления и квитирования, некоторые из которых показаны на Рис. 12.24, г и Рис. 12.25. Например, выдачей активного уровня на линию квитирования готовности к передаче (Clear То Send — CTS) модем может сообщить локальному терминалу о том, что удаленный терминал освободил телефонную линию. Для организации дуплексной линии связи необходимо две линии данных плюс общий провод как опция.

Рис. 12.24. Некоторые варианты последовательной передачи данных

Стандарт RS-232 рассчитан на дальность до 15 м (50 футов) при максимальной скорости 20 Кбод, что достигается использованием для передачи лог. 0 (это состояние линии часто называется space) напряжения +12 В, а для передачи лог. 1 (mark) — напряжения —12 В. Минимальное же напряжение, при котором приемник может распознавать состояние линии, составляет ±3 В. Интерфейс стандарта RS-423 (1978 г.), показанный на Рис. 12.24, б, похож на RS-232, но позволяет управлять несколькими (до десяти) приемными устройствами на расстоянии 1.2 км (6000 футов) при скорости до 1 Кбод и на расстоянии до 12 м (40 футов) при скорости 100 Кбод.

Интерфейсы RS-232 и RS-423 являются несимметричными (или небалансными), поскольку приемник контролирует потенциал между сигнальной линией и локальным общим проводом. И хотя «земли» передатчика и приемника, как правило, объединяются между собой, импеданс этой линии при ее значительной протяженности может привести к появлению большой разности потенциалов на ее концах, в результате чего уменьшится помехоустойчивость. Более того, любая наведенная извне помеха вносит в различные сигналы неодинаковые искажения, что вызвано неидентичностью электрических характеристик сигнальных линий. Поэтому такие интерфейсы и называются несимметричными.

Интерфейсы RS-422 (1978 г.) и RS-485 (1983 г.) относятся к классу симметричных. В таких интерфейсах каждая линия связи состоит из двух проводников, обычно свитых между собой, называемых витой парой. Логические уровни в такой линии представляются разностью потенциалов между проводниками, а не относительно общего провода. Обозначим проводники буквами А и В, тогда логическому нулю будет соответствовать соотношение А < В, алогической единице — А > В. На стороне приемника разницы потенциалов, превышающей значение ±200 мВ, будет достаточно для устойчивого распознавания логического уровня, при том, что передатчик обычно формирует сигналы ΔV= ±5 В. Так как проводники А и В имеют одинаковые электрические характеристики и свиты друг с другом, они совершенно идентичны для наводимых помех. Поскольку один и тот же сигнал окажется приложенным к обоим проводникам, а приемник контролирует разность потенциалов, отсекая синфазное напряжение величиной до ±7 В, очевидно, что помехоустойчивость такой симметричной линии связи гораздо выше, чем несимметричной. Имеющиеся в продаже кабели с витыми парами, используемые в локальных сетях (Local Area Network — LAN), обычно содержат три или четыре пары проводников, причем каждая пара имеет свой шаг скрутки. Это сделано для того, чтобы уменьшить уровень перекрестных помех между линиями. В шине USB, применяющейся в ПК, для передачи сигнала тоже используется симметричная линия связи.

Основным отличием между стандартами RS-422 и RS-485 является возможность использования в последнем нескольких передатчиков, так же как и приемников, что позволяет реализовать многоабонентскую сеть. Поскольку в каждый момент времени может быть активен только один передатчик, буфер передатчика должен иметь вход разрешения для выбора ведущего устройства. На линии RS-422 может быть только один передатчик, поэтому нет необходимости его запрещать.

Интерфейс RS-232 изначально был разработан для организации соединения терминал-модем, однако в настоящее время сфера его применения намного шире (см. Рис. 12.25). На Рис. 12.24, г показана простая дуплексная система с частотной манипуляцией (Frequency Shift Keying — FSK), в которой состояния mark/space в одном канале представляются сигналами с частотами 1070/1270 Гц, а в другом — 2025/2225 Гц. Указанные частоты хорошо подходят для передачи по обычной телефонной линии, имеющей полосу пропускания 300 Гц…3.4 кГц. Линии квитирования DCD (обнаружение несущей), CTS (готовность к приему) и RTS (готовность к передаче) используются для аппаратного управления потоком.

В большинстве модемов в настоящее время используется фазовая манипуляция (Phase Shift Keying — PSK). При этом для кодирования 3-битных групп кодов в одном временном интервале обычно используется не менее восьми различных фаз сигнала одной и той же частоты, сдвинутых друг относительно друга на 45°. За счет этого можно увеличить скорость передачи данных при той же скорости передачи сигналов, хотя и ценой снижения помехоустойчивости.

В качестве примера, на Рис. 12.25 показано соединение между микроконтроллером PIC и последовательным портом компьютера (или любым другим устройством, имеющим порт RS-232). Микросхема МАХ233 компании Maxim является сдвоенным приемопередатчиком RS-232, осуществляющим двустороннее преобразование сигналов + 12 В <=> 0 В (лог. 0) и -12 В <=> +5 В (лог. 1). Если линии квитирования не используются, что обычно имеет место при реализации простейших линий связи, ПК можно «обдурить», соединив выводы порта так, как показано на Рис. 12.25 (выход RTS соединен с входом CTS, а выход DTR — с входом DSR). В этом случае ПК будет считать, что последовательный интерфейс постоянно готов к приему данных. Микросхема МАХ232 имеет в общей сложности по два буфера на прием и на передачу, поэтому при необходимости ее можно будет использовать также для буферирования линий квитирования.

На Рис. 12.25 тот же микроконтроллер управляет полудуплексной линией связи стандарта RS-485, используя преобразователь уровней МАХ485 фирмы Maxim. Оба буфера имеют собственные входы разрешения с противоположными активными уровнями (буфер передатчика — ВЫСОКИЙ, а буфер приемника — НИЗКИЙ). Микроконтроллер может включать соответствующий буфер в зависимости от направления обмена. Также с помощью микросхемы МАХ485 можно реализовать дуплексный канал с использованием двух линий связи.

Данные по интерфейсу RS-485 можно передавать и по синхронному протоколу. При этом, разумеется, необходимо будет выделить отдельный буфер для передачи тактового сигнала.

Рис. 12.25. Взаимодействие с ПК по интерфейсу RS-232 и с внешним миром по интерфейсу RS-422/485

Примеры

Пример 12.1

В Примере 11.2 мы написали подпрограмму, сравнивающую фиксированное значение TRIP с байтом, считанным из порта В. В ряде случаев может потребоваться подстройка программы под изменяющиеся условия работы путем модификации порогового значения по командам извне. Вместо того чтобы использовать второй порт ввод/вывода, было предложено передавать новое значение в последовательном виде на вывод RA4, используя вывод RA3 для подключения к линии тактовых сигналов. Предполагая, что состояние линии данных стабильно при ВЫСОКОМ уровне на линии тактового сигнала, напишите подпрограмму, считывающую новое значение и записывающую его в ячейку TRIP.

Решение

Один из возможных вариантов решения этой задачи приведен в Программе 12.17. Эта подпрограмма отслеживает появление ВЫСОКОГО уровня на линии тактового сигнала, при котором, согласно условию задания, сигнал на линии данных стабилен. Изменяя значение бита переноса в соответствии с состоянием линии данных и выполняя операцию сдвига через перенос, осуществляется побитовая загрузка нового значения в память. Причем очередная итерация цикла завершается только после того, как на линии тактового сигнала вновь появляется НИЗКИЙ уровень.

Программа 12.17. Подпрограмма изменения порогового значения из Программы 11.6

; ***************

; * ФУНКЦИЯ: Задвигает значение порога TRIP, которое затем используется в качестве операнда п/п СОМР *

; * ВХОД: Изменение значения битов данных на RA4 происходит при НИЗКОМ уровне на RA3 *

; * ВЫХОД: COUNT = 00, принятое значение — в TRIP *

; ***************

SER_TRIP movlw 8 ; Счетчик битов

               movwf COUNT

SER_TRIP_LOOP

               btfss PORTA,3 ; Ждем 1 на такт, линии

                  goto SER_TRIP_LOOP

               bcf STATUS,С ; Обнуляем флаг переноса

               btfsc PORTA,4 ; На линии данных 1?

               bsf STATUS,С ; ЕСЛИ да, ТО устанавливаем флаг переноса

               rlf TRIP,f ; Вдвигаем бит

SER_TRIP_LOOP2

               btfsc PORTA,3 ; Дожидаемся появления 0 на такт, линии

                  goto SER_TRIP_LOOP2

               decfsz COUNT,f

                  goto SER_TRIP_LOOP

               return

Эта подпрограмма похожа на подпрограмму SPI_READ (см. Программу 12.3), за исключением того, что тактовый сигнал формируется внешним устройством, т. е. микроконтроллер PIC в данном случае выступает в роли ведомого. В реальных системах, где ведомый микроконтроллер должен обладать возможностью сообщать ведущему о необходимости передачи нового байта, такая схема может вызвать определенные проблемы. Указанную возможность можно реализовать, используя дополнительную линию порта ввода/вывода для передачи квитирующего сигнала CTS. Этот сигнал будет генерировать прерывание на стороне ведущего и инициировать обмен. Конечно же, этим ведущим может быть другой микроконтроллер PIC, и в этом случае мы получим очень простой вариант объединения двух микроконтроллеров. При использовании микроконтроллера с встроенным последовательным портом прерывания могут генерироваться автоматически — такой подход наиболее часто используется для реализации многопроцессорных сетей.

Пример 12.2

Напишите подпрограмму I2C_IN, обратную по своему действию подпрограмме I2C_OUT из Программы 12.9. Предполагается, что в вашем распоряжении имеются те же переменные, а принятое значение должно сохраняться в регистре DATA IN.

Решение

Подпрограмма I2C_IN, код которой приведен в Программе 12.18, загружает принимаемое значение в регистр DATA_IN посредством восьми операций сдвига через флаг переноса; значение флага соответствует состоянию вывода SDA. Одновременно на линии тактового сигнала SCL формируются импульсы в соответствии со спецификацией шины I2С, как и в подпрограмме I2C_OUT из Программы 12.9. В соответствии с этим протоколом ведущий приказывает ведомому остановить посылку данных путем выдачи на линию SDA ВЫСОКОГО уровня во время 9-го тактового импульса (см. Рис. 12.13). Наличие во время этого временного интервала НИЗКОГО уровня на линии данных называется АСК (подтверждение), а наличие ВЫСОКОГО уровня — NACK (нет подтверждения). Наша подпрограмма может генерировать оба сигнала, в зависимости от значения переменной ACKNO, которое задается вызывающей подпрограммой. Если при вызове подпрограммы содержимое регистра ACKNO равно нулю, то после приема 8-го бита данных отсылается АСК. Соответственно, любое ненулевое значение регистра ACKNO приведет к отсылке ведомому сигнала NACK. После получения этого сигнала ведомый прекратит передачу и начнет отслеживать появление на шине состояний СТАРТ/СТОП.

Программа 12.18. Подпрограмма получения байта по шине I 2 С

; **************

; * ФУНКЦИЯ: Принимает байт от ведомого, отсылая  в ответ АСК или NACK *

; * ВХОД: ACKNO = 00 для отсылки АСК, ИНАЧЕ NACK *

; * РЕСУРСЫ: п/п START и STOP, макрокоманда Delay_600 *

; * ВЫХОД: Байт данных, посланный ведомым — в DATA_IN, ведомому отослан АСК или NACK, на SCL — НИЗКИЙ уровень *

; **************

I2C_IN bcf INDF,SCL ; Гарантируем наличие НИЗКОГО уровня на SCL

          movlw 8 ; Счетчик цикла = 8

          movwf COUNT

I2C_IN_LOOP

          bcf INDF,SCL ; Формируем на тактовой линии

          Delay_600 ; отрицательный импульс

          Delay_600

          bsf INDF,SCL ; минимальной длительности

          bcf STATUS,С ; Сбросим флаг переноса

          btfsc INDF,SDA ; Проверяем значение принятого бита

             bsf STATUS,С ; ЕСЛИ 1, ТО устанавливаем флаг С

          rlf DATA_IN,f ; и вдвигаем его в регистр

          decfsz COUNT,f ; Декрементируем счетчик цикла

             goto I2C_IN_LOOP ; и повторяем восемь раз

; Теперь посмотрим, что надо отослать (АСК или NACK)

          bcf INDF,SCL ; Выставим на SCL НИЗКИЙ уровень

          bsf INDF,SDA ; Высвободим линию данных (NACK)

          movf ACKNO,f ; Проверим регистр

          btfsc STATUS,Z ; ЕСЛИ не равно 0, ТО ничего не делаем

            bcf INDF,SDA ; ИНАЧЕ выставляем на линию данных НИЗКИЙ уровень (АСК)

          Delay_600 ; Удерживает на тактовой линии

          Delay_600 ; НИЗКИЙ уровень

          bsf INDF,SCL ; Теперь выставляем ВЫСОКИЙ уровень

          Delay_600

          bcf INDF,SCL ; На линии SCL оставляем НИЗКИЙ уровень

          return

Пример 12.3

Во многих микроконтроллерных устройствах требуется сохранять данные в энергонезависимой памяти для того, чтобы считывать их после повторного включения. В качестве примера можно указать счетчик суммарного пробега, пройденного автомобилем, значение которого должно сохраняться независимо от состояния аккумулятора. Обычно такого рода данные хранятся в EEPROM-памяти, которая была подробно описана на стр. 43. Хотя во многих микроконтроллерах PIC имеется встроенный модуль EEPROM, о котором мы поговорим в главе 15, его емкость ограничена в лучшем случае 256 байтами. При больших объемах необходимо задействовать внешние микросхемы EEPROM. Большинство таких микросхем используют интерфейс SPI или I2С, как, например, микросхема 24LCXXX, применяющаяся в схеме на Рис. 12.26. Микросхемы EEPROM с последовательным интерфейсом серии 24LCXXX, выпускающиеся в 8-выводных корпусах, имеют емкость от 1 Кбит (24LC01B) до 512 Кбит (24LC512), организованных побайтно; т. е. от 128 байт до 64 Кбайт.

Рис. 12.26. Применение I 2 С-совместимых микросхем EEPROM серии 24ХХХ

Микросхемы EEPROM серии 24ХХХ имеют следующие характеристики:

• I2С-совместимый интерфейс с максимальной частотой 400 кГц (VDD = 5 В) и 100 кГц при(VDD = 2.5 В.

• Возможность защиты содержимого микросхемы от записи (режим ПЗУ), используя вывод WP.

• Типичная длительность цикла записи — 2 мс.

• Долговечность — не менее 1 000 000 циклов записи на ячейку.

• Ток потребления — 3 мА в режиме записи, 1 мА в режиме чтения и 100 мкА в режиме ожидания.

• Встроенный генератор высокого напряжения для программирования.

На примере микросхемы 24LC01B покажем, какие операции необходимо выполнить для инкрементирования содержимого трех ячеек, расположенных в младших адресах, где хранится суммарный путь в милях или километрах (единица измерения меняется в зависимости от рынка, для которого предназначен автомобиль). Предположим, что каждый километр/милю генерируется прерывание для микроконтроллера и что наш код является частью процедуры обработки прерывания. Кроме того, в нашем распоряжении имеются ресурсы, используемые подпрограммами, коды которых приведены в Программах 12.9 и 12.18.

Решение

Прежде чем приступить к написанию собственно кода, необходимо познакомиться с протоколом обмена, поддерживаемым микросхемами серии 24ХХХ. Этот протокол в виде временных диаграмм сигналов на линии данных приведен на Рис. 12.27.

Рис. 12.27. Временные диаграммы операций чтения и записи микросхем EEPROM

Инициирование обмена всегда осуществляется ведущим (микроконтроллером), который формирует на шине состояние СТАРТ, после чего передает управляющий байт. В этом байте содержится адрес ведомого 1010, адрес конкретной микросхемы А2А1А0, а также бит R/W¯: . Однако, хотя в управляющий байт и включены биты адреса (а соответствующие им выводы микросхемы показаны на Рис. 12.26), в последних версиях микросхем EEPROM малого объема возможность изменения адреса микросхемы не реализована. Так сделано потому, что в случае необходимости увеличения объема памяти гораздо проще и эффективнее заменить микросхему на другую, большей емкости, поскольку цоколевка у всех микросхем серии одинакова. Так, заменив микросхему 24LC01B на 24LC08B, мы получим восьмикратное увеличение объема без вмешательства в схему самого устройства. Микросхемы же большей емкости, такие как 24LC256, используют выводы адреса для расширения системы, так как в этом случае придется подключать к шине дополнительные микросхемы. Так, при использовании восьми микросхем 24LC512 мы получим энергонезависимую память объемом 512 Кбайт.

Как правило, после управляющего байта передается значение адреса в памяти, куда будут записываться или откуда будут считываться данные. Если говорить конкретно о микросхеме 24LC01B, то в ней данные организованы в виде 128 однобайтных ячеек, каждая из которых может быть записана или считана независимо от других. То есть в ней используется 7-битный адрес, для передачи которого вполне достаточно одного байта. Эта схема годится также для микросхемы 24LC02B, однако для всех остальных микросхем требуется адрес, разрядность которого больше 8 бит. В микросхемах от 24LC04 до 24LC16 для передачи трех старших битов адреса используются биты А[2:0] управляющего байта, в результате чего разрядность адреса увеличивается до 11 бит. Микросхемы EEPROM, имеющие объем более 16 Кбит (24LC32 и далее), требуют уже двух байтов адреса, которые передаются вслед за управляющим байтом.

Байты адреса посылаются в EEPROM в пакетах записи, как показано на Рис. 12.27, а, т е. со сброшенным битом R/W¯ управляющего байта. Если в данную ячейку необходимо записать байт данных, то он будет передан сразу же после байта адреса, а затем на шине будет сформировано состояние СТОП. Если же до формирования состояния СТОП будет передано более одного байта, то они будут сохранены во внутреннем буфере небольшого объема, а реальное программирование начнется только после появления состояния СТОП. Микросхема 24LC02B может запомнить до восьми байтов (одну страницу) данных, инкрементируя при получении очередного байта три младших бита адреса. При переходе через границу страницы ранее загруженные значения перезаписываются. Размер страницы зависит от модели устройства. Например, в 24LC256 используются 64-байтные страницы. На Рис. 12.27, а показан процесс записи трех байтов в микросхему 24LC01B. Поскольку используемые нами ячейки расположены в младших адресах (h’00’, h’01’ и h’02’), то переполнения не произойдет.

После обнаружения микросхемой состояния СТОП запускается процесс записи буферированных данных в заданные ячейки. Длительность процесса программирования в микросхемах семейства 24LCXXX составляет от 2 до 5 мс. Если в течение этого времени ведущий попытается обратиться к микросхеме, то она при получении первого (управляющего) байта отошлет NACK, что можно использовать в качестве индикатора занятости. В Программе 12.19 этот бит проверяется при посылке управляющего байта.

Процесс чтения содержимого EEPROM, показанный на Рис. 12.27, б, немного сложнее. Как и в предыдущем случае, транзакция начинается посылкой адреса устройству. Затем ведущий формирует состояние ПОВСТАРТ, после чего повторно передает управляющий байт с установленным битом R/W¯, свидетельствующий о том, что ведущий будет работать в качестве приемника данных. После этого микросхема EEPROM отсылает байт, расположенный по адресу, указанному ведущим, который при получении байта выставляет подтверждение. Процесс передачи и приема байтов (с постоянным инкрементированием адреса) будет продолжаться до тех пор, пока ведущий в ответ на очередной байт не выставит NACK. После этого ведомый освободит шину и ведущий сможет сформировать состояние СТОП. Если первый пакет, содержащий адрес ячейки памяти, был опущен, то чтение начнется с адреса, на единицу большего того, к которому производилось последнее обращение.

Программа, код которой приведен в Программе 12.19, в точности выполняет все операции, показанные на Рис. 12.27. После посылки начального адреса h’00’ микроконтроллер переходит в режим чтения и считывает три последовательно расположенных байта из EEPROM-памяти. Чтение завершается возвратом NACK с последующим формированием состояния СТОП. Поскольку все три байта расположены друг за другом, используется автоматическое инкрементирование адреса. После инкрементирования 3-байтного значения оно передается обратно в EEPROM после повторной передачи адреса 1-й ячейки (h’00’). Процесс завершается формированием состояния СТОП.

Программа 12.19. Инкрементирование значения одометра, хранящегося в энергонезависимой памяти

EXTRA_MILE ; Считываем три байта, хранящиеся по адресам 00:01:02h

            call START ; Начинаем с формирования состояния СТАРТ

;1-й управляющий байт — адрес микросхемы ------------

            movlw b’10100000’ ; Адрес ведомого, ведущий-передатчик

            movwf DATA_OUT ; Копируем в буферный регистр

            call I2C_OUT ; Передаем

            movf ERR, f ; Проверяем наличие подтверждения

            btfsc STATUS,Z ; ЕСЛИ ноль, ТО продолжаем

              goto EXTRA_MILE ; ИНАЧЕ пробуем снова

; Адрес 00 --------------

            clrf DATA_OUT ; Формируем адрес

            call I2C_OUT ; Передаем

; 2-й управляющий байт для инициирования операции чтения --------

            call START

            movlw b’10100001’ ; Адрес ведомого, ведущий-приемник

            movwf DATA_OUT ; Копируем в буферный регистр

            call 2C_OUT ; Передаем

; Теперь считываем три байта данных

            clrf ACKNO ; Разрешаем формирование подтверждения

            call I2C_IN ; Считываем старший байт из ячейки с адресом 00h

            movf DATA_IN,w ; Берем байт

            movwf MSB ; и сохраняем его в памяти

            call I2C_IN ; Считываем средний байт из ячейки с адресом 01h

            movf DATA_IN,w ; Берем байт

            movwf NSB ; и сохраняем его в памяти

            incf ACKNO,f ; Выставить NACK

            call I2C_IN ; Считываем старший байт из ячейки с адресом 02h

            movf DATA_IN,w ; Берем байт

            movwf LSB ; и сохраняем его в памяти

            call STOP ; Завершаем операцию чтения

; Теперь инкрементируем 3-байтное число

            incf LSB,f ; Прибавляем единицу

            btfss STATUS,Z ; Проверяем на ноль

              goto PUT_BACK ; ЕСЛИ не 0, ТО продолжаем

            incfsz NSB,f ; Инкрементируем средний байт

              goto PUT_BACK ; ЕСЛИ не 0, ТО продолжаем

            incf MSB,f

PUT_BACK call START ; Начинаем операцию записи

            movlw b’10100000’ ; Пакет записи

            movwf DATA_OUT

            call I2C_OUT

            clrf DATA_OUT ; Адрес 00h

            call I2C_OUT

            movf MSB,w ; Берем новое значение старшего байта

            movwf DATA_OUT

            call I2C_DUT

            movf NSB,w ; Берем новое значение среднего байта

            movwf DATA_OUT

            call I2C_OUT

            movf LSB,w ; Берем новое значение младшего байта

            movwf DATA_OUT

            call I2C_OUT

            call STOP

Пример 12.4

Взяв за основу базовый принцип асинхронной передачи данных и дополнив его некоторыми принципами, лежащими в основе синхронного протокола I2С, мы сможем организовать асинхронную передачу данных в обоих направлениях по одной-единственной линии связи (в полудуплексном режиме). Одним из примеров такого скрещивания является интерфейс 1-WireTM, характеристики которого показаны на Рис. 12.28.

В схеме, приведенной на Рис. 12.28, а, используется микросхема цифрового термометра DS18S20 (Maxim/Dallas), управление которой осуществляется посредством одной линии порта микроконтроллера, выступающего в качестве ведущего шины 1-Wire.

Рис. 12.28. Взаимодействие с микросхемой цифрового термометра DS18S20 (1-Wire)

Микросхема DS18S20 имеет следующие характеристики:

• Диапазон измеряемой температуры от —55 до +85 °C с шагом 0.5 °C; результат представляется в виде 16-битного числа со знаком.

• Точность измерения ±0.5 % в диапазоне -10…+85 °C.

• Время преобразования — не более 750 мс.

• Нулевой ток потребления в режиме ожидания.

• Может питаться от линии данных, диапазон напряжения питания от +3 до +5.5 В.

• Возможность работы в многоточечной сети.,

Выполнение различных операций, поддерживаемых термометром, таких как «Преобразование» (h’44’) и «Чтение температуры» (h’BE’), инициируется ведущим посылкой соответствующих 8-битных значений. Процесс передачи каждого из этих значений начинается с посылки старт-бита (), за которым следуют восемь слотов записи, как показано на Рис. 12.28, б. Как и в случае шины I2С, состояние ВЫСОКОГО уровня на линии данных DQ формируется за счет ее подтяжки к шине питания, соответственно ведущий имитирует посылку лог. 1 переключением линии порта на вход (см. Рис. 12.14,б). В этом состоянии ведущий может контролировать линию на предмет данных, посылаемых ведомым, как показано на Рис. 12.28, в.

Для нашего примера потребуется написать две подпрограммы, одна из которых будет передавать байт в ведомое устройство, а другая — считывать один байт из него.

Решение

Из Рис. 12.28, б видно, что процесс записи одного бита в ведомое устройство состоит из следующих этапов:

1. Ведущий инициирует процесс обмена, выставляя на линию данных НИЗКИЙ уровень на время не менее 1 мкс.

2. Ведущий либо оставляет на линии НИЗКИЙ уровень (запись лог. 0), либо высвобождает линию (запись лог. 1) на время 60…120 мкс.

3. Ведомый считывает состояние линии через 15…45 мкс после начала слота.

4. Ведущий освобождает линию (если он записывал лог. 0) на время не менее 1 мкс для приведения системы в исходное состояние.

В подпрограммах, код которых приведен в Программе 12.20, предполагается, что вывод порта, управляющий линией данных DQ, уже сконфигурирован так же, как и выводы, управляющие линиями шины I2С (см. стр. 396), — жесткий НИЗКИЙ уровень и открытый выход, подтянутый к линии питания для формирования ВЫСОКОГО уровня. Также мы предполагаем, что в нашем распоряжении имеется макрокоманда Delay_us, которая формирует задержку длительностью K мкс, где К — параметр, передаваемый в макрокоманду.

Delay_us macro К; К — длительность задержки в мкс

              local DELAY_US_LOOP

              movlw (K*XTAL)/(4*3)+1 ; 1~

DELAY_US_LOOP

              addlw -1 ; Декрементируем счетчик: N~

              btfss STATUS,Z ; до нуля: N + 1~

                 goto DELAY_US_LOOP ;:2(N — 1)~

              endm

Дополнительная операция прибавления единицы в выражении для вычисления количества итераций цикла предназначена для округления К в бóльшую сторону

Выполнение обеих подпрограмм начинается с выставления на линию DQ НИЗКОГО уровня на время не менее 1 мкс, в результате чего на линии формируется состояние СТАРТ Запись каждого бита на шину происходит в слоте длительностью 60…120 мкс и инициируется либо выдачей на линию НИЗКОГО уровня (0), либо ее высвобождением (1). Ведомый считывает состояние линии данных через 15 мкс после начала временного слота. Хотя длительность временного слота и не критична, необходимо быть внимательным, поскольку состояние НИЗКОГО уровня длительностью 480…960 мкс интерпретируется ведомым как команда сброса (см. Вопрос для самопроверки 12.3).

Для передачи одного байта используются восемь временных слотов, разделенных паузами длительностью не менее 1 мкс. Значение, передаваемое в каждом слоте, соответствует значению флага переноса, которое формируется в результате циклического сдвига байта данных DATA_OUT. После восьми циклов сдвига/выдачи процесс завершается.

Чтение байта из ведомого устройства происходит следующим образом:

1. Ведущий инициирует процесс чтения, выставляя на линию НИЗКИЙ уровень на время не менее 1 мкс.

2. Ведущий считывает значение бита, выдаваемое на линию ведомым, которое должно быть корректным в течение 15 мкс после формирования фронта состояния СТАРТ.

3. Через 15 мкс ведомый высвобождает линию, на которой к моменту завершения 60-мкс слота в результате подтяжки должен появиться ВЫСОКИЙ

4. Ведущий ждет не менее 1 мкс перед формированием следующего слота.

Указанный алгоритм реализуется подпрограммой чтения READ_1W, которая считывает состояние линии данных до наступления 15 мкс с момента начала временного слота, т. е. в тот момент, когда напряжение на линии, определяемое ведомым, уже установилось. Значение считанного бита используется для установки/сброса флага переноса, который затем вдвигается в переменную DATA_IN. После восьми операций чтения/сдвига в этой переменной окажется принятый байт данных.

В отличие от протокола I2С, протокол 1-Wire предполагает наличие только одного ведущего устройства. Однако все ведомые устройства имеют уникальный 64-битный адрес, хранящийся во внутреннем ПЗУ. Первые восемь битов обозначают код семейства 1-Wire, к примеру, микросхема DS18S20 имеет код h’10’. Следующие 48 бит являются уникальным серийным номером, а последние восемь битов представляют собой контрольную сумму всех предыдущих байтов.

Программа 12.20. Подпрограммы чтения и записи по шине 1-Wire

; *************

; * ФУНКЦИЯ: Передает 1-байтное значение ведомому 1-Wire *

; * РЕСУРСЫ: Макрокоманда Delay_us, формирующая задержку N мкс *

; * ВХОД: Передаваемй байт в DATA_OUT *

; * ВЫХОД: DATA_OUT обнуляется; W, STATUS изменяются *

; *************

WRITE_1W movlw 8 ; Количество проходов цикла

                 movwf COUNT

W_LOOP    bcf INDF,DAT ; Спадающий фронт — СТАРТ

                 Delay_us 1 ; Ждем 1 мкс

                 rrf DATA_OUT,f ; Выдвигаем байт через перенос

                 btfsc STATUS,С ; Бит равен 1?

                 bsf INDF,DAT ; ЕСЛИ да, ТО выставляем ВЫСОКИЙ уровень

                 Delay_us d’60’ ; Удерживаем в течение 60 мкс

                 bsf INDF,DAT ; Высвобождаем линию

                 Delay_us 1 ; Ждем 1 мкс

                 decfsz COUNT,f ; Повторяем восемь раз

                    goto W_LOOP

                 return

; ******************

; * ФУНКЦИЯ: Принимает 1-байтное значение от ведомого 1-Wire *

; * РЕСУРСЫ: Макрокоманда Delay_us, формирующая задержку N мкс *

; * ВХОД: Нет *

; * ВЫХОД: Принятый байт в DATA_IN; W, STATUS изменяются *

; ******************

READ_1W movlw 8 ; Количество проходов цикла

                movwf COUNT

R_LOOP bcf INDF,DAT ; Спадающий фронт — СТАРТ

             Delay_us 1 ; Ждем 1 мкс

             bsf INDF,DAT ; Высвобождаем линию

             Delay_us 8 ; Ждем 8 мкс, чтобы дать возможность ведомому выставить данные

             bcf STATUS,С ; Сбрасываем флаг переноса

             bcfsc INDF,DAT ; Проверяем состояние входа

             bsf STATUS,С ; ЕСЛИ 1, TO устанавливаем флаг переноса

             rrf DATA_IN,f ; Задвигаем бит в регистр

             Delay_us d’48’ ; Ждем до конца слота

                 goto R_LOOP

             decfsz COUNT,f ; Повторяем восемь раз

             return

; ********************

; * ФУНКЦИЯ: Сбрасывает ведомого 1-Wire *

; *********************

RESET_1W bcf INDF,DAT ; Выставляем НИЗКИЙ уровень

                 Delay_us d’140’ ; Ждем 480…960 мкс

                 Delay_us d’140’ ; С помощью макрокоманды можно получить

                 Delay_us d’140’ ; величину задержки (3*0.2)*255, только

                 Delay_us d’80’ ; если процессор работает на 20 МГц

                 bsf INDF,DAT ; Высвобождаем линию

                 Delay_us d’60’ ; Ведомый выставляет НИЗКИЙ уровень через 15…60 мкс

                 RESET_LOOP

                 btfss INDF,DAT ; А затем высвобождает линию

                    goto R_LOOP ; Ждем, пока на линии не появится ВЫСОКИЙ уровень

                 return ;

Вопросы для самопроверки

12.1. Перепишите Программу 11.5 со стр. 351, но с использованием модуля SPI, показанного на Рис. 12.4. Подсказка: вместо проверки итоговых 1-байтных значений более эффективным решением может стать побитовая проверка вдвигаемого значения.

12.2. Покажите, как можно подключить четыре АЦП МАХ518 (см. Рис. 12.16) к одной шине I2С и как можно загрузить значение 1-го канала третьего АЦП.

12.3. Обмен по шине 1-Wire начинается с формирования ведущим импульса сброса, при котором ведущий выставляет на линию НИЗКИЙ уровень на время 480…960 мкс, после чего линия высвобождается. В ответ на это ведомый выставляет на линию НИЗКИЙ уровень с задержкой не более 60 мкс. Это состояние удерживается на линии в течение 60…240 мкс, после чего ведомый высвобождает линию. Напишите подпрограмму, выполняющую описанные действия. Предполагается, что в вашем распоряжении имеются ресурсы Программы 12.20.

12.4. Контроль по четности представляет собой метод, при котором значение числа всегда является четным или же нечетным. Для этого на стороне передатчика к слову данных добавляется дополнительный бит, значение которого подобрано таким образом, чтобы удовлетворять указанному критерию. Например, при контроле по нечетности (odd) 8-битного числа Ь’01101111’ оно преобразуется в число Ь’101101111’. Приемник же проверяет полученное значение на предмет его нечетности. Если один (или любое нечетное количество) бит был поврежден из-за помех, возникнет ошибка четности (parity error).

Используя модуль USART микроконтроллера PIC, напишите программу, переключающую модуль в режим передачи 9-битных слов и вычисляющую значение бита для контроля по нечетности содержимого DATA_OUT. Этот бит затем должен заноситься в бит TX9D регистра TXSTA перед загрузкой байта данных в регистр TXREG и его передачей.

12.5. Перепишите подпрограмму GETCHAR из Программы 12.14 в виде процедуры обработки прерывания (назовем ее GETCH). Сравните эти два подхода к решению задачи приема символа.

12.6. Система сбора данных считывает значение температуры каждые 15 мин. Потребляемый ток сведен к минимуму за счет использования низковольтной версии микроконтроллера, работающей при напряжении 3 В, и кварцевого резонатора с частотой 32.768 кГц. В этих условиях ток потребления микросхемы с работающим Таймером 1 составляет не более 70 мкА (типовое значение — 45 мкА). Отсчеты запоминаются во внешней микросхеме EEPROM с интерфейсом 12С, однако питание на нее подается только на время записи — в качестве источника питания EEPROM используется отдельный вывод порта. Устройство должно работать от одного комплекта батарей в течение 6 месяцев, находясь при этом на дне озера. Можете ли вы подобрать подходящую микросхему EEPROM из семейства 24LCXXX и оценить требуемую емкость батареи в мА∙ч?

 

Глава 13

Главное — время

Во многих системах ключевые операции тем или иным образом связаны со временем. Это может быть измерение длительности какого-либо события, подсчет числа внешних событий или же управление внешним объектом в течение определенного периода времени. В качестве примера можно указать задачу измерения интервала между импульсами, формируемыми датчиком при прохождении мимо него зубцов маховика коленчатого вала двигателя. Впоследствии это значение может быть использовано для определения скорости вращения вала (см. Рис. 3.8. на стр. 78).

В тех случаях, когда время становится одним из важнейших критериев, для реализации указанных функций обычно используются аппаратные счетчики, а не программно-формируемые задержки. В данной главе мы познакомимся с различными модулями таймеров, встречающимися в микроконтроллерах PIC среднего уровня. После прочтения этой главы вы:

• Узнаете, как можно увеличить надежность микроконтроллерной системы с помощью сторожевого таймера, а также научитесь использовать интегрированный модуль сторожевого таймера микроконтроллеров PIC.

• Сможете использовать модуль базового 8-битного Таймера 0 как в режиме счетчика, так и в режиме таймера.

• Познакомитесь с возможностями модуля 16-битного Таймера 1 и разберетесь, каким образом он взаимодействует с модулями захвата/сравнения/ШИМ (Capture/Compare/PWM — ССР).

• Сможете использовать модуль 8-битного Таймера 2 совместно с модулями ССР для формирования сигнала с широтно-импульсной модуляцией.

Многие системы на базе микроконтроллеров работают в сложной электромагнитной обстановке, когда помехи наводятся как по сигнальным линиям, так и по линиям питания. Типичным примером такого устройства является система управления приборной панелью автомобиля, на которую воздействуют помехи, создаваемые высоковольтными разрядами в блоке зажигания, и пульсации напряжения питания, вызываемые работой генератора. Даже если поместить блок в экран, а на всех линиях поставить фильтры, никто не сможет гарантировать, что в какой-нибудь момент времени программа не собьется с корректного положения в памяти программ и микроконтроллер не «сойдет с ума», что вполне может привести к серьезным последствиям в работе системы управления. Иногда эти проблемы можно решить ручным сбросом системы. Однако во многих случаях это невозможно, например в случае имплантированного кардиостимулятора или космического зонда.

Один из способов решения данной проблемы заключается в использовании связки генератор/двоичный счетчик, которая будет сбрасывать процессор при переполнении счетчика. Если программа будет периодически обнулять этот счетчик во избежание переполнения, то микроконтроллер никогда не сбросится. Если по какой-либо причине микроконтроллер выйдет из основного цикла, в котором выполнялся сброс счетчика, то счетчик рано или поздно переполнится и микроконтроллер будет сброшен, а программа начнет выполняться с самого начала. Эта схема называется сторожевым таймером (watchdog timer), поскольку увеличивает безопасность системы.

Чтобы исключить использование внешних сторожевых таймеров, все микроконтроллеры PIC, даже представители самой старой линейки начального уровня, имеют встроенный модуль сторожевого таймера, структурная схема которого приведена на Рис. 13.1. Встроенный генератор сторожевого таймера никак не связан с основным тактовым генератором процессора и, если сторожевой таймер включен, постоянно генерирует сигнал с номинальным периодом 18 мс. В качестве времязадающего элемента этого генератора используется внутренняя RC-цепочка, поэтому в зависимости от конкретного экземпляра, температуры и напряжения питания период генератора может изменяться от 7 мс (—40 °C, VDD = 6 В) до 33 мс (+85 °C, VDD = 2 В) — см. Рис. 15.8 на стр. 561.

Рис. 13.1. Встроенный в микроконтроллеры PIC модуль сторожевого таймера с подключенным к нему постделителем

Генератор сторожевого таймера подключен к 8-битному постделителю (postscaler). С его помощью период тайм-аута сторожевого таймера можно увеличить до 0.018 х 128 ~= 2.3 с (0.9…4.2 с). Конкретное значение периода тайм-аута определяется состоянием битов PS[2:0] регистра OPTION_REG (см. Рис. 13.2). Генератор сторожевого таймера и счетчик постделителя (эту связку мы будем называть блоком сторожевого таймера) сбрасываются при выполнении команды clrwdt (CLeaR WatchDoG Timer — сброс сторожевого таймера). Соответственно, для предотвращения наступления тайм-аута сторожевого таймера необходимо периодически вызывать эту команду.

Рис. 13.2. Формат регистра OPTION_REG

Постделитель сторожевого таймера является разделяемым ресурсом, поскольку также используется модулем Таймера 0 (см. Рис. 13.3). Очевидно, что этот узел не может использоваться одновременно обоими модулями. Бит PSA регистра OPTION_REG определяет, к какому из модулей подключен данный узел. По умолчанию после сброса микроконтроллера постделитель подключен к сторожевому таймеру, а множитель периода сторожевого таймера равен 128.

Компания Microchip рассматривает сторожевой таймер больше как системный ресурс, нежели как периферийный модуль. Поэтому пользователь должен разрешить работу сторожевого таймера программированием бита конфигурации WDTE при записи кода в память программ (см. Рис. 10.6 на стр. 312). Например, так:

_config _HS_OSC & _WDT_ON & _PWRTE_OFF & _CP_OFF

или с помощью аналогичной директивы, поддерживаемой конкретным Си-компилятором. К примеру, в компиляторе CCS используется следующая директива:

#fuses HS, WDT, NOPUT, NOPROTECT

В приведенных выше строках работа сторожевого таймера разрешается. Для отключения сторожевого таймера необходимо использовать константы _WDT_OFF и NOWDT соответственно.

Если сторожевой таймер включен, то при сбросе по питанию выполняется инициализация модуля сторожевого таймера и деактивизируется (устанавливается в 1) 4-й бит регистра STATUS —  (см. Рис. 4.6 на стр. 95). Через заданный промежуток времени произойдет переполнение сторожевого таймера и бит ТО будет сброшен, как указано в Табл. 10.4 на стр. 322. Большинство программ для микроконтроллеров представляют собой бесконечный цикл, в теле которого вызываются различные подпрограммы. Если сделать так, чтобы при нормальном выполнении программы периодически выполнялась команда clrwdt, то независимо от возникающих событий можно будет — гарантировать, что тайм-аут сторожевого таймера не наступит. В случае же сбоя программы наступит тайм-аут, в результате чего микроконтроллер автоматически сбросится и начнет выполнять программу с адреса вектора сброса h’000’. Однако состояние флага  при этом не изменится, чем можно воспользоваться при необходимости отличить сброс по тайм-ауту сторожевого таймера от «нормального» сброса. Флаг  доступен только для чтения, т. е. он не может быть установлен обычной командой, как bsf STATUS,NOT_TO (NOT_TO — символическое имя для бита , определенное в стандартном заголовочном файле). Команда clrwdt деактивирует  (а также флаг , сбрасываемый командой sleep) и, разумеется, перезапускает блок сторожевого таймера.

В качестве примера давайте рассмотрим систему, выполняющую подсчет консервных банок, перемещаемых по конвейеру (см. Рис. 13.4), и накапливающую суммарное значение в регистре BEAN_COUNT. При сбросе по питанию в указанный регистр заносится нулевое значение. Если по какой-либо причине возникнет сбой программы и в результате тайм-аута сторожевого таймера микроконтроллер сбросится, то данное значение не должно измениться. Для этого нам придется написать код, проверяющий состояние бита  и выполняющий требуемые действия, например:

      __config _WDT_ON; Включаем сторожевой таймер

        org h’000’ ; Вектор сброса

MAIN btfSS STATUS,NOT_TO ; Сброс от сторожевого таймера?

            clrf BEAN_COUNT ; ЕСЛИ нет, ТО обнуляем счетчик

; Остальной инициализационный код ------------

             clrwdt ; Устанавливаем NOT_ТO и сбрасываем сторожевой таймер

Строго говоря, при инициализации после сброса по питанию команду clrwdt вызывать не обязательно. Однако из-за этой секции инициализации увеличивается время перехода к основной части программы и, соответственно, время до запланированного вызова команды clrwdt. Так что дополнительная команда clrwdt в конце любого блока инициализации является полезной предосторожностью.

Поскольку генератор сторожевого таймера полностью независим от системного тактового сигнала, он продолжает работать даже после перевода микроконтроллера в «спящий» режим. Для этого команда sleep сбрасывает сторожевой таймер и деактивирует флаг . К тому же она активизирует флаг  (STATUS[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
), указывающий на то, что процессор находится в «спящем» режиме. Благодаря всем этим действиям между выполнением команды sleep и наступлением тайм-аута сторожевого таймера проходит время, равное одному периоду сторожевого таймера. Если тайм-аут наступит при нахождении микроконтроллера в спящем режиме, то микроконтроллер проснется и продолжит выполнение программы с команды, следующей за командой sleep. Обычно этой командой является команда сброса сторожевого таймера clrwdt.

При необходимости программа может определить, что выход из спящего режима произошел по тайм-ауту сторожевого таймера, проверив флаги  и . В микроконтроллерах младшего уровня тайм-аут сторожевого таймера является единственным способом вывода микроконтроллера из спящего режима, не считая аппаратного сброса. В микроконтроллерах среднего и верхнего уровней выход из спящего режима тоже может осуществляться по внешнему прерыванию, а также, в некоторых случаях, по сигналу от Таймера 1 и аналоговых модулей, которые могут работать от собственного тактового генератора. Следует иметь в виду, что при включенном сторожевом таймере ток потребления микроконтроллера в спящем режиме возрастает с 0.9 мкА (5 мкА шах) до 7.5 мкА (30 мкА шах) (цифры приведены для модели PIC16F87X при VDD = 3 В, ТА —40…+85 °C). Если требуется длительная работа устройства от батарей (см., например, Вопрос для самопроверки 12.6 на стр. 449), то это может оказаться серьезной проблемой. Для сравнения скажу, что процессор, работающий на частоте 32.768 кГц при напряжении 3 В и выключенном сторожевом таймере, потребляет ток около 20 мкА (35 мкА max).

* * *

Модели младшего уровня имеют базовый 8-битный счетчик/предделитель, который первоначально назывался счетчиком/таймером реального времени (Real-Time Clock/Counter — RTCC). Хотя этот термин до сих пор встречается в документации на старые микроконтроллеры линейки PIC16CXXX, появление дополнительных таймеров привело к возникновению более логичного термина — Таймер 0. Тем не менее сокращение «RTCC» до сих пор встречается в старых книгах и программном обеспечении. Например, в компиляторе CCS задание внешнего тактового сигнала Таймера 0 с активным спадающим фронтом и коэффициента деления предделителя, равного 4, осуществляется вызовом функции setup_counters (rtcc_ext_1_to_h, rtcc_div_4).

Из Рис. 13.3 можно заметить, что Таймер 0 представляет собой 8-битный счетчик, расположенный в регистре с адресом h’01’, подключенный к 8-битному предделителю. Таким образом, с помощью трех битов PS[2:0] регистра OPTION_REG мы можем задавать восемь различных частот сигнала, подаваемого на вход счетного регистра таймера. Поскольку предделитель Таймера 0 является также постделителем сторожевого таймера, для его подключения к Таймеру 0 необходимо сбросить бит PSA регистра OPTION_REG.

Рис. 13.3. Упрощенная структура Таймера 0

По умолчанию после сброса предделитель подключен к сторожевому таймеру. В этом случае счетчик таймера может работать либо от внутреннего системного тактового сигнала частотой fOSC/4 (т. е. с частотой, в четыре раза меньшей частоты на выводе XTAL1) или от внешнего сигнала, поступающего на вход RA4/T0CKI (или GP5/T0CKI) микроконтроллера. Для переключения между внутренним и внешним тактовым сигналом служит бит T0CS регистра OPTION_REG (OPTION_REG[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
). При работе от внешнего тактового сигнала активный фронт, по которому происходит инкрементирование счетного регистра, задается битом T0SE регистра OPTION_REG (OPTION_REG [4]Здесь имеется в виду не размер данных, которыми оперирует микроконтроллер, а число битов, использующихся для записи слова команды. — Примеч. пер.
).

Очевидно, что появление активного фронта на входе T0CKI никак не синхронизировано с внутренним тактовым сигналом микроконтроллера. Чтобы можно было обычным образом считывать и изменять содержимое счетного регистра Таймера 0, потребовалось ввести узел синхронизации. Синхронизация осуществляется с использованием 2-ступенчатого сдвигового регистра, подключенного к тактовому входу счетного регистра Таймера 0. Работа синхронизатора вызывает задержку длительностью 2 машинных цикла (1 мкс при резонаторе частотой 8 МГц). Такая же задержка возникает между операцией записи нового значения в счетный регистр Таймера 0 (h’01’) и реальным обновлением его содержимого при работе таймера непосредственно от внутреннего тактового сигнала.

При переполнении Таймера 0 (11111111 —> 00000000) устанавливается флаг прерывания T0IF (INTCON[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
). Если при этом установлен бит разрешения прерывания T0IE (INTCON[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
), то автоматически будет сгенерировано прерывание (см. Рис. 7.3 на стр. 213).

Длительность каждого из интервалов ВЫСОКОГО и НИЗКОГО уровней внешнего сигнала, используемого для непосредственного тактирования Таймера 0, должна быть не менее 2tOSC + 20 нc. Таким образом, при использовании 8-МГц резонатора (tOSC = 125 нc), как Thigh, так и 7iow должны быть не менее 270 нc, в результате чего максимальная частота счета составит 1.8 МГц. При использовании предделителя указанный минимальный суммарный период 4tOSC + 40 нc может быть уменьшен в заданное число раз. При этом к сигналу, подаваемому на предделитель, предъявляется единственное требование — длительность его импульсов должна быть не менее 10 нc. Таким образом, при использовании коэффициента деления 256 на вход T0CKI можно подавать сигнал частотой 50 МГц.

Предделитель недоступен для чтения, поэтому таймер не является 16-битным счетчиком в строгом смысле этого слова. Чтение счетного регистра таймера не влияет на предделитель, а вот любая команда, осуществляющая запись в счетный регистр (например clrf h’01’, movwf h’01’), наряду с изменением состояния Таймера 0 сбрасывает как предделитель, так и узел синхронизатора тактового сигнала.

Как уже говорилось, после сброса предделитель подключен к сторожевому таймеру и для подключения его к таймеру необходимо сбросить бит PSA. Однако в результате этого переключения может произойти сброс микроконтроллера от сторожевого таймера, даже если последний выключен. Поэтому компания Microchip рекомендует перед изменением бита PSA выполнять команду clrwdt, как это сделано в следующем фрагменте кода. В данном фрагменте осуществляется инициализация предделителя следующими параметрами: коэффициент деления 4, вход T0CKI, инкрементирование по  фронту.

clrwdt ; Сбрасываем предделитель и сторожевой таймер

bsf STATUS,RP0 ; Переключаемся в 1-й банк

movlw b’11110001’ ; Внешний тактовый сигнал, активный фронт — спадающий

movwf OPTION_REG ; Предделитель 1:4, подключенный к Таймеру 0

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

Разумеется, переключать предделитель между Таймером 0 и сторожевым таймером можно и в процессе выполнения основной программы. По уже указанным причинам перед изменением регистра OPTION_REG следует выполнить команду clrwdt во избежание непроизвольного сброса от сторожевого таймера.

Таймер 0 используется главным образом либо для счета внешних событий, либо для измерения времени между внешними событиями. Кроме того, он может использоваться для управления выводами порта, позволяя отказаться от подпрограмм, формирующих временные задержки при помощи циклов.

Проиллюстрируем использование Таймера 0 в качестве счетчика событий на двух примерах. В первом примере, где мы также задействуем сторожевой таймер, производится подсчет консервных банок, перемещающихся по конвейеру (см. Рис. 13.4). После прохождения очередных 24 банок датчик должен сформировать импульс для упаковочной машины, чтобы заполненная коробка была заменена пустой. Длительность этого импульса должна составлять всего несколько микросекунд. Кроме того, необходимо предусмотреть двухбайтный счетчик, в котором будет накапливаться общее количество упакованных коробок с момента последнего сброса микроконтроллера. В конце смены это значение пересылается в центральный компьютер завода для инвентаризации.

Рис. 13.4. Подсчет консервных банок, проходящих по конвейеру

Сначала разберемся с секцией инициализации. Код этой секции, приведенный ниже, начинается с проверки флага . Если этот флаг сброшен, то фаза инициализации пропускается, поскольку сброс произошел из-за тайм-аута сторожевого таймера. В противном случае вывод T0CKI настраивается на вход, а вывод RB1 — на выход (на последнем формируется импульс управления упаковочной машиной).

    include "p16F877a.inc"

    __config _WDT_ON ; Разрешаем работу сторожевого таймера

    cblock h’20’

     _work:1, _status:1

     COUNT:2

     endc

     org 0 ; Вектор сброса

     btfss STATUS,NOT_TO ; Сброс от сторожевого таймера?

        goto MAIN_LOOP ; ЕСЛИ да, TO пропускаем секцию инициализации

     goto MAIN ; ИНАЧЕ начинаем с нуля

     org 4 ; Вектор прерывания

     goto ISR ; Обработчик

MAIN bsf PORTB,1 ; Неактивное состояние линии управления упаковщиком

         bsf STATUS,RP0 ; Переключаемся в 1-й банк

         bsf TRISA,4 ; Вывод TOCKI — вход, а вывод RB1, управляющий

         bcf TRISB,1 ; упаковочной машиной, — выход

         movlw b’00101111’ ; Таймер 0 работает от внешнего сигнала, активный фронт -

         movwf OPTION_REG ; спадающий, предделитель подключен к сторожевому таймеру

         movlw b’0110’ ; He забыть переключить порт А в цифровой режим

         movwf ADCON1 ; To есть выключить аналоговые входы

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

         bsf INTCON,T0IE ; Разрешаем прерывание от Таймера 0

         movlw -d'24' ; Загружаем в таймер -24 (E8h)

         movwf TMR0

         clrf COUNT+1 ; Сбрасываем 2-байтный счетчик коробок

         clrf COUNT

         bsf INTCON,GIE ; Разрешаем прерывания

; Фоновая процедура, которая выполняет различные операции

MAIN_LOOP

         clrwdt ; Периодически сбрасываем сторожевой таймер

... ...; Остальной код фонового цикла

Находясь в 1-м банке, мы также инициализируем регистр OPTION_REG — подключаем предделитель к сторожевому таймеру и увеличиваем его период тайм-аута в 128 раз. После этого конфигурируем Таймер 0, который должен тактироваться по спадающему фронту сигнала, поступающего на вход T0CKI. И наконец, после возвращения в 0-й банк заносим в счетный регистр таймера значение h’E8’ (т. е. -24), чтобы после поступления 24 импульсов отдатчика происходило переполнение таймера и генерировалось прерывание. После этого для разрешения данного прерывания мы устанавливаем флаги T0IE и GIE регистра INTCON.

Основная фоновая программа начинается с команды clrwdt. Если время выполнения одного прохода основного бесконечного цикла будет не более 7 х 128 = 0.8961 с, т. е. меньше периода сторожевого таймера, то тайм-аут никогда не наступит.

Теперь нам осталось только написать процедуру обработки прерывания, которая будет автоматически вызываться после накопления 24 банок, т. е. после поступления 24 импульсов на вход Таймера 0 и его переполнения. При этом устанавливается флаг T0IF и микроконтроллер переходит по адресу вектора сброса h’004’. В нашем инициализационном коде по указанному адресу мы разместили команду goto ISR. Сам код обработчика прерывания приведен в Программе 13.1.

Программа 13.1 . Процедура обработки прерывания счетчика консервных банок

; ****************

; * В обработчике формируется импульс управления упаковочной *

; * машиной и реинициализируется Таймер 0 значением -24. Также *

; * в COUNT:2 накапливается общее количество упакованных коробок *

; * для анализа в фоновом цикле *

; ****************

; Сначала сохраняем контекст

ISR movwf _work ; Сохраняем W

     swapf STATUS,w ; и регистр STATUS

     movwf _status

; =============

; Основной код

     btfss INTCON,T0IF ; Было переполнение таймера?

        goto ISR_EXIT ; ЕСЛИ нет, ТО ложная тревога

      bcf PORTB,1 ; Формируем передний фронт импульса

      movlw -d’24’ ; Реинициализируем Таймер 0

      movwf TMR0

      incf COUNT+1,f ; Увеличиваем счетчик коробок на 1

      btfsc STATUS,Z

        incf COUNT,f

      bcf INTCON,T0IF ; Сбрасываем флаг прерывания

      bsf PORTB,1 ; Формируем задний фронт импульса

; ===============

ISR_EXIT swapf _status,w ; Восстанавливаем регистр STATUS

              movwf STATUS

              swapf _work,f ; Восстанавливаем W,

              swapf _work,w ; не затрагивая STATUS,

              retfie ; и выходим из прерывания

Основной код обработчика заключен в «обертку», выполняющую переключение контекста согласно Программе 7.2 (стр. 226). Ядро обработчика выполняет следующие операции:

• Формирует импульс управления упаковочной машиной на выводе RB1.

• Заносит в счетный регистр таймера число —24.

• Инкрементирует двухбайтную переменную общего количества коробок.

• Сбрасывает флаг прерывания от Таймера 0 — T0IF.

При входе в обработчик прерывания проверяется флаг T0IF, и если он не установлен, то осуществляется выход из процедуры обработки прерывания. Если бы в устройстве использовались и другие источники прерывания, то вместо выхода из обработчика нам надо было бы перейти к другой его секции, как показано в листинге, приведенном на стр. 219.

Альтернативный подход с использованием внешнего прерывания можно посмотреть в Программе 7.2, приведенной на стр. 226.

Во втором примере демонстрируется работа модуля Таймера 0 в качестве собственно таймера, выполняющего измерение времени между событиями. В данном случае этими событиями являются всплески ЭКГ, изображенные на Рис. 7.1 (стр. 208). При обнаружении такого всплеска пиковый детектор прерывает работу микроконтроллера, в котором организован 2-байтный счетчик, работающий от внешнего генератора частотой 10 кГц. Таким образом, мы можем с дискретностью 100 мкс (назовем эту величину «тиком») определить интервал между событиями. Мы несколько изменим требования, предъявляемые к системе, чтобы избавиться от внешнего генератора, и воспользуемся для накопления числа тиков Таймером 0 (один тик в данном случае будет равен 1 мс).

Для решения данной задачи нам потребуется так сконфигурировать Таймер 0, чтобы при его работе от основного тактового сигнал микроконтроллера (через предделитель) переполнение происходило бы каждую миллисекунду (1000 мкс). Если мы возьмем кварцевый резонатор с частотой 4.096 МГц, то из уравнения

Тайм-аут = 1000 мкс = (4/4.096) х 256 х Коэффициент деления предделителя

получим, что коэффициент деления предделителя должен быть равен 4. Учитывая указанные требования, напишем секцию инициализации:

       org 0 ; Вектор сброса

        goto MAIN ; Фоновая программа

        org 4 ;Вектор прерывания

        goto ISR ; Обработчик

MAIN clrwdt ; Сбрасываем сторожевой таймер

         bsf STATUS,RP0 ; Переключаемся в 1-й банк

         movlw b’00000001’ ; Прерывание по спадающему фронту, внутр. такт, сигнал

         movwf OPTION_REG ; Предделитель — 1:4, подключен к Таймеру 0

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

         clrf NEW ; Обнуляем флаг нового события

         bsf NTCON,T0IE ; Разрешаем прерывание от Таймера 0

         bsf INTCON,INTE ; Разрешаем внешнее прерывание

         bsf INTCON,GIE ; Разрешаем работу системы прерываний

         clrf TMR0 ; Сбрасываем таймер

         clrf COUNT ; Обнуляем счетчик тиков

         clrf COUNT+1

Помимо разрешения прерывания от Таймера 0, устанавливается также флаг INTE для разрешения внешнего прерывания с вывода INT, на который подается сигнал с пикового детектора. При этом нам не требуется обнулять ни Таймер 0, ни 2-байтный счетчик тиков, поскольку первый отсчет из серии всегда будет неверным — ведь сердцебиение пациента не синхронизировано со сбросом микроконтроллера! Однако регистр NEW, в который заносится ненулевое значение при каждом обнаружении импульса ЭКГ, сбрасывается.

Основное ядро обработчика прерывания, код которого приведен в Программе 13.2, выполняет следующие действия:

1. По прерыванию от Таймера 0:

• инкрементирует 2-байтный счетчик тиков;

• сбрасывает флаг прерывания от таймера T0IF;

• выходит из прерывания.

2. По внешнему прерыванию от пикового детектора:

• копирует содержимое счетчика тиков в РОНы;

• обнуляет Таймер 0;

• устанавливает флаг NEW;

• сбрасывает флаг внешнего прерывания;

• выходит из прерывания.

Программа 13.2. Измерение периода ЭКГ с разрешением 1 мс

; *******************

; * По прерыванию от Таймера 0 в обработчике инкрементируется *

; * 2-байтный счетчик COUNT *

; * По внешнему прерыванию COUNT:2 копируется в DATA:2 *

; * и устанавливается флаг NEW, извещающий фоновую программу *

; * о готовности новых данных *

; ********************

; Сначала сохраним контекст

ISR movwf _work ; Сохраняем W

      swapf STATUS,w; и регистр STATUS

      movwf _status

; ********************

; Основной код

      btfss INTCON,T0IF ; Сердечный импульс?

        goto HEART_BEAT ; ЕСЛИ да, TO обрабатываем его

      incf COUNT+1,f ; Регистрируем очередной 1-мс тик

      btfsc STATUS,Z ; ЕСЛИ перешли через ноль,

        incf COUNT,f ; ТО инкрементируем старший байт

      bcf INTCON,T0IF ; Сбрасываем флаг прерывания

      goto ISR_EXIT

HEART_BEAT ; Сюда попадаем при обнаружении импульса ЭКГ

      movf COUNT+1,w ; Берем младший байт периода

      movwf DATUM+1 ; Копируем в пользовательский регистр

      movf COUNT,w ; Берем старший байт периода

      movwf DATUM

      clrf COUNT+1 ; Обнуляем счетчик тиков

      clrf COUNT

      btfsc INTCON,INTF ; Сбрасываем флаг прерывания

      incf NEW,f ; Сообщаем о наличии новых данных

; ********************

ISR_EXIT swapf _status,w ; Восстанавливаем регистр STATUS

              movwf STATUS

              swapf _work,f ; Восстанавливаем W,

              swapf _work,w ; не затрагивая STATUS,

              retfie ; и выходим из прерывания

По внешнему прерыванию оба байта из регистров COUNT: COUNT+1 копируются в пользовательские регистры DATUM: DATUM+1, после чего счетчик тиков и Таймер 0 обнуляются для регистрации следующего события. Фоновая программа постоянно опрашивает регистр NEW, ненулевое содержимое которого говорит о том, что имеется новое значение. В дальнейшем это значение можно будет, например, записать в последовательную EEPROM, как в Примере 12.3 на стр. 439, или же передать по последовательному каналу в ПК для последующей обработки и отображения.

* * *

Большинство PIC-микроконтроллеров среднего и старшего уровней имеют, по крайней мере, два дополнительных таймера/счетчика со следующими возможностями:

Таймер 1

Этот 16-битный таймер имеет собственный вспомогательный генератор и программируемый предделитель. Состояние этого таймера можно запомнить по внешнему событию. Кроме того, он может изменять состояние определенного вывода микроконтроллера при достижении некоторого предустановленного значения.

Таймер 2

Этот 8-битный счетчик имеет как программируемый предделитель, так и программируемый постделитель. Модуль счета данного таймера может задаваться программистом. Кроме того, этот таймер можно использовать для аппаратной генерации сигнала с широтно-импульсной модуляцией.

Модуль захвата/сравнения/ШИМ

Оба таймера могут работать совместно с модулем захвата/сравнения/ШИМ (Capture/Compare/PWM — ССР), который позволяет считывать текущее состояние Таймера 1 («захват»), сравнивать состояние Таймера 1 с заданным значением («сравнение»), а также обеспечивает автоматическую генерацию ШИМ-сигнала на базе Таймера 2.

Таймер 1 состоит из базового 16-битного счетчика, реализованного в виде пары РСН, которые называются TMR1L (младший байт) и TMR1H (старший байт). При переполнении этого счетчика устанавливается флаг прерывания TMR1IF в регистре прерываний от периферийных устройств PIR1 (PIR1 [0]).

Для тактирования этого таймера может использоваться внешний сигнал — либо подаваемый на вход TICKI микроконтроллера, либо от собственного генератора Таймера 1. Кроме того, в качестве тактового сигнала таймера может использоваться системный тактовый сигнал частотой fOSC/4. Независимо от источника тактового сигнала его частота может быть уменьшена с помощью счетчика предделителя. Внешние импульсы дополнительно могут синхронизироваться с системным генератором. Управляемая версия модуля таймера позволяет извне запрещать подачу тактового сигнала на счетный регистр с помощью вывода  микроконтроллера.

Регистр управления Таймера 1 T1CON (см. Рис. 13.5) используется для задания различных функций таймера. После сброса все биты этого регистра равны О, что соответствует следующим установкам: Таймер 1 и его внешний генератор отключены, коэффициент деления предделителя равен 1, для тактирования таймера используется системный тактовый сигнал.

Рис. 13.5. Функциональная схема Таймера 1

∙ TMR1ON

Установка бита T1CON[0] в 1 разрешает работу Таймера 1. В одних случаях связанные с Таймером 1 выводы микроконтроллера автоматически переключаются в режим входа независимо от установок регистров TRIS, а в других — программа должна принудительно переключить эти выводы на вход.

∙ TMR1CS, T10SCEN

Если бит TMR1CS (T1CON[1]Этот сайт посвящен оригинальному изданию книги на английском языке, и все перечисленные ниже материалы представлены также на английском. — Примеч. ред.
) установлен в 1, то Таймер 1 будет тактироваться от системного тактового сигнала. В противном случае используется внешний источник тактовых импульсов.

В последнем случае счет осуществляется либо по нарастающему фронту сигнала на выводе T1CKI, либо, если бит разрешения генератора Таймера 1 TIOSCEN (T1CON[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
) установлен в 1, по сигналу «собственного» генератора таймера, независимого от основного генератора микроконтроллера. Наличие такого генератора позволяет избежать подбора частоты основного кварцевого резонатора в соответствии с требованиями таймера, чем мы и воспользовались в нашем детекторе пиков ЭКГ на базе Таймера 0 (см. стр. 460). В качестве времязадающего элемента в этом генераторе используется кварцевый резонатор, подключаемый к выводам TIOSCO/TICKI и T10SC1. Максимальная частота такого резонатора составляет 200 кГц, однако, как правило, используется часовой кварц с частотой 32.768 кГц (215 Гц).

∙ T1CKPS[1:0]

Независимо от источника тактовых импульсов инкрементирование 16-битного счетного регистра может производиться как непосредственно по данному сигналу, так и по каждому второму, четвертому или восьмому импульсу. Это определяется установками битов T1CON[5:4], как показано на Рис. 13.5.

Переполнение Таймера 1 и установка флага прерывания TMR1IF происходит после 216 = 65 536 событий, считая от нуля. Этот флаг, в свою очередь, может использоваться для прерывания работы процессора, если парный ему бит маски TMR1IE в регистре PIE1 (см. Рис. 7.5 на стр. 223) установлен в 1. Разумеется, никто не запрещает отслеживать это событие путем обычного опроса данного флага. В любом случае флаг TMR1IF должен быть сброшен вручную после обнаружения переполнения.

К примеру, при использовании резонатора с частотой 32.768 кГц переполнение Таймера 1 будет происходить каждые 2 с при T1CKPS[1:0] = 00 и каждые 16 с при T1CKPS[1:0] = 11.

По умолчанию сигнал с выхода программируемого предделителя синхронизируется с системным тактовым сигналом, что приводит к появлению задержки, равной двум машинным циклам. Однако в отличие от Таймера 0 в этом таймере сигнал может передаваться в обход сдвигового регистра синхронизатора при установке бита  (T1CON[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
) в 1. Наличие асинхронного режима позволяет использовать Таймер 1 с внешним источником тактового сигнала при нахождении микроконтроллера в «спящем» режиме. Поскольку сдвиговый регистр синхронизатора тактируется системным тактовым сигналом fOSC, который в «спящем» режиме отключается, возможность обхода данного регистра просто необходима. Также асинхронный режим необходимо использовать, когда частота внешнего тактового сигнала в 4 раза больше частоты системного резонатора (в этом случае наличие синхронизатора приведет к пропуску некоторой части событий).

За исключением указанных случаев, бит  должен быть сброшен, поскольку отсутствие синхронизации может привести к непредсказуемому результату при попытке записи в счетный регистр Таймера 1 в тот момент, когда осуществляется его инкрементирование по внешнему событию. Если требуется изменить состояние Таймера 1 в асинхронном режиме, то его необходимо остановить сбросом бита TMR1ON. Так, для записи в Таймер 1 константы h’8000’:

movlw h’80’ ; Новое значение старшего байта

bcf T1CON,TMR1ON ; Останавливаем таймер

movwf TMR1H ; Загружаем в Таймер 1 число 8000h

clrf TMR1L

bsf T1CON,TMR1ON ; Перезапускаем таймер

Изменение состояния Таймера 1 всегда вызывает сброс счетчика предделителя.

Если Таймер 1 работает в синхронном режиме, его значение можно изменять «на лету». Необходимо только быть аккуратным при изменении обоих байтов счетчика, поскольку может случиться так, что после изменения старшего байта переполнение младшего произойдет до записи в него нового значения, в результате чего старший байт изменится нежелательным образом. Чтобы избежать этого, перед записью нового значения следует обнулить младший байт. Вот как, например, можно записать в Таймер 1 «налету» число h’9FFF’:

movlw h’9F’ ; Новое значение старшего байта

clrf TMR1L ; Предохраняемся от переполнения младшего байта

movwf TMR1H ; Обновляем старший байт счетного регистра

movlw h’FF’ ; Новое значение младшего байта

movwf TMR1L ; Обновляем младший байт счетного регистра

Содержимое Таймера 1 можно прочитать в любой момент времени даже при работе в асинхронном режиме. Однако, поскольку одновременно можно считать только один байт, возможна ситуация, при которой между двумя последовательными операциями чтения произойдет переполнение таймера, например:

; Предположим, что текущее состояние Таймера 1 — h’80FF’

     movf TMR1L,w; Читаем младший байт = h’FF’

     movwf TEMPL; Запоминаем

; ««в этот момент состояние Таймера 1 изменилось на h’8100’»»

     movf TMR1H,w; Читаем старший байт = h’81’

     movwf TEMPH; Запоминаем. Считанное значение равно h’81FF’!!!

В результате выполнения предыдущего кода мы получим значение h’81FF’ вместо h’80FF’. Возникновение такой ситуации наиболее вероятно при генерации прерывания от другого периферийного устройства между последовательными операциями чтения.

Чтобы результат считывания был предсказуемым, можно использовать два способа. Первый способ заключается в остановке таймера перед считыванием его состояния. Второй же способ заключается в том, что сначала считывается старший байт, а после считывания младшего байта проводится проверка, не изменилось ли состояние старшего байта, как показано в следующем примере:

Т1_СЕТ movf TMR1H,w ; Считываем старший байт

             movwf TEMPH ; Запоминаем

             movf TMR1L,w ; Считываем младший байт

             movwf TEMPL ; Запоминаем

; Теперь проверим старший байт на предмет изменения

             movf TMR1H,w ; Снова считываем старший байт

             subwf TEMPH,w ; Проверим на равенство с предыдущим значением

             btfss STATUS,Z ; ЕСЛИ не отличается, ТО пропускаем

                goto T1_GET ; ИНАЧЕ выполняем чтение повторно

Так или иначе, но при необходимости считывания точных значений крайне желательно запрещать прерывания сбросом бита GIE до окончания чтения обоих байтов. Альтернативный способ, позволяющий считывать два байта за один цикл, обсуждается на стр. 469.

При работе Таймера 1 от внутреннего тактового сигнала (TMR1CS = 0) синхронизация не требуется. В этом случае значение бита  игнорируется.

∙ TMR1GE, T1GINV

Некоторые последние модели, такие как PIC12F675, имеют управляемую версию модуля Таймера 1 (см. Рис. 13.5). При установленном бите TMR1GE (T1CON[6 |) изменение состояния таймера может быть приостановлено подачей на вывод  ВЫСОКОГО уровня.

В других вариантах управляемого Таймера 1 для задания активного уровня сигнала на выводе  используется бит T1GINV (T1CON[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
). Такой модуль таймера (реализованный, к примеру, в микроконтроллере PIC16F684) может управляться не только сигналом с вывода , но и сигналом с выхода 2-го компаратора (см. Рис. 14.6 на стр. 497). Указанная возможность позволяет измерять временные параметры аналоговых сигналов.

В качестве примера предположим, что нам требуется разработать экономичную систему сбора данных о температуре, которая будет считывать состояние датчика и передавать данное значение на базовый компьютер каждые 15 мин. Предположим, что для этого мы воспользуемся Таймером 1, работающим от кварцевого резонатора с частотой 32.768 кГц.

Поскольку максимально возможный период переполнения таймера составляет всего 16 с (см. стр. 464), для отсчета интервала в 900 с нам потребуется хранить число переполнений. Задав период переполнения таймера, равный 4 с, получим, что для отсчета требуемых 15 мин нам потребуется отсчитать 900/4 = 225 переполнений. Соответственно, процедура инициализации и общая структура программы будут похожи на код, приведенный в Программе 13.3. В данном случае Таймер 1 конфигурируется для использования внешнего генератора с коэффициентом предделителя 2, что даст нам значение тика, равное 4 с.

Программа 13.3. Формирование 15-минутного интервала

        include "p16f877a.inc"

         __config _WDT_OFF & _CP_OFF

        cblock h’20’

          JIFFY:1

         endc

         org 0

MAIN movlw Ь’00011111’ ; Таймер включен, внешний тактовый сигнал асинхронный режим

         movwf T1CON ; Внешний генератор включен, предделитель — 1:2

         clrf JIFFY ; Обнуляем счетчик тиков

         bsf STATUS,RP0 ; Переключаемся в 1-й банк

         bsf PIE1,TMR1IE ; Разрешаем прерывание от Таймера 1

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

DOOZE sleep ; Ждем прерывания

           bcf PIR1,TMR1IF ; Сбрасываем флаг прерывания

           incf JIFFY,f ; Запоминаем очередной тик

           movlw d’225’ ; Уже 225 тиков =15 мин?

           subwf JIFFY,w

           btfss STATUS,Z ; ЕСЛИ да, ТО делаем что-нибудь полезное

             goto DOOZE ; ИНАЧЕ ждем еще 15 с

; Делаем выборку

           clrf JIFFY ; Сбрасываем счетчик тиков

           call SAMPLE ; Считываем температуру и передаем ее

           goto DOOZE ; Начинаем отсчет следующего интервала

Для снижения энергопотребления микроконтроллер будет бóльшую часть времени находиться в «спящем» режиме, пробуждаясь каждые четыре секунды. Для этого бит маски TMR1IE (PIE1 [0]) устанавливается в 1. После выхода микроконтроллера из «спящего» режима флаг прерывания TMR1IF сбрасывается и инкрементируется значение счетчика тиков. Затем оно сравнивается с константой 225. В случае равенства счетчик обнуляется и вызывается подпрограмма, осуществляющая передачу нового значения температуры на базовый компьютер.

Следует отметить, что включенный Таймер 1 увеличивает потребление микроконтроллера на величину порядка 20 мкА. Особенно следует обращать на это внимание, если Таймер 1 используется для вывода микроконтроллера из режима пониженного потребления, в котором потребление микроконтроллера с выключенной периферией составляет всего 0.9 мкА (все цифры приведены для моделей PIC16F87X).

* * *

Совместно с Таймером 1 (а также, как мы увидим позже, и с Таймером 2) используется один (например, в PIC16F62X) или два (например, в PIC16F87X) модуля захвата/сравнения/ШИМ (Capture/Compare/PWM — ССР). Каждый модуль ССР, по существу, состоит из 16-битного регистра (поскольку счетный регистр Таймера 1 является двухбайтным) и 16-битного цифрового компаратора для сравнения состояния Таймера 1 и содержимого регистра модуля ССР. Поскольку модули ССР1 и ССР2 практически идентичны (они используют один и тот же задающий Таймер 1), отличаясь только различными контактами ввода/вывода ССР1 и ССР2, мы будем рассматривать модуль ССР1. Там, где это необходимо, различия между модулями будут указаны отдельно.

Модуль ССР выполняет три основных функции:

• При его работе в режиме захвата (Capture) появление заданного события на выводе, связанном с модулем ССР, вызывает копирование состояния счетного регистра Таймера 1 в регистр ССР. Эту возможность можно использовать для определения момента наступления данного события или его длительности с разрешением вплоть до 12.5 нc.

• При работе в режиме сравнения (Compare) в случае равенства счетного регистра Таймера 1 и содержимого регистра модуля ССР изменяется состояние соответствующего вывода модуля или осуществляется сброс Таймера 1. Эта возможность может использоваться для аппаратного формирования сигналов с разрешающей способностью 200 нc.

• При работе в режиме ШИМ (PWM) модуль ССР совместно с Таймером 2 может использоваться для аппаратного формирования сигнала с широтноимпульсной модуляцией разрядностью до 10 бит (разрешение 0.1 %) с программируемыми периодом и скважностью.

Во всех случаях, когда используется Таймер 1, он должен работать в синхронном режиме, т. е. бит  должен быть сброшен в 0.

Каждый модуль ССР имеет свой регистр управления, используемый для задания конфигурации модуля. Во всех случаях используемые модулями выводы необходимо вручную конфигурировать как вход или выход в соответствии с режимом работы модуля.

Работа модуля в режиме захвата показана на Рис. 13.6. В этом режиме имеются следующие подрежимы:

∙ 0000

При сбросе по питанию или по снижению напряжения питания все биты регистра обнуляются. При этом модуль ССР отключается, а предделитель сбрасывается. Чтобы избежать непредусмотренных прерываний при изменении режима работы модуля, его рекомендуется выключать перед сменой режима.

∙ 0100

По спадающему  фронту на выводе ССР1 оба байта счетного регистра Таймера 1 одновременно копируются в пару регистров CCPR1H: L. При этом устанавливается флаг прерывания CCP1IF (PIR1 [2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
) и, при установленном бите маски CCP1IE (PIE1 [2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
), генерируется прерывание.

Модуль ССР2 функционирует точно так же, за исключением того, что соответствующие биты флага CCP2IF и маски CCP2IE прерывания расположены в регистрах PIR2 (PIR2[0]) и PIE2 (Р1Е2[0]) соответственно. Причем во многих моделях среднего уровня эти биты являются единственными задействованными в указанных регистрах.

∙ 0101

Описанный выше процесс захвата производится по нарастающему  фронту на выводе модуля ССР.

∙ 0110

Захват производится по четвертому нарастающему фронту на выводе ССРn.

∙ 0111

Захват производится по шестнадцатому нарастающему фронту на выводе ССРn.

Рис. 13.6. «Захват» времени наступления события

После наступления заданного события процессор может считать сохраненное значение (время) либо в обработчике прерывания, либо после установки опрашиваемого флага CCPIF в 1. Если Таймер 1 после каждого события сбрасывается, то данное значение представляет собой время, прошедшее с момента наступления предыдущего события. Если же инкрементирование Таймера 1 не прекращается, то для определения времени между событиями достаточно вычесть новое значение из значения, запомненного во время предыдущего прерывания. Поскольку режим модуля ССР допускается изменять «налету», мы можем измерять интервал между нарастающим и спадающим фронтами на входе модуля ССР1, переключая между операциями захвата бит ССР1М[0]. При изменении режима возможна самопроизвольная установка флага прерывания ССР1IF. Чтобы предотвратить генерацию ложного прерывания, необходимо перед изменением режима сбрасывать бит ССР1IЕ, а после изменения режима — бит ССР1IF. Также можно использовать разные модули для захвата по каждому из фронтов, скажем, модуль ССР1 — для захвата по нарастающему фронту, а модуль ССР2 — для захвата по спадающему фронту (см. Пример 13.3).

Как бы это ни казалось странным, но если вывод ССР сконфигурировать как выход, то захват состояния таймера можно будет осуществлять, программно изменяя состояние данного вывода. Эта особенность позволяет использовать модуль ССР для определения длительности какого-либо внутреннего события или же для «хитрого» одновременного считывания обоих байтов таймера. Позже результат можно будет считать из регистров модуля безо всяких проблем, связанных с раздельным чтением двух регистров.

В качестве примера воспользуемся модулем ССР для измерения периода сигнала кардиограммы, подключив пиковый детектор, показанный на Рис. 7.1 (стр. 208), к выводу ССР1. Предполагая, что Таймер 1 работает в синхронном режиме от собственного кварцевого резонатора частотой 32.768 кГц, инициализационная часть программы может выглядеть следующим образом:

movlw b’00001011’ ; Таймер включен, внешний такт, сигнал, синхр. режим

movwf T1CON ; Генератор включен, предделитель — 1:1

movlw b’00000100’ ; Режим захвата по спадающему фронту

movwf CCP1CON

clrf NEW ; Обнуляем флаг NEW

clrf TMR1H ; Обнуляем Таймер 1

clrf TMR1H

bsf STATUS,RP0 ; Переключаемся в 1-й банк

bsf PIE1,CCP1IE ; Разрешаем прерывание от ССР1

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

bcf PIR1,CCP1IF ; Сбрасываем флаг прерывания

bsf INTCON,PEIE ; Разрешаем прерывания от периферийных устройств

bsf INTCON,GIE ; Разрешаем работу системы прерываний

В обработчике прерывания просто считывается содержимое регистра ССР, которое затем сохраняется в двух временных регистрах. Затем в регистр NEW заносится ненулевое значение, извещающее фоновую программу о наличии нового значения. После этого Таймер 1 сбрасывается для регистрации следующего события.

При использовании резонатора частотой 32.768 кГц и отключенном предделителе разрешающая способность считываемого значения составит 30.5 мкс. Переполнение Таймера 1 при таких параметрах конфигурации будет происходить каждые 2 с, и этого достаточно для регистрации сердечного ритма частотой до 30 ударов в минуту (см. Программу 13.4).

Программа 13.4. Определение момента появления точки R на ЭКГ

; **************

; Сначала сохраняем контекст обычным образом

ISR movwf _work ; Сохраняем W

      swapf STATUS,w ; и регистр STATUS

      movwf _status

; ===========

; Основной код

      btfss PIR1,CCP1IF ; Было прерывание от CCP1?

        goto ISR_EXIT ; ЕСЛИ нет, ТО ложная тревога

      incf NEW, f ; Сообщаем о новом захвате

      bcf PIR1,CCP1IF ; Сбрасываем флаг прерывания

      movf CCPR1L,w ; Считываем младший байт

      movwf TEMP+1 ; Запоминаем его

      movf CCPR1H,W ; Считываем старший байт

      movwf TEMP ; Запоминаем его

      clrf TMR1L ; Обнуляем Таймер 1

      clrf TMR1H

; ============

ISR_EXIT swapf _status,w ; Восстанавливаем регистр STATUS

              movwf STATUS

              swapf _work,f ; Восстанавливаем регистр W,

              swapf _work,w ; не затрагивая регистр STATUS,

              retfie ; и выходим из прерывания

В системах с повышенной надежностью также может быть разрешено прерывание по переполнению Таймера 1. Возникновение этого прерывания означает, что последующее захваченные данные будут некорректны, хотя можно подсчитать количество таких тайм-аутов и таким образом увеличить длительность определяемого интервала. Однако в нашей системе это прерывание было бы логичнее использовать для включения сигнала тревоги!

Режимы 1000…1011, указанные на Рис. 13.7, соответствуют четырем режимам сравнения. В этих режимах производится сравнение 16-битного счетного регистра Таймера 1 с содержимым пары регистров CCPR1H: L. При совпадении указанных значений устанавливается флаг прерывания CCP1IF (PIR1[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
) и, при установленном бите маски ССР1IЕ (PIE1[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
), генерируется прерывание.

Помимо установки флага CCP1IF при наступлении события «совпадение» может выполняться одно из четырех действий, определяемое состоянием битов ССР1М[3:0]:

Рис. 13.7. Модуль ССР1 в режиме «Сравнение»

1000: Установить вывод микроконтроллера при совпадении

На вывод ССР1 выставляется ВЫСОКИЙ уровень. Защелка модуля ССР может быть сброшена только переключением модуля в режим 0000, т. е. при его выключении.

1001: Сбросить вывод микроконтроллера при совпадении

На вывод ССР1 выставляется НИЗКИЙ уровень. Защелка модуля ССР может быть установлена только выключением модуля.

1010: Генерировать прерывание при совпадении

Состояние вывода ССР1 не изменяется, единственным действием является установка флага ССР1 IF.

1011: Сформировать специальное событие при совпадении

Таймер 1 сбрасывается. При использовании модуля ССР2 (это единственное отличие в функционировании модулей ССР1 и ССР2) можно запустить преобразование АЦП (см. Рис. 14.11 на стр. 510).

В режимах 1000 и 1001 выводы микроконтроллера, используемые модулями ССР1 и ССР2, должны быть сконфигурированы как выходы. При отключенном модуле ССР (после любого сброса) эти выводы будут отображать состояние соответствующих битов порта. В качестве примера давайте рассмотрим выключение модуля ССР. Поскольку единственным способом переключения защелки модуля в ее исходное состояние является запись в регистр CCPCON режима 0000, логично будет записать это значение в соответствующий бит порта для исключения нежелательных выбросов. К примеру, при использовании режима 1001 бит регистра порта следует при инициализации установить в 1, чтобы после сброса на этом выводе присутствовал ВЫСОКИЙ уровень. В микроконтроллерах, выпускающихся в корпусах с количеством выводов более 28, вывод ССР1, как правило, задействует линию RC2, а вывод ССР2 — линию RC1.

В качестве примера предположим, что нам необходимо сконфигурировать Таймер 1 также, как и в предыдущем примере, — чтобы его переполнение происходило каждые 10 с. Для этого нам необходимо задать период тайм-аута, равный 16 с (коэффициент деления предделителя 8), а затем укоротить цикл. Соответственно в регистр CCPR1 следует загрузить 10/16 от максимального значения (216 х 10/16), что составит h’A000’. При достижении таймером данного значения он будет автоматически сбрасываться, и если бит маски CCP1IE (а также биты PEIE и GIE) установлен, то будет генерироваться прерывание.

Инициализационный код для данной задачи выглядит следующим образом:

movlw h’A0’ ; Загружаем в CCPR1 число h’A000’

movwf CCPR1H;

clrf CCPR1L;

movlw b’00001011’ ; Режим ССР1 — 1011. Специальное событие

movwf CCP1C0N

movlw b’00111011’ ; Таймер 1 вкл. (1), внешний такт. сигнал (1)

movwf T1CON ; Синхр. режим (0), генератор вкл. (1), 1:8 (111)

bsf STATUS,RP0 ; Переключаемся в 1-й банк

bsf PIE1,CCP1IE ; Разрешаем прерывание от ССР1

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

bsf INTCON,PEIE ; Разрешаем прерывания от таймера/ССР

bsf INTCON,GIE ; Разрешаем работу системы прерываний

После выполнения этих команд микроконтроллер будет автоматически прерываться каждые 10 с.

Поскольку в режиме 1011 вывод ССР1 отключен от модуля ССР, указанный вывод может использоваться как обычный вывод порта ввода/вывода независимо от модуля ССР1.

* * *

Таймер 2 представляет собой 8-битный счетчик с программируемым предделителем и постделителем, как показано на Рис. 13.8. Этот счетчик всегда тактируется от системного тактового сигнала. В отличие от двух предыдущих таймеров, выходным сигналом является сигнал не с выхода счетчика, а с выхода компаратора Таймера 2. Этот блок сравнивает состояние Таймера 2 с содержимым регистра периода PR2. При равенстве указанных значений на выходе формируется импульс, который сбрасывает Таймер 2 в момент прихода следующего счетного импульса. Эта возможность, в частности, может использоваться для задания скорости передачи по интерфейсу SPI модуля SSP, как показано на Рис. 12.9 (стр. 385). В соответствии с установками постделителя по истечении заданного числа этих сбросов (от 1 до 16) будет устанавливаться флаг прерывания от Таймера 2 TMR2IF в регистре PIR1 (PIR1[1]Этот сайт посвящен оригинальному изданию книги на английском языке, и все перечисленные ниже материалы представлены также на английском. — Примеч. ред.
), а при установленном бите маски прерывания TMR2IE также будет генерироваться прерывание.

Значения коэффициентов деления пред- и постделителя, а также управление Таймером 2 осуществляется с использованием регистра управления T2CON, как описано ниже. После сброса микроконтроллера все биты этого регистра сбрасываются, выключая Таймер 2 и устанавливая коэффициенты деления, равные 1.

Рис. 13.8. Упрощенная функциональная схема Таймера 2

∙ TMR2ON

Включение Таймера 2 осуществляется установкой бита T2CON[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
в 1.

∙ T2CKPS[1:0]

Инкрементирование счетного регистра таймера может осуществляться либо с частотой тактового сигнала fOSC/4, или же с частотой, меньшей в 4 или 16 раз. Три возможные установки битов Т2СON[1:0] показаны на Рис. 13.8.

∙ TOUTPS[3:0]

Число периодов Таймера 2, после которых устанавливается флаг прерывания TMR2IF, может быть задано с помощью битов TOUTPS[3:0] (T2CON[5:2]). Этот 4-битный код n соответствует коэффициенту 1:(n + 1); от Ь’0000’ = 1:1 до Ь’1111’ = 1:16.

Преимуществом такой архитектуры является то, что подстройку точного значения периода тайм-аута можно осуществить без использования модуля ССР, просто записью требуемого значения в регистр периода. Длительность интервала до установки флага TMR2IF будет определяться выражением

(4/f OSC ) х Предделитель х (PR + 1) х Постделитель.

В качестве примера предположим, что нам необходимо формировать прерывание 100 раз в секунду. Если предположить, что микроконтроллер работает от резонатора с частотой 4 МГц, то, задав коэффициент деления предделителя равным 4, мы получим период тактового сигнала Таймера 2, равный 4 мкс. Если в регистр периода загрузить число 249, то период импульсов на выходе компаратора Таймера 2 составит 250 х 4 = 1 мс. А задав коэффициент деления постделителя, равный 10 (1001), получим период прерывания 10 мс (частота 100 Гц). Изменяя коэффициент деления постделителя от 1 до 16, мы сможем регулировать период генерации прерывания от 1 до 16 мс. Для точной подстройки периода с шагом, равным 4 х Постделитель [мкс], можно изменять содержимое регистра PR2.

Инициализационный код для этого примера выглядит следующим образом:

movlw b’01001101’ ; Постделитель 1:10 (1001), Таймер 2 вкл. (1)

movwf T2CON ; Предделитель 1:4 (01)

bsf STATUS,RP0 ; Переключаемся в 1-й банк

movlw d’49’ ; Задаем период, равный 249

movwf PR2

bsf PIE1,TMR2IE ; Разрешаем прерывание от Таймера 2

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

bsf INTCON,PEIE ; Разрешаем все прерывания от таймеров и модуле

bsf INTCON,GIE ; Разрешаем прерывания

В компиляторе CCS имеется своя функция для инициализации Таймера 2 —

setup_timer_2(<режим>, <период>, <постделитель>):

       setup_timer_2(T2_DIV_BY_4, 249,10);

       enable_interrupts(INT_TIMER2);

       enable_intqrrypts(GLOBAL);

Содержимое счетного регистра таймера можно прочитать с помощью функции get_timer2 (), а изменить — с помощью функции set_timer_2 ().

В качестве одного из наиболее распространенных применений микроконтроллерных устройств можно назвать задачу управления силовыми цепями, такими как нагревательные элементы, осветительные приборы, а также управление скоростью электродвигателей. В принципе для этого можно было бы использовать цифро-аналоговый преобразователь, подобный изображенному на Рис. 12.16 (стр. 399), управляющий мощным усилителем. Однако такая схема линейного управления дорога и крайне неэффективна из-за большой мощности, рассеиваемой на усилителе. Гораздо эффективнее и удобнее будет быстро включать/выключать нагрузку с достаточно высокой частотой. Мощные ключевые элементы, такие как тиристоры, рассеивают относительно небольшую мощность, поскольку в выключенном состоянии ток через них не протекает, а падение напряжения на открытом ключе практически равно нулю.

Примеры таких сигналов показаны на Рис. 13.9. Средняя амплитуда вычисляется как A х N, где N — коэффициент заполнения. При изменении N ot 0 до 100 %, средняя мощность, выделяемая на нагрузке, будет изменяться пропорционально — и все это без использования аналоговых элементов. Такой метод преобразования цифрового сигнала в аналоговую форму называется широтно-импульсной модуляцией (ШИМ).

Тепловая или механическая инерция большинства мощных нагрузок такова, что даже при относительно низкой частоте переключения (обычно не менее 100 Гц) «выбросы» будут сглаживаться. Низкие частоты переключения более эффективны, поскольку энергия рассеивается при каждом переключении. Если же ШИМ используется для более привычного цифро-аналогового преобразования, например, в аудиотехнике, то для снижения уровня высокочастотных гармоник полученный сигнал следует пропустить через фильтр нижних частот. При этом для отсечения нежелательных гармоник и уменьшения необходимой степени фильтрации частота выборок должна быть как минимум в 10 раз больше максимальной частоты аналогового сигнала (см. Рис. 14.3 на стр. 494).

Рис. 13.9. Широтно-импульсная модуляция

Формирование ШИМ-сигнала обычно осуществляется с использованием счетчика и схемы сравнения. Выход управляется защелкой, которая устанавливается при каждом переполнении таймера. Сброс защелки производится при достижении счетчиком числа, соответствующего коэффициенту заполнения. Чем больше это число, тем больше доля времени, в течение которого на выводе будет присутствовать ВЫСОКИЙ уровень.

В качестве примера рассмотрим 3-битный счетчик, в котором длительность рабочего импульса задана равной Ь’011’:

В этом примере ВЫСОКИЙ уровень на выводе будет присутствовать в течение трех тактов, что даст нам коэффициент заполнения, равный 3/8 или 37.5 %. Изменяя это число, можно регулировать среднюю мощность от 0 до 87.5 % с шагом 1/8.

В микроконтроллерах PIC для формирования таких сигналов используются модули ССР. При работе модуля в режиме ШИМ в качестве основного счетчика используется Таймер 2, а число, определяющее коэффициент заполнения, загружается через регистр с двойным буферированием в 10-битный компаратор. На Рис. 13.10 показана структурная схема модуля ССР1 в режиме ШИМ, формирующего сигнал на соответствующем выводе. При наличии в микроконтроллере модуля ССР2 он может использоваться аналогичным образом, только сигнал в этом случае будет формироваться на выходе ССР2. Любой из выводов, используемый для формирования ШИМ-сигнала, должен быть сконфигурирован как выход сбросом соответствующего бита регистра TRIS. Несмотря на то что оба модуля ССР могут работать параллельно с различными значениями коэффициента заполнения, период формируемых этими модулями ШИМ-сигналов будет одинаковым, поскольку они используют один и тот же Таймер 2.

Рис 13.10. Таймер 2 и модуль ССР в режиме ШИМ

Период

Временная развертка осуществляется Таймером 2, как было показано на Рис. 13.8. Величина периода переполнения зависит от длительности машинного цикла 4 х tOSC, коэффициента деления предделителя и содержимого регистра периода PR2. Учитывая, что Таймер 2 сбрасывается по следующему тактовому сигналу после достижения равенства с PR2, суммарный период повторения вычисляется следующим образом:

(4 x t OSC ) х Коэфф. деления предделителя х (PR2 + 1).

Например, при 16-МГц резонаторе, коэффициенте деления 16 и значении h’63’ = d’99’ в регистре PR2 получим

Период = (4 х  1 / 16 ) х 16 х (99 + 1) = 400 мкс

При каждом переходе Таймера 2 через значение, записанное в регистре периода, происходит три события:

1. Таймер 2 сбрасывается в 0 (если PR2 не равен 0).

2. Защелка модуля ССР устанавливается, и на выводе ССР1 появляется ВЫСОКИЙ уровень.

3. 10-битное значение, представляющее собой длительность рабочего импульса в следующем периоде ШИМ-сигнала, копируется из ведущего регистра в ведомый.

Скважность

Значение, подаваемое на 10-битный компаратор ШИМ, хранится в двухуровневом 10-битном регистре. Значение, загружаемое программой, хранится в регистре CCPR1L (8 старших битов) и в битах CCP1C0N[5:4] (два младших бита) — на Рис. 13.10 все эти биты вместе названы ведущим регистром. Содержимое ведущего регистра можно изменить в любой момент времени, выполнив две команды movwf. Это значение продвигается по конвейеру и поступает на компаратор только в конце каждого периода. Такое решение уменьшает вероятность появления выбросов в середине периода из-за произвольного характера изменений содержимого ведущего регистра относительно состояния Таймера 2. Роль ведомого регистра выполняет регистр CCPR1H совместно с 2-битной внутренней защелкой. При работе в режиме ШИМ регистр CCPR1H доступен только для чтения. Это сделано специально, чтобы исключить прямой доступ к значению, определяющему скважность сигнала.

Счетный регистр Таймера 2 является 8-битным. Для увеличения его разрядности до 10 бит в соответствии с разрядностью значения, задающего скважность сигнала, добавляется два младших бита. Эти биты берутся либо от счетчика предделителя, который используется для снижения частоты системного тактового сигнала перед подачей его на счетный регистр таймера, либо, если коэффициент деления предделителя равен единице, от 2-битного счетчика, формирующего внутренние тактовые сигналы (см. Рис. 4.4 на стр. 92). В том и другом случае максимальное разрешение длительности рабочего импульса получается равным 10 бит (1:1024) при частоте счета, в 4 раза превышающей частоту тактирования 8-битного счетного регистра Таймера 2.

При равенстве 10-битного значения счетчика числу, определяющему длительность импульса (скважность), защелка ШИМ сбрасывается, и на выводе ССР1 появляется НИЗКИЙ уровень. В этом состоянии вывод удерживается до начала формирования следующего периода сигнала в момент переполнения Таймера 2, после чего описанный цикл повторяется. Во всех случаях число в регистре CCPRL1 должно быть меньше, чем в регистре PR2, ведь в противном случае защелка ШИМ никогда не сбросится! Если в регистре PR2 находится число h’FF’, то разрешение системы максимально и равно 10 бит. Меньшие значения в регистре периода приводят к уменьшению разрешающей способности сигнала. Например, если PR2 = h’3F’, то разрешающая способность сигнала будет равна 8 бит (шесть битов счетного регистра Таймера 2 и два дополнительных).

В качестве примера предположим, что нам необходимо формировать сигнал с периодом 400 мкс (частота 2.5 кГц) при частоте системного резонатора, равной 16 МГц. При этом коэффициент деления предделителя Таймера 2 равен 16, а в регистре PR2 находится число h’63’. Для получения сигнала с коэффициентом заполнения, равным 25 % (как на Рис. 13.9, а), можно написать следующий инициализационный код:

bsf STATUS,RP0 ; Переключаемся в 1-й банк

movlw h’63’ ; Загружаем в регистр периода d’99’

movwf PR2

bcf TRISC,2 ; Переключаем ССР1 на выход

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

movlw h’19’ ; Устанавливаем ведущий регистр на 1/4 от полной шкалы (h’63/4’)

movwf CCPR1L ; То есть b’00011001’

movlw b’00001100’ ; Модуль ССР1 в режим ШИМ (1100)

movwf CCP1CON ; с CCP1CON[5:4] (00)

movlw b’00000110’ ; Предделитель Таймера 2–1:16 (10)

movwf T2CON ; Включаем Таймер 2 (1). Начинаем генерацию сигнала

Постделитель Таймера 2 при формировании ШИМ-сигнала не используется, однако влияет на установку флага TMR2IF, как и обычно. Флаг CCP1IF в данном режиме не изменяется.

Во многих силовых приложениях необходимо формировать два или четыре сигнала для управления нагрузкой, включенной по мостовой схеме. Некоторые модели микроконтроллеров, такие как PIC16F684 с усовершенствованным модулем ССР, специально предназначены для управления такими мостовыми схемами. В этих микроконтроллерах также автоматически формируется задержка между включением соседних каналов (так называемое «мертвое время»). Эта задержка необходима для того, чтобы исключить появление сквозных токов, которые могут возникнуть при одновременном переключении ключевых элементов схемы.

Примеры

Пример 13.1

Покажите, как можно использовать Таймер 0 для формирования на выходе RA0 ШИМ-представления байта, находящегося в регистре DATUM. Полагая, что частота резонатора равна 8 МГц, рассчитайте период ШИМ-сигнала.

Решение

Время наступления переполнения Таймера 0 будет зависеть от значения, загруженного в счетный регистр таймера в начале периода. Если мы загрузим в этот регистр дополнительный код значения (отрицательное число), то длительность периода будет пропорциональна этому числу — чем оно больше, тем больше времени пройдет до переполнения таймера. И, наоборот, при загрузке в счетный регистр таймера самого числа DATUM период переполнения таймера будет обратно пропорционален этому значению. Загружая поочередно в счетный регистр таймера обратное значение DATUM (и выставляя при этом на выход ВЫСОКИЙ уровень) и собственно значение DATUM (выставляя НИЗКИЙ уровень), мы получим сигнал, период которого будет приблизительно равен периоду переполнения Таймера 0 при его нормальной работе (256 тактов).

В Программе 13.5 Таймер 0 конфигурируется для работы на частоте 2 МГц (fOSC/4) без использования предделителя. Таким образом, итоговая частота ШИМ-сигнала составит 2/256 МГц = 7.8125 кГц. В обработчике прерывания, которое генерируется при переполнении Таймера 0, проверяется состояние бита PORTA[0], и если он сброшен, то его состояние изменяется и вычисляется дополнительный код заданного значения (инвертирование плюс единица). Однако из-за наличия синхронизатора между записью в счетный регистр Таймера 0 и реальным его изменением проходит 2 такта, поэтому для компенсации этой задержки дополнительно прибавляется двойка. Если же в бите порта уже присутствует единица, то он сбрасывается, а в счетный регистр таймера заносится исходное значение, увеличенное на 2.

Наличие такой компенсации может вызвать проблемы при крайних значениях коэффициента заполнения. Почему это происходит и что можно предпринять для улучшения данной ситуации?

Программа 13.5. Широтно-импульсная модуляция с использованием Таймера 0

MAIN bsf STATUS,RP0 ; Переключаемся в 1-й банк

         movlw b’00001000’ ; Внутренний такт, сигнал, предделитель выкл.

         movwf OPTION_REG

         bcf TRISA,0 ; RA0 — выход

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

         bsf INTCON,T0IE ; Разрешаем прерывание от Таймера 0

         bsf INTCON,GIE ; Разрешаем все прерывания

; <<<<Остальной код фоновой программы>>>>

; *************

; * Обработчик прерывания формирует ШИМ-сигнал на выводе RA0 *

; * Значение периода в DATUM. PORTA[0] — текущее состояние ШИМ *

; *************

; Сначала сохраним контекст -

ISR movwf _work; Сохраняем W

      swapf STATUS,w ; и регистр STATUS

      movwf _status

; *************

; Основной код

      btfss INTCON,T0IF ; Было переполнение Таймера 0?

        goto ISR_EXIT ; ЕСЛИ нет, TO ложная тревога

      bcf INTCON,T0IF ; Сбрасываем флаг прерывания

      movf DATUM,w ; Берем значение

      btfsc PORTA,0 ; Сейчас на выходе НИЗКИЙ уровень?

        goto MAKE_L0 ; ЕСЛИ нет, ТО выставляем НИЗКИЙ

МАКЕ_Н1 bsf PORTA,0 ; ИНАЧЕ выставляем ВЫСОКИЙ уровень,

      xorlw b’11111111’ ; вычисляем дополнительный код

      addlw 1 ; (инвертируем и прибавляем 1)

      goto SET_UP ; и загружаем значение в Таймер 0

MAKE_L0 bcf PORTA,0 ; Выставляем на вывод НИЗКИЙ уровень

SET_UP addlw 2 ; Компенсация задержки синхронизатора

            movwf TMR0 ; Инициализирум счетный регистр таймера

; *************

ISR_EXIT swapf _status,w ; Восстанавливаем регистр STATUS

              movwf STATUS

              swapf _work,f ; Восстанавливаем регистр W,

              swapf _work,w ; не затрагивая регистра STATUS,

              retfie ; и выходим из прерывания

Пример 13.2

Некий тахометр предназначен для регистрации скорости вращения двигателя в диапазоне 0…120 000 об/мин. При каждом обороте вала двигателя генерируется один импульс. Для подсчета числа этих импульсов в секунду и вычисления соответствующего значения в об/мин предполагается использовать микроконтроллер PIC16F877. Используя два или три имеющихся в этой модели таймера, можете ли вы разработать схему подключения микроконтроллера и написать соответствующую программу для решения данной задачи?

Решение

Скорость в 12 000 об/мин соответствует 200 оборотам в секунду. Таким образом, в качестве счетчика импульсов мы можем использовать Таймер 0, тактируемый непосредственно с вывода T0CKI, без предделителя.

Таймер 1 совместно с модулем ССР1, работающим в режиме сравнения, будет использоваться для формирования секундного интервала. Этот таймер тактируется от собственного генератора с часовым кварцем, а его состояние изменяется от h’0000’ до h’7FFF’. Однако для облегчения перевода единиц (об/мин = 60 х об/с) предлагается уменьшить интервал счета в 60/64 раз, чтобы реализовать эквивалентное соотношение — ([об/с] х 60/64) х 64. Это можно сделать, уменьшив модуль счета до h’7FFF’ х 60/64 = h’77FF’. Итоговое умножение на 64 можно выполнить либо сдвигом результата на шесть разрядов влево (<<6), либо, что более эффективно, копированием полученного значения в об/с в старший байт результата в об/мин и сдвигом его на два разряда вправо, т. е.

[об/мин] = ([об/с] х 256) >> 2.

Очевидно, что такой подход намного эффективнее, чем использование секундного интервала и умножения на 60.

Возможный вариант программы, реализующей описанный алгоритм, приведен в Программе 13.6. В секции инициализации выполняются следующие операции:

• Таймер 0 переключается в режим счета по спадающему фронту сигнала на входе T0CKI.

• Модуль ССР1 переключается в режим сравнения 1011 для сброса Таймера 1 по событию «совпадение».

• Разрешается прерывание по этому событию.

• В регистры CCPR1H: L заносится значение, соответствующее интервалу 60/64 с.

В процедуре обработки прерывания число оборотов в секунду, считанное из Таймера 0 и расширенное до двухбайтного значения, сохраняется во временных регистрах. После этого Таймер 0 обнуляется, а сохраненное значение преобразуется к об/мин, как было описано выше. После двукратного сдвига вправо два старших бита регистра RPM сбрасываются, чтобы исключить воздействие флага переноса. Итоговое 14-битное число в регистрах RPM: RPM+1 является искомым результатом, который впоследствии может использоваться в фоновой программе для выдачи на дисплей или, быть может, для передачи в компьютер по последовательному каналу.

Для повышения надежности программы в обработчике прерывания необходимо также проверять состояние флага прерывания по переполнению Таймера 0, который может использоваться для включения индикатора, предупреждающего о превышении допустимой скорости.

Программа 13.6. Программное обеспечение тахометра

MAIN movlw h’77’ ; Загружаем число h’77FF’, чтобы сформировать

        movwf CCPR1H ; интервал длительностью 60/64 с

        movlw h’FF’

        movwf CCPR1L

        bsf STATUS,RP0 ; Переключаемся в 1-й банк

        movlw b’00111000’ ; Таймер 0 — внешний сигнал, спад, фронт

        movwf OPTION_REG ; Без предделителя

        bsf PIE1,CCP1IE ; Разрешаем прерывание от ССР1

        movlw b’00000110’ ; Все выводы порта А — цифровые

        movwf ADCON1

        bcf STATUS,RP0 ; Возвращаемся в 0-й банк

        movlw b’00001011’ ; Модуль ССР в режиме сравнения (1011)

        movwf CCP1CON ; сбрасывает Таймер 1

        movlw b’00001011’ ; Таймер 1 — предделитель 1:1, собств. генератор,

        movwf T1CON ; синхронный режим

        clrf NEW ; Сбрасываем флаг

        bsf INTCON,PEIE ; Разрешаем прерывания от Таймера/ССР

        bsf INTCON,GIE ; Разрешаем все прерывания

        clrf TMR0 ; Обнуляем счетчик импульсов

        clrf TMR1H

        clrf TMR1L

; <<<< Остальной код фоновой программы >>>>

; **********************

; Сначала сохраним контекст

ISR movwf _work ; Сохраняем W

     swapf STATUS,w ; и регистр STATUS

     movwf _status

; ***********************

; Основной код

     btfss PIR1,CCP1IF ; Сброс Таймера 1 от ССР1?

        goto ISR_EXIT ; ЕСЛИ нет, ТО ложная тревога

     incf NEW,f ; Индицируем наличие нового значения

     movf TMR0,w ; Берем подсчитанное число импульсов

     clrf TMR0 ; Обнуляем счетчик

     movwf RPM ; Сохраняем результат во временном регистре

; Теперь умножим на 64

     clrf RPM+1 ; Обнуляем младший байт

     rrf RPM,f ; об/м — старший бит, т. е. х256

     rrf RPM+1,f ; >>2 для преобразования об/с в об/мин

     rrf RPM,f

     rrf RPM+1,f

     bcf RPM,7 ; Сбрасываем два старших бита

     bcf RPM,6

     bcf PIR1,CCP1IF ; Сбрасываем флаг прерывания

; ****************************

ISR_EXIT swapf _status,w ; Восстанавливаем регистр STATUS

              movwf STATUS

              swapf _work,f ; Восстанавливаем регистр W,

              swapf _work,w ; не затрагивая регистра STATUS,

              retfie ; и выходим из прерывания

Пример 13.3

Необходимо с помощью микроконтроллера PIC16F877 измерить длительность некоторого события. Этим событием является ВЫСОКИЙ уровень сигнала, как показано на Рис. 13.11. Предполагается, что частота системного резонатора равна 8 МГц, а длительность измеряемого импульса не превышает 100 мс.

Рис 13.11. Длительность импульса в качестве длительности события

Решение

Один из возможных вариантов решения этой задачи заключается в одновременной подаче отслеживаемого сигнала на выводы ССР1 и ССР2. Используя один из модулей для захвата нарастающего фронта, а другой — спадающего фронта, можно будет вычислить интервал между событиями, равный разности между двумя сохраненными значениями. В Программе 13.7 по нарастающему фронту импульса Таймер 1 обнуляется, соответственно состояние Таймера 1, захваченное по спадающему фронту, представляет собой искомую длительность. Если таймер будет работать от системного тактового сигнала с коэффициентом деления предделителя, равным 4, то инкрементирование счетного регистра будет происходить с частотой 500 кГц, т. е. временное разрешение составит 2 мкс. Максимальная длительность, которая может быть измерена при такой конфигурации, равна 216 х 2 мкс = 131.077 мс. Этого достаточно для работы с нашим сигналом, длительность которого не превышает 100 мс.

Обработчик прерывания, код которого приведен в Программе 13.7, просто проверяет по очереди флаги прерывания от каждого модуля ССР и выполняет соответствующие блоки программы. Если установлен флаг прерывания от модуля ССР1 (обнаружен нарастающий фронт  сигнала), то Таймер 1 обнуляется для запуска нового счета. Инкрементирование этого таймера осуществляется с частотой 500 кГц и при появлении спадающего фронта  сигнала его состояние считывается модулем ССР2 и помещается в 16-битный регистр CCPR2H: L. Затем в обработчике прерывания это значение, представляющее длительность импульса в 2-мкс тиках, копируется в два пользовательских регистра — ТIМЕ: ТIМЕ+1.

Вообще говоря, операция сброса Таймера 1, осуществляемого по первому событию, вносит некоторую погрешность, поскольку занимает определенное время. В нашем случае это не принципиально, однако при измерении более коротких интервалов с большим разрешением могут возникнуть проблемы. В этих случаях можно оставить Таймер 1 работать в непрерывном режиме, а длительность импульса определять как разность между двумя захваченными 16-битными значениями.

Программа 13.7. Измерение длительности импульса

MAIN movlw b100000101’ ; Модуль ССР1 — захват по нарастающему фронту

         movwf CCP1CON

         movlw b’00000100’ ; Модуль ССР1 — захват по спадающему фронту

         movwf CCP2CON

         bsf STATUS,RP0 ; Переключаемся в 1-й банк

         bsf PIE1,CCP1IE ; Разрешаем прерывание от ССР1

         bsf PIE2,CCP2IE ; Разрешаем прерывание от ССР2

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

         movlw b’00100001’ ; Таймер 1 включен (1), внутренний генератор (0!

         movwf T1CON ; Синхронный режим (0), предделитель 2:1 (10)

         clrf NEW ; Сбрасываем признак нового значения

         bsf INTCON,PEIE ; Разрешаем прерывания от Таймера/ССР

         bsf INTCON,GIE ; Разрешаем работу системы прерываний

<<<< Остальной код фоновой программы >>>>

; **************

; Сначала сохраним контекст

ISR movwf _work ; Сохраняем W

      swapf STATUS,w ; и регистр STATUS

      movwf status

; **************

; Основной код

      btfsc PIR1,CCP1IF ;Прерывание от CCP1 (нараст. фронт)?

         goto CAPTURE1 ;ЕСЛИ да, ТО обработаем его!

      btfss PIR2,CCP2IF ;Прерывание от ССР2 (спад, фронт)?

         goto ISR_EXIT

CAPTURE2

      movf CCPR2L,w ; Берем младший байт захваченного значения

      movwf TIME+1 ; и сохраняем его

      movf CCPR2H,w ; Берем старший байт захваченного значения

      movwf TIME ; и сохраняем его

      bcf PIR2,CCP2IF ; Сбрасываем флаг прерывания

      incf NEW,f ; Сообщаем фоновой программе о наличии нового значения

        goto ISR_EXIT

CAPTURE1

     clrf TMR1L ; Обнуляем счетный регистр таймера

     clrf TMR1H

     bcf PIR1,CCP1IF ; Сбрасываем флаг прерывания

; ****************

ISR_EXIT swapf _status,w ; Восстанавливаем регистр STATUS

              movwf STATUS

              swapf _work, f ; Восстанавливаем регистр W,

              swapf _work,w ; не затрагивая регистра STATUS,

              retfie ; и выходим из прерывания

Вопросы для самопроверки

13.1. Используя Таймер 1 совместно с модулем ССР1, напишите программу, формирующую на выходе ССР1 меандр с периодом 20 мс. Частоту кварцевого резонатора примите равной 8 МГц. Подсказка: помните, что состояние выхода модуля ССР изменяется только при событии «совпадение», поэтому режим сравнения потребуется переключать «налету» каждые 10 мс.

13.2. В схеме ультразвукового дальномера, приведенной на Рис. 7.9 (стр. 236), используется внешний генератор частотой 17.2 кГц, который прерывает работу микроконтроллера каждые 58 мкс, т. е. с периодом, соответствующим времени прохождения звуковой волной расстояния в один сантиметр в воздухе. Полагая, что микроконтроллер работает на частоте 20 МГц, покажите, как можно использовать Таймер 2 для генерации прерывания с такой периодичностью и точностью, составляющей более 0.1 %.

13.3. Микроконтроллеры PIC среднего уровня имеют только один вход внешнего прерывания, INT. Предложите вариант использования Таймера 0 для симуляции дополнительного внешнего прерывания на выводе T0CKI.

13.4. При программной реализации асинхронного канала последовательной передачи данных со скоростью 300 бод, необходимо формировать задержки длительностью 3.33 мс. Предполагая, что микроконтроллер работает на частоте 8 МГц, покажите, как можно использовать таймер для генерации прерывания с периодичностью, равной длительности битового интервала. Усовершенствуйте процедуру таким образом, чтобы она поддерживала скорости передачи до 19 200 бод (каждое последующее значение скорости получается удвоением предыдущего).

13.5. Покажите, как можно использовать Таймер 1, работающий от собственного генератора с резонатором 32.768 кГц, для реализации часов реального времени (регистры HOURS: MINUTES: SECONDS) системы центрального отопления из Примера 7.3 (стр. 231).

13.6. В Си-компиляторе CCS имеются встроенные функции для работы с таймерами и модулями ССР. Например, запись в счетный регистр Таймера 1 можно осуществить вызовом функции set_timer1 (<значение>). Для считывания состояния таймера предназначена функция get_timer1 () >). Функция setup_timer1 (<режим>) используется для инициализации таймера. Аналогично, функция setup_ccp1 (<режим>) предназначена для инициализации регистра CCP1CON. При задании конфигураций Таймера 1 и модуля ССР1 используются следующие константы:

Значение, передаваемое в подпрограмму, получается объединением указанных констант с помощью оператора ИЛИ «|».

Покажите, как можно переписать ответ на Вопрос для самопроверки 13.5 с использованием языка Си. В компиляторе CCS функцию можно объявить в качестве обработчика прерывания от модуля ССР1, поставив перед ней директиву #int_ccp1 (см. Программу 9.6 на стр. 293 для дополнительной информации). При этом в вашем распоряжении имеется зарезервированная переменная ССР_1, представляющая содержимое 16-битного регистра CCPR1H: L.

13.7. Широтно-импульсная модуляция может использоваться для управления скоростью вращения электродвигателя постоянного тока за счет изменения среднего тока, протекающего по его обмотке. Однако запуск такого электродвигателя представляет известную проблему, поскольку ток обмотки при пуске в несколько раз превышает ток, протекающий в установившемся режиме. Для предотвращения выхода из строя силового управляющего транзистора предлагается постепенно увеличивать скважность ШИМ-сигнала с О до максимального значения в течение нескольких секунд. Покажите, как это можно осуществить с помощью микроконтроллера PIC, работающего на частоте 4 МГц, и его модуля ССР.

13.8. Дорожные светофоры на регулируемых пешеходных переходах в Англии при нажатии на любую из кнопок разрешения перехода работают по следующему алгоритму:

1. Зеленый свет (нормальный режим).

2. Оранжевый свет в течение 3 с.

3. Красный свет, сопровождающийся звуковым сигналом в течение 15 с.

4. Мигающий оранжевый свет — пять вспышек длительностью по 3 с с трехсекундными паузами между вспышками.

5. Возврат в нормальный режим.

Используя подходящий микроконтроллер PIC с модулем Таймера 1, напишите программу, управляющую сигналами светофора и звуковым излучателем. Хотя световые сигналы расположены по обе стороны дороги, можете считать, что они соединены параллельно и включаются ВЫСОКИМ уровнем на соответствующем выводе порта. Управляющие кнопки CROSS_REQUEST0 и CROSS_REQUEST1 при нажатии формируют лог. О на входе микроконтроллера. Звуковой излучатель включается НИЗКИМ уровнем на соответствующем выводе порта микроконтроллера.

 

Глава 14

Этот безумный аналоговый мир

Принимая во внимание тот факт, что основной задачей цифровых микроконтроллеров является отслеживание и управление состоянием реального окружения, которое по своей природе имеет аналоговый характер, нам придется рассмотреть методы взаимодействия между аналоговым и цифровым миром. Часто все, что нам требуется, — это сравнить уровни двух аналоговых сигналов. Однако в более сложных случаях входной аналоговый сигнал необходимо преобразовывать в его цифровой эквивалент, т. е. выполнять аналого-цифровое преобразование (АЦП). В дальнейшем полученный двоичный код можно будет обработать привычным образом. И наоборот, если выходной сигнал должен быть аналоговым, необходимо выполнять цифро-аналоговое преобразование (ЦАП).

Из этих операций, схематично изображенных на Рис. 14.1, наиболее сложной является операция аналого-цифрового преобразования. Во многих микроконтроллерах PIC имеется встроенный модуль многоканального АЦП. А вот для формирования аналогового выходного сигнала, как правило, приходится использовать дополнительные внешние элементы.

Рис. 14.1. Аналоговый мир — цифровая обработка

В данной главе мы с вами познакомимся с характеристиками аналоговых и цифровых сигналов, а также рассмотрим преобразование этих сигналов в обоих направлениях применительно к микроконтроллерам PIC. После прочтения этой главы, вы:

• Поймете взаимосвязь между аналоговыми и цифровыми сигналами.

• Осознаете причину, по которой выборку аналогового сигнала необходимо производить с частотой, превышающей максимальную частоту этого сигнала, по крайней мере, в 2 раза.

• Узнаете, как с помощью метода последовательного приближения можно преобразовать аналоговое напряжение в его двоичный эквивалент.

• Разберетесь в работе модулей аналогового компаратора, источника опорного напряжения и АЦП, а также научитесь конфигурировать эти модули.

• Узнаете, как следует конфигурировать линии ввода/вывода микроконтроллера, чтобы они могли работать с аналоговыми или цифровыми сигналами.

• Сможете писать ассемблерные программы, считывающие значения аналоговых сигналов с использованием опроса, прерываний, «спящего» режима, а также опрашивающие состояние аналогового компаратора.

• Сможете писать программы на языке высокого уровня Си, осуществляющие инициализацию различных аналоговых модулей и взаимодействие с ними.

• Узнаете, как можно управлять микросхемой внешнего ЦАП через параллельный порт.

Информация, передаваемая при помощи аналогового сигнала, содержится в определенных параметрах, таких как амплитуда, частота или фаза, которые могут принимать любые значения из непрерывного диапазона величин. Хотя такое определение подразумевает изменение аналоговых значений в диапазоне ±, на практике этот диапазон обычно ограничен. Так, ртутный термометр может измерять температуру в диапазоне, скажем, от -10 до +180 °C. При температуре, меньшей нижней границы, вся ртуть окажется спрятанной в колбе. А при температуре, превышающей верхнее значение, термометр просто взорвется!

Теоретически квантовая природа вещества подразумевает наличие некоторого нижнего предела, после которого изменения любых параметров приобретают дискретный характер. Однако на практике максимальное значение разрешающей способности, необходимое для обработки, определяется шумами и ограниченной точностью источников сигналов.

В цифровых сигналах информация представляется в виде совокупности дискретных символов. В зависимости от числа и типа этих символов возможно представление только конечного числа значений. Так, в двоичной системе n-битное число может в лучшем случае представлять 2n уровней. Хотя такое грубое представление может показаться несопоставимым с бесконечным числом значений, которые с равной вероятностью может принимать эквивалентный аналоговый сигнал, сетку (шаг) квантования можно подобрать таким образом, чтобы обеспечить точность, требуемую для решения каждой конкретной задачи. Так, в системах передачи голоса по телефонным линиям вполне достаточно точности около 1 %. В этом случае можно использовать 8-битное представление аналогового сигнала, которое даст нам 256 дискретных значений, что соответствует разрешающей способности около 0.5 %. В музыкальном компакт-диске используется 16-битное представление (65 536 разрядов) — разрешающая способность около 0.0015 %.

Из сказанного можно понять, что любой процесс, включающий в себя преобразование между аналоговым и цифровым представлением, пройдет через этап квантования. Соответственно, нам необходимо рассмотреть, каким образом этот этап влияет на информационное содержимое соответствующих сигналов.

В качестве примера рассмотрим ситуацию, представленную на Рис. 14.2. В данном случае входной сигнал преобразуется в 3-битный код. Процесс квантования (оцифровки) сигнала заключается в сравнении аналогового значения со значениями фиксированного числа уровней — в данном случае восемью. В качестве цифрового эквивалента исходного сигнала принимается ближайший по значению уровень. Так, на Рис. 14.2 входное напряжение величиной 0.0536 из полного диапазона 0.4285 оказывается больше напряжения, соответствующего 3-му уровню. Соответственно, его квантованное значение принимается равным 3-му уровню и выражается числом Ь’011’.

Получившаяся ошибка, равная -0.0536, называется шумом квантования, и полностью ее избежать невозможно (см. также Рис. 14.3, г). Кривая распределения

Рис. 14.2. Процесс квантования

ошибки квантования приведена в нижней части Рис. 14.2, и, как можно увидеть, она зависит только от числа уровней квантования. Эту ошибку можно легко определить, вычислив квадрат среднего значения интеграла вероятности ошибок. Взяв квадратный корень от результата, мы получим среднеквадратичное значение шума:

Среднеквадратичное значение вычисляется по формул:

Таким образом, среднеквадратичное значение шума равно L/√12 = L/2√3, где L — число уровней квантования.

Основной оценкой качества системы является отношение сигнал/шум (S/N). Если принять, что сигнал имеет синусоидальную форму с размахом 2n ∙L, то среднеквадратичное значение сигнала будет равно (2n ∙L/2)/√2, т. е. пик. значение/√2. Таким образом, n-разрядная двоичная система имеет отношение сигнал/шум:

или в децибелах:

S/N = 20 log1.22 x 2 n = (6.02 n + 1.77)дБ.

Динамический диапазон квантованной системы определяется отношением полной шкалы (2n∙L) к разрешающей способности L. То есть он равен 2й, или, в децибелах, 20log2n =20∙n∙log2 = 6.02n. Разрешающая способность может также выражаться в процентах — такой параметр называется процентной разрешающей способностью (см. Табл. 14.1).

Из Табл. 14.1 четко виден экспоненциальный характер изменения этих параметров относительно разрядности двоичного значения. Однако сложность реализации этого преобразования и, соответственно, ее стоимость тоже подчиняется этому закону. Так, при использовании 20-битного преобразования на полной шкале 1 В, уровень квантования получится меньше 1 мкВ. В телефонных системах с импульсно-кодовой модуляцией (ИКМ) используется 8-битное кодирование, однако уровни квантования расположены неравномерно — более часто при меньших значениях амплитуды. Такое решение позволяет снизить шипение в трубке во время пауз в разговоре! Линейное 8-битное преобразование подходит для большинства общих применений, обеспечивая разрешающую способность лучше ± 1/4 %. На самом деле видеоизображение имеет приемлемое качество уже при 4-битном разрешении, а для воспроизведения музыки вообще достаточно однобитного квантования, т. е. простого указания полярности сигнала!

Величины отношения S/N, приведенные в Табл. 14.1, являются теоретически достижимыми максимальными значениями, поскольку ошибки преобразования между представлениями сигнала, а также эффект наложения спектров (мы обсудим это чуть ниже) вносят свой вклад в искажение сигнала.

С точки зрения аналогового мира время является величиной непрерывной, тогда как в цифровых системах выборка значений происходит через дискретные промежутки времени. Теорема отсчетов Шеннона гласит, что при частоте отсчетов, большей или равной удвоенному значению частоты самой высокочастотной составляющей в сигнале, потери информации не произойдет. Физический смысл этого нижнего предела, называемого частотой Найквиста (Котельникова), можно понять, рассмотрев спектр последовательности амплитудно-модулированных импульсов. Идеальные импульсы (импульсы нулевой длительности и единичной площади) представляются в частотной области бесконечной последовательностью гармоник одинаковой амплитуды, отстоящих друг от друга на величину, равную частоте следования импульсов. Реальные импульсы имеют похожий спектр, однако амплитуда гармоник снижается с ростом частоты.

Если мы промодулируем эту импульсную последовательность узкополосным сигналом Asincωft, то в частотной области эта операция будет эквивалентна умножению гармонического спектра (импульс) на величину Asincωft, давая суммарную и разностную составляющие:

Asincωft x Bsincωht = AB/2∙(sin(ωh + ωf)∙t + ∙(sin(ωh - ωf)∙t)

для каждой из гармоник ωh.

Более сложные узкополосные сигналы можно представить в виде ограниченной по частоте (fm) совокупности отдельных синусоидальных сигналов. Исходя из полученного соотношения, каждая из этих гармоник будет находиться как ниже (суммарная составляющая), так и выше (разностная составляющая) центральной частоты. Из Рис. 14.3, б можно увидеть, что для того, чтобы боковые полосы не перекрывались, гармоники (кратные частоте выборки) должны располагаться с интервалом не менее 2хfm.

Для восстановления исходного узкополосного сигнала из импульсной последовательности можно воспользоваться фильтром нижних частот, как показано на Рис. 14.3, г. Реальные фильтры будут пропускать определенные гармоники, хотя и ослабляя их. При более внимательном рассмотрении спектра сигнала на Рис. 14.3, г можно заметить остаток нижней боковой полосы первой гармоники, попавшей в полосу пропускания фильтра. Однако наибольшие искажения в восстановленном аналоговом сигнале возникли из-за ошибок квантования, вызванных грубой 3-битной дискретизацией. Подобная система будет иметь отношение S/N на уровне 20 дБ.

Чтобы снизить требования, предъявляемые к восстанавливающему фильтру, частота отсчетов выбирается, как правило, несколько выше частоты Найквиста. За счет этого появляется защитный промежуток между спектрами. Например, системы телефонной связи с ИКМ ограничивают входной аналоговый сигнал на уровне 3.4 кГц, однако частота выборки при этом составляет 8 кГц. Аналогично, в музыкальных компакт-дисках используется частота дискретизации 44.1 кГц, при этом максимальная частота сигнала составляет всего 20 кГц.

Еще один пример дискретизации с частотой ниже частоты Найквиста показан на Рис. 14.4. В данном случае частота дискретизации составляет всего 0.75 от частоты узкополосного сигнала. Результат восстановления сигнала посредством фильтрации полученной импульсной последовательности, показанный на Рис. 14.4, б, мягко говоря, не очень похож на исходный сигнал. Этот ложный сигнал называется помехой дискретизации или ложной частотой (alias). В случае, когда во входном аналоговом сигнале присутствуют составляющие с частотой, которая больше половины частоты дискретизации, скажем, из-за шумов, они приводят к появлению искажений в восстановленном сигнале. По этой причине аналоговые сигналы перед подачей на АЦП обычно пропускают через ФНЧ. Данный процесс известен как защита от наложения спектров.

* * *

При работе с аналоговыми сигналами во многих случаях достаточно просто знать, как соотносится контролируемое напряжение с опорным значением Vref. Например, сигнал, изображенный на Рис. 14.5 (см. также Рис. 14.20), представляет собой ток разряда двухфазного дефибриллятора ЭКГ, формируемый датчиком тока (преобразователем ток — напряжение) на основе эффекта Холла. В режиме покоя (когда дефибриллятор не используется) напряжение на выходе датчика держится на уровне 2.6 В. Когда дефибриллятор начинает разряжаться, это напряжение в течение нескольких десятков микросекунд резко увеличивается до 3.6 В.

Рис. 14.3. Процесс аналого-цифрового преобразования

Рис. 14.4. Эффект наложения спектров

Если микроконтроллеру необходимо отслеживать напряжение в течение последующих нескольких десятков миллисекунд, скажем, для вычисления суммарной энергии разряда, то для запуска этого процесса ему необходимо знать, когда напряжение превысит пороговое значение. На Рис. 14.5 в качестве порогового выбрано напряжение 3.4 В. Разумеется, можно просто с большой частотой считывать аналоговый сигнал с помощью встроенного модуля АЦП (если он есть), как описано далее на стр. 511, однако на реализацию этой процедуры непрерывного считывания и проверки уйдет большая часть вычислительных ресурсов процессора. Программа получилась бы более эффективной, если бы имелась возможность автоматической генерации прерывания при превышении входным напряжением порогового значения, а уже обработчик прерывания запускал бы процедуру считывания и анализа сигнала в режиме реального времени.

На Рис. 14.5 аналоговый сигнал Vdefb подается на неинвертирующий (+) вход аналогового компаратора. К инвертирующему входу компаратора подключен источник опорного напряжения 3.4 В. Когда напряжение Vdefb становится больше напряжения Vref, сигнал на выходе компаратора меняется с лог. 0 на лог. 1, и, наоборот, при Vdefb < Vref выходе компаратора снова появляется лог. 0.

По своей сути аналоговый компаратор является дифференциальным усилителем с высоким коэффициентом усиления без отрицательной обратной связи (ООС). Имея очень большое значение коэффициента усиления при разомкнутой цепи ООС, усилитель будет переходить в состояние насыщения с уровнем выходного сигнала, близким к отрицательному или положительному напряжению источника питания, если напряжения на его входах отличаются даже на чрезвычайно малую величину. Таким образом, в качестве компаратора может применяться обычный ОУ, однако лучше использовать специализированные микросхемы, формирующие на выходе стандартные логические уровни, а также мгновенно срабатывающие при переходе медленно меняющегося сигнала через пороговое значение.

Рис. 14.5. Использование аналогового компаратора для определения начального момента разряда дефибриллятора ЭКГ

Все три используемые нами модели микроконтроллеров PIC имеют встроенный модуль компаратора. В 8-выводной модели PIC12F675 реализован только один аналоговый компаратор. Однако для моделей в корпусах с большим количеством выводов (в частности, для моделей серии PIC16F87XA) более типичным является наличие сдвоенного компаратора, различные варианты включения которого приведены на Рис. 14.6.

Рис. 14.6. Режимы работы модуля аналогового компаратора в микроконтроллерах PIC 16F87X

Регистр управления компаратора CMCON, обычно расположенный, по адресу h’9C’, используется для выбора одной из восьми возможных конфигураций модуля, показанных на Рис. 14.6. Конфигурация компаратора определяется битами режима СМ[2:0] (CMCON[2:0]). В конкретном случае PIC16F987XA при сбросе микроконтроллера в эти биты заносится число b’111’, при котором модуль аналогового компаратора полностью выключен. Во многих других устройствах режимом по умолчанию является режим Ь’000’, при котором компараторы тоже отключены, однако используемые ими выводы микроконтроллера сконфигурированы как аналоговые входы.,

Запомните универсальное правило для всех микроконтроллеров PIC с аналоговыми модулями: все выводы микроконтроллера, которые могут работать как аналоговые (обычно выводы порта А, Е или GP), всегда после сброса по включению питания становятся аналоговыми входами. Это сделано для того, чтобы предотвратить повреждение входных цифровых буферов (см. Рис. 11.7 на стр. 340) на тот случай, если при включении микроконтроллера на выводе будет присутствовать аналоговое напряжение величиной, скажем, 2.6 В. Если данный вывод будет сконфигурирован как цифровой вход, воспринимающий сигналы с напряжением, близким к 0 В или к напряжению питания, то такое промежуточное напряжение может привести к одновременному открытию обоих входных транзисторов. В результате через них потечет сквозной ток, который способен вызвать тепловой пробой. Поскольку аналоговые напряжения не имеют каких-либо четко определенных значений, то даже в случае, когда вывод сконфигурирован как аналоговый вход, в схему часто вводится внешний последовательный резистор, который служит для ограничения тока в том случае, если аналоговое напряжение превысит напряжение питания микроконтроллера или станет отрицательным, как показано на Рис. 14.20.

В микроконтроллерах линейки PIC16F87XA для сохранения совместимости с более старыми моделями PIC16F87X, не имевшими модуля аналогового компаратора, такая конфигурация (переключение) выводов полностью отключена по умолчанию. Однако все устройства данной линейки имеют модуль встроенного АЦП, который при сбросе по питанию переключает все связанные с ним выводы в режим аналоговых входов, выполняя, таким образом, описанное выше правило. При нахождении модуля компаратора в режиме Ь’111’ его потребление минимально, поэтому этот режим следует использовать, если микроконтроллер не работает с аналоговыми сигналами и модуль компаратора не используется, особенно в «спящем» режиме.

По большому счету в зависимости от режима работы модуля в распоряжении пользователя оказываются либо два полностью независимых компаратора, либо два компаратора с объединенными неинвертирующими входами, которые могут использоваться для подачи общего опорного сигнала. Выходное значение любого активного компаратора можно считать в любой момент времени из 6-го (C1OUT) и 7-го (C2OUT) битов регистра CMCON. На выходе каждого компаратора имеется программируемый инвертор, управляемый битами C1INV и C2INV регистра CMCON (CMCON[4]Здесь имеется в виду не размер данных, которыми оперирует микроконтроллер, а число битов, использующихся для записи слова команды. — Примеч. пер.
и CMCON[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
соответственно). При Vin+ > Vin- и сброшенном бите инвертирования выходное значение компаратора будет равно 1, в противном случае — 0. Как было указано на стр. 466, в некоторых исполнениях Таймера 1 выход 2-го компаратора может использоваться для блокирования счетных импульсов таймера. Используя эту возможность, можно измерять время, в течение которого уровень аналогового сигнала превышал пороговое напряжение. В режимах Ь’011’ и b’101’ выходное значение компараторов также можно считать с выводов RA4/C10UT и RA5/C20UT микроконтроллера (в других моделях используемые линии портов могут отличаться от указанных). Для этого данные выводы должны быть сконфигурированы как выходы при помощи сброса соответствующих битов регистра TRIS.-Аналогично, любые выводы параллельных портов, используемые в качестве аналоговых входов, должны быть сконфигурированы как входы.

При изменении выходного сигнала компаратора устанавливается флаг прерывания от компаратора CMIF, расположенный у микроконтроллера PIC16F687XA в регистре PIR[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
, а при установленном бите маски CMIE (Р1Е2[6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
для PIC16F87XA) будет сгенерировано прерывание от компаратора, если, разумеется, бит глобального разрешения прерываний также установлен в 1. Поскольку эта линия прерывания используется обоими компараторами, программа должна хранить информацию о предыдущих значениях битов C1OUT и C2OUT, чтобы иметь возможность определить, состояние какого из компараторов действительно изменилось. Эта информация может обновляться в обработчике прерывания. После чтения регистра CM CON несоответствие между новым и предыдущим состояниями компаратора, вызвавшее установку флага прерывания, будет устранено — точно так же, как и в случае прерывания по изменению состояния выводов порта В, описанного на стр. 347. Только после выполнения этой операции можно сбрасывать флаг CMIF. Если режим компаратора изменяется «на лету», то перед этим изменением следует запретить прерывание от компаратора. Выждав после изменения режима не менее 10 мкс (в течение этого времени стабилизируются значения сигналов), регистр CMCON необходимо повторно считать для сброса возможного несоответствия, а затем сбросить флаг CMIF перед повторным разрешением работы системы прерываний.

Поскольку модуль компаратора не использует системный тактовый сигнал, активный компаратор можно задействовать для вывода микроконтроллера из «спящего» режима при переходе внешнего сигнала через пороговое значение Vref, что вызывает установку флага CMIF. После «пробуждения» микроконтроллер должен убрать несоответствие (прочитать регистр CMCON) и сбросить флаг CMIF в теле основной программы (после команды sleep) или в обработчике прерывания, если было разрешено прерывание от компаратора.

Необходимо отметить, что включенный компаратор потребляет ток, который намного больше базового значения потребления в «спящем» режиме. Например, типичный ток потребления микроконтроллеров PIC12F629/675 в «спящем» режиме составляет 2.9 нА при напряжении 5 В (995 нА mах), а модуль компаратора в среднем потребляет 11.5 мкА (16 мкА mах). Так что если компараторы не используются во время «сна» микроконтроллера, то они должны быть выключены.

В режиме b’110’ каждый из компараторов может контролировать один из двух сигналов, определяемый состоянием бита входного ключа компаратора CIS (CMCON[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
), который при включении питания сбрасывается в 0. Неинвертирующие входы обоих компараторов в этом режиме подключены к внутреннему источнику опорного напряжения, формируемого модулем опорного напряжения компаратора (Comparator Voltage Reference — CVR).

Этот модуль CVR имеется во всех моделях микроконтроллеров с модулем компаратора. Как видно из Рис. 14.7, данный модуль представляет собой аналоговый мультиплексор с подключенной к нему резистивной цепочкой, на выходе которого в соответствии со значениями битов CVR[3:0] регистра управления CVRCON (CVRCON[3:0]) может быть сформировано одно из 16 различных напряжений. Модуль опорного напряжения включается при установке бита разрешения CVREN (CVRCON[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
). При этом цепочка последовательно соединенных резисторов, номинальное сопротивление каждого из которых равно 2 кОм, подключается к шине питания VDD.

Рис. 14.7. Модуль опорного напряжения компаратора

В распоряжении пользователя имеется два диапазона опорного напряжения. Конкретный диапазон задается битом CVRR (CVRCON[5]См., например: Рональд Дж. Точчи, Нил С.Уидмер . Цифровые системы. Теория и практика: 8-е изд.: Пер. с англ. — М.: Издательский дом «Вильямс», 2004.
), который подключает или отключает дополнительный резистор сопротивлением 8R в конец цепочки. Обозначив 4-битное значение CVR[3:0] как я, получим:

где n изменяется в диапазоне от 0 до 15.

Погрешность установки напряжения составляет 1/2 шага, но в реальности абсолютное значение выходного напряжения модуля прямо пропорционально напряжению питания, величина которого обычно задается не слишком точно. Кроме того, значение VDD может изменяться при уходе напряжения источника питания или батареи из-за температуры или тока нагрузки. Даже любая помеха по шине питания отразится на опорном напряжении, хотя действие помех в какой-то степени можно ослабить посредством фильтрующих конденсаторов и корректной разводкой линий питания. Поэтому в тех случаях, когда требуется точное значение напряжения, часто используются внешние прецизионные источники опорного напряжения. В частности, при работе модуля компаратора в режиме Ь’100’ этот источник подключается к выводу RA3 (см. Рис. 14.20).

Предположим, что мы собираемся получить пороговое напряжение величиной 3.4 В (Рис. 14.5) при VDD = 5 В. Нам придется использовать верхний диапазон, т. е. CVRR = 0. Вычислим значение битов CVR[3:0]:

5 х (0.25 + n/32) = 3.4

0.25 + n/32 = 3.4/5

n = (3.4/5 — 0.25) х 32 = 13.76

Таким образом, наиболее близкое к заданному напряжение получится при n = 14. Задав CVR[3:0] = b’1110’, получим Vref = 3.4375 В.

В некоторых моделях имеется дополнительный управляющий бит, подключающий выход модуля опорного напряжения к выводу порта, что позволяет использовать его с внешними узлами схемы. Когда бит CVROE (CVRCON[6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
) установлен в 1, аналоговое напряжение Vref выдается на соответствующий вывод микроконтроллера. Из-за относительно высокого выходного сопротивления, которое к тому же зависит от выбранной величины опорного напряжения, компания Microchip рекомендует буферировать внутренний источник опорного напряжения — обычно с помощью операционного усилителя. При необходимости, задавая коэффициент усиления такого усилителя, можно более точно задавать напряжение Vref. С помощью внешнего ОУ также можно реализовать фильтрацию этого сигнала для снижения уровня высокочастотных помех. При таком режиме работы модуль опорного напряжения может использоваться как простой 4-битный цифро-аналоговый преобразователь.

Инициализационный код, осуществляющий настройку модулей компаратора и опорного напряжения для нашего примера с дефибриллятором (используется 1-й компаратор, вход которого подключен к RA3), будет иметь следующий вид:

include "p16f877a.inc"

bsf STATUS,RP0 ; Переключаемся в 1-й банк

movlw b’00001110’ ; Режим компаратора 110

movwf CMCON ; Подключен к RA3 (CIS = 1)

movlw b’10001110’ ; Модуль CVREF включен (1), наружу не выведен

movwf CVRCON ; Верхний диапазон (0), CVR[3:0] = 1110

bsf PIE2,CMIE ; Разрешаем прерывания от компаратора

call DELAY_10US ; Ждем 10 мкс, пока выходной сигнал модуля установится

movf CMCON,f ; Читаем CMCON, чтобы сбросить признак изменения

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

bcf PIR2,CMIF ; Сбрасываем флаг прерывания от компаратора

bsf INTCON,PEIE ; Разрешаем прерывания от периферийных устройств

bsf INTCON,GIE ; Разрешаем работу системы прерываний

Обратите внимание на то, что перед разрешением прерываний формируется задержка длительностью 10 мкс, необходимая для установления внутренних аналоговых сигналов. Последующее чтение регистра CMCON сбрасывает возможное несоответствие между сохраненным и текущим состоянием компаратора, после чего сбрасывается флаг прерывания от компаратора CMIF. И наконец, как обычно, разрешается работа системы прерываний установкой битов маски PEIE и GIE регистра INTCON.

В документации на некоторые модели, например PIC12F675, данный модуль называется просто модулем опорного напряжения. В таких моделях регистр управления называется VRCON. Соответственно в названии различных битов этого регистра отсутствует первая буква «С», например VREN вместо CVREN.

* * *

Во многих случаях необходимо иметь больше информации об аналоговом сигнале, нежели мы можем получить путем «тупого» сравнения сигнала с опорным напряжением. Возьмем, к примеру, ситуацию, показанную на Рис. 14.5. В данном случае для вычисления мощности импульса нам потребуется определять квадрат отклонения уровня сигнала от базового значения и интегрировать его по времени. В таких случаях входной сигнал после считывания необходимо преобразовывать в цифровую форму.

Функция преобразования аналоговой величины в цифровой эквивалент может быть выражена следующим образом:

где ki — i-й двоичный коэффициент, имеющий значение 0 или 1, a Vin =< Vref (Vref — фиксированное аналоговое опорное напряжение). Таким образом, Vin представляется в виде двоичной доли Vref, а коэффициенты к(являются искомыми значениями разрядов двоичного числа.

Чтобы понять, как можно реализовать подобные вычисления на практике, рассмотрим механическую аналогию метода последовательного приближения. Предположим, что у нас имеется объект неизвестной массы W (эквивалент Vin), безмен (эквивалент аналогового компаратора) и набор точных гирь известной массы 1, 2, 4 и 8 г (общая масса гирь эквивалентна величине опорного напряжения Vref). Тогда для определения массы груза можно воспользоваться следующим алгоритмом:

1. Поместить 8 г на тарелку. Если груз слишком тяжелый, то убрать его (k1 = 0), в противном случае оставить (k1 = 1).

2. Поместить 4 г на тарелку. Если груз слишком тяжелый, то убрать его (k2 = 0), в противном случае оставить (k2 = 1).

3. Поместить 2 г на тарелку. Если груз слишком тяжелый, то убрать его (k3 = 0), в противном случае оставить (k3 = 1).

4. Поместить 1 г на тарелку. Если груз слишком тяжелый, то убрать его (k4 = 0), в противном случае оставить (k4 = 1).

В итоге мы получим ближайшее значение, не превышающее искомое, равное суммарной массе гирь, оставшихся на тарелке. Так, если W было равно 6.2 г, то в случае 4-битной системы мы получим 4 г + 2 г = 6 г (Ь’0110’).

В электронике для реализации метода последовательного приближения используются наборы прецизионных резисторов или конденсаторов, объединенные таким образом, чтобы можно было последовательно уменьшать в 2 раза фиксированное напряжение Vref, подаваемое на аналоговый компаратор, который играет роль весов.

В большинстве микроконтроллеров для деления опорного напряжения используются наборы конденсаторов, емкости которых пропорциональны степеням двойки (Рис. 14.8). Конденсаторы небольшой емкости можно легко реализовать в кремниевом кристалле интегральной микросхемы, и, хотя точное их значение будет несколько отличаться от партии к партии, в каждом конкретном экземпляре микросхемы емкости всех конденсаторов будут соответствовать друг другу, причем это соответствие будет сохраняться при изменении температуры и напряжения питания. Емкость, кратная базовой, может быть реализована параллельным соединением конденсаторов. Как правило, роль этой емкости выполняет емкость перехода затвор-исток полевого транзистора, являющегося базовым элементом любой КМОП ИС.

Перед запуском процесса преобразования все конденсаторы подключаются к неизвестному аналоговому входному напряжению Vin как показано на Рис. 14.8, а. При осуществлении выборки (sampling) эти конденсаторы заряжаются через внутренние и внешние сопротивления с учетом времени установления внутренних аналоговых ключей. Возьмем в качестве примера модуль 10-битного АЦП, изображенный на Рис. 14.11. В этом случае к выводу AN подключается набор параллельно соединенных конденсаторов номинальной емкостью 0.12 пФ. Таким образом, их суммарная емкость равна 120 пФ (120 х 2-12 Ф). Внутреннее сопротивление имеет величину порядка 7.5 кОм, которое, однако, сильно зависит от температуры и напряжения питания. Рекомендуется, чтобы внешнее сопротивление составляло не более 2.5 кОм — в этом случае напряжение смещения, вызванное токами утечки ± 1/2 мкА, будет меньше уровня квантования (младшего значащего бита).

Рис. 14.8. Инициализация набора конденсаторов 4-битного преобразователя

Постоянная времени τ (R∙C при указанных значениях равна 120 х 10–12 х 104 = 1.2 мкс для суммарного сопротивления 7.5 + 2.5 = 10 кОм. Чтобы получить точность не хуже 0.05 % итогового напряжения, т. е. 1/2 10-битного уровня квантования, возьмем 8 х τ ~= 10 мкс. В документации максимальное время установления ключа указывается равным 10 мкс, но в нашем примере примем его равным 2 мкс. Таким образом, даже в наихудшем случае для полного заряда набора конденсаторов хватит 20 мкс.

Наш последующий анализ строится на предположении, что к моменту преобразования конденсаторы должны быть заряжены до полного значения входного напряжения. Это замечание справедливо в случае осуществления выборки по одному из нескольких аналоговых каналов или в том случае, если с момента последней выборки прошло достаточно времени для полного стекания заряда. Меньшее выходное сопротивление источника сигнала приведет к уменьшению постоянной времени. Разумеется, для оценки максимально достижимой частоты выборки к указанному времени накопления следует прибавить время собственно преобразования.

Во время выборки (S) верхние по схеме обкладки конденсаторов имеют нулевой потенциал, а нижние заряжаются до Vin. При переводе ключа в положение «хранение» (Н), как показано на Рис. 14.8, б, нижние обкладки конденсаторов оказываются соединенными с общим проводом, а верхние обкладки — ни к чему не подсоединены. Как известно, напряжение на конденсаторе может измениться только в том случае, если произойдет перенос заряда между обкладками, ΔQ = СΔV. Таким образом, изменение напряжения на нижних обкладках конденсаторов на величину ΔV = — Vin приведет к появлению на верхних обкладках потенциала, равного 0 — Vin, поскольку заряд не может исчезнуть с обкладки, которая никуда не подключена. Таким образом, в начале процесса преобразования на инвертирующем входе аналогового компаратора присутствует напряжение — Vin.

Четырехбитный вариант схемы последовательного приближения, являющейся «сердцем» модуля АЦП, в упрощенном виде показан на Рис. 14.9. Поэтапное выполнение операций осуществляется сдвиговым регистром SRG (см. Рис. 2.22 на стр. 51) после установки бита  регистра управления АЦП. При поступлении на этот регистр тактовых импульсов на каждом его выходе поочередно появляется лог. 1, активизируя каждый этап преобразования:

Набор конденсаторов переключается в положение «Хранение», и все конденсаторы, начиная с конденсатора, имеющего наибольшую емкость, по очереди подключаются к линии Vref. Выходной сигнал компаратора определяет состояние соответствующего бита регистра последовательного приближения (SAR). Подробно этот процесс показан на Рис. 14.10. После четырех таких операций «установка — проверка — сброс» результат из SAR передается в регистр данных АЦП. При этом сбрасывается флаг , свидетельствуя об окончании процесса преобразования, и устанавливается флаг прерывания ADIF. И в завершение аналоговый вход снова подключается к конденсаторам (состояние «Выборка»), в результате чего они заряжаются для следующего преобразования, которое можно будет выполнить после небольшой паузы.

Суммарное время преобразования приблизительно равно шести периодам сигнала tAD, подаваемого на тактовый вход сдвигового регистра секвенсора (контроллера последовательности) — по одному периоду на каждый бит плюс один для каждого из интервалов выборки и хранения. В 10-битном модуле время преобразования приблизительно равно 12 периодам тактового сигнала АЦП. Если же говорить конкретно о микроконтроллерах PIC, то минимальный период тактового сигнала составляет примерно 1.6 мкс (~= 600 кГц) для всех устройств, кроме самых старых моделей PIC16C71/711, в которых это значение равно 2 мкс. Нижняя граница периода не нормируется, однако из-за постепенного стекания заряда из конденсаторов следует избегать тактовых частот с периодом tAD более 20 мкс (50 кГц). Из Рис. 14.11 видно, что в качестве тактового сигнала АЦП может использоваться сигнал от одного из четырех источников. Первые три сигнала получают из системного тактового сигнала, прошедшего через предделитель, а четвертый формируется встроенным RС-генератором, период tAD которого составляет около 4 мкс.

Процесс преобразования, при котором каждая последующая доля Vref добавляется и при необходимости исключается из начального значения, показан на Рис. 14.10. Как мы уже видели на Рис. 14.8, в конце этапа выборки верхние обкладки конденсаторов заряжаются до уровня — Vin. В качестве примера предположим, что Vin = 0.4285∙Vref.

Рис. 14.9. Упрощенная схема 4-битного АЦП последовательного приближения

1. Процесс начинается с подключения источника опорного напряжения Vref к нижней обкладке конденсатора самой большой емкости, что определяется защелкой SAR8 (Рис. 14.9). Это вызывает инжекцию заряда величиной ΔQ = Ctotal∙Vref, который будет одинаков как для конденсатора C1 емкостью 8 единиц, так и для остальных конденсаторов, суммарная емкость которых также равна восьми единицам (Рис. 14.10). Таким образом, напряжение на узле N возрастает на Vref/2 до уровня —0.4285 + 0.5 = +0.0715∙Vref. В общем случае ΔVN = VrefCk/Ctotal. В результате на выходе компаратора появляется лог. 0 и защелка SAR8 соответственно сбрасывается, приводя напряжение на конденсаторах к значениям, которые были перед началом данного этапа.

Рис. 14.10. Реализация метода последовательного приближения

2. SAR4 подключает источник Vref к следующему конденсатору наибольшей емкости, в результате чего напряжение на узле N возрастает на Vref/4 (т. е. на 4/16) — В итоге на инвертирующем входе компаратора появляется напряжение —0.4285 + 0.25 = —0.1785∙Vref, что приводит к появлению на выходе компаратора лог. 1. Защелка SAR4 остается установленной, при этом напряжение узла остается равным —0.1785∙Vref.

3. SAR2 подключает источник Vref к следующему конденсатору наибольшей емкости, в результате чего напряжение на узле N возрастает на Vref/8 (т. е. нa 2/16). Итоговое напряжение -0.1785 + 0.125 = -0.0535∙Vref приводит к появлению на выходе компаратора лог. 1. Защелка SAR2 остается установленной, при этом напряжение узла остается равным —0.0535∙Vref.

4. SAR1 подключает источник Vref к конденсатору наименьшей емкости, в результате чего напряжение на узле N возрастает на Vref/16 (т. е. на 1/16) — Итоговое напряжение -0.0535 + 0.0635 = +0.009∙Vref приводит к появлению на выходе компаратора лог. 0 и сбросу защелки SAR1.

Таким образом, в регистре SAR окажется код Ь’0110’ или 0.375 В, представляющий 4-битное число, наиболее близко соответствующее напряжению Vi n = 0.4825∙Vref. Остаток, равный 0.0535∙Vref, представляет собой погрешность квантования.

В большинстве микроконтроллеров используется 8- или 10-битная матрица конденсаторов. Теоретически этот метод можно легко применить и для преобразования с большей разрядностью, однако на практике при этом возникают проблемы, связанные с согласованием конденсаторов большей емкости. Кроме того, наличие помех от работы внутренних логических узлов приводит к тому, что в подавляющем большинстве процессоров разрядность модуля АЦП ограничивается 12 битами. Также выпускаются внешние быстродействующие АЦП последовательного приближения разрядностью больше 12 бит, но они обычно используют наборы резисторов, соединенные по лестничной схеме, и относительно дороги (по сравнению с 8-битными микроконтроллерами).

Разброс емкостей конденсаторов, напряжения смещения, сопротивление внутренних ключей, токи утечки, а также нелинейность характеристики аналогового компаратора — все это является причиной погрешностей, возникающих при преобразовании. Анализ различных методик измерения указанных погрешностей выходит за рамки данной книги, однако в документации на любую микросхему АЦП (или модуль АЦП микроконтроллера) приводится список источников этих погрешностей и их величины, выраженные в единицах младшего значащего бита (LSB). Так, в справочных данных модуля 10-битного АЦП микроконтроллера PIC16F675 указано, что его суммарная абсолютная погрешность составляет ±1 LSB. Это гарантирует монотонность передаточной характеристики, т. е. что при любом приращении входного напряжения изменение двоичного кода никогда не произойдет в обратном направлении. Эта ошибка нормируется при Vref = VDD. Если же Vref будет меньше VDD, то точность ухудшится, хотя в большинстве случаев приемлемый результат достигается при напряжении вплоть до 2 В.

В микроконтроллере PIC12F675, а также в моделях линейки PIC16F87X имеется интегрированный модуль 10-битного АЦП. В более старых устройствах, таких как PIC16F73, использовался 8-битный вариант этого модуля, очень похожий по своей структуре и принципу работы на своего старшего 10-битного собрата, показанного на Рис. 14.11, который мы и будем рассматривать. Модули АЦП во всех микроконтроллерах PIC используют наборы конденсаторов с параметрами, указанными выше. Однако, с точки зрения пользователя, подробности процесса преобразования гораздо менее важны, нежели вопросы практического использования этого модуля.

Во всех микроконтроллерах с АЦП на входе последнего расположен аналоговый мультиплексор. Это позволяет программе обрабатывать до восьми аналоговых сигналов, по одному в каждый момент времени. Два регистра управления позволяют выбрать конкретный канал и определяют источник тактового сигнала. Кроме того, с помощью этих регистров можно сконфигурировать соответствующие выводы микроконтроллера как аналоговые (состояние по умолчанию после подачи питания) или цифровые, а также задать конфигурацию источника опорного напряжения. Преобразование инициируется установкой бита , который также служит для индикации завершения преобразования, а 10-битный результат затем можно считать из двух 8-битных регистров данных.

Разобьем наше описание модуля АЦП на две части. Сначала рассмотрим процесс инициализации и конфигурирования модуля, а уже только потом — собственно процесс преобразования.

Инициализация

При конфигурировании модуля необходимо учитывать следующие моменты:

1. Каким образом можно включить модуль?

2. Как следует тактировать модуль?

3. Какие каналы требуется использовать?

4. Хватит ли 8-битного результата?

Все эти опции задаются с помощью регистров управления АЦП ADCON0 и ADCON1.

ADON (включение модуля АЦП)

После подачи питания на микроконтроллер модуль АЦП находится в выключенном состоянии. Для его включения необходимо записать 1 в бит ADON (ADCON[0]). Включенный модуль потребляет в среднем 220 мкА (PIC16F87X), даже не осуществляя преобразований. Поэтому в тех случаях, когда энергопотребление микроконтроллера является критичным фактором, модуль АЦП следует выключать (если, разумеется, он не используется в программе). Обратите внимание, что бит  нельзя устанавливать той же командой, которая выполняет включение АЦП, во избежание запуска преобразования одновременно с включением модуля.

Рис. 14.11. Модуль 8-канального 10-битного АЦП микроконтроллеров PIC16F87X

ADCS[1:0] (выбор тактового сигнала АЦП)

Для работы модулю АЦП требуется тактовый сигнал для выполнения последовательности операций установки/проверки, проиллюстрированных на Рис. 14.10. Если частота этого сигнала будет слишком высока, то при переключении элементов схемы уравновешивания требуемые значения напряжений не будут успевать устанавливаться. В справочных данных на микроконтроллер нормируется минимальное значение периода тактового сигнала АЦП tAD, равное 1.6 мкс (3 мкс при пониженном напряжении питания). Соответственно, максимальное значение частоты преобразования составляет примерно 600 кГц. Так, чтобы получить tAD = 1.6 мкс (5/8 МГц) при использовании 5-МГц резонатора, нам придется загрузить в биты ADCS[1:0] число 01, соответствующее коэффициенту деления 8. В Табл. 14.2 приведены подходящие установки для пяти наиболее часто используемых значений частот кварцевых резонаторов.

Примечания:

1. Стандартные модели, в среднем 4 мкс.

2. Модели с расширенным диапазоном температур и низковольтные исполнения, в среднем 6 мкс.

Для обеспечения функционирования АЦП в системах с низкой тактовой частотой, в частности в тех, где системный генератор работает от часового кварца 32.768 кГц, в модуле предусмотрен отдельный RС-генератор. Поскольку этот генератор полностью независим от системного тактового сигнала, то при его использовании преобразование может выполняться при нахождении микроконтроллера в «спящем» режиме. В таком случае для «пробуждения» микроконтроллера можно использовать прерывание по завершении преобразования. Выполнение преобразования при выключенном системном тактовом сигнале увеличивает точность, поскольку при этом минимизируются наводки со стороны цифровых узлов микроконтроллера. Если при системной тактовой частоте более 1 МГц АЦП работает от встроенного RС-генератора, Microchip рекомендует выполнять преобразование в «спящем» режиме, поскольку отсутствие синхронизации между двумя тактовыми сигналами увеличивает помехи, наводимые на аналоговые узлы микроконтроллера.

В отличие от других моделей в микроконтроллере PIC12F675 имеется три бита выбора тактового сигнала АЦП, что обеспечивает дополнительный коэффициент деления на 64. Эта опция полезна при работе с 20-МГц резонатором — ее использование позволяет получить минимально возможное значение периода tAD (3 мкс) при наибольшем допустимом напряжении питания для данного устройства.

CHS[2:0] (выбор канала)

Микроконтроллеры с модулями АЦП имеют возможность оцифровывать напряжение с нескольких аналоговых входов. Количество этих входов (каналов) может варьироваться от 4 (используются линии порта GP) в крошечном 8-выводном PIC12F675 до 8 (используются линии портов А и Е) в 40-выводных микроконтроллерах PIC16F874/7.

При сбросе по включению питания все разделяемые выводы портов по умолчанию конфигурируются как аналоговые входы (см. стр. 496). Как можно увидеть из Рис. 14.12, у контакта ввода/вывода, работающего в качестве аналогового входа, просто отключается входной цифровой буфер — сравните с Рис. 11.3 на стр. 333. Остальные элементы схемы при этом работают, как обычно. Из всего этого можно сделать следующие выводы:

• При чтении бита порта, сконфигурированного как аналоговый вход, из-за отключенного входного цифрового буфера всегда будет возвращаться лог. 0.

• Буфер TRIS работает, как обычно, поэтому соответствующий бит регистра TRIS должен быть установлен в 1. Таким образом, вывод порта, сконфигурированный как аналоговый, должен работать как вход для предотвращения конфликта между аналоговым сигналом Vin и цифровым выходным сигналом триггера данных.

• АЦП может считывать аналоговое напряжение с вывода микроконтроллера, даже если он не был сконфигурирован как аналоговый. Однако в этом случае находящийся в активном состоянии цифровой буфер может потреблять излишний ток, который превысит приведенный в спецификации.

Рис. 14.12. Конфигурирование аналоговых входов портов А и Е

PCFG[3:0] (конфигурация аналогового порта)

Если в данном конкретном приложении требуется меньше аналоговых каналов, чем имеется в модуле, то некоторые неиспользуемые каналы могут быть задействованы, как обычно, т. е. в качестве цифровых линий ввода/вывода. Для задания конфигурации аналогового порта предназначены биты PCFG[3:0] (ADCC)N1[3:0]). Возможные комбинации, число и положение этих битов зависит от модели микроконтроллера. Для микроконтроллеров линейки PIC16F87X возможные значения битов и соответствующие им конфигурации выводов приведены на Рис. 14.11. К примеру, если в вашем проекте требуется только один аналоговый канал, то, загрузив в указанные биты значение Ь’1110’, вы получите один аналоговый вход RA0/AIN0, а остальные выводы (RA5, RA[3:1] и RE[2:0]) сможете использовать для других целей.

Даже если не требуется обработка аналоговых сигналов, регистр ADCON1 все равно необходимо конфигурировать — в этом случае используются значения Ь’0110’ или Ь’0111’, при которых все выводы, которые могут использоваться АЦП, конфигурируются как цифровые. Невыполнение этого требования представляет собой одну из наиболее распространенных ошибок, поскольку большинство современных моделей имеют аналоговые модули и, как было указано на стр. 496, при сбросе по включению питания все соответствующие выводы по умолчанию конфигурируются как аналоговые. Соответственно, при чтении состояния таких выводов будет всегда возвращаться 0. Как уже было отмечено, все выводы, используемые для считывания аналогового сигнала, должны быть сконфигурированными как входы (1 в соответствующих битах регистров TRIS).

Как мы видели из Рис. 14.10, операция последовательного приближения заключается в последовательном сравнении с долями фиксированного опорного напряжения, каждая последующая из которых в 2 раза меньше предыдущей. Соответственно, точность данной операции зависит от качества этого опорного напряжения. Как правило, указанный параметр (точность) определяется ценой единицы младшего бита (LSB), т. е. шагом квантования. В случае 10-битного преобразования эта величина составляет Vref/1024, или более 0.1 % опорного напряжения.

В качестве опорного напряжения можно использовать напряжение питания самого микроконтроллера, скажем, 5 В. Так, при значении битов PCFG[3:0] = = b’1110’ вывод RA0 конфигурируется как аналоговый, а в качестве опорного используется напряжение VDD. В этом случае значение, полученное в результате оцифровки, даст нам долю от напряжения питания, которой соответствует входное аналоговое напряжение.

Использование напряжения питания в качестве опорного является не самым лучшим выбором с точки зрения помехозащищенности. К тому же его значение может изменяться в некоторых пределах. Если требуется более высокая точность или опорное напряжение, отличное от напряжения источника питания, то для подключения внешнего источника опорного напряжения можно задействовать определенные аналоговые входы. Все модули АЦП позволяют использовать хотя бы одно внешнее напряжение. Что же касается PIC16F87X, то в этом микроконтроллере можно использовать одно или два внешних опорных напряжения. В частности, при PCFG[3:0] = Ь’0101’ выводы RA[1:0] конфигурируются в качестве аналоговых входов, а вывод RA3 используется для подключения внешнего прецизионного источника опорного напряжения Vref+ (см. Рис. 14.20). Величина Vref+ может находиться в пределах от VDD — 2.5 В до VDD + 0.3 В (при этом она не должна быть менее 2 В).

В некоторых случаях может потребоваться измерение напряжений относительно уровня, отличающегося от VSS (0 В или земля). Модули АЦП в некоторых моделях, например в PIC16F87X, позволяют задать отдельное нижнее опорное напряжение Vref-. Скажем, при PCFG[3:0] =Ь’1101’ тоже обеспечивается два аналоговых канала, а вывод RA3 используется для подачи опорного напряжения Vref+. Только вывод RA2 в этом случае используется для подачи опорного напряжения Vref-, которое должно быть в пределах -0.3…2 В. А весь диапазон Vref+ — Vref- не может быть меньше 2 В.

ADFM (формат результата преобразования)

В рассматриваемом нами модуле АЦП используется два регистра для хранения 10-битного результата. Поскольку суммарная разрядность пары регистров ADRESH: ADRESL составляет 16 бит, то возможны два способа размещения 10-битного результата в этих регистрах.

В большинстве приложений вполне хватает 8-битных значений — в таких случаях можно спокойно отбросить два младших бита результата преобразования. Из Рис. 14.13, а видно, что эту операцию проще всего осуществить, выравнивая результат преобразования влево и игнорируя содержимое регистра ADRESL.

Рис. 14.13. Выравнивание 10-битного результата в 16-битном поле

Если же необходимо полное 10-битное значение, то бит ADCONl[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
следует установить в 1 для выравнивания результата по правому краю. Как видно из Рис. 14.13, б, в этом случае результат представляет собой 10-битное число, расширенное до 16 бит заполнением старших битов нулями. Соответственно для обработки этого значения можно использовать обычную 16-битную арифметику.

Процесс преобразования

После того как модуль АЦП сконфигурирован, оцифровка выбранного аналогового канала, с точки зрения пользователя, выглядит достаточно просто. Предполагая пока, что прерывания не используются, можно выделить следующие этапы преобразования (включая, для полноты, этап инициализации), которые в графическом виде изображены на Рис. 14.14:

1. Конфигурирование модуля АЦП:

• Конфигурирование выводов портов как аналоговых входов и/или входов опорного напряжения (ADCON1).

• Выбор источника тактового сигнала АЦП (ADCON0).

• Выбор входного канала АЦП (ADCON0).

• Включение модуля АЦП (ADCON0).

2. Ожидание требуемого времени установления, около 20 мкс.

3. Запуск преобразования установкой бита GO/DONE.

4. Ожидание завершения преобразования (сброса бита ).

5. Чтение регистров результата ADRES.

6. Переход к этапу 1 или 2 для выполнения следующего преобразования (зависит от программы).

Предположим в качестве примера, что нам необходимо поочередно считывать каждый из восьми аналоговых каналов микроконтроллера PIC16F874/7, выводя старшие восемь битов результата в порт В, а номер канала — в младшие три бита порта D. Частота основного резонатора составляет 20 МГц, в качестве опорного напряжения используется напряжение питания микроконтроллера.

Код, приведенный в Программе 14.1, предполагает, что после сброса модуль АЦП был сконфигурирован следующим образом:

Переключаемся в 1-й банк

Все разделяемые линии порта А — аналоговые

include "p16f877a.

bsf STATUS,RP0 ; Переключаемся а 1-й банк

clrf ADCON1 ; Все разделяемые линии порта А — аналоговые

clrf TRISB ; Все выводы порта В — выходы

movlw b’11111000’ ; Младшие 3 бита порта D — выходы

movwf TRISD

bcf STATUS,RP0 ; Возвращаемся в 0-й банк

movlw b’10000001’ ; fosc/32 (10), СН0 (000)

movwf ADCON0 ; Не запускать преобразование (0), включить АЦП (1)

В данном случае разрешается использование всех восьми аналоговых каналов с внутренним ИОН, результат преобразования выравнивается полевому краю. Регистр ADCON1 инициализируется значением , при котором в качестве источника тактового сигнала используется fOSC/32 (20/32 = 625 кГц), что соответствует периоду tAD = 1.6 мкс, выбирается 0-й канал АЦП (что в принципе без разницы) и разрешается работа модуля. Поскольку бит  сброшен, преобразование пока не запускается.

Рис. 14.14. Временная развертка процесса преобразования

Основная программа, код которой приведен в Программе 14.1, постоянно крутится в бесконечном цикле. В каждом проходе этого цикла из ADRESH считывается оцифрованный результат преобразования очередного канала и копируется в регистр данных порта В. Перед оцифровкой значение счетчика каналов CHANNEL выдается в порт D в качестве числа по модулю 3.

Программа 14.1 . 8-канальная система сбора данных

MAIN clrf CHANNEL ; Используется в качестве счетчика каналов

MAIN_LOOP

         movf CHANNEL,w ; Берем номер канала

         andlw b’00000111’ ; Обнуляем старшие 5 бит

         movwf PORTD ; Копируем в порт D

         call GET_ANALOG ; Оцифровываем, результат возвращается в W

         movwf PORTB ; Копируем его в порт В

         incf CHANNEL,f ; Переходим к следующему каналу

         goto MAIN_LOOP ; и так без конца

; ********************************

; * ФУНКЦИЯ: Аналого-цифровое преобразование n-го канала *

; * РЕСУРСЫ: Подпрограмма DELAY_17US, регистр TEMP *

; * ВХОД: Номер канала в W *

; * ВЫХОД: Оцифрованное 8-битное значение в W *

; *********************************

GET_ANALOG

         movwf TEMP ; Копируем номер канала в TEMP

         bcf STATUS,С ; Сдвигаем на три бита влево,

         rlf TEMP,f

         rlf TEMP,f

         rlf TEMP,w ; помещая результат в W

         bcf ADCON0,CHS0 ; Обнуляем биты выбора канала

         bcf ADCON0,CHS1

         bcf ADCONO,CHS2

         addwf ADCONO,f ; Заносим номер канала в ADCON0 [5:3]

         call DELAY_17US ; Ждем 17 мкс для установления

         bsf ADCON0,GO ; Запускаем преобразование

GET_ANALOG_LOOP

         btfsc ADCONO,GO; Проверим завершение преобразования

            goto GET_ANALOG_LOOP

         movf ADRESH,w ; Считываем результат после сброса бита

GO/NOT_DONE

return

; ********************************

; * ФУНКЦИЯ: Формирует 17-мкс задержку при частоте 20 МГц (85 циклов) *

; * РЕСУРСЫ: Нет *

; * ВХОД: Нет *

; * ВЫХОД: W обнуляется *

; ********************************

DELAY_17US

          movlw d’20’ ; Параметр задержки

DELAY_17US_LOOP

          addlw -1 ; Декрементируем

          btfss STATUS,Z ; до нуля

             goto DELAY_17US_LOOP

          return

Собственно считывание данных осуществляется в подпрограмме GET_ANALOG, при вызове которой в младших трех битах рабочего регистра передается номер требуемого канала. Это значение копируется во временный регистр TEMP, содержимое которого затем сдвигается на три бита влево, чтобы переданный номер канала оказался в позиции битов CHSn регистра ADCON0. После сброса битов CHS[2:0] полученное значение складывается с содержимым ADCON0, в результате чего в битах CHS[2:0] оказывается номер канала.

После установки требуемого номера канала вызывается подпрограмма задержки для формирования паузы, необходимой для установления (стабилизации работы) ключа. Поскольку нам достаточно 8-битного разрешения, для заряда конденсаторов с погрешностью до 0.25 % финального (установившегося) значения достаточно задержки всего 6τ ~= 7 мкс (в худшем случае — 10 мкс), см. стр. 504. Затем для запуска преобразования устанавливается бит  регистра ADCON0. Завершение процесса преобразования контролируется по сбросу этого бита. К этому моменту в регистре ADRESH будет находиться 8-битный результат преобразования.

В общем каждое преобразование занимает около 13 х 16 ~= 21 мкс, таким образом, на оцифровку одного канала затрачивается 17 + 21 = 38 мкс. Соответственно, оцифровка всех восьми каналов (один проход) занимает 38 х 8 ~= 300 мкс, что дает нам скорость, примерно равную 3300 проходам в секунду.

Вместо того чтобы опрашивать состояние бита, окончание преобразования можно определять по генерации прерывания. В частности, если преобразование выполняется в то время, пока микроконтроллер находится в «спящем» режиме, то прерывание может использоваться для его «пробуждения». Модуль АЦП может работать во время «сна» микроконтроллера, поскольку имеет собственный тактовый генератор, независимый от системного тактового генератора микроконтроллера. Основным положительным моментом в выполнении преобразования во время «сна» микроконтроллера является то, что благодаря выключенному системному генератору оно выполняется в более спокойной электромагнитной обстановке. Отрицательной стороной можно назвать увеличение длительности преобразования, поскольку при выходе микроконтроллера из «спящего» режима формируется задержка длительностью 1024 такта, необходимая для перезапуска системного генератора (см. стр. 309).

Этот собственный генератор может использоваться и при работе микроконтроллера в нормальном режиме. Однако из-за отсутствия синхронизации между ним и системным тактовым генератором, возникают помехи от наложения тактовых сигналов, представляющие достаточно серьезную проблему, особенно при тактовых частотах микроконтроллера выше 1 МГц.

Для выполнения преобразования в «спящем» режиме необходимо выполнить следующее:

1. Выбрать в качестве источника тактового сигнала АЦП собственный RС-генератор модуля (ADCS1:0 =11).

2. Сбросить флаг ADIF для предотвращения немедленной генерации прерывания.

3. Установить биты масок ADIE и PEIE для разрешения прерывания от АЦП, которое будет использоваться для вывода микроконтроллера из «спящего» режима.

4. Если вы не хотите, чтобы после пробуждения микроконтроллера произошел переход к обработчику прерывания, необходимо сбросить бит общего разрешения прерываний GIE.

5. Для запуска преобразования сбросить бит  регистра ADCON0, после чего сразу же выполнить команду sleep.

6. После «пробуждения» микроконтроллера считать оцифрованное значение из регистров ADRESH: L.

В качестве примера напишем новый вариант подпрограммы GET_ANALOG из Программы 14.1, использующий «спящий» режим. На этот раз в секции инициализации необходимо указанным выше образом сконфигурировать систему прерываний, чтобы обеспечить вывод микроконтроллера из «спящего» режима при установке флага ADIF (которая происходит одновременно со сбросом бита ) после завершения преобразования.

include "p16f877а. inc"

   bsf STATUS,RP0 ; Переключаемся в 1-й банк

   clrf ADCON1 ; Все разделяемые линии порта А — аналоговые

   clrf TRISB ; Все выводы порта В — выходы

   movlw b’11111000’ ; Младшие 3 бита порта D — выходы

   movwf TRISD

   bsf PIE1,ADIE ; Разрешаем прерывание от АЦП

   bcf STATUS,RP0 ; Возвращаемся в 0-й банк

   movlw b’11000001’ ; Xta1/32 (10), СН0 (000)

   moywf ADCQN0 ; Не запускать преобразование (0), включить АЦП (1)

   bcf PIR1,ADIF ; Сбрасываем флаг прерывания

   bsf INTCON,PEIE ; Разрешаем прерывания от периферийных устройств

   bsf INTCON,GIE ; и прерывания вообще

Помимо инициализации системы прерываний, еще одно изменение связано с установкой битов ADCONO[7:6], которые на этот раз равны Ь’11’, чтобы выбрать внутренний RC-генератор для тактирования АЦП.

Код подпрограммы GET_ANALOG для работы в «спящем» режиме, приведенной в Программе 14.2, практически идентичен исходному варианту, за исключением следующих моментов:

1. Если запрос на прерывание может генерироваться другими периферийными устройствами, то бит GIE необходимо сбрасывать.

2. Перед запуском преобразования необходимо сбрасывать флаг ADIF для предотвращения преждевременного выхода из «спящего» режима.

3. Команда sleep расположена сразу после команды установки бита . При работе АЦП от собственного тактового генератора перед началом преобразования автоматически вставляется дополнительная задержка длительностью tDA, гарантирующая, что преобразование начнется только после исполнения команды sleep.

4. В данном случае нет необходимости опрашивать состояние флага , поскольку микроконтроллер возобновит выполнение программы только после завершения преобразования. В нашем примере бит маски GIE сбрасывается, поэтому при наличии прерываний от других периферийных устройств этот бит после «пробуждения» необходимо установить повторно. Если перед входом в «спящий» режим не сбрасывать бит GIE, то после «пробуждения» процессор автоматически перейдет к выполнению обработчика прерывания.

Программа 14.2. Оцифровка канала в 8-канальной системе сбора данных

; ******************

; * ФУНКЦИЯ: Аналого-цифровое преобразование n-го канала *

; * РЕСУРСЫ: Подпрограмма DELAY_17US, регистр TEMP *

; * ВХОД: Номер канала в W *

; * ВЫХОД: Оцифрованное 8-битное значение в W *

; ******************

GET_ANALOG

       movwf TEMP ; Копируем номер канала в TEMP

       bcf STATUS,С ; Сдвигаем на три бита влево,

       rlf TEMP,f

       rlf TEMP,f

       rlf TEMP,w ; помещая результат в W

       bcf ADCON0,CHS0 ; Обнуляем биты выбора канала

       bcf ADCON0,CHS1

       bcf ADCON0,CHS2

       addwf ADCONO,f ; Заносим номер канала в ADCON0 [5:3]

       call DELAY_17US ; Ждем 17 мкс для установления

       bcf INTCON,GIE ; Запрещаем все прерывания

       bcf PIR1,ADIF ; Предварительно сбрасываем флаг ADIF

       bsf ADCON0,GO ; Запускаем преобразование

        sleep ; Немного поспим

        bsf INTCON,GIE ; Разрешаем прерывания (если необходимо)

        movf ADRESH,w ; Считываем результат после пробуждения

        return

В качестве заключительного примера давайте напишем на Си программу для микроконтроллера PIC16F874 (20 МГц), который должен работать как компаратор, наподобие устройства из Примера 11.2 (стр. 354). В данном случае мы будем сравнивать 8-битное слово N, подаваемое в параллельном виде на порт В, с цифровым представлением аналогового сигнала 1-го канала АЦП. Результат сравнения будет выставляться на выходы RC[2:0] в виде 3-битного кода: Ь’001’ — при аналоговом сигнале, меньшем N, Ь’О10’ — в случае равенства и Ь’100’ — при аналоговом сигнале, большем N. Компаратор должен иметь гистерезис величиной ±1 бит, названный в программе delta. Таким образом, если при предыдущем сравнении аналоговый сигнал оказался меньше N, то новый уровень будет равен N + 1. В обратном случае уровень переключения становится равным N — 1.

Функция compare () из Программы 14.3 предполагает, что микроконтроллер уже инициализирован следующим образом:

#include <16f874.h>

#byte P0RT_B = 0x06

#byte PORT_C = 0x07

#device ADC=8 /* Результат преобразования — 8-битное число */

/* Объявляем функцию, в которую в качестве параметра передается гистерезис (+1 или -1) и которая возвращает новое значение гистерезиса */

unsigned int compare(unsigned int delta);

void main(void)

{

     unsigned int hysteresis = 0;

     set_tris_c(0xF8);

     setup_adc(ADC_CLOCK_DIV_32);

     setup_adc_ports(RA0_RA1_RA3_ANALOG);

     set_adc_channel(1);

Ниже приведены основные функции компилятора CCS для работы с модулем АЦП.

∙ setup_adc(ADC_CLOCK_DIV_32)

Эта функция загружает требуемое значение в биты ADCS1[1:0], определяющие источник тактового сигнала модуля; в данном случае используется деленный на 32 сигнал от тактового генератора процессора. Для выбора внутреннего RС-генератора следует использовать константу ADC_CLOCK_INTERNAL.

∙ setup_adc_ports(RA0_RA1_RA3_ANALOG)

Эта функция конфигурирует биты PCFG[3:0] регистра ADCON1, определяющие, какие из выводов порта будут аналоговыми, какие — цифровыми и будет ли использовано внешнее опорное напряжение. Константа RA0_RA1_RA3_ANALOG соответствует такой конфигурации, при которой в качестве аналоговых входов используются линии порта RA3 и RA[1:0] (с внутренним источником опорного напряжения), тогда как остальные линии порта остаются цифровыми — PCFG[3:0] = b’0100’ (см. Рис. 14.11). Если же мы хотим использовать вывод RA3 для подключения внешнего ИОН Vref+, то в качестве параметра функции следует указать константу RA0_RA1_ANALOG_RA3_REF. Эти константы, применимые для каждого конкретного устройства, определены в соответствующих заголовочных файлах, в нашем случае — в файле 16f874.h. Для всех устройств, имеющих в своем составе модули АЦП, определены, по меньшей мере, две константы: ALL_ANALOG И NO_ANALOGS.

∙ set_adc_channel(n);

Эта функция используется для загрузки номера текущего канала в биты CHS[2:0] регистра ADCON0.

∙ read_adc();

Эта функция устанавливает флаг  регистра ADCON0 и возвращает содержимое регистров ADRESH: L после сброса данного бита.

∙ #device ADC=8

Этой директивой задается выравнивание 10-битного результата преобразования по левой границе (см. Рис. 14.13). В таком случае функция read_adc () возвращает 8-битное целое число, считываемое из регистра ADRESH. При наличии в тексте программы директивы #device ADC=10 эта же функция возвращает 2-байтное значение типа long int.

В функцию compare () из Программы 14.3 в качестве параметра передается значение гистерезиса, названного delta, который может быть равен +1 или -1 (h’FF’). Результат преобразования сохраняется в локальной переменной analog, которая затем сравнивается с содержимым порта В плюс delta. По результату сравнения на линии RC[2:0] порта С выдается соответствующий код.

Программа 14.3. Цифро-аналоговый компаратор с гистерезисом

unsigned int compare(unsigned int delta)

{

    unsigned int analog;

    analog = read_adc();

    if(analog > PQRT_B + delta)

         {PORT_C = 0x04; delta = 0xff;}

    else

         if(analog == PORT_B)

              {PORT_C = 0x02;}

         else

              {PORT_C = 0x01; delta = 1;}

    return delta;

}

В соответствии с результатом сравнения также обновляется значение переменной delta, т. е. delta = +1, если analog < (PORTJB + delta), и delta = -1, если analog > (PORT_B + delta). Новое значение delta возвращается функцией в вызывающую программу, что позволяет той обновить значение своей локальной переменной (назовем ее hysteresis). Таким образом, для одновременного формирования выходного сигнала компаратора и обновления значения переменной hysteresis в вызывающей программе должно присутствовать следующее выражение:

hysteresis = compare(hysteresis);

В качестве альтернативы можно было бы объявить переменную hysteresis вне функции main (). Тогда эта переменная стала бы глобальной, т. е. ее значение было бы доступно всем функциям программы и его не потребовалось бы передавать между функциями.

* * *

Преобразование цифровой величины в эквивалентное аналоговое напряжение является более простой операцией, нежели рассмотренное нами аналого-цифровое преобразование, да и требуется не так часто. Наверное, именно по этим причинам цифро-аналоговые преобразователи (ЦАП) достаточно редко встречаются в составе большинства микроконтроллеров.

Мы с вами уже знаем, что одним из методов цифро-аналогового преобразования является управление коэффициентом заполнения импульсной последовательности, имеющей фиксированную частоту, как показано на Рис. 13.9 (стр. 476). Чем меньше в данном случае исходное значение, тем меньше длительность импульсов и тем меньше напряжение на выходе ФНЧ, который выполняет усреднение или, иначе, выделяет постоянную составляющую. И, наоборот, большому значению соответствует большой коэффициент заполнения, который в свою очередь приводит к появлению высокого напряжения.

Цифро-аналоговое преобразование с помощью ШИМ может иметь очень высокую точность и быть простым в реализации. Однако для удаления из выходного сигнала гармоник, кратных частоте импульсов, требуется очень хорошая фильтрация, что приводит к увеличению времени отклика на изменение цифрового значения. Обычно ШИМ используется для управления мощными нагрузками, такими как электродвигатели или нагревательные элементы, в которых сглаживание осуществляется за счет инерционности самих исполнительных устройств. Более того, импульсный характер сигнала как нельзя лучше подходит для управления мощностью с помощью тиристорных схем.

Другой способ формирования аналогового сигнала заключается в коммутации отводов многозвенного резисторного делителя, каждая ступень которого изменяет выходное напряжение на величину, соответствующую младшему биту. Этот принцип использовался в модуле опорного напряжения компаратора, показанном на Рис. 14.7. Однако для осуществления цифро-аналогового преобразования требуется намного больше резисторов. Так, для 10-битного ЦАП требуется цепочка из 1024 резисторов.

В продаже имеется очень много микросхем ЦАП, управляемых извне. Две такие микросхемы были показаны на Рис. 12.3 и Рис. 12.5 (стр. 374 и 379 соответственно). Передача цифрового значения в эти микросхемы осуществлялась последовательно. Теперь же для полноты картины давайте познакомимся с микросхемой, имеющей параллельный интерфейс для ввода цифровых данных.

Подавляющее большинство микросхем ЦАП основаны на многозвенной резистивной цепи типа R-2R, подобной изображенной на Рис. 14.15, а. Напряжение, прикладываемое к каждому отводу при замыкании соответствующего ключа, передается на выходной узел после ослабления (деления). Как мы увидим чуть позже, каждая последующая ступень ослабляет это напряжение Ьi в 2 раза, формируя для N-битного значения следующую весовую функцию:

Сопротивление в точке А схемы, показанной на Рис. 14.15,б, равно R (2R||2R), в результате чего напряжение ослабляется в 2 раза. По мере продвижения к правому краю цепочки этот процесс повторяется, деля каждое из напряжений на два. Так, в точке В напряжение Ь0/2 уменьшается в 2 раза, в результате чего мы получаем = Ь0/4. Поскольку схема симметрична, сопротивление каждого узла с левой стороны также равно 2R. Это означает, что со стороны любого цифрового ключа общее сопротивление равно 2R + 2R||2R = 3R. Это очень важно, поскольку характеристики транзисторного ключа, такие как его сопротивление, зависят от тока, и поддержание их на одном уровне уменьшает ошибку преобразования.

Рис. 14.15. Цифро-аналоговое преобразование с помощью многозвенной резистивной цепи типа R-2R

Для простоты мы ограничились рассмотрением только для трех битов. Однако данный пример можно расширить простым переносом левого оконечного резистора и вставкой требуемого количества секций. Это не влияет на сопротивление узла с правой стороны и, соответственно, не изменяет режимов работы расположенных правее секций. Если мы еще раз взглянем на наши рассуждения, то увидим, что нигде в вычислениях не фигурирует абсолютное значение сопротивления. На самом деле точность преобразования зависит только от соотношения R:2R. Дело в том, что резисторы с точным соотношением сопротивлений изготовить на кремниевой подложке относительно легко, в отличие от резисторов с точными абсолютными значениями сопротивлений. По этой причине в большинстве интегральных микросхем ЦАП используются многозвенные цепи R-2R.

В качестве примера возьмем широко распространенную микросхему МАХ506 компании Maxim, изображенную на Рис. 14.16. Это устройство в 20-выводном корпусе содержит четыре независимых ЦАП, использующих одно внешнее опорное напряжение VKf. Цифровые данные подаются на выводы D[7:0], а один из четырех регистров-защелок выбирается с помощью адресных входов А[1:0]. После защелкивания байт данных перегружается в выбранный регистр и появляется на соответствующем выходе VOUTn .

Рис. 14.16. Счетверенный 8-битный ЦАП МАХ506 компании Maxim

Это выходное напряжение будет находиться в диапазоне от нуля (аналоговая земля — AGND) — для входного кода h’00’ и до Vref — для входного кода h’FF’. Когда вывод VSS подключен к общему проводу, напряжение Vref может иметь любое значение от 0 В до VDD (+5 В). Однако напряжение на выводе VSS может достигать значения -5 В, и в этом случае Vref может лежать в диапазоне ±5 В. Если Vref отрицательно (в случае двухполярного источника питания), то выходное напряжение также будет отрицательным. В любом случае выходное напряжение определяется выражением D х Vref, где D — входной цифровой код, соответствующий долям из диапазона 0…1 (h’00’…h’FF’).

Микросхема МАХ505 представляет собой 24-выводную модификацию предыдущей микросхемы, которая позволяет использовать с каждым из четырех ЦАП отдельный источник опорного напряжения. Кроме того, в этой микросхеме защелки ЦАП отделены от резистивной цепи преобразования дополнительным уровнем защелок, управляемых одним и тем же сигналом . Такая двойная буферизация позволяет программисту обновлять выходное значение всех четырех ЦАП одновременно после загрузки регистров каждого канала.

Для примера предположим, что выводы адреса МАХ506 подключены к выводам RA[1:0] микроконтроллера, а вывод RA2 микроконтроллера управляет входом  для защелкивания адресованного байта данных, формируемого на выводах порта В. Тогда для формирования на выходе DACD пилообразного сигнала, показанного на Рис. 14.17, можно написать следующую процедуру:

     movlw b’0111’ ; DACD — 3-й канал (b’11’), WR = 1

     movwf PORTA ; Выдаем на выводы WR и A1:0 МАХ506

LOOP movwf PORTB ; Данные передаем на выводы D7:0 МАХ506

     bcf PORTA,2 ;WR = 0; Защелкиваем данные,

     bsf PORTA, 2 ;WR = 1; формируя импульс на входе WR

     addlw 1 ; Инкрементируем счетчик

        goto LOOP ; и так без конца

Предполагается, что все линии порта В и линии RA[2:0] порта А уже сконфигурированы как выходы.

Пилообразный выходной сигнал ЦАП, изображенный на Рис. 14.17, формируется при использовании микроконтроллера с 12-МГц резонатором. При длительности каждой итерации цикла, равной шести машинным циклам, период пилообразного сигнала получится равным (256 х 6)/3 ~= 0.5 мс.

Рис. 14.17. Формирование пилообразного сигнала с использованием ЦАП МАХ506

Примеры

Пример 14.1

Диапазон входного напряжения аналоговых каналов в большинстве модулей АЦП ограничен положительным диапазоном 0…Vref+, где в качестве Vref+ может выступать либо напряжение питания VDD, либо внешнее напряжение, подаваемое на вход RA3 и лежащее в диапазоне 3 B…VDD. Однако во многих случаях возникает необходимость оцифровки биполярных аналоговых сигналов. Сконструируйте простую резистивную цепочку для сдвига биполярного напряжения из диапазона ±10 В в однополярный диапазон 0…5 В, полагая, что Vref+ равно +5 В. Доработайте конструкцию, добавив фильтр, устраняющий эффект наложения спектров, и полагая, что выборка осуществляется с частотой 5000 отсчетов/с.

Решение

Один из возможных вариантов решения этой задачи представлен на Рис. 14.18. Сопротивления трех резисторов должны быть такими, чтобы при входном напряжении О В на входе AN формировалось бы напряжение, равное половине шкалы (Vref+/2 = 2.5 В). Кроме того, входное напряжение должно быть ослаблено в четыре раза. В общем виде это соотношение можно выразить следующим образом: Vin = ±G x Vref+.

Рис. 14.18. Резистивная цепочка для сдвига уровня напряжения

Сопротивления резисторов определяются из следующих соображений:

1. Когда Vin = 0, напряжение на суммирующем узле равно половине диапазона, что соответствует выходному значению Ь’10000000’. Для этого сопротивление параллельно соединенных резисторов R1 и R2 должно быть равно сопротивлению R3, т. е.

R3 = R1||R2.

2. Ослабление сигнала осуществляется делителем напряжения, составленным из резисторов R1 и R2||R3. Соответственно значение G определяется из выражения

2G = (R1 + (R2||R3)/(R2||R3);

в нашем случае G = 2.

После ряда преобразований получим

R1 = (G — 1) x R2

R2 = G x R3

Понятно, что у нас имеется три неизвестных и всего два уравнения, поэтому для начала мы должны выбрать значение для одного из параметров. Задав сопротивление R3 равным 5 кОм, получим R2 = 2 х 5 = 10 кОм и R1 = 10 кОм.

Со стороны входа микроконтроллера все три резистора оказываются соединенными параллельно, поэтому выходное сопротивление нашей схемы равно 2.4 кОм. Это значение удовлетворяет требованию, предъявляемому модулем АЦП, по сохранению ошибки, вызванной токами утечки, в пределах младшего значащего бита для 10-битного преобразования. При 8-битном преобразовании значения резисторов следует увеличить в 4 раза.

С помощью конденсатора небольшой емкости, подключенного к суммирующему узлу, можно реализовать простейший ФНЧ первого порядка для ослабления высокочастотных составляющих, наводимых внешними источниками, такими как тактовый генератор микроконтроллера. Этот же ФНЧ выполняет роль фильтра, устраняющего эффект наложения спектров, как было показано на Рис. 14.4. При частоте выборок, равной 5000 отсчетам в секунду, частота среза фильтра не должна превышать 2.5 кГц — половины частоты выборок. Поскольку ослабление в таком фильтре составляет всего 6 дБ/октаву, то лучше выбрать частоту среза 1/2πCR, равную 1 кГц. Таким образом, получаем

1/2πCR = 1000

C = 10-6/4.8xπ

C ~= 66 нФ

Чтобы еще больше снизить шумы, конденсатор фильтра должен иметь хорошие высокочастотные параметры (на высоких частотах конденсаторы становятся индуктивностями) и вместе с резистором должен быть размещен как можно ближе к входу, а рядом с ним не должно проходить никаких цифровых линий. Хорошей практикой является развязывание опорного напряжения и напряжения питания с помощью танталовых электролитических конденсаторов малой емкости и/или керамических конденсаторов емкостью 0.1 мкФ для уменьшения помех, вызванных работой микроконтроллера и других устройств, питающихся от того же источника. Используя отдельные линии питания и земли для подключения микроконтроллера к источнику питания, можно еще больше снизить уровень помех от этого источника.

Пример 14.2

Одной из задач интеллектуального биомедицинского монитора является периодическое измерение пикового напряжения сигнала ЭКГ. Значение, соответствующее данной точке R (см. Рис. 7.1 на стр. 208), должно выводиться через порт В, и при обновлении этого значения на выводе RA5 должен формироваться положительный импульс. Предполагая, что для реализации указанного устройства используется микроконтроллер PIC16F87X, а сигнал ЭКГ поступает на вход RA1, разработайте возможную методику решения указанной задачи. Для прерывания процессора 2000 раз в секунду мы будем использовать Таймер 0 (см. Программу 13.2 на стр. 461). Напишите процедуру обработки прерывания, соответствующую разработанному алгоритму.

Решение

Как и любой биомедицинский параметр, сигнал ЭКГ отличается от такта к такту амплитудой, формой и периодом. Даже если бы это было и не так, несовершенство элементов системы сбора данных, в особенности кожных электродов, может привести к медленному дрейфу базовой линии (постоянной составляющей сигнала). Поэтому величину порогового напряжения, начиная с которого мы будем отслеживать появление пикового (точка R) значения сигнала, необходимо во время каждого периода принимать равной некоторой части амплитуды предыдущего пика.

Один из возможных вариантов реализации этого метода приведен на Рис. 14.19. В данном случае после каждого импульса порог слегка уменьшается, чтобы исключить пропуск последующего пика с меньшей амплитудой. Примем минимальную частоту ЭКГ равной 40 ударам в минуту (период 1.5 с). Тогда если при каждой выборке мы будем уменьшать пороговое значение на 1/64 бита, то при частоте 2000 выборок/с максимальное уменьшение составит ~= 47. Для этого пороговое значение THRESHOLD в Программе 14.4 хранится как двухбайтное значение в формате целое: дробное, и после каждой выборки, в которой пиковое значение MAXIMUM не обновляется, из порогового значения вычитается 1/64 целого (т. е. дробное, равное Ь’00000100’). Изменяя вычитаемое, можно управлять скоростью изменения порогового значения.

Рис. 14.19. Стратегия определения пикового значения сигнала ЭКГ

Данная программа работает по следующему алгоритму:

1. ВЫПОЛНИТЬ преобразование для получения значения ANALOG.

2. ЕСЛИ (ANALOG > THRESHOLD)

• MAXIMUM = ANALOG

• THRESHOLD = ANALOG

• PORTB = ANALOG

• RA5 = 1

3. ИНАЧЕ

• THRESHOLD = THRESHOLD — 1/64

• RA5 = 0

При изменении порогового значения THRESHOLD (в случае, если ANALOG > THRESHOLD) в регистр, содержащий целую часть числа, заносится новое значение MAXIMUM, а регистр с дробной частью обнуляется. Если интерпретировать эту пару байтов как 16-битное слово, то пороговое значение можно вычислить как MAXIMUM х 256 или, иначе, THRESHOLD = MAXIMUM << 8. Предполагается, что значение THRESHOLD было обнулено фоновой программой на этапе инициализации и что мы используем 8-битное аналого-цифровое преобразование.

Если оцифрованное значение меньше порогового, то из младшего байта, расположенного в регистре THRESHOLD+1, вычитается h’04’ = Ь’00000100’, и, если при этом возникает заем, декрементируется старший байт THRESHOLD. При равенстве порогового значения нулю эта операция вычитания пропускается — таким образом, предотвращается потеря значимости.

В Программе 14.4 используется подпрограмма GET_ANALOG из Программы 14.1, а также необходимая для ее работы подпрограмма формирования 17-мкс задержки. Однако, поскольку в данном случае интервал между вызовами подпрограммы достаточно велик, длительность задержки при необходимости можно уменьшить до 10 мкс.

Программа 14.4. Определение пикового значения ЭКГ

; ************************************

; * ФУНКЦИЯ: Обработчик для обновления параметров ЭКГ *

; * ВХОД: По прерыванию от Таймера 0 *

; * ВЫХОД: Обновляет MAXIMUM и THRESHOLD: THRESHOLD+1 *

; * РЕСУРСЫ: П/п GET_ANALOG, возвращающая 8-битное значение *

**************************************

; Сначала сохраняем контекст

EKG_ISR movwf _work; Сохраняем W

             swapf STATUS,w; и регистр STATUS

             movwf _status

; ===========================

             btf ss INTCON,T0IF ; Это было прерывание от Таймера 0?

                goto EKG_EXIT ; ЕСЛИ нет, ТО выходим

             bcf INTCON,T0IF ; Сбрасываем флаг

             movlw 1 ; Запускаем преобразование

             call GET_ANALOG ; по 1-му каналу

             movwf TEMP ; Сохраняем оцифрованное значение

             subwf THRESHOLD,w ; THRESHOLD — ANALOG

             btf sc STATUS,С ; ЕСЛИ нет заема, ТО

                goto BELOW ; не обновляем MAXIMUM

             movf TEMP,w ; ИНАЧЕ берем оцифрованное значение

             movwf MAXIMUM ; которое становится новым MAXIMUM

              movwf PORTB ; Выдаем наружу

              bsf PORTA,5 ; Сообщаем об этом

              movwf THRESHOLD ; Теперь обновляем 2-байтный

              clrf THRESHOLD+1 ; порог

                 goto EKG_EXIT ; и выходим

; Сюда попадаем, если входной сигнал ниже порога

BELOW bcf PORTA,5; Сообщаем об отсутствии обновления

; Теперь уменьшаем порог на 1/64 до нуля

            movf THRESHOLD,f; Целая часть порога равна нулю?

            btfsc STATUS,Z; ЕСЛИ нет, ТО пропускаем

               goto EKG_EXIT ; ЕСЛИ да, ТО выходим

            movlw h’04’ ; 1/64 = b’000001000’

            subwf THRESHOLD+1,f ; Вычитаем из байта дробной части

            btfss STATUS,С ; Пропускаем, если нет заема

              decf THRESHOLD,f ; ИНАЧЕ декрементируем целую часть

; ==========================

EKG_EXIT swapf _status,w ; Восстанавливаем STATUS

             movwf STATUS

             swapf _work,f ; Восстанавливаем W,

             swapf _work,w ; не затрагивая регистр STATUS,

             retfie ; и выходим из прерывания

В Программе 14.5 приведена программа на языке Си, реализующая тот же самый алгоритм. Директива #int_rtcc указывает компилятору интерпретировать описанную после нее функцию как процедуру обработки прерывания от часов реального времени (Таймера 0). Переменные threshold и maximum в функции ecg_isr () объявлены как статические (static). Это означает, что их значения будут сохранены после выхода из функции и доступны при последующем входе в функцию. По умолчанию локальные переменные функций сохраняют свое значение только на время выполнения функции. В качестве альтернативного варианта можно было бы объявить такие переменные вне всех функций. В этом случае переменные становятся глобальными, и их значения сохраняются на протяжении всего времени работы программы.

Программа 14.5. Определение пикового значения ЭКГ на Си

#byte PORT_B =6 /* Вывод RA5 — 5-й бит порта А

#bit RA5 =5.5 /* Порт В — регистр 06

#int_rtcc

void ecg_isr(void)

{

       unsigned int analog;

       static unsigned long int threshold = 0;

       static unsigned int maximum;

       analog = read_adc();

       if (analog > threshold»8)

      {

           maximum = analog; /* Новое максимальное значение */

            PORT_B = analog; /* Выдаем наружу */

            threshold = maximum << 8; /* Новый 2-байтный порог */

            RA5 =1; /* Сообщаем об этом */

       }

       else

            if(threshold >= 0x0004) /* ЕСЛИ порог не менее h’0004’, */

            {

                threshold = threshold — 0x0004; /* ТО уменьшаем на 1/64 */

                RA5 =0; /* Сообщаем об отсутствии обновления */

            }

}

Переменная threshold имеет тип long int, поэтому компилятор CCS будет интерпретировать ее как 16-битное число. Обнуление переменной threshold выполняется единожды при запуске программы, поскольку переменная объявлена как static. И опять же, такое поведение отличается от поведения автоматических переменных, создаваемых по умолчанию.

При занесении в переменную threshold нового максимального значения последнее умножается на 256 путем сдвига на восемь разрядов влево. Хороший компилятор автоматически преобразует выражение N*256 к виду N << 8 или же, что еще лучше, просто возьмет в качестве результата операции старший байт пары.

Пример 14.3

Микроконтроллер используется для вычисления энергии разряда двухфазного дефибриллятора, приведенного на Рис. 14.5. Когда микроконтроллер обнаруживает начало разряда, он должен сделать 256 выборок с частотой 20 000 выборок в секунду. При этом энергия определяется как сумма квадратов отклонений сигнала от базового уровня — предполагается, что сопротивление цепи «грудная клетка пациента — электроды» остается постоянным на протяжении всего разряда.

Для получения опорного напряжения используется внешний ИОН с выходным напряжением 4.096 В, что при 8-битном преобразовании даст нам разрешение, равное 16 мВ. После начала разряда на выводе RA4 необходимо сформировать импульс для запуска развертки запоминающего осциллографа, позволяющего сохранить осциллограмму сигнала для архивных целей. А после окончания разряда старший байт значения энергии должен быть выведен в порт В для отображения на дисплее.

Покажите, как можно с помощью микроконтроллера PIC16F87XA с 20-МГц резонатором реализовать указанную измерительную систему. Можете считать, что на ИОН можно подавать смещение наподобие стабилитрона. На практике для достижения более точных результатов может использоваться дополнительный переменный резистор, с помощью которого осуществляется подстройка выходного напряжения ИОН в небольших пределах.

Решение

Подходящая схема показана на Рис. 14.20. Собственно сигнал, изменяющийся в диапазоне +1.8…+3.6 В (см. Рис. 14.5), подается на вход 0-го аналогового канала RA0/AIN0. Резистор сопротивлением 10 кОм защищает аналоговый вход от перегрузки, одновременно выполняя, совместно с конденсатором 3300 пФ, роль фильтра, устраняющего эффект наложения спектров, с частотой среза около 450 кГц. Поскольку реальные дефибрилляторы работают с очень большими напряжениями (порядка 25 кВ), для защиты микроконтроллера от высоковольтных выбросов используется два диода 1N4004, дополняющие внутренние защитные диоды (см. Рис. 14.12).

Рис. 14.20. Измерение энергии разряда дефибриллятора

Внешний ИОН напряжением 4.096 В подключен непосредственно к выводу RA3, который является входом внешнего опорного напряжения (конфигурация АЦП — Ь’0101’). Чтобы снизить уровень помех на линиях VDD и Vref+, к ним подключены развязывающие танталовые конденсаторы емкостью 1 мкФ.

Для определения начального момента разряда используется внутренний аналоговый компаратор, как было показано на Рис. 14.5. При работе компаратора в режиме Ь’1110’ для формирования внутреннего опорного напряжения может использоваться модуль CVREF, как было описано на стр. 501. При сброшенном бите CIS (см. Рис. 14.6) компаратор 1 может использовать 0-й аналоговый канал совместно с модулем АЦП.

И, наконец, вывод RA4 и все выводы порта В должны быть сконфигурированы как выходы. Первый из указанных выводов используется для формирования синхроимпульса, а порт В — для вывода полученного результата.

    include "p16f877a.inc"

    org 0 ; Вектор сброса

    goto SET_UP ; Переход на фоновую программу

    org 4 ; Вектор прерывания

    goto ECG_ISR ; Обслуживаем прерывание от компаратора

SET_UP

    bsf STATUS f RP0 ; Переключаемся в 1-й банк

    movlw b’00000110’ ; Режим работы компаратора = 110, CIS = 0

    movwf CMCON

    call DEIAY_17US ; Идем 17 мкс для установления напряжений

    movf CMCON,f ; Читаем СМСОN, чтобы сбросить признак изменения

    bsf PIE2,CMIE ; Разрешаем прерывания от компаратора

    movlw b’10001110’ ; Модуль CVREF вкл. (1), внутр. ИОН (0)

    movwf CVRCON ; Верхний диапазон (0), CVR[3:0] = 1110

    movlw b’00000101’ ; RA0/1 — аналоговые входы

    movwf ADCON1 ; RA3 — вход опорного напряжения

    movlw b’101111b’ ; RA4 — выход

    movwf TRISA

    clrf TRISB ; Все выводы порта В — выходы

    bcf STATUS,RP0; Возвращаемся в 0-й банк

    movlw b’100000011 ; Включаем модуль АЦП (fosc/32)

    movwf ADCONO

    bcf PIR2,CMIF ; Сбрасываем флаг прерывания от компаратора

    bsf INTCON,PEIE ; Разрешаем прерывания от периферийных устройств

    bsf INTCON,GIE ; Разрешаем работу системы прерываний

Выше приведена секция инициализация для нашей программы. В данном случае модули конфигурируются следующим образом:

1. Модуль аналогового компаратора работает в режиме b’110’, при этом CIS = 0. Для выдерживания интервала, требуемого для установления внутренних сигналов модуля, используется подпрограмма DELAY_17US (чтобы не писать отдельный код для формирования 10-мкс задержки). После этого выполняется чтение регистра CMCON для сброса признака изменения состояния компаратора. Затем сбрасывается флаг CMIF и разрешаются прерывания.

2. Модуль CVREF работает в режиме b’1110’ и использует верхний диапазон. Таким образом, он формирует опорное напряжение 3.4375 В.

3. Модуль АЦП работает в режиме Ь’0101’, при котором выводы RA0 и RA1 используются в качестве аналоговых входов, а вывод RA3 — для подачи внешнего опорного напряжения Vref+. Результат преобразования выравнивается по левой границе для упрощения получения 8-битного результата. В качестве тактового сигнала АЦП используется системный сигнал, деленный на 32. При этом в соответствии с Табл. 14.2 частота преобразования получается равной 625 кГц.

4. PORTA[4]Здесь имеется в виду не размер данных, которыми оперирует микроконтроллер, а число битов, использующихся для записи слова команды. — Примеч. пер.
конфигурируется как выход. Остальные выводы порта А остаются входами для обеспечения функциональности аналоговых входов AIN0, AIN1 и AIN3. Все выводы порта В конфигурируются как выходы.

Код собственно программы приведен в Программе 14.6. В процедуре MAIN микроконтроллер просто переключается в «спящий» режим до момента изменения состояния аналогового компаратора, по которому генерируется прерывание. После возврата управления в фоновую процедуру старший байт трехбайтной переменной, в которой находится вычисленное значение мощности, копируется в порт В, а затем описанный процесс повторяется.

Программа 14.6. Измерение энергии разряда дефибриллятора

MAIN sleep ; Ждем

         nop

         movf POWER,w ; Берем старший байт значения

         movwf PORT В ; и выводим его в порт В

         goto MAIN

; **************************************

* ФУНКЦИЯ: Обработчик прерывания, в котором вычисляется энергия разряда дефибриллятора *

* ВХОД: По прерыванию от модуля компараторов *

* ВЫХОД: Обновляется значение POWER:3 *

* РЕСУРСЫ: П/п GET_ANALOG, возвращающая 8-битное значение, *

* РЕСУРСЫ: п/п SQUARE, выполняющая умножение 8x8 битов *

; ***************************************

Сначала сохраняем контекст

ECG_ISR movwf _work ; Сохраняем W

             swapf STATUS,w ; и регистр STATUS

             movwf status

; ==============================

            btfss PIR2,CMIF ; Это прерывание от компаратора?

               goto ECG_EXIT ; ЕСЛИ нет, ТО выходим

            clrf POWER ; Обнуляем регистры результата

            clrf POWER+1

            clrf POWER+2 ; Младший байт

            clrf COUNT ; Обнуляем счетчик (256 итераций)

            bcf PORTA,4 ; Формируем на RA4

            bsf PORTA,4 ; синхроимпульс

             bcf PORTA, 4

ACQUIRE clrw; Канал 0 (W = h'00')

            call GET_ANALOG ; Запускаем преобразование

            addlw -BASELINE ; Определяем разность с базовым напряжением

            bcfsc STATUS,С ; ЕСЛИ заем (С==0), ТО обходим,

                goto ECG_CONTINUE ; так как разность положительна

            xorlw b’11111111’ ; ИНАЧЕ инвертируем и прибавляем

ECG_CONTINUE

            call SQR ; Возводим в квадрат

            movf SQUARE+1,w ; Берем младший байт квадрата напряжения

            addwf POWER+2,f ; Прибавляем к младшему байту результата

            btfss STATUS,С ; Проверяем перенос

               goto NEXT_BYTE ; ЕСЛИ нет, ТО складываем след, байты

            movlw 1 ; Инкрементируем средний байт результата

            addwf POWER+1,f

            btfsc STATUS,С ; Был перенос?

               incf POWER,f ; ЕСЛИ да, ТО инкрементируем старший байт

NEXT BYTE

            movf SQUARE,w ; Берем старший байт квадрата напряжения

            addwf POWER+1,f ; Прибавляем к среднему байту результата

            btfsc STATUS,С ; Проверяем перенос

               incf POWER,f ; ЕСЛИ нет, ТО инкрементируем старший байт

            call DELAY_470US ; Ждем перед следующей выборкой

            incfsz COUNT,f ; Инкрементируем счетчик цикла и повторяем

               goto ACQUIRE ; операции, если он не равен 0

;========================

EGG_EXIT sf STATUS,RP0 ; Сначала сбрасываем признак изменения

            movf CMCON,f ; состояния компаратора,

            bcf STATUS,RP0 ; читая CMCON из 1-го банка,

            bcf PIR2,CMIF ; и сбрасываем флаг прерывания

            swapf _status,w ; Восстанавливаем регистр STATUS

            movwf STATUS

            swapf _work,f ; Восстанавливаем W, не затрагивая

            swapf _work,w ; регистр STATUS,

            retfie ; и выходим из прерывания

После сохранения контекста обработчик прерывания сначала проверяет источник прерывания, а затем сбрасывает счетчик итераций цикла и три регистра, используемые для накопления суммы из 256 квадратов отсчетов напряжения. После этого на выводе RA4 формируется короткий импульс, извещая внешние устройства о начале разряда.

Для получения 8-битного оцифрованного значения используется подпрограмма GET_ANALOG из Программы 14.1. Затем вычисляется отклонение от базового уровня, которое принимается равным 2.6 В (см. Рис. 14.5). Если эта величина отрицательна (входное напряжение меньше 2.6 В), что определяется по формированию признака заема при вычитании, то вычисляется дополнительный код байта разности (см. стр. 22) для перевода отрицательного значения в положительное. Полученный модуль напряжения затем возводится в квадрат с использованием подпрограммы SQR, код которой был приведен в Программе 8.3 (стр. 258). Затем содержимое 2-байтной глобальной переменной SUM: SUM+1 прибавляется к общей сумме, хранящейся в регистрах POWER: POWER+1:POWER+2.

Этот процесс повторяется 256 раз, причем перед каждой последующей итерацией цикла формируется задержка длительностью 470 мкс. В итоге считывание сигнала с вывода RA0 осуществляется каждые 500 мкс, что соответствует заданной частоте дискретизации (2 кГц). После завершения серии выборок, на что уходит примерно 128 мс, производится чтение модуля компаратора для сброса признака изменения. Это делается в конце цикла, а не в начале, потому что если входное напряжение перейдет через порог срабатывания компаратора (3.3475 В), то будет зафиксировано новое изменение! Затем флаг CMIF сбрасывается, и восстанавливается контекст программы.

Разумеется, наша программа очень примитивна. Например, базовый уровень может изменяться с течением времени, что влечет за собой необходимость его определения перед запуском преобразования. И, наоборот, при высокой стабильности этого значения его можно запомнить в энергонезависимой памяти, как будет описано в следующей главе. Использование фиксированного числа отсчетов может оказаться ограничивающим фактором. Вместо этого можно выполнять операции считывания до тех пор, пока полученное значение не окажется меньше некоторого порога.

Пример 14.4

Используя язык Си, покажите, как можно в микроконтроллере PIC16F874 считать 10-битный результат оцифровки 3-го аналогового канала при нахождении микроконтроллера в «спящем» режиме.

Решение

В компиляторе CCS имеется функция sleep () для перевода микроконтроллера в «спящий» режим; эта функция просто транслируется в команду sleep. Для преобразования во время «спящего» режима нельзя применять функцию read_adc (), которую мы использовали в Программе 14.3, поскольку в «спящем» режиме процессор остановлен. Вместо этого нам потребуется перед входом в «спящий» режим вручную изменить состояния определенных битов, относящихся к прерываниям, наподобие того, как это было сделано в ассемблерной Программе 14.2. При «пробуждении» можно по отдельности прочитать содержимое регистров ADRESH: L и объединить эти значения для формирования 10-битного результата.

Код для решения этой задачи приведен в Программе 14.7. Биты PEIE, ADIF и  определены с помощью директивы #bit. На этот раз функция setup_adc () вызывается с параметром ADC_CLOCK_INTERNAL, соответствующим работе модуля АЦП от собственного RC-генератора, что необходимо для осуществления преобразования в «спящем» режиме.

Программа 14.7. Преобразование в «спящем» режиме на Си

#include <16f874.h>

#device ADC=10 /* Используем 10-битное преобразование */

#use delay(clock=8000000) /* Частота резонатора — 8 МГц */

#bit ADIF = 0х0C.6 /* Флаг прерывания от модуля АЦП в РIR1[6] */

#bit PEIE = 0x0B.6 /* Бит разрешения прерываний от ЛУ */

INTCON[6]

#bit GO = x1F.2 /* Бит GO/NOT_DONE — ADCON0[2] */

void main(void)

{

     unsigned long int result ; /* 16-битная переменная для хранения результата */

     sec_tris_a (0х0Е);

     setup_adc(ADC_CLOCK_INTERNAL);

     setup_adc_ports(RA0_RA1_RA3_ANALOG);

     set_adc_channel(3);

     delay_us(17); /* Ждем для установления напряжений */

     disable_interrupts(GLOBAL); /* Запрещаем все прерывания (GIE и PEIE=1) */

     ADIF = 0;

     enable_interrupts (INT_AD);

     PEIE =1; /* Разрешаем прерывания от ПУ */

     /* Основной код */

     GO = 1;

     sleep();

      result = ((long)ADRESH << 8) + ADRESL ; /* После «пробуждений» считываем оба байта */

}

Встроенная функция disable_interrupts (GLOBAL) сбрасывает оба бита — GIE и PEIE. Обратная по смыслу функция enable_interrupts (GLOBAL) устанавливает оба бита, однако нам необходимо установить только PEIE, a GIE оставить сброшенным. Поэтому мы «напрямую» устанавливаем бит PEIE = 1. Аналогично, для сброса флага ADIF используется выражение ADIF = 0. Перед вызовом функции sleep () мы запускаем преобразование, вручную устанавливая бит . После возврата из функции sleep () сначала считывается регистр ADRESH и преобразуется к типу long int, чтобы компилятор интерпретировал его как 16-битный объект. Затем это значение умножается на 256, поскольку оно является старшим байтом 16-битного объекта. В результате сложения с полученным 16-битным числом регистра ADRESL его содержимое помещается в младший байт результата.

Вопросы для самопроверки

14.1. В Примере 14.2 пороговое значение уменьшалось по линейному закону. Несмотря на то что такой подход достаточно эффективен при априори известном периоде сигнала, который к тому же изменяется в незначительных пределах, в остальных случаях лучшие результаты можно получить, используя экспоненциальное изменение порога. Для получения такой характеристики из результата выборки необходимо вычитать не константу, а некоторую фиксированную долю полученного значения. Покажите, как можно модифицировать Программы 14.4 и 14.5, чтобы при каждой выборке пороговое значение уменьшалось примерно на 0.025 % (-1/4096), и определите постоянную времени в пересчете на количество отсчетов.

14.2. В реальных аналоговых сигналах обязательно присутствуют шумы. На практике это часто приводит к необходимости фильтрации или сглаживания. В любом случае шумы, наводимые извне, не должны иметь составляющих значительной амплитуды, с частотой, которая больше половины частоты дискретизации, поскольку при восстановлении эти составляющие окажутся сдвинутыми в полосу пропускания, как показано на Рис. 14.4. Сигнал необходимо пропускать через фильтр нижних частот перед аналого-цифровым преобразованием, как показано на Рис. 14.20.

Хотя такой внешний фильтр, устраняющий эффект наложения спектров, должен быть, по определению, реализован аппаратно (например, на RС-цепочке), помехи, попадающие в полосу пропускания системы, можно сгладить, используя программную фильтрацию. Один из простейших вариантов цифровой фильтрации заключается в считывании нескольких выборок с последующей выдачей усредненного результата. Например, при суммировании 16 отсчетов и последующем четырехкратном сдвиге результата вправо (16) случайный шум уменьшится в √16 = 4 раза.

Другим методом, хорошо известным тем, кто занимается статистикой, является метод скользящего среднего, который часто используется, например, для вычисления среднего курса акций в течение месяца. Достаточно эффективный алгоритм такого рода получается при усреднении по трем точкам:

где Sn — n-й отсчет, полученный аналоговым модулем.

Покажите, как можно модифицировать подпрограмму GET_ANALOG, чтобы она запоминала значение двух последних отсчетов и возвращала усредненное значение.

14.3. Предположим, что ЦАП МАХ506 используется для реализации автоматической регулировки усиления (АРУ) сигнала, подаваемого на аналоговый вход микроконтроллера в ЭКГ-мониторе из Примера 14.2. Задача АРУ заключается в том, чтобы максимальное значение сигнала на аналоговом входе лежало в диапазоне 3/4…7/8 полной шкалы. Как бы вы реализовали такую подсистему? Подсказка: вспомните, что выходной сигнал каждого канала ЦАП МАХ506 определяется собственным аналоговым входом и общим опорным напряжением Vref, которое может лежать в диапазоне 0…VDD.

14.4. Входной синусоидальный сигнал необходимо подвергнуть двухполупериодному выпрямлению, т. е. отрицательные полуволны должны поменять свой знак. Напишите подпрограмму, выполняющую данное действие, полагая, что 8-битное оцифрованное значение находится в регистре ADRESH, а выходное значение должно быть передано через порт В на внешний ЦАП.

14.5. В основе схемы, изображенной на Рис. 14.21, лежит Fig. 10 из документа AN546 «Using the Analog-to-Digital (A/D) Converter». Такая схема используется для формирования внешнего опорного напряжения для экономичных устройств. Можете ли вы объяснить, как она работает, и из каких соображений выбирается номинал токоограничивающего резистора?

Рис. 14.21. Управляемый источник опорного напряжения

 

Глава 15

Хранить вечно!

В большинстве микроконтроллеров PIC среднего и верхнего уровней имеется отдельная EEPROM-память небольшого объема, управление и доступ к которой осуществляются посредством регистров специального назначения, как и для других периферийных устройств. Наличие встроенной энергонезависимой памяти дает программисту возможность считывать и модифицировать различные статические данные, такие как показания автомобильного одометра, которые должны сохраняться при отсутствии питания (см. Пример 12.3 на стр. 439). Разумеется, для этой цели можно воспользоваться и внешней микросхемой EEPROM, например из линейки 24ХХХ (см. Рис. 12.26 на стр. 439). Однако при небольшом объеме данных, требующих хранения, использование внутренней EEPROM увеличивает надежность устройства и уменьшает его стоимость, габаритные размеры и энергопотребление.

Так что приступим к изучению возможностей этой энергонезависимой памяти. После прочтения этой главы вы:

• Ознакомитесь с характеристиками модуля EEPROM.

• Узнаете, как выполняется чтение/запись данных из/в модуль EEPROM.

• Поймете, каким образом в некоторых моделях микроконтроллеров FLASH-память программ можно использовать для хранения долговременных данных.

• Сможете сравнить модуль EEPROM и FLASH-память программ в качестве устройства хранения долговременных данных.

Устаревший к настоящему моменту микроконтроллер PIC16C84, выпущенный в 1994 году, был первым микроконтроллером PIC, у которого память программ была сделана по технологии EEPROM. Как мы уже видели на Рис. 2.13 (стр. 42), электрически стираемое ППЗУ похоже на обычное СППЗУ (EPROM). однако для его стирания не требуется источник ультрафиолетового излучения. Несмотря на то что технология EEPROM более дорога, чем EPROM, ее использование для реализации памяти программ было оправдано повышенным удобством при разработке опытных образцов устройств, а также при использовании микроконтроллера в учебных целях и радиолюбительской практике. В то же время появился и модуль EEPROM-памяти данных, в котором, отдельно от обычной памяти данных микроконтроллера, могло храниться до 64 байт долговременных данных.

Микроконтроллер PIC16C84 и аналогичная ему модель с FLASH-памятью программ PIC16F84 долгое время оставались единственными представителями в линейке микроконтроллеров PIC, имеющими память EEPROM-типа, — до появления в 1998 году микроконтроллера PIC16F87X. В большинстве своем все микроконтроллеры среднего уровня, выпущенные после 2000 года, были либо совершенно новыми моделями с FLASH-памятью программ, либо аналогами своих предшественников линейки PIC16CXXX с памятью программ EPROM-типа. Все используемые в данной книге модели имеют FLASH-память программ и модуль EEPROM.

Прежде чем приступить к изучению этого модуля, будет полезно ознакомиться с приложениями, требующими использования энергонезависимой памяти. Хорошим примером такого приложения является смарт-карта (см. Рис. 12.1 на стр. 369). В этой карте должны храниться, помимо всего прочего, номер счета, PIN-код, даты начала и конца срока действия карты. Некоторые из этих данных, такие как номер счета, являются по сути дела фиксированными. А защищенные данные могут изменяться пользователем в любой момент времени с помощью терминала. Если карточка используется в качестве банковской, то должна быть предусмотрена возможность записи на счет посредством банкомата информации о доступном кредите, а также изменение этой информации после совершения оплаты. Размеры смарт-карт и требования, предъявляемые к стоимости используемых в них процессоров, таковы, что наличие интегрированной EEPROM-памяти данных жизненно необходимо.

На Рис. 15.1 показана логическая организация модуля EEPROM моделей PIC16F62X. Матрица памяти модуля не имеет никакого отношения к обычным областям памяти программ и памяти данных. Доступ к ней осуществляется посредством четырех РСН, которые используются для указания адреса интересующего нас байта, для хранения считываемого или записываемого байта данных, а также для управления процессами чтения и записи.

Матрица EEPROM-памяти

Модуль EEPROM микроконтроллеров среднего уровня поддерживает до 256 8-битных ячеек. В моделях PIC16F627/8, PIC16F873/4 и PIC12F629/75 реализовано только 128 младших ячеек. В моделях PIC16F648 и PIC16F876/7 реализованы все 256 ячеек. Вот основные параметры этого модуля:

• Не менее 1 000 000 (107 typ) циклов стирания/записи на ячейку при напряжении 5 В и температуре 25 °C.

• Максимальная длительность цикла стирания/записи — 8 мс (4 мс typ).

• Срок сохранности данных более 40 лет (100 лет для микроконтроллеров линейки PIC16F62X).

Рис. 15.1. Модуль EEPROM модели PIC16F62X

∙ EEADR (регистр адреса EEPROM)

Восьмибитный регистр может адресовать до 256 (28) байтов. Если в конкретной модели реализована EEPROM-память меньшего объема, то старшие биты адреса должны быть сброшены, чтобы обращение всегда происходило в пределах физического адресного пространства. В микроконтроллерах PIC16F627/8 допустимыми адресами являются адреса из диапазона h’00’…h’7F’.

∙ EEDATA (регистр данных EEPROM)

Регистр данных либо содержит 8-битное число, считанное из EEPROM, либо значение, которое пользователь собирается записать в адресованную ячейку EEPROM-памяти.

∙ EECON1 (регистр управления 1 EEPROM)

Модуль EEPROM может работать в двух режимах — чтение и запись. Управление и контроль процессов чтения и записи осуществляются с помощью регистра EECON1 (см. Рис. 15.2).

Рис. 15.2. Регистр EECON1 модели PIC16F62X

∙ EECON2 (регистр управления 2 EEPROM)

Этот регистр физически не реализован и при его чтении всегда возвращается нулевое значение. Однако он используется для разрешения инициирования операции записи в EEPROM. Для этого в него необходимо загрузить непосредственно друг за другом два числа: сначала Ь’01010101’ (h’55’), а потом b’10101010’ (h’AA’). Эта «пляска с бубном» введена специально, чтобы исключить случайное изменение информации в EEPROM.

Чтобы прочитать данные из EEPROM, необходимо выполнить следующие операции:

1. Загрузить адрес интересующей нас ячейки в регистр EEADR.

2. Установить бит RD для запуска цикла чтения.

3. Бит RD сразу же автоматически сбрасывается, а искомое 8-битное число можно будет в следующем машинном цикле считать из регистра EEDATA.

Подпрограмма EE_GET, код которой приведен в Программе 15.1, реализует описанный алгоритм и возвращает значение из EEPROM в рабочем регистре. Причем это значение остается в регистре EEDATA до повторного использования регистра.

Программа 15.1 . Чтение байта из EEPROM

; **************

; * ФУНКЦИЯ: Читает один байт из модуля EEPROM *

; * ВХОД: Адрес в EEADR *

; * ВЫХОД: Значение байта в W и в EEDATA *

; **************

EE_GET bsf STATUS,RP0 ; Переключаемся в 1-й банк

            movlw b’00000001’ ; Устанавливаем RD для запуска цикла чтения

            movwf EECON1 ; Считываем байт в EEDATA

            movf EEDATA,w ; Копируем его в W

            bcf STATUS,RP0 ; Возвращаемся в 0-й банк

            return ; перед возвратом

Процесс записи данных в EEPROM намеренно сделан более запутанным, чтобы уменьшить вероятность случайного запуска цикла записи из-за ошибки в программе или неправильной работы процессора, скажем, по причине сбоя в питании. Запись значения в заданную ячейку осуществляется по следующему алгоритму:

1. Скопировать адрес искомой ячейки в регистр EEADR.

2. Установить бит WREN (EECON1[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
) для разрешения операции записи.

3. Запретить все прерывания.

4. Записать в регистр EECON2 число h’55’.

5. Записать в регистр EECON2 число h’AA’.

6. Установить бит WR для инициирования цикла записи.

7. Сбросить бит WREN.

8. Разрешить прерывания.

9. Дождаться сброса бита WR, свидетельствующего о завершении процесса записи, и выйти из подпрограммы.

Цикл записи не запустится, если операции с номерами 4…6 не будут выполнены непосредственно друг за другом. Так, если во время записи этой кодовой последовательности произойдет прерывание, то операция записи будет прервана. Поэтому перед загрузкой кодовой последовательности необходимо запрещать прерывания, сбрасывая бит GIE.

При необходимости, по завершении цикла записи может генерироваться прерывание. Это прерывание разрешается установкой бита маски EEIE (PIE1[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
). После установки флага прерывания EEIF (PIR1[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
) прерывание генерируется обычным образом. Флаг EEIF должен сбрасываться вручную в обработчике прерывания.

Бывает так, что процессор сбрасывается, скажем, по тайм-ауту сторожевого таймера до завершения цикла записи. В этом случае данные в EEPROM могут оказаться поврежденными. Если операция записи была преждевременно прекращена из-за сброса микроконтроллера, то будет установлен флаг WRERR (EECON[3]New Scientist , vol.59, no. 2141, 4 July 1998, p.139.
). В остальных случаях для повышения надежности данные после завершения цикла записи можно считать обратно и убедиться в их целостности. К этому времени бит WREN можно уже сбросить, чтобы исключить несанкционированную запись. Сброс этого бита до завершения цикла записи не оказывает влияния на операцию.

Приведенный выше алгоритм реализован в Программе 15.2. Значения байта данных и его адреса заносятся в регистры EEDATA и EEADR вызывающей программой. Возврат из подпрограммы происходит только после завершения цикла записи, который длится около 4 мс. Такое решение гарантирует, что указанные РСН не будут изменены во время цикла, что может привести к неверным результатам.

Программа 15.2. Запись байта в EEPROM

; ************************

; * ФУНКЦИЯ: Пишет один байт в модуль EEPROM *

; * ВХОД: Байт данных в EEDATA, адрес байта в EEADR *

; * ВЫХОД: Прерывания запрещены в течение 9 маш. циклов *

; * ВЫХОД: Используется 0-й банк памяти *

; ************************

ЕЕ PUT bsf STATUS,RP0 ; Переключаемся в 1-й банк

           bcf STATUS,RP1

           bsf EECON1,WREN ; Разрешаем запись

           bcf INTCON,GIE ; Запрещаем все прерывания

           movlw h’55’ ; Загружаем кодовую последовательность

           movwf EECON2

           movlw h’AA’

           movwf EECON2

           bsf EECON1,WR ; Инициируем цикл записи

           bcf EECON1,WREN ; Запрещаем дальнейшие операции записи

           bsf INTCON,GIE ; Разрешаем прерывания

EE_EXIT btfsc EECON1,WR ; Проверяем, запись завершена?

               goto EE_EXIT ; ЕСЛИ нет, ТО проверяем снова

             bcf STATUS,RP0 ; Возвращаемся в 0-й банк

             return ; и выходим из подпрограммы по окончании цикла записи

Чтобы проиллюстрировать работу с EEPROM, вернемся к Примеру 12.3 (стр. 439), в котором мы сохраняли 3-байтные показания одометра во внешней последовательной EEPROM. Однако на этот раз мы воспользуемся встроенной EEPROM-памятью. Предположим также, что показания одометра хранятся в ячейках EEPROM с адресами h’10’…h’12’.

В новой программе, код которой приведен в Программе 15.3, для чтения и последующей записи 3-байтного значения одометра из/в модуль EEPROM используются подпрограммы EE_GET и EE_PUT. Адрес первого (старшего) байта в начале подпрограммы копируется в регистр EEADR, а по ходу выполнения подпрограммы для указания на требуемые ячейки этот регистр инкрементируется и декрементируется.

Программа 15.3. Инкрементирование значения одометра, хранящегося в модуле EEPROM

; **********************

; * ФУНКЦИЯ: Инкрементирует 3-байтное значение одометра *

; * РЕСУРСЫ: Подпрограммы EE_GET и EE_PUT *

; * ВХОД: Текущее значение в EEPROM по адресам 10:11:12h *

; * ВЫХОД: Измененное значение в EEPROM по тем же адресам, *

; * ВЫХОД: а также находится в регистрах LSB: NSB: MSB *

; **********************

EXTRA_MILE

        bsf STATUS,RP0 ; Переключаемся в 1-й банк

        movlw h’10’; Адрес старшего байта показаний одометра

        movwf EEADR ; Копируем в регистр адреса EEPROM

        call EE_GET ; Читаем байт из EEPROM

        movwf MSB ; и кладем его в регистр MSB

        bsf STATUS,RP0 ; Снова в 1-й банк

        incf EEADR,f ; Адрес среднего байта показаний одометра

        call EE_GET ; Читаем байт из EEPROM

        movwf NSB ; и кладем его в регистр NSB

        bsf STATUS,RP0 ; Снова в 1-й банк

        incf EEADR,f ; Адрес младшего байта показаний одометра

        call EE_GET ; Читаем байт из EEPROM

        movwf LSB ; и кладем его в регистр LSB

; Теперь инкрементируем 3-байтное значение

        incf LSB, f ; Прибавляем 1

        btfss STATUS,Z ; Равно нулю?

           goto PUT_BACK ; ЕСЛИ нет, ТО продолжаем

        incfsz NSB, f ; Инкрементируем средний байт

           goto PUT_BACK ; ЕСЛИ не ноль, ТО продолжаем

        incf MSB, f

; Помещаем обновленное значение одометра обратно в EEPROM

PUT_BACK movf LSB,w ; Берем новое значение младшего байта

        bsf STATUS,RP0 ; Переключаемся в 1-й банк

        movwf EEDATA ; Кладем его в регистр данных EEPROM

        call EE_PUT ; Пишем в EEPROM по адресу h’12’

        movf NSB,w ; Берем новое значение среднего байта

        bsf STATUS,RP0 ; Снова в 1-й банк

        movwf EEDATA ; Кладем его в регистр данных EEPROM

        decf EEADR,f ; Адресуем средний байт

        call EE_PUT ; Пишем в EEPROM по адресу h’11’

        movf MSB,w ; Берем новое значение младшего байта

        bsf STATUS,RP0 ; Снова в 1-й банк

        movwf EEDATA ; Кладем его в регистр данных EEPROM

        decf EEADR,f ; Адресуем старший байт

        call EE_PUT ; Пишем в EEPROM по адресу h’10’

        return

После считывания и копирования 3-байтного значения показаний одометра в память оно инкрементируется точно так же, как и в Программе 12.19 (стр. 442). Обновленное значение затем повторно заносится в EEPROM в обратном порядке, при этом значение регистра EEADR декрементируется. Подпрограмма EE_PUT проверяет завершение цикла записи перед выходом, поэтому в вызывающей программе эту проверку можно не выполнять.

Помимо изменения содержимого EEPROM из программы, ее можно инициализировать при программировании микроконтроллера (при занесении кода программы в память программ), как показано на Рис. 10.6, а (стр. 312). Как мы уже говорили, в памяти данных микроконтроллера имеется специальная область, расположенная по адресам h’2000’…h’30FF’, доступ к которой может осуществляться только в режиме программирования. Из Рис. 10.6, б и Рис. 10.6, в мы видели, что конфигурационный байт расположен по адресу h’2007’. Содержимое модуля EEPROM также находится в этом адресном пространстве по адресам h’2100’…h’21FF’ Поэтому для занесения в EEPROM-память значений функции sin(x) от 0° до 90° с шагом 10° в исходном коде программы должны присутствовать следующие строки:

      org h’2100’ ; Адресное пространство модуля EEPROM

SINE de 0, h’2С’, h’57’, h’7F’, h’A4’, h’C4’

       de h’DD’, h’F0’, h’FB’, h’FF’

в которых содержимое EEPROM задается посредством ассемблерной директивы de (Data EEPROM). После занесения программы в микроконтроллер содержимое модуля EEPROM будет выглядеть так, как показано на Рис. 15.3.

Рис. 15.3. Первые 32 байта внутренней EEPROM-памяти, содержащей таблицу значений функции sin(x)

Любые данные, занесенные таким образом в память, могут впоследствии быть считаны программой. Например, чтобы узнать значение sin(50), надо будет прочитать ячейку EEPROM с адресом h’05’ (50/10), в которой хранится число h’C4’ или 196 десятичное (196/256 = 0.76525).

Несмотря на то что память программ можно инициализировать аналогичным образом, используя директиву dw, как это сделано в Программе 15.5, такая возможность используется довольно редко. Это связано с тем, что в соответствии с идеологией гарвардской архитектуры, базирующейся на разделении адресных пространств памяти программ и памяти данных, ни одна из команд не сможет считать эти данные. Команды могут обращаться только к памяти данных. Однако все более-менее современные PIC-микроконтроллеры с FLASH-памятью программ позволяют программам косвенным образом читать и писать эти данные аналогично тому, как это делается при работе с модулем EEPROM. К таким микроконтроллерам, в частности, относятся все модели группы PIC16F87X. Причем между исходными моделями и более поздними версиями с суффиксом «А» имеются определенные различия. Но сначала мы поговорим о первых.

Обе модели PIC16F873/4 имеют FLASH-память программ объемом 4 Кбайт и модуль EEPROM объемом 128 байт, тогда как модели PIC16F876/7 имеют уже 8 Кбайт памяти программ и 256 байт EEPROM. В остальном эти модели полностью идентичны.

Основные характеристики модуля EEPROM микроконтроллеров группы PIC16F87XA:

• Не менее 100 000 (максимум 106) циклов стирания/записи EEPROM-памяти на ячейку.

• Не менее 10 000 (максимум 105) циклов стирания/записи FLASH-памяти программ.

• Максимальная длительность цикла записи/стирания составляет 8 мс (4 мс typ) как для модуля EEPROM, так и для FLASH-памяти.

Хотелось бы обратить внимание на максимальное число циклов перезаписи FLASH-памяти программ (10 000). Несмотря на то что такого значения более чем достаточно при изменении программы устройства, оно накладывает определенные ограничения на использование памяти программ в качестве хранилища долговременных данных. По этой причине FLASH-память программ более пригодна для хранения неизменяющихся данных, таких как таблицы соответствия, нежели для хранения информации, требующей частого обновления, такой как показания одометра.

FLASH-память занимает меньше места на кристалле, чем обычная EEPROM-память. Хотя это и ускоряет процесс записи, однако заряды, которые в конечном счете стекают через изоляцию плавающего затвора, оказывают отрицательное воздействие на механизм хранения и приводят к более раннему ухудшению параметров памяти.

На Рис. 15.4 показан модуль EEPROM модели PIC16F87X вместе с памятью программ. Такое представление справедливо, поскольку регистры EEDATA и EEADR используются для работы с обеими областями памяти. Разумеется, память программ имеет как больший объем (8 Кбайт против 256 байт), так и большую разрядность (14 бит против 8). Именно поэтому в микроконтроллерах были реализованы дополнительные РСН, использующиеся для хранения старшего байта адреса (EEADRH) и данных (EEDATH).

Рис. 15.4. FLASH - и EEPROM -память моделей PIC16F87X как хранилище данных

Как мы скоро убедимся, процессы чтения и записи обеих областей памяти очень похожи. Память, к которой осуществляется обращение, задается управляющим битом EEPGD регистра EECONl (EECONl[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
). За исключением появления этого нового бита и переноса битов EEIF и EEIE в регистры PIR2 и PIE2 соответственно, регистр EECON1, показанный на Рис. 15.4, ничем не отличается от регистра базового варианта модуля из модели PIC16F62X, изображенного на Рис. 15.2. Виртуальный регистр EECON2 остался таким же.

Чтение и запись модуля EEPROM производятся точно так же, как и в более простых моделях PIC16F62X. Единственное изменение, которое необходимо внести в подпрограммы EE_GET и EE_PUT, связано с тем, что в новых моделях регистры EEDATA и EEADR находятся во 2-м банке, а регистры EECONl и EECON2 — в 3-м банке.

Процесс чтения из FLASH-памяти похож на процесс чтения из модуля EEPROM, только при этом используются 2-байтные регистры адреса и данных. Однако не забывайте, что мы работаем с той же памятью программ, откуда коды команд считываются в исполнительный блок процессора. Из-за этого после команды установки бита RD (EECON 1 [0]) должны располагаться две пустые команды пор. Одним словом, чтение FLASH-памяти программ осуществляется по следующему алгоритму:

1. Скопировать адрес интересующей нас ячейки в регистры EEADRH: EEADR.

2. Установить бит EEPGD, показывая, что мы обращаемся к памяти программ.

3. Установить бит RD для запуска цикла чтения.

4. Бит RD сразу же автоматически сбрасывается, и искомое 14-битное значение можно обычным образом считать из регистров 2-го банка памяти EEDATH: EEDATA.

Этот алгоритм реализован в подпрограмме FLASH_GET, текст которой приведен в Программе 15.4. При этом предполагается, что при входе в подпрограмму адрес ячейки уже загружен в регистры EEADRH: EEADR.

Программа 15.4. Чтение слова из FLASH-памяти программ

; *****************

; * ФУНКЦИЯ: Считывает одно слово из FLASH-памяти программ PIC16F877 *

; * ВХОД: Адрес в EEADRH:EEADR *

; * ВЫХОД: Данные в EEDATH:EEDATA. Используется 0-й банк *

; *****************

FLASH_GET

       bsf STATUS,RP1 ; Переключаемся в 3-й банк

       bsf STATUS,RP0

       movlw b’10000000’ ; Указываем на память программ,

       movwf EECON1 ; устанавливая EEPGD в EECON1[7]

       bsf EECON1,RD ; Устанавливаем RD для запуска цикла чтения

       nop;

       nop;

       bcf STATUS,RP1 ; Возвращаемся в 0-й банк

       bcf STATUS,RP0

       return

Для примера напишем подпрограмму, которая будет возвращать квадрат целого числа от 0 до 100, загружаемого в регистр EEDATH: EEDATA. Разумеется, эту операцию можно выполнить путем умножения, однако в учебных целях мы реализуем это вычисление при помощи таблицы преобразования, размещенной в памяти программ. Поскольку содержимым этой таблицы являются константы, мы можем загрузить данные в FLASH-память одновременно с занесением всего остального кода программы.

В Программе 15.5 таблица размещается по адресу h’300’ памяти программ. Директива dw похожа на директиву de, однако каждое из значений, указываемых в этой директиве, является 14-битным. Для удобства мы также воспользовались директивой radix, чтобы указать систему счисления констант. В нашем случае все константы интерпретируются ассемблером как десятичные числа.

Сразу же за таблицей располагается исполняемый код. Это делает Программу 15.5 отчасти похожей на класс языка Си++, который содержит в себе как члены-данные, так и члены-функции (подпрограммы).

Программа 15.5. Табличное возведение в квадрат целого числа

     radix decima1

     __config _CPD_OFF & _WRT_ENABLE_OFF

     org h’300’ ; Таблица начинается с адреса h’300’ памяти программ.

; **************

; * ФУНКЦИЯ: Возвращает квадрат целого числа *

; * РЕСУРСЫ: Подпрограмма FLASH_GET *

; * ВХОД: Целое в W (от 0 до 100) *

; * ВЫХОД: 14-битное значение квадрата в SQRH:SQRL. *

; * Рабочий банк памяти — 0-й *

; **************

TABLE_QF_SQUARES; Таблица десятичных констант

dw 0,1,4,9,16,25,36,49,64,81,100,121,144,169,196,225

dw 256,289,324,361,400,441,484,529,576,625,696,729,784,841

dw 900,961,1024,1089,1156,1225,1296,1369,1444,1521,1600,1681

dw 1764,1849,1936,2025,2116,2209,2304,2401,2500,2601,2704

dw 2809,2916,3025,3136,3249,3364,3481,3600,3721,3844,3969

dw 4049,4225,4356,4489,4624,4761,4900,5041,5184,5329,5476

dw 5625,5776,5929,6084,6241,6400,6561,6724,6889,7056,7225

dw 7396,7569,7744,7921,8100,8281,8464,8649,8836,9025,9216

dw 9409,9604,9801,10000

SQUARE bsf STATUS,RP1 ; Переключаемся во 2-й банк

             bcf STATUS,RP0

             movwf EEADR ; Формируем адрес

             movlw 3

             movwf EEADRH

             call FLASH_GET ; Считываем n-й элемент таблицы

             bsf STATUS,RP1 ; Снова идем во 2-й банк

             bcf STATUS,RP0

             movf EEDATA,w ; Берем младший байт результата

             bcf STATUS,RP1 ; 0-й банк

             movwf SQRL ; Копируем в SQRL (0-й банк)

             bsf STATUS,RP1 ; Снова идем во 2-й банк

             movf EEDATH,w ; Берем старший байт результата

             bcf STATUS,RP1 ; 0-й банк

             movwf SQRH ; Копируем в SQRH (0-й банк)

            return

Адрес nn-го элемента таблицы формируется в подпрограмме загрузкой числа nn, переданного в рабочем регистре, в регистр EEADR и записью константы h’03’ в регистр EEADRH. В результате указанных действий мы получаем двухбайтный адрес вида h’3nn’. После этого вызовом подпрограммы FLASH_GET из таблицы считывается 14-битное число. Затем подпрограмма копирует содержимое регистров EEDATH: EEDATA в регистры SQRH: SQRL. Так как эти регистры расположены в 0-м банке, то после копирования содержимого каждого из регистров данных модуля EEPROM, расположенных во 2-м банке, в рабочий регистр, нам приходится переключаться в 0-й банк. Поскольку микроконтроллеры PIC16F874/7 имеют 16 РОН, отображенных на все четыре банка памяти, было бы неплохо разместить регистры SQRH: SQRL именно в этой общей области памяти.

После занесения программы в FLASH-память микроконтроллера внешним программатором содержимое памяти программ начиная с адреса h’300’ будет выглядеть так, как показано на Рис. 15.5.

Рис. 15.5. Фрагмент FLASH-памяти программ, в котором записана таблица преобразования и подпрограмма SQUARE

Несмотря на то что в данном примере положение таблицы было выровнено по 256-байтной границе, на практике она может быть размещена в любом месте памяти. В общем случае для адресации nn-ячейки таблицы к полному 14-битному адресу начала таблицы требуется прибавить смещение nn. Как это можно сделать, обсуждается в Вопросе для самопроверки 15.2.

Процесс записи FLASH-памяти в микроконтроллерах линейки PIC16F87X также практически идентичен процессу записи в EEPROM, отличаясь, как и в случае операции чтения, только двумя командами пор. Правда, после запуска цикла записи выполнение программы приостанавливается примерно на 4 мс. В течение этого времени производится стирание и последующая запись нового значения в адресованную ячейку памяти программ. Затем программа возобновляет работу в нормальном режиме. Итак, запись в FLASH-память осуществляется по следующему алгоритму:

1. Загрузить адрес конечной ячейки в регистры EEADR: EEADRH.

2. Установить бит EEPGD, показывая, что мы обращаемся к памяти программ.

3. Установить бит WREN в EECON[2]Имеется в виду оригинальное издание книги на английском языке. — Примеч. ред.
для разрешения операции записи.

4. Запретить все прерывания, если они используются.

5. Записать h’55’ в регистр EECON2.

6. Записать h’AA’ в регистр EECON2.

7. Установить бит WR для инициирования цикла записи.

8. Выполнить две пустые команды пор.

9. Сбросить бит WREN.

10. При необходимости разрешить прерывания.

11. Дожидаться сброса бита WR, свидетельствующего о завершении цикла записи, нет необходимости, поскольку на время записи работа процессора приостанавливается и возобновляется только по окончании записи.

Подпрограмма FLASH_PUT, код которой приведен в Программе 15.6, написана в предположении, что при входе в подпрограмму адрес ячейки уже находится в регистрах EEADRH: EEADR, а 14-битное значение — в регистрах EEDATH: EEDATA.

Программа 15.6. Запись в FLASH-память программ

; ************************

; * ФУНКЦИЯ: Записывает одно слово в FLASH-память программ *

; * ВХОД: Слово данных в EEDATH: EEDATA *

; * ВХОД: Адрес ячейки в EEADDRH: EEADDR *

; * ВХОД: На время записи прерывания запрещаются *

; * ВЫХОД: Рабочий банк — 0-й *

; *************************

FLASH_PUT

       bsf STATUS,RP0 ; Переключаемся в 3-й банк

       bsf STATUS,RP1

       bsf EECON1,EEPGD ; Пишем в память программ

       bsf EECON1,WREN ; Разрешаем операцию записи

       bcf INTCON,GIE ; Запрещаем все прерывания

       movlw h’55’ ; Загружаем кодовую последовательность

       movwf EECON2;

       movlw h’AA’

       movwf EECON2

       bsf EECON1,WR ; Инициируем цикл записи

       nop

       nop

       bcf EECON1,WREN ; Запрещаем последующую запись

       bsf INTCON,GIE ; Разрешаем прерывания

       bcf STATUS,RP1 ; Возвращаемся в 0-й банк

       bcf STATUS,RP0

       return ; и выходим из n/n по окончании цикла записи

Все устройства, имеющие память программ с возможностью электрического стирания, содержат в слове конфигурации биты защиты кода. Основной задачей функции защиты кода является предотвращение считывания содержимого памяти программ внешним программатором. Таким образом, обеспечивается защита от несанкционированного доступа к коду программы. Что касается моделей PIC16F87X, в них за защиту кода программы отвечают два бита слова конфигурации СР[1:0], расположенные в битах 13:12 и продублированные в битах 5:4. При СР = 00 защищена вся память программ, при СР = 01 — только старшая половина памяти, при СР = 10 — только старшие 256 байт, а при СР = 11 защита полностью отключена (состояние битов по умолчанию, см. Рис. 10.6, в на стр. 312). Если хоть какая-нибудь область памяти программ защищена, то внешний программатор не сможет выполнить запись ни в одну из ее ячеек. При этом чтение запрещено только для защищенных областей. В процессе разработки и отладки устройств защита памяти программ обычно отключается, поскольку на этом этапе предполагается частое изменение содержимого памяти программ. Если же потребуется снять защиту, то у моделей с FLASH-памятью можно стереть все содержимое памяти программ, используя внешний программатор, при этом в слово конфигурации будет записано значение по умолчанию (во всех битах — 1). Такая возможность, по определению, отсутствует в микроконтроллерах PIC16CXXX.

Защита кода также влияет и на внутренние операции записи в память программ с помощью кода, подобного представленному на Рис. 15.6. Из самой программы запись может осуществляться только в незащищенные участки памяти программ при условии, что бит WRT установлен в 1 (состояние по умолчанию). Запись 0 в этот бит (_WRT_ENABLE_OFF) запретит внутреннюю запись в память программ независимо от установок битов защиты кода. На операцию внутреннего чтения биты защиты кода никак не влияют. Директива __config, присутствующая в Программе 15.5, используется для отключения защиты всей памяти программ, что, вообще говоря, делать не обязательно, поскольку в таком состоянии биты находятся по умолчанию.

Рис. 15.6. Конфигурационное слово моделей PIC16F87XA

В моделях с суффиксом «А» защита содержимого памяти программ осуществляется несколько иначе, как можно увидеть из Рис. 15.6. В этих моделях имеется единственный бит защиты СР, предназначенный для защиты всей памяти программ от считывания или от записи извне. Причем даже при включенной защите памяти программ внутренние операции записи и чтения FLASH-памяти разрешены. Для предотвращения внутренней записи в указанные выше участки памяти программ используются два бита WRT[1:0]. Внутренние операции чтения памяти программ разрешены всегда.

За исключением самой старой модели PIC16F84, во всех микроконтроллерах с модулем EEPROM имеется бит CPD, при записи 0 в который запрещается доступ извне к внутренней EEPROM-памяти данных.

Модели группы PIC16F87X могут осуществлять запись в память программ отдельными словами. Однако в микроконтроллерах PIC16F87XA внутренняя организация FLASH-памяти программ была изменена. Вследствие этого запись в память программ указанных моделей осуществляется блоками по 4 подряд идущих слова. Младшие биты адреса первого слова блока должны быть равны 00. К примеру, если программист собирается записать новое 14-битное слово в память программ по адресу h’500’, ему также придется выполнить запись по адресам h’501’, h’502’ и h’503’. В процессоре имеется четыре внутренних 14-битных буферных регистра, как показано на Рис. 15.7. При каждой операции записи данные просто копируются в соответствующий буфер, определяемый значением двух младших битов адреса. После записи в последний буферный регистр при EEADR[1:0] = 11 (в нашем примере это соответствует адресу h’503’) блок из четырех слов по адресам h’500’.h’503’ стирается, а затем содержимое всех четырех буферных регистров одновременно заносится в память программ.

Внутренние буферные регистры недоступны из программы при помощи обычных команд пересылки данных. Вместо этого запись каждого слова блока осуществляется точно так же, как запись отдельного слова, реализованная в подпрограмме FLASH_PUT (Программа 15.6). При выполнении первых трех операций записи данные просто сохраняются в буферных регистрах, а задержки длительностью 4 мс не происходит. Вызов этой же подпрограммы с адресом, равным последнему адресу блока, запускает «реальную» запись с приостановкой работы процессора.

Рис. 15.7. Запись в FLASH-память программ в моделях PIC16F87XA

Чтобы проиллюстрировать процедуру блочной записи, рассмотрим следующий пример. Имеется четыре 2-байтных значения, размещенные в РОН с названиями DATA_ARRAY…DATA_ARRAY+7 в порядке от старшего байта к младшему, которые необходимо записать в память программ микроконтроллера PIC16F877A.

Подпрограмма, код которой приведен в Программе 15.7, написана в предположении, что данные уже находятся в ОЗУ и что адрес первой из ячеек памяти программ уже занесен в регистры EEADRH: EEADR. Запись каждого слова в буферные регистры и инициирование цикла записи осуществляются в цикле. В качестве указателя для работы с массивом в памяти данных используется регистр FSR, как это было показано на Рис. 5.8 (стр. 126). Каждое двухбайтное значение по очереди копируется в регистры EEDATH: EEDATA, после чего подпрограмма FLASH_PUT из Программы 15.6 запускает цикл псевдозаписи. После каждого прохода цикла адрес ячейки памяти программ, находящийся в регистре EEADR, инкрементируется (EEADRH не трогаем). После четвертого прохода запускается реальный цикл записи. К сожалению, в регистре EECONl отсутствует флаг, по которому можно было бы отличить этот цикл записи от трех предыдущих. Вместо этого мы проверяем состояние двух младших битов регистра EEADR. Когда они снова становятся равными Ь’00’, процесс завершается.

Несмотря на то что записывать необходимо полный блок, можно изменять значение только одного, двух или трех слов блока. Для этого все данные, которые нужно оставить неизменными, сначала считываются из памяти программ в память данных, а затем записываются вместе с изменяемыми данными.

Программа 15.7. Блочная запись FLASH-памяти программ в моделях PIC16F87XA

; **************************

; ФУНКЦИЯ: Пишет блок из 4-х слов в память программ *

; ВХОД: Начальный адрес блока в EEADRH:ADDR *

; ВХОД: Четыре слова в массиве DATA_ARRAY:8 *

; ВЫХОД: Четыре слова записаны в память программ *

; ВЫХОД: Рабочий банк — 0-й *

; РЕСУРСЫ: Подпрограмма FLASH_PUT *

; ***************************

FLASH_BLAST

             bsf STATUS,RP1 ; Переключаемся во 2-й банк

             bcf STATUS,RP0

             movlw DATA_ARRAY ; Загружаем в FSR адрес

             movwf FSR ; младшего байта массива данных в ОЗУ

; Теперь выполняем 4 цикла записи —

FB LOOP movf INDF,w ; Считываем старший байт слова и

              movwf EEDATH ; помещаем его в старший регистр данных

              incf FSR,f ; Указываем на младший байт

              movf INDF,w ; Считываем младший байт слова и

              movwf EEDATA ; помещаем его в младший регистр данных

              incf FSR, f ; Указываем на старший байт следующего слова

              call FLASH_PUT ; Пишем в буферный регистр

              bsf STATUS,RP1 ; Снова переключаемся во 2-й банк

              bcf STATUS,RP0

              incf EEADR,f ; Инкрементируем адрес в памяти программ

              movf EEADR,w ; Проверим младшие биты на равенство 00

              andlw b’00000011’ ; Выделяем эти биты

              btfss STATUS,Z ; ЕСЛИ оба равны нулю, ТО выходим

                 goto FB_LOOP ; ИНАЧЕ пишем следующее слово

             bcf STATUS,RP1 ; Возвращаемся в 0-й банк

             return ; выходим по окончании цикла записи

Примеры

Пример 15.1

В компиляторе CCS имеются следующие встроенные функции для работы с модулем EEPROM:

read_eeprom(<адрес>);

Возвращает байт, находящийся по указанному адресу EEPROM.

write_eeprom(<адрес>, <данные>);

Заносит значение, переданное во втором параметре, по указанному адресу EEPROM (первый параметр). Возврат из функции происходит только после завершения цикла записи.

Напишите функцию на языке Си, которая бы обновляла показания одометра, хранящиеся в EEPROM, аналогично Программе 15.3.

Решение

Как и в исходной ассемблерной программе, код которой приведен в Программе 15.3, новая функция (см. Программу 15.8) состоит из трех частей:

1. На этом этапе объявляется массив из 3 байт, названный odometer [], который служит в качестве временного хранилища показаний одометра, содержащихся в EEPROM. Массив заполняется с помощью трех вызовов функции read_eeprom ().

2. После загрузки 3-байтного значения в память данных оно инкрементируется с использованием оператора выбора if-else:

а) Инкрементируется младший байт и проверяется на нулевое значение. Если он не равен нулю, операция инкрементирования завершается, в противном случае происходит переход к обработке среднего байта.

б) Инкрементируется средний байт и проверяется на нулевое значение. Если он не равен нулю, операция инкрементирования завершается, в противном случае происходит переход к обработке старшего байта.

в) Инкрементируется старший байт.

3. В заключение каждый байт заносится обратно в EEPROM с помощью функции write_eeprom ().

Программа 15.8. Инкрементирование показаний одометра на Си

void odometer(void)

{

       unsigned int odometer[3]; /* Объявляем З-байтный массив */

       odometer[0] = read_eeprom(0x10); /* Считываем текущее значение */

       odometer[1] = read_eeprom(0x11);

       odometer[2] = read_eeprom(0x12);

       /* Инкрементируем число, находящееся в массиве */

       if(++odometer[0]!= 0)

             break;

       else

             if(++odometer[1]!= 0)

                   break;

             else

                   odometer[2]++;

       /* Теперь заносим инкрементированное значение в EEPROM */

       write_eeprom(0x10, odometer[0]);

       write_eeprom(0x11, odometer[1]);

       write_eeprom(0x12, odometer[2]);

}

Если сравнить размеры ассемблерного кода самостоятельно написанной Программы 15.3 и сгенерированного при компиляции Программы 15.8 (для PIC16F62X), то можно увидеть, что при ручном кодировании размер программы получается практически в 2 раза меньше (54 команды против 105).

Пример 15.2

Рассмотрим контроллер сауны, построенный на базе микроконтроллера PIC. Задачей такого контроллера является контроль температуры и управление нагревателем и охладителем. Кроме того, в нем должна быть предусмотрена тревожная сигнализация и возможность экстренного отключения в случае перегрева.

В принципе для построения такого контроллера можно использовать 8-выводной микроконтроллер с внешним датчиком температуры. Однако кто-то предложил в качестве дешевого, хотя и довольно любительского датчика температуры использовать встроенный сторожевой таймер микроконтроллера, а точнее, зависимость его периода от температуры.

Были исследованы 8 экземпляров микроконтроллеров из одной партии, причем при каждой контрольной температуре они выдерживались по 30 мин. В результате был построен график, показанный на Рис. 15.8. Каждая точка этого графика была получена усреднением 500 периодов сторожевого таймера при данной температуре.

Рис. 15.8. Экспериментальная зависимость периода сторожевого таймера от температуры

Данные, представленные на Рис. 15.8, базируются на документе AN720 «Measuring Temperature Using the Watch Dog Timer (WDT)». Два графика представляют максимальное и минимальное значение периода сторожевого таймера тестируемых устройств. Измерение периода сторожевого таймера производилось по числу переполнений Таймера 0, работающего от внутреннего сигнала 4 МГц. К сторожевому таймеру был подключен постделитель с коэффициентом деления 8.

Из приведенных графиков можно заметить, что между величиной периода и температурой существует четкая корреляция. Однако, несмотря на предсказуемость общего характера зависимости, значения смещения и крутизны характеристики будут своими у каждой модели. К примеру, коэффициент пропорциональности у всех восьми протестированных устройств колеблется от 2.28 до 2.42 отсчета на градус Цельсия. Поэтому перед использованием системы ее необходимо будет калибровать. Если для какого-либо конкретного устройства известно количество отсчетов при заданной температуре T0 и коэффициент пропорциональности k, то величину отклонения от температуры T0, соответствующую количеству отсчетов COUNTn , можно будет определить по формуле

ΔТ = (COUNTn — COUNT0) x k.

Для калибровки этих устройств было решено выдерживать партию в холодильнике при 0 °C и записывать 2-байтное значение отсчетов в модуль EEPROM. Затем устройства подвергались нагреву до 30 °C, после чего разность между новым и исходным значением запоминалась в отдельном байте EEPROM. После этого осуществлялось перепрограммирование микроконтроллеров — вместо программы калибровки в память программ заносилась рабочая программа, вычисляющая по значению периода сторожевого таймера COUNTn текущую температуру:

Т = (COUNT0 — COUNTn ) x (Diff/30),

где COUNT0 — 2-байтное значение из EEPROM, содержащее количество отсчетов при 0 °C, a Diff — 1-байтное значение из EEPROM, характеризующее изменение количества отсчетов при увеличении температуры до 30 °C.

Покажите, как можно написать программу калибровки, реализующую указанный алгоритм.

Решение

Можно выделить пять задач, которые необходимо выполнять при наступлении тайм-аута сторожевого таймера:

• Усреднение текущего значения числа переполнений Таймера 0 с предыдущими значениями, полученными при низкой температуре (0 °C), за исключением первого отсчета.

• Усреднение текущего значения числа переполнений Таймера 0 с предыдущими значениями, полученными при высокой температуре (30 °C), за исключением первого отсчета.

• Запоминание значения, соответствующего низкой температуре, в EEPROM.

• Вычисление разности между значениями для высокой и низкой температур и запоминание ее в EEPROM.

• Вход в бесконечный пустой цикл.

Для указания, какая из четырех активных задач должна выполняться, можно использовать линии порта ввода/вывода микроконтроллера. Так, наличие ВЫСОКОГО уровня на выводе GPIO0 означает, что текущее число переполнений Таймера 0 необходимо прибавить к уже имеющемуся 2-байтному значению для низкой температуры. После каждого сложения, за исключением первого, результат необходимо усреднить делением на два. Этот ВЫСОКИЙ уровень должен удерживаться на выводах GPIO0 всех микроконтроллеров партии в течение нескольких минут после установления температуры холодильника на уровне 0 °C.

Подача на GPIO0 НИЗКОГО, а на GPIO1 — ВЫСОКОГО уровней на короткое время вызывает сохранение вычисленного значения в EEPROM. При подаче НИЗКОГО уровня на оба входа никаких действий не производится. Это состояние соответствует времени перед стабилизацией температурного режима и времени после программирования EEPROM,

Таймер 0 и сторожевой таймер инициализируются одновременно при сбросе по включению питания. Эта операция будет выполнена лишь один раз при подаче питания на микроконтроллер, уже помещенный в термокамеру. Все последующие сбросы будут происходить по тайм-ауту сторожевого таймера. Для определения причины сброса можно использовать флаг ТО регистра STATUS (см. стр. 453).

Из текста Программы 15.9 видно, что переход к секции кода, в которой выполняется инициализации таймеров и переменных, производится только в том случае, если флаг  при сбросе установлен в 1, т. е. при включении питания. После этого программа входит в бесконечный цикл, организованный командой goto $ (ассемблер заменяет символ $ текущим адресом команды), которая просто переходит сама на себя.

Программа 15.9. Процедура инициализации и обработчик прерывания для программы калибровки контроллера сауны

     include "p12f629.inc”

       __config _WDT_ON & _CP_OFF & _INTRC_OSC_NOCLKOUT & _MCLRE_OFF

      cblock h’20’

         _work 1, _status:1

         FIRST_HI:1, FIRST_LO:1

         ROLL_OVER:2, LO_TEMP:2, HI_TEMP:2

         DELTA _TEMP:1

        endc

        org 0

START goto MAIN

        org 4

        goto ISR

; При каждом сбросе инициализируем Таймер 0, порт ввода/вывода и пр.

MAIN movlw h’0 7’; Выключаем аналоговый компаратор

         movwf CMCON

         movlw b’11011010’ ; Предделитель — к WDT, коэффициент 1:8

         bsf STATUS,RP0 ; Переключаемся в 1-й банк

         movwf OPTION_REG ; Таймер 0 тактируется внутренним сигналом

         bcf STATUS,RP0 ; Возвращаемся в 0-й банк

         clrf TMR0 ; Обнуляем таймер

         bsf INTCON,T0IE ; Разрешаем прерывание от Таймера 0

         bsf INTCON,GIE ; Разрешаем все прерывания

         btfss STATUS,NOT_TO ; ЕСЛИ тайм-аут сторожевого таймера,

            goto READING ; ТО идем на обработку значений

; ИНАЧЕ это был сброс по питанию

         clrf ROLL_OVER+1 ; Обнуляем 2-байтный счетчик переполнений Таймера 0

         clrf ROLL_OVER

         clrf FIRST_HI

         clrf FIRST_LO

         clrf LO_TEMP+1 ; Обнуляем регистры результата для низкой температуры

         clrf LO_TEMP

         clrf HI_TEMP+1 ;и для высокой температуры

         clrf HI_TEMP

         clrwdt ;Сбрасываем сторожевой таймер

           goto $ ;Ждем следующего сброса от сторожевого таймера

; *******************************

; * В обработчике прерывания при прерывании от Таймера 0 *

; * инкрементируем 2-байтное число переполнений таймера *

; *******************************

;Сначала сохраняем контекст

ISR movwf _work ; Сохраняем W

      swapf STATUS,w ; и регистр STATUS

      movwf status

; *******************************

; Основной код обработчика

      incf ROLL_OVER+1,f ; Регистрируем новое переполнение

      btfsc STATUS,Z ; Пропускаем, если не ноль

        incf ROLL_OVER,f ; Инкрементируем старший байт

      bcf INTCON,TOIF ; Сбрасываем флаг прерывания

; ********************************

      swapf _status,w ; Восстанавливаем исходное значение

      movwf STATUS ; регистра STATUS

      swapf _work,f ; Восстанавливаем исходное значение W

      swapf _work,w ; не меняя STATUS

      retfie ; и выходим из прерывания

В этой же программе приведен код процедуры обработки прерывания от Таймера 0. В обработчике осуществляется инкрементирование 2-байтной переменной, расположенной в регистрах ROLL_OVER: ROLL_OVER+1, которая представляет собой числовое выражение длительности периода сторожевого таймера, считываемое системой при сбросе по тайм-ауту сторожевого таймера.

Во время работы программы процессор будет периодически сбрасываться по тайм-ауту сторожевого таймера. А поскольку при этом событии бит  сбрасывается, будет выполняться переход к секции reading, код которой приведен в Программе 15.10. В этой секции проверяется состояние каждого из четырех входов GPIO[3:0]. При обнаружении на каком-либо входе ВЫСОКОГО уровня выполняется одна из четырех описанных выше задач. Если на всех выводах присутствует НИЗКИЙ уровень, то программа просто очищает регистры ROLL_OVER и ROLL_OVER+1, перезапускает Таймер 0 и сторожевой таймер, после чего входит в бесконечный цикл. Эти операции, обозначенные меткой reading_exit, выполняются в конце всех четырех задач.

Программа 15.10. Считывание нового значения количества периодов

; *******************************

; * Сюда попадаем при сбросе по тайм-ауту сторожевого таймера *

; *******************************

READING btfsc GPIO,0 ; Новое значение при низкой температуре?

                 goto NEW_LO ; ЕСЛИ да, ТО переходим на эту секцию!

              btfsc GPIO,1 ; Новое значение при низкой температуре?

                 goto NEW_HI ; ЕСЛИ да, ТО переходим на эту секцию!

              btfsc GPIO,2 ; Обновление среднего для низкой температуры?

                 goto UPDATE_LO ; ЕСЛИ да, ТО переходим на эту секцию!

              btfsc GPIO,3 ; Обновление разности для высокой температуры?

                 goto UPDATE_HI ; ЕСЛИ да, ТО переходим на эту секцию!

              goto READING_EXIT ; ИНАЧЕ ничего не делаем

NEW_LO movf ROLL_OVER+1,w ; Берем младший байт текущего числа переполнений

             addwf LO_TEMP+1,f ; и прибавляем его к младшему байту накопленного значения

             btfsc STATUS,С ; Проверяем перенос

                incf LO_TEMP,f ; ЕСЛИ был, ТО учитываем его

             movf ROLL_OVER,w ; Берем старший байт текущего числа переполнений

             addwf LO_TEMP,f ; и прибавляем его к старшему байту накопленного значения

             movf FIRST_LO,f ; Это был 1-й отсчет?

             btfsc STATUS,Z

                goto FIRST_TIME_LO ; ЕСЛИ да, ТО отметим это!

             rrf LO_TEMP,f ; ИНАЧЕ делим сумму на два

             rrf LO_TEMP+1,f

                goto READING_EXIT ; и выходим

FIRST_TIME_LO ; При первом отсчете ничего не делаем

            incf FIRST_LO,f ; Первый отсчет уже был

               goto READING_EXIT

NEW_HI movf ROLL_OVER+1,w ; Берем младший байт текущего числа переполнений

            addwf HI_TEMP+1,f ; и прибавляем его к младшему байту накопленного значения

            btfsc STATUS,С ; Проверяем перенос

               incf HI_TEMP,f ; ЕСЛИ был, ТО учитываем его

            movf ROLL_OVER,w ; Берем старший байт текущего числа переполнений

            addwf HI_TEMP,f ; и прибавляем его к старшему байту накопленного значения

            movf FIRST_HI,f ; Это был 1-й отсчет?

            btfsc STATUS,Z

               goto FIRST_TIME_HI ; ЕСЛИ да, ТО отметим это!

            rrf HI_TEMP,f ; ИНАЧЕ делим сумму на два

            rrf HI_TEMP+1,f

            goto READING_EXIT ; и выходим

FIRST_TIME_HI ; При первом отсчете ничего не делаем

            incf FIRST_HI,f ; Первый отсчет уже был

READING_EXIT ; Сбрасываем Таймер 0

            clrf TMR0 ; Перезапускаем сторожевой таймер

            clrwdt

            clrf ROLL_OVER+1 ; Обнуляем число переполнений

            clrf ROLL_OVER

            goto $ ; Ждем следующего сброса от сторожевого таймера

Также в Программе 15.10 приведены секции кода, соответствующие первым двум задачам. В этих секциях 2-байтное количество переполнений Таймера 0 прибавляется к значению, хранящемуся в регистрах LO_TEMO: LO_TEMP+1 или Н1_ТЕМР:Н1_ТЕМР+1 соответственно, после чего для усреднения результат делится на два сдвигом на один бит вправо. Поскольку суммарное число переполнений достаточно скромное, двух байтов вполне достаточно, чтобы избежать переполнения. Если мы будем в течение нескольких минут многократно выполнять указанные операции, то в результате получим усредненное значение.

Если считывание результата производится в первый раз, то операция деления на два пропускается, а в соответствующую переменную-флаг FIRST_LO или FIRST_HI заносится ненулевое значение.

Основная процедура, относящаяся к теме данной главы, приведена в Программе 15.11. При ВЫСОКОМ уровне на выводе GPIO2 2-байтное число переполнений при низкой температуре LO_TEMP: LO_TEMP+1 заносится в два младших байта EEPROM с использованием подпрограммы ee_put из Программы 15.2.

При ВЫСОКОМ уровне на выводе GPIO3 вычисляется разность между 2-байтными значениями, полученными при высокой и низкой температурах. Если посмотреть на график, приведенный на Рис. 15.8, то можно заметить, что разность отсчетов при изменении температуры на 30 °C в любом случае не превысит 256, так что для хранения этой разности достаточно будет одного байта. Данное значение сохраняется в EEPROM обычным образом.

Программа 15.11. Обновление информации в EEPROM

UPDATE_LO

        movf LO_TEMP,w ; Берем старший байт значения для низкой температуры

        bsf STATUS,RP0 ; Переключаемся в 1-й банк

        movwf EEDATA ; Кладем в регистр данных EEPROM

        clrf EEADR ; Адрес в EEPROM — h’00’

        call EE_PUT ; Запоминаем значение

        movf LO_TEMP+1,w ; Берем младший байт значения для низкой температуры

        bsf STATUS,RP0 ; Переключаемся в 1-й банк

        movwf EEDATA ; Кладем в регистр данных EEPROM

        incf EEADR,f ; Адрес в EEPROM — h’01’

        call EE_PUT ; Запоминаем значение

        goto READING_EXIT

UPDATE_HI ; Вычисляем HI_TEMP-LO_TEMP и сохраняем разность в EEPROM

; Достаточно вычесть только младшие байты, поскольку разность не может быть больше 256

        movf HI_TEMP+1,w ; Берем старший байт значения при высокой

        subwf L0_TEMP+1,w ; Вычитаем младший байт значения при низкой температуре

        movwf DELTA_TEMP ; температуре Запоминаем разность

        bsf STATUS,RP0 ; Переключаемся в 1-й банк

        movwf EEDATA ; Разность — по адресу h’02’

        movlw 2

        movwf EEADR

        call EE_PUT

        goto READING_EXIT

Пример 15.3

В Примере 14.3 мы вычисляли энергию разряда дефибриллятора путем суммирования квадратов отклонений напряжения от базового значения. Причем после анализа графика в качестве базового было принято значение 2.6 В. Это среднее значение может изменяться от экземпляра к экземпляру прибора, а также с течением времени. Поэтому было решено доработать программное обеспечение, введя в него возможность самообучения, которое будет осуществляться, скажем, при замыкании кнопки, подключенной к выводу RA4. При нажатии на кнопку надо будет выполнить 256 выборок значений напряжения в режиме ожидания, с последующим их сложением для получения 2-байтной суммы. Взяв старший байт ЭТОЙ суммы, МЫ получим усредненное значение напряжения (взятие старшего байта 2-байтного числа эквивалентно его делению на 256). Это значение мы запишем в EEPROM по адресу h’00’ и впоследствии будем использовать в качестве базового уровня, периодически обновляя его при необходимости. Предполагая, что в вашем распоряжении имеется подпрограмма GET_ANALOG из Программы 14.1 (стр. 516), напишите соответствующую подпрограмму.

Решение

Из Рис. 14.20 на стр. 534 видно, что напряжение с датчика тока дефибриллятора подается на вывод RA0/AIN0 микроконтроллера. С учетом того что модуль АЦП уже инициализирован, как это было сделано в Программе 14.6 на стр. 536, нам останется только 256 раз считать оцифрованное значение с 0-го канала АЦП для накопления 16-битной суммы. Взяв старший байт этой суммы, мы получим усредненное значение, т. е. частное от деления суммы на 256. Если во время взятия отсчетов дефибриллятор находился в режиме ожидания, то полученное среднее значение будет представлять собой базовое напряжение.

После определения базового напряжения это однобайтное значение можно записать в EEPROM обычным образом. Впоследствии его можно будет считать из EEPROM и использовать вместо константы BASELINE (Программа 14.6).

В Программе 15.12 регистр COUNT используется в качестве счетчика итераций цикла. В каждом проходе цикла новое значение АЦП прибавляется к общей сумме, накапливаемой в регистрах ACCUMULATOR: ACCUMULATOR+1. После выхода из цикла содержимое регистра ACCUMULATOR (старший байт суммы) заносится в EEPROM по адресу h’00’ с использованием подпрограммы EE_PUT.

Программа 15.12. Определение базового напряжения

; *******************

; * ФУНКЦИЯ: Суммирует 256 выборок аналогового сигнала для *

; * ФУНКЦИЯ: нахождения среднего значения, являющегося *

; * ФУНКЦИЯ: базовым напряжением, которое запоминается в *

; * ФУНКЦИЯ: модуле EEPROM *

; * РЕСУРСЫ: Подпрограммы GET_ANALOG, EE_PUT *

; * ВХОД: Нет *

; * ВЫХОД: Среднее значение 0-го канала — в EEPROM (h’00’) *

; *******************

LEARN clrf BASE ; Обнуляем старший байт суммы

           clrf BASE+1 ; Обнуляем младший байт суммы

           clrf COUNT ; Обнуляем счетчик цикла

LEARN_LOOP

           clrw ; Работаем с 0-м аналоговым каналом

           call GET_ANALOG ; Оцифровываем

           addwf BASE+1,f ; Прибавляем к младшему байту суммы

           btfsc STATUS,С ; Был перенос?

             incf BASE,f ; ЕСЛИ да, TO инкрементируем старший байт

           decfsz COUNT,f ; Уменьшаем счетчик цикла на единицу

             goto LEARN_LOOP

; Запоминаем среднее в EEPROM

           movf BASE,w ; Берем среднее значение

           bsf STATUS,RP1 ; Переключаемся во 2-й банк

           clrf EEADR ; Будем писать по адресу h’00’

           movwf EEDATA; Загружаем байт данных

           call EE_PUT ; Запоминаем его

           return ; Все сделали

В реальной ситуации лучшего результата можно достичь, беря 65 536 отсчетов и накапливая их в 3-байтной сумме. И опять же, старший байт этой суммы будет представлять собой усредненное значение.

Вопросы для самопроверки

15.1. В соответствии с хорошим стилем программирования данные, записываемые в EEPROM, следует верифицировать. Как можно модифицировать подпрограмму EE_PUT из Программы 15.2, чтобы она возвращала в регистре ERROR число —1 в случае, если запись прошла неудачно? В противном случае в этом регистре должен быть ноль.

15.2. В Программе 15.5 мы поместили таблицу преобразования в память программ, выровняв ее для упрощения вычисления 1-байтного индекса по 256-байтной границе (а именно h’300’). В результате, чтобы обратиться к элементу nn таблицы, нам достаточно поместить адрес h’3nn’ в регистры EEADRH: EEADR.

Размещение сегментов программы по адресам, заданным пользователем, является не очень хорошей идеей, поскольку при последующих модификациях программы может произойти наложение участков кода. Более надежным будет оставить размещение меток на совести ассемблера. Однако в нашем случае необходимо прибавлять значение nn к адресу, по которому ассемблер разместил таблицу TABLE. К сожалению, адрес памяти программ 13-битный, а микроконтроллеры PIC выполняют арифметические операции только с 8-битными числами. В Micmchip-совместимых ассемблерах предусмотрены директивы high и low, с помощью которых можно обратиться соответственно к старшему и младшему байту адреса метки, например movlw low TABLE. Используя эти директивы, доработайте подпрограмму SQUARE, чтобы она могла работать при отсутствии в программе директивы org h’300’.

15.3. В Microchip-совместимых ассемблерах имеется директива da, которая может использоваться для описания строк символов в памяти программ, например:

MESSAGE da "Hello world\n",0

Эта директива помещает символы, расположенные между кавычками, в память программ, причем в каждое 14-битное слово заносится по два символа, представленных 7-битным кодом ASCII. Завершается строка словом с нулевым значением. Служебный символ \п означает «новая строка», его ASCII-код равен h’0A’.

Полагая, что мы работаем с PIC16F87X, напишите подпрограмму, называемую PDATA, для выборки каждого символа из памяти программ и передачи его на терминал с помощью подпрограммы PUTCHAR из Программы 12.14 (стр. 421).

15.4. В некоторых системах безопасности гостиниц для электронных замков номеров используются перепрограммируемые смарт-карты на базе микроконтроллеров PIC. При регистрации в гостинице в эту карту заносятся следующие данные:

1. 4-разрядный номер комнаты, например 1311.

2. Дата начала срока действия ключа, например 13072005.

3. Дата окончания срока действия ключа, например 15072005.

Предположим, что микроконтроллер смарт-карты имеет встроенный модуль EEPROM, а для обмена информацией с терминалом на ресепшине используется подпрограмма обмена по последовательному порту, аналогичная реализованной в Программе 12.14 на стр. 421. Данные передаются в указанном порядке в кодировке ASCII, причем перед началом пакета передается символ STX, после завершения пакета — ЕТХ, а сами данные внутри пакета разделяются символом SP (см. Табл. 1.1 на стр. 18). Напишите подпрограмму, выполняющую разбор принимаемых данных и записывающую их в EEPROM.

 

Глава 16

Дальнейшее развитие

В 1994 году компания Microchip представила на суд общественности первого представителя старшей линейки микроконтроллеров PIC17C42, работающего на частоте 25 МГц. В основу этого микроконтроллера легла та же гарвардская RISC-архитектура, которая использовалась в предыдущих моделях, однако количество команд было увеличено до 55 (при этом система команд была совместима снизу вверх). Новая архитектура обеспечивала поддержку памяти бóльших объемов, а также имела расширенные возможности в части косвенной адресации, управления прерываниями и стеком.

В 1999 году было представлено семейство микроконтроллеров PIC18CXXX с максимальной тактовой частотой 40 МГц и 16-битным набором команд. Количество команд в этих микроконтроллерах было увеличено до 75, причем большинство из них были введены для поддержки языков высокого уровня. Также в этих микроконтроллерах была реализована более развитая система прерываний, косвенной адресации и управления стеком.

Чтобы несколько сбалансировать наше обсуждение, посвященное большей частью микроконтроллерам среднего уровня, в этой главе приводится обзор расширенного семейства на примере микроконтроллеров линейки PIC18FXX2. Эти модели специально предназначены для замены более ранних моделей среднего уровня линейки PIC16F87X, на примере которых мы и изучали микроконтроллеры PIC.

После прочтения этой главы вы:

• Сможете критически сравнить архитектуры семейств среднего и расширенного уровня.

• Познакомитесь с побайтно адресуемой структурой 16-битной памяти программ.

• Разберетесь в организации памяти данных, позволяющей при помощи регистра выбора банка использовать до 16 банков памяти по 256 регистров каждый.

• Узнаете, как можно использовать три 12-битных регистра FSR/i для косвенной адресации с пред/пост инкрементом/декрементом, а также для относительной адресации со смещением, хранящимся в рабочем регистре.

• Поймете, каким образом реализована поддержка приоритетов прерываний.

• Познакомитесь с основными изменениями в стандартных периферийных модулях.

• Узнаете, каким образом был расширен набор команд.

Линейка микроконтроллеров PIC18FXX2 была представлена в 2001 году. Эти микроконтроллеры предназначались для замены моделей среднего уровня PIC16F87X, а также более старых PIC16C73/4. Всего в этой линейке было выпущено 4 микроконтроллера.

PIC18F242

Этот микроконтроллер, выпускающийся в 28-выводном корпусе, имеет память программ объемом 8 Кслов и память данных объемом 788 байт.

PIC18F252

Эта модель идентична предыдущей, но имеет память программ объемом 16 Кслов и память данных объемом 1536 байт.

PIC18F442

Этот микроконтроллер представляет собой 40/44-выводный вариант модели PIC18F242.

PIC18F452

Этот микроконтроллер представляет собой 40/44-выводный вариант модели PIC18F252.

Кроме несколько урезанного набора периферийных модулей в 28-выводных моделях PIC18F2X2, эти микроконтроллеры практически идентичны соответствующим моделям PIC18F4X2, архитектура которых изображена на Рис. 16.1.

Чтобы сравнить эти микроконтроллеры с их предшественниками — PIC16F87X (см. Рис. 10.1 на стр. 303), мы рассмотрим блок выборки, исполнительный блок, периферийные устройства, а также отличие системы команд.

Рис. 16.1. Архитектура 40-выводных микроконтроллеров PIC18F442/52

Блок выборки

Блок выборки команд, находящийся в верхней левой части Рис. 16.1, имеет гарвардскую архитектуру с двухуровневым конвейером, в котором могут находиться две 16-битные команды, за счет чего циклы выборки и исполнения команд осуществляются параллельно. Команды хранятся в FLASH-памяти программ в виде 16-битных слов. Как уже говорилось в главе 15, микроконтроллер может самостоятельно читать и записывать данные в свою память программ. В семействе PIC18XXXX содержимое памяти программ рассматривается как совокупность 8-битных данных, передаваемых в обоих направлениях посредством регистра специального назначения TABLAT. Адрес байта в памяти программ, к которому производится обращение, содержится в трех регистрах указателя TBLPTR. Для копирования адресованного байта в регистр TBLAT используется команда tblrd. Соответственно команда tblwt используется для записи данных в память программ, однако реальная запись осуществляется 8-байтными блоками при помощи регистра EECON1, подобно тому, как это было показано на Рис. 15.7 (стр. 558).

Большинство команд в микроконтроллерах с расширенным ядром — 16-битные (см. Рис. 16.4), и только несколько команд занимают два слова памяти программ. Однако для облегчения доступа к таблицам однобайтных данных и строкам память программ организована побайтно. Как можно увидеть из Рис. 16.2, все команды занимают по два байта памяти программ и размещаются только по нечетным адресам. Например, 6-я команда будет располагаться по адресу h’000A’, а 7-я команда — по адресу h’000C’. Такое размещение команд вызвано отсутствием 0-го бита в 21-битном счетчике команд. Соответственно при линейном выполнении программы содержимое счетчика команд изменяется с шагом 2. Таким образом, данная архитектура поддерживает память программ объемом до 220 слов или 221 байт. Между тем в моделях, которые мы взяли в качестве образца, реализован 17-битный счетчик команд.

Рис. 16.2. Упрощенное представление памяти программ моделей PIC18FX42

Как и в микроконтроллерах среднего уровня, счетчик команд при сбросе обнуляется. Младший байт счетчика команд РС[7:0] напрямую доступен через регистр PCL, а регистр PCLATH выступает в качестве буфера, как показано на Рис. 4.8 (см. стр. 103), позволяя изменять старший байт счетчика команд одновременно с записью нового значения в регистр PCL. Однако в отличие от микроконтроллеров среднего уровня при чтении регистра PCL в регистр PCLATH копируется содержимое среднего байта счетчика команд РС[15:8]. Регистр PCLATU работает точно так же, но обеспечивает доступ к старшему байту счетчика команд РС[21:16]. Такое нововведение позволяет программе считывать и записывать все 21 бит счетчика команд.

Глубина стека была увеличена с 8 до 31 уровня. При переполнении или, наоборот, опустошении стека микроконтроллер автоматически сбрасывается. Доступ к регистру указателя стека STKPTR осуществляется точно так же, как и к остальным РСН, а 21-битное значение может считываться с вершины стека в три регистра TOS или заноситься в стек из указанных регистров. Все это обеспечивает большую гибкость при манипулировании содержимым стека и передаче данных через стек.

Исполнительный блок

Как и в микроконтроллерах младшего и среднего уровня, АЛУ обрабатывает данные побайтно. Поэтому, несмотря на усовершенствованное ядро, микроконтроллеры старшего семейства тоже относятся к классу 8-битных. В АЛУ этого семейства появился аппаратный умножитель 8x8, для поддержки которого были введены команды mullw (умножить константу на рабочий регистр) и mulwf (перемножить рабочий регистр и регистр данных). Результат умножения (16 бит) заносится в два регистра специального назначения PRODH: PRODL (см. Программу 16.1, стр. 580).

Изменения в ядре коснулись и рабочего регистра — в этом семействе к нему можно обращаться как к обычному регистру специального назначения, расположенному в памяти данных. То есть он может выступать в качестве операнда команд, напрямую оперирующих регистрами данных. Например, команда decfsz WREG, f декрементирует содержимое рабочего регистра как РСН с именем WREG.

Регистр STATUS больше не используется для переключения банков памяти, а освободившееся место занято флагами N (флаг отрицательного значения) и OV (переполнение) для полноценной поддержки операций сложения и вычитания в дополнительном коде (см. стр. 22).

В памяти данных старшего семейства, структура которой показана на Рис. 16.3, хранится большая часть данных, обрабатываемых АЛУ, а также регистры специального назначения. Как и во всех микроконтроллерах PIC, в каждой ячейке памяти содержится один байт, однако схема адресации регистров разительно отличается от той, которая использовалась в семействе среднего уровня (см. Рис. 4.7 на стр. 97).

Рис. 16.3. Структура памяти данных старшего семейства

Из Рис. 16.3 видно, что память данных разбита на 16 банков по 256 регистров каждый, что дает максимальный объем памяти, равный 4 Кбайт. Переключение этих банков осуществляется посредством регистра выбора банка BSR. Текущий банк задается битами BSR[3:0] и выбирается с помощью дешифратора 4x16. Поскольку сам регистр BSR расположен в 15-м банке, а указывает после сброса по питанию на нулевой банк, то для изменения регистра BSR, независимо от используемого в данный момент банка памяти, была введена специальная команда lbsr. Так, если нам необходимо переключиться на 5-й банк, достаточно выполнить команду lbsr 5.

Младшие 128 адресов 0-го банка, отведенные под пользовательские регистры общего назначения (РОН), и старшие 128 регистров 15-го банка, хранящие РСН, названы на Рис. 16.3 банком быстрого доступа (Access bank). К ячейкам банка быстрого доступа можно обращаться напрямую, игнорируя установки регистра BSR. Чтобы указать на то, каким образом будет произведено обращение к памяти данных, используется 8-й бит 16-битного слова команды, как показано на Рис. 16.4. Если сравнить этот рисунок с форматом слова команды среднего семейства, показанного на стр. 98, то можно заметить, что разрядность поля адреса регистра увеличилась с 7 до 8 бит, что обусловлено увеличением размера банка памяти с 27 = 128 байт до 28 = 256 байт. При сброшенном бите «а», обращение производится к банку быстрого доступа, в противном случае команда обратится к банку, заданному регистром BSR. Если после включения микроконтроллера оставить содержимое регистра BSR без изменений (h’00’), то команды смогут обращаться ко всем 256 регистрам 0-го банка и всем 128 РСН 15-го банка. Например, для копирования содержимого регистра h’026’ в рабочий регистр мы должны будем выполнить команду movf h’026’,w,0, а для копирования регистра h’096’ — команду movf h’096’,w,1. Схема доступа определяется последним числом (0 или 1). На практике такое явное указание способа обращения применяется достаточно редко, поскольку ассемблер автоматически использует банк быстрого доступа (т. е. команду формата, 0) для адресации регистров из диапазона h’000’…h’07F’ и h’F80’…h’FFF’.

Рис. 16.4. Формат слова типичной команды, обращающейся к памяти данных

В моделях PIC18FX52 для хранения регистров общего назначения предназначена область памяти вплоть до верхней границы 5-го банка, т. е. по адрес h’5FF’ включительно. В микроконтроллере PIC18FX42, оделенном памятью не так щедро, для этой цели используются банки 0…2, т. е. ячейки с адресами до h’2FF’ включительно.

Любые микропроцессоры и микроконтроллеры могут использовать косвенную или индексную адресацию для эффективной работы с массивами данных и таблицами, размещенными в ОЗУ. В младшем и среднем семействах косвенная адресация осуществляется с использованием регистра FSR, выступающего в качестве указателя на память данных, и специального механизма, включающего обращение к виртуальному регистру INDF. Точно такой же подход используется и в старшем семействе, но на более глубоком уровне. В этом семействе имеется три отдельных регистра косвенной адресации — FSR0, FSR1 и FSR2. Каждый из этих регистров-указателей реализован в виде двух РСН, как показано на Рис. 16.5, а. То есть в регистре косвенной адресации может храниться 12-битное число, что позволяет ему указывать на любой регистр в адресном пространстве памяти данных, не обращая внимания на ее сегментированную структуру. Команда загрузки регистра косвенной адресации lfsr позволяет за одно действие скопировать 12-битную константу в любой из трех регистров FSR. Так, чтобы регистр FSR2 указывал на регистр h’500’, достаточно выполнить одну команду lfsr 2, h’500’.

Каждый из регистров косвенной адресации имеет несколько различных режимов работы, показанных на Рис. 16.5, а. Конкретный режим определяется по тому, к какому из пяти виртуальных регистров производится обращение. Вот эти регистры (i = 0, 1 или 2):

∙ INDF i (простая косвенная адресация )

Команда clrf INDF2 обнулит регистр, адрес которого находится в регистре FSR2.

∙ POSTDEC i (косвенная адресация с постдекрементом)

Команда clrf POSTDEC2 обнулит регистр, адресованный 12-битным регистром FSR2, а затем декрементирует содержимое регистра косвенной адресации.

∙ POSTINC i (косвенная адресация с постинкрементом)

Команда clrf POSTINC2 обнулит регистр, адресованный 12-битным регистром FSR2, а затем инкрементирует содержимое регистра косвенной адресации.

∙ PREINC i (косвенная адресация с прединкрементом)

Команда clrf PREINC2 сначала инкрементирует содержимое регистра FSR2, а затем обнулит регистр, адресованный регистром FSR.

∙ PLUSW i (относительная косвенная адресация)

Если в рабочем регистре находится число h’06’, то команда clrf PLUSW2 обнулит регистр, адрес которого получается сложением содержимого FSR2 и константы h’06’. Ни содержимое регистра FSR2, ни содержимое рабочего регистра при этом не изменяется. Содержимое рабочего регистра интерпретируется как число со знаком, представленное в дополнительном коде (—128…+127).

Рассмотрим в качестве примера два массива по восемь байтов, обозначенных метками NUM1 и NUM2 (см. Рис. 16.5, б). Предположим, что нам необходимо перемножить i-е элементы этих массивов для получения массива из восьми 16-битных чисел NUM3. Старший байт i-го элемента массива NUM3 будет располагаться по смещению —8 относительно младшего байта. В Программе 16.1 для обращения к массиву NUM1 используется регистр FSR0, а для обращения к массиву NUM2 — регистр FSR1. Поскольку массив NUM3 представляет собой по существу два байтовых массива, сдвинутых друг относительно друга на восемь байтов, регистр FSR2 используется для указания на массив младших байтов произведений. При инициализации в эти указатели с помощью команды Ifsr заносятся адреса последних элементов каждого массива, выделенные на Рис. 16.5, б серым цветом.

Рис. 16.5. Косвенная адресация посредством регистра FSR0

Программа 16.1. Перемножение двух массивов однобайтных значений

; **********************

; ФУНКЦИЯ: Перемножает NUM1[8] х NUM2[8] = NUM3[16]

; ВХОД: Глобальные массивы NUM1[8], NUM2[8]

; ВЫХОД: Глобальный массив NUM3[16]

; **********************

ARRAY_MUL lfsr 0,NUM1+7 ; Указываем на последний элемент NUM1

                    Ifsr 1,NUM2+7 ; Указываем на последний элемент NUM2

                    lfsr 2,NUM3+d’15’ ; Указываем на младший байт последнего элемента NUM3

                    movlw 8 ; Инициализируем счетчик цикла

                    movwf COUNT

M_LOOP        movf POSTDECO,w ; Берем NUM1[n]

                    mulwf POSTDEC1 ; Умножаем на NUM2[n]

                    movlw -8 ; Задаем смещение для обращения к старшим байтам NUM3

                    movf f PRODH,PLUSW2 ; Сохраняем старший байт произведения

                    movf f PRODL,POSTDEC2 ; Сохраняем младший байт произведения

                    decfsz COUNT,f ; Возвращаемся к началу цикла

                       goto MLOOP

                    return

Основной в программе является команда mulwf. Эта команда формирует в регистрах специального назначения PRODH: PRODL 16-битное произведение, получаемое перемножением содержимого рабочего регистра и заданного регистра данных. Режим косвенной адресации с постдекрементом используется как для пересылки первого сомножителя в рабочий регистр, так и для указания второго сомножителя.

Для копирования содержимого регистра PRODL в младший байт текущего элемента массива NUM3[], а регистра PRODH — в старший байт, в программе используется команда movff, осуществляющая пересылку между двумя регистрами данных. Эта команда, занимающая два слова памяти программ (см. стр. 589), использует для идентификации регистра-источника и регистра-приемника 12-битные адреса, что позволяет ей обращаться к любой ячейке памяти данных, не используя механизм банков. Сначала содержимое PRODH копируется в старший байт элемента массива с использованием режима относительной косвенной адресации. Поскольку в рабочий регистр было предварительно записано число -8 (h’F8’), содержимое регистра PRODH будет скопировано в регистр, адрес которого на 8 меньше адреса, находящегося в регистре FSR2. А содержимое регистра PRODL копируется в младший байт элемента массива, адресуемый регистром FSR2, с использованием режима косвенной адресации с постдекрементом.

Периферийные устройства

Вообще говоря, модули периферийных устройств в рассматриваемых моделях практически ничем не отличаются от аналогичных модулей, имеющихся в микроконтроллерах среднего уровня. Укажем основные отличия.

Параллельные порты

С каждым из параллельных портов, изображенных на Рис. 16.1, теперь связано три регистра вместо двух, описанных в главе 11. Регистр PORTT все также управляет состоянием контакта ввода/вывода, направление передачи данных через который все также определяется регистром TRISX Кроме того, у каждого порта появился регистр защелки LATX Несмотря на то что каждый из этих регистров защелки имеет уникальный адрес, например регистр LATB размещен по адресу h’F8A’, они физически не реализованы. Взаимосвязь между этими РСН можно понять из Рис. 16.6, который следует сравнить с аналогичным Рис. 11.3 на стр. 333. Единственное принципиальное отличие между рисунками — появление дополнительного тристабильного буфера LAT, выделенного серым цветом. При чтении регистра LAT, например с помощью команды movf LATB, w, возвращается состояние самого триггера данных. Соответствующая команда movf PORTB, w считывает реальное состояние выводов порта В. Обычно обе эти операции дают одинаковый результат. Однако, как мы обсуждали на стр. 337, если величина втекающего или вытекающего тока превышает паспортные значения или если нагрузка имеет большую емкость, а период переключения достаточно мал, то результат выполнения команды типа «чтение-модификация-запись», примененной к регистру PORTX, будет непредсказуем. Манипулирование содержимым регистра защелки вместо содержимого соответствующего регистра порта даст нам определенную независимость от условий работы схемы. Например, команда btfsc LATB,7 пропустит следующую команду, если 7-й бит порта В сброшен, даже если вывод RB7 окажется подтянутым к ВЫСОКОМУ уровню из-за слишком большого вытекающего тока. Очевидно, что в этом случае надежность программы будет гораздо выше, нежели при использовании эквивалентной команды btfsc PORTB,7.

При записи в регистр LATX или соответствующий ему регистр PORTX изменяется состояние триггера данных. Таким образом, команда movwf LATB по своему действию идентична команде movwf PORTB.

Рис. 16.6. Упрощенная схема одной линии порта ввода/вывода микроконтроллеров старшего семейства

Таймер 0

Таймер 0, который практически в неизменном виде перешел из семейства младшего уровня в семейство среднего уровня, теперь стал 16-битным и обзавелся собственным предделителем, не связанным с постделителем сторожевого таймера. При необходимости этот таймер можно переключить в 8-битный режим с помощью нового регистра управления T0CON.

Таймер 3

В микроконтроллерах старшего семейства реализован дополнительный 16-битный таймер, который похож по своей структуре на Таймер 1. Таймер 3 может тактироваться от внешнего низкочастотного генератора Таймера 1, который также может использоваться в качестве системного при необходимости уменьшения потребляемого тока. Каждый из модулей ССР может работать как с Таймером 1, так и с Таймером 3, что дает нам возможность использования двух независимых временных шкал.

Некоторые представители старшего семейства имеют более крупные корпуса и больший ассортимент периферийных модулей, чем рассматриваемые модели. Так, 80-выводной микроконтроллер PIC18F8720 имеет память программ объемом 128 Кбайт, память данных объемом 3840 байт, EEPROM объемом 1024 байта и предоставляет пользователю до 68 линий ввода/вывода. На входе модуля 10-битного АЦП в этой модели имеется 16-канальный мультиплексор. Кроме того, в данном микроконтроллере реализовано два модуля USART и пять модулей CCP/PWM, а также дополнительный 8-битный таймер.

Обработка прерываний

Как и в моделях среднего семейства, каждое периферийное устройство может генерировать запрос на прерывание. Вдобавок к внешнему прерыванию INT, которое теперь называется INTO, появилось два новых внешних прерывания — INT1 (вывод RB1) и INT2 (вывод RB2). Для поддержки этих внешних прерываний были введены три соответствующих регистра INTCON.

Наиболее заметным отличием в системе прерываний стало появление двух уровней приоритета. В микроконтроллерах среднего уровня при обработке запроса какого-либо разрешенного прерывания все прерывания от других источников автоматически запрещались из-за сброса бита GIE регистра INTCON. При выходе из обработчика прерывания по команде retfie бит GIE устанавливался снова, чтобы можно было обработать отложенные или последующие запросы прерываний. Несмотря на то что такой механизм необходим для предотвращения конфликтов между запросами прерываний, он может вызвать определенные проблемы. Возьмем, к примеру, биомедицинский монитор, в котором обработчик прерывания используется для управления достаточно медленной линией связи, предназначенной для передачи телеметрических данных в центральный процессор с периодом в один час. Представьте, что у пациента произошла остановка сердечной деятельности. Приоритеты здесь очевидны, однако датчик, формирующий последнее прерывание, окажется заблокированным!

В микроконтроллерах расширенного семейства каждому источнику прерывания сопоставлено три бита. Так, у модуля АЦП имеется флаг прерывания ADIF (PIR1 [6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
) для индикации запроса прерывания, бит маски ADIE (Р1Е[6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
) для разрешения прерывания от этого источника, а также бит приоритета ADIP (IPR1 [6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
), определяющий приоритет прерывания от данного источника: ADIP = 1 — высокий приоритет (состояние после сброса по питанию), ADIP = 0 — низкий приоритет.

При возникновении запроса прерывания от источника с низким приоритетом, происходит переход по вектору низкоприоритетных прерываний, расположенному по адресу h’018’, и сбрасывается бит GIEL (глобальное разрешение прерываний с низким приоритетом) в INTCON[6]В данной книге для отделения целой части числа от дробной используется точка, а не запятая. — Примеч. ред.
. В результате обработка любого другого прерывания с низким приоритетом будет невозможна до завершения обработки текущего прерывания. Однако если во время обработки низкоприоритетного прерывания будет получен запрос прерывания от источника с высоким приоритетом, то выполнение текущего обработчика приостановится и процессор перейдет по вектору высокоприоритетного прерывания, расположенному по адресу h’008’ (что соответствует адресу слова h’004’ вектора прерывания предыдущих семейств). Одновременно с этим сбрасывается бит GIEH (глобальное разрешение прерываний с высоким приоритетом) в INTCON[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
, запрещая все прерывания независимо от их приоритетов.

При переходе к соответствующему вектору прерывания содержимое счетчика команд помещается на вершину стека, как и в семействе среднего уровня. Помимо этого, в трех скрытых регистрах, называемых иногда быстрым стеком (fast stack), сохраняется содержимое рабочего регистра, регистра STATUS и регистра BSL. Для восстановления сохраненного контекста одновременно со счетчиком команд необходимо, чтобы определенный бит слова команды retfie был установлен в 1. Для этого команда retfie записывается с параметром: retfie 1 или, более удобочитаемо, retfie FAST. Если же команда будет записана без параметров, то этот бит окажется сброшен и при возврате из обработчика прерывания будет восстановлен только счетчик команд.

Необходимо очень аккуратно использовать этот механизм быстрого сохранения/восстановления контекста, поскольку в быстром стеке может храниться только одна копия этих системных регистров. Если запрос прерывания с высоким приоритетом прервет выполнение обработчика прерывания с низким приоритетом, то содержимое быстрого стека будет перезаписано новыми значениями! Поэтому при наличии в программе прерываний с разными приоритетами команда retfie FAST должна использоваться только в обработчиках прерываний с высоким приоритетом. Если же прерывания в программе вообще не используются, то быстрый стек можно использовать для сохранения контекста при вызове обычных подпрограмм. Так, по команде call FAST происходит вызов подпрограммы с одновременным сохранением контекста, а по команде return FAST — возврат из подпрограммы с восстановлением контекста.

В трех регистрах INTCON хранятся флаги прерываний, биты маски и приоритета для прерывания от Таймера 0, прерывания по изменению состояния порта В и трех внешних прерываний. Управление остальными прерываниями осуществляется с помощью регистров PIR1:2, РIЕ1:2 и IRP1:2. При включении питания схема обработки прерываний работает так же, как и неприоритетная схема среднего семейства, т. е. состояние битов приоритета игнорируется. Кроме того, для разрешения работы приоритетной системы прерываний должен быть установлен в 1 бит IPEN регистра управления сбросом RCON (RCON[7]Разумеется, существует множество других цифровых кодировок, к примеру 6-точечный код Брайля для слепых.
).

Система команд

В Табл. 16.1 перечислены все 75 команд, поддерживаемые ядром PIC18. Сравнивая эту таблицу с приведенной в Приложении Г, можно заметить, что сохранились все предыдущие команды, за исключением команды clrw. Эта команда стала избыточной, поскольку рабочий регистр теперь может обрабатываться как обыкновенный регистр данных и, соответственно, его сброс может быть осуществлен командой clrf WREG. Также была расширена функциональность ряда старых команд, например, в части воздействия на флаги отрицательного значения (N) и переполнения (OV). У нескольких команд появились расширенные варианты; например, команда addwfс прибавляет содержимое рабочего регистра к указанному регистру данных с учетом переноса (см. Программу 16.2).

Условные обозначения:

√ - Воздействует на флаг; LLL - 12-битная константа; fn - n -й бит регистра; w - Рабочий регистр; TOS - Вершина стека; != - Условие неравенства; SP - Указатель стека; offset - Смещение ±128 слов; • - Не воздействует на флаг; d - Адресат: 0 = w, 1 = f; PC - Счетчик команд; f - Регистр данных; (TOS) - Содержимое вершины стека; ++ -  Инкрементирование; # - Константа; Offset - Смещение ± 1024 слова; LL - 8-битная константа; b - Обращение к ОЗУ посредством BSR; РС++ - Пропуск следующей команды; WDT - Сторожевой таймер; == - Условие равенства; -- - Декрементирование; ааа - Адрес; GIE - Бит глобального разрешения прерываний

Чтобы завершить главу, перечислим основные отличия нового набора команд, используя то же деление по выполняемым функциям, что и в главе 5.

Команды пересылки данных

Наиболее важным нововведением в этой группе команд является команда movff, которую мы уже использовали в Программе 16.1. Эта команда позволяет копировать содержимое любого регистра-источника в любой регистр-приемник, невзирая на сегментированную структуру памяти данных и не используя рабочий регистр. Поскольку для указания каждого из регистров требуется полный 12-битный адрес, команда movff занимает два слова в памяти программ и выполняется за два машинных цикла. Двоичный код этой команды выглядит следующим образом:

Четыре старших бита второго слова b’1111’ имитируют код операции команды nор. Это отличительная особенность всех двухсловных команд, позволяющая избежать проблем при переходе на середину такой команды (в качестве примера см. Программу 16.4).

Команда lfsr, также занимающая два слова в памяти программ, загружает 12-битную константу (значение адреса) в один из трех регистров косвенной адресации FSR, как показано в Программе 16.1. Команда lbsr является однословной.

Команды арифметических операций

Одним из назначений микроконтроллеров PIC18XXX является реализация приложений начального уровня для цифровой обработки сигналов (DSP, ЦОС) в реальном масштабе времени. Задачи ЦОС требуют значительной вычислительной мощности, поэтому эта категория команд претерпела, наверное, самые большие изменения.

Одним из наиболее вопиющих недостатков набора команд младшего и среднего семейств является отсутствие в нем команд сложения с учетом переноса и вычитания с учетом заема. По этой причине операции сложения и вычитания многобайтных чисел получаются достаточно громоздкими и медленными. Команда addwfс прибавляет содержимое рабочего регистра W к содержимому указанного регистра данных плюс значение бита переноса. Результат, как и прежде, помещается либо в рабочий регистр, либо обратно в регистр данных. При этом значение бита переноса изменяется соответствующим образом. Для примера в Программе 16.2 приведен код подпрограммы, выполняющей сложение двух 3-байтных чисел и получающей 4-байтный результат. За исключением операции сложения младших байтов, при переходе к старшим байтам учет бита переноса осуществляется естественным образом, независимо от требуемой точности. Без такой команды каждое сложение пришлось бы сопровождать операцией условного инкрементирования.

Программа 16.2. Сложение трехбайтных чисел

TP_ADD clrf NUM3_V ; Обнуляем старший байт результата

             movf NUM1_L,w ; Берем младший байт 1-го числа

            addwf NUM2_L , w; Прибавляем младший байт 2-го числа

            movwf NUM3_L ; Сохраняем младший байт результата

            movf NUM1_H,w ; Берем средний байт 1-го числа

            addwfс NUM2_H,w ; Прибавляем средний байт 2-го числа

            movwf NUM3_H ; Сохраняем средний байт результата

            movf NUM1_U,w ; Берем старший байт 1-го числа

            addwfс NUM2_U,w ; Прибавляем старший байт 2-го числа

            movwf NUM3_U ; Сохраняем старший байт результата

            btfsc STATUS,С ; Пропускаем, ЕСЛИ нет переноса

              incf NUM3_V,f ; ИНАЧЕ прибавляем 1

Аналогичным образом команда subwfb вычитает из заданного регистра содержимое W плюс бит заема, сформированный предыдущими арифметическими операциями. Также появилась команда subfwb, которая выполняет вычитание в обратном порядке, т. е. рабочий регистр вычитается из регистра данных.

Все команды сложения, вычитания, а также команды инкрементирования и декрементирования обеспечивают полную поддержку чисел со знаком, представленных в дополнительном коде (в сочетании с флагами N и OV). Команды incf и decf теперь воздействуют на все флаги арифметических операций, упрощая, таким образом, выполнение многобайтных вычислений (напоминаю, раньше эти команды воздействовали только на флаг Z). Новая команда negf вычисляет дополнительный код числа, находящегося в заданном регистре. Соответственно если в регистре находилось число со знаком, то значение знака меняется на противоположное.

Одной из наиболее важных операций при цифровой обработке сигналов является операция умножения. Поэтому в систему команд были добавлены две команды умножения, вычисляющие за один машинный цикл 16-битное произведение двух беззнаковых 8-битных чисел, как показано в Программе 16.1. На базе этих команд можно создавать относительно короткие подпрограммы для умножения многобайтных чисел, представленных в дополнительном коде.

Команда десятичной коррекции daw упрощает реализацию сложения чисел, представленных в BCD-коде. Как мы уже говорили на стр. 111, при выполнении операций над такими числами с использованием обычной двоичной арифметики результат необходимо корректировать, чтобы исключить шесть избыточных значений Ь’1011’…Ь’1111’. Команда daw осуществляет такую коррекцию, будучи выполнена сразу же после команд сложения или инкрементирования упакованных BCD-чисел (два BCD-разряда хранятся в одном байте). Пример использования этой команды приведен в Программе 16.3, которая выполняет те же действия, что и Программа 4.1. Заметьте, что команда daw не преобразует двоичное число в BCD-формат, она просто осуществляет коррекцию после сложения данных, уже представленных в упакованном BCD-формате.

Программа 16.3 . Инкрементирование упакованного BCD-числа с использованием команды daw

BCD_INC incf BCD,w ; Инкрементируем BCD-число по правилам двоичной арифметики

              daw ; Корректируем его для приведения к формату BCD

              movwf BCD ; и помещаем результат обратно в регистр

К командам сброса и установки отдельных битов регистров данных была добавлена третья команда btg, позволяющая инвертировать значение заданного бита регистра. Для примера в Программе 16.4 генерируется последовательность из 20 прямоугольных импульсов на выводе RA0, длительность каждого из которых составляет 8 машинных циклов. Причем формирование этих импульсов осуществляется по аналогии с кодом, относящимся к Рис. 5.19 (стр. 156). Обратите внимание на использование команды decfsz непосредственно с рабочим регистром, который в данном случае представляется в виде РСН с именем WREG.

Программа 16.4 . Переключение вывода RA0

; Конфигурируем порт А

         movlw b’0110’ ; Конфигурируем порт А как цифровой

         movwf ADCON1

         bcf LATA,0; Начнем с НИЗКОГО уровня на выводе RA0

         bcf TRISA,0 ; Делаем RA0 выходом

; Где-то в программе ----------

         movlw d’40’ ; Загружаем в W число 40

LOOP btg LATA,0; Изменяем состояние вывода RA0 1~

         decfsz WREG,f ; Декрементируем до нуля 1|3~

           goto LOOP ; ИНАЧЕ выходим из цикла 2~

... ...; Далее

В ядре PIC18 команда goto занимает два слова памяти программ. Поэтому при выполнении команды decfsz произойдет переход на второе слово команды goto. Именно по этой причине машинный код второго слова всех четырех двухсловных команд таков, что при непосредственном переходе на это слово оно интерпретируется как команда nор. Из-за этого время выполнения команды увеличивается на один машинный цикл.

И наконец, команда setf предоставляет программисту возможность непосредственно устанавливать все биты заданного регистра в 1, дополняя, таким образом, команду clrf, имевшуюся в предыдущих семействах.

Команды логических операций и операций сдвига

В данной категории изменения коснулись только группы команд циклического сдвига. Как мы видели из Рис. 5.13 (стр. 148), команды rlf и rrf сдвигают содержимое заданного регистра через бит переноса. Новые же команды осуществляют сдвиг в обход этого бита. Эти команды имеют мнемоники rIncf (сдвиг влево, не через флаг переноса) и rrncf (сдвиг вправо, не через флаг переноса). Команды сдвига, доставшиеся в наследство от семейства среднего уровня, для единообразия были переименованы в rlcf и rrcf. Еще раз напоминаю, что, поскольку к рабочему регистру можно обращаться как к регистру данных, его содержимое можно сдвигать точно так же, как и содержимое любого другого регистра.

Команды передачи управления

Все команды, имевшиеся в предыдущих семействах, т. е. команды goto, call и три команды возврата, были сохранены. Однако первые две из них теперь занимают два слова в памяти программ и способны осуществлять переход в пределах 20 Мелов, позволяя, таким образом, забыть о страничной организации памяти программ, которая имела место в микроконтроллерах с ядром PIC16. Кроме того, команда call может теперь сохранять контекст программы в теневом стеке (если прерывания не используются) при ее вызове с параметром FAST. Возврат из подпрограммы, вызванной таким образом, осуществляется командой return FAST.

Большинство переходов, осуществляемых с помощью команд goto, относительно короткие. К примеру, команда goto LOOP в Программе 16.4 возвращается назад всего на две команды. Новая однословная команда bra позволяет осуществлять переход в пределах —1023…+1024 слов. Таким образом, используя вместо goto LOOP команду bra LOOP, мы экономим одно слово памяти программ, хотя время выполнения команды остается равным двум машинным циклам. Аналогичным образом команда относительного вызова подпрограмм rcall позволяет осуществлять вызов близлежащих подпрограмм, правда, она не имеет возможности сохранения контекста.

К командам безусловного перехода добавились десять команд условного перехода, в которых переход осуществляется по определенному значению того или иного флага регистра STATUS, за исключением флага DC. В принципе проверить состояние этих флагов можно с помощью команд btfssn и btfsc, однако все, что мы получим в результате их выполнения, — это пропуск единственного слова памяти программ. А команды условного перехода позволяют осуществлять переход в пределах +128…—127 слов при установке определенного флага регистра STATUS, как, например, команда Ьс (переход, если флаг С установлен), или его сбросе, например, Ьnс (переход, если флаг С сброшен). Предположим, что на порт С микроконтроллера подается значение от датчика температуры, представленное в дополнительном коде. В следующем фрагменте кода при положительной температуре выполняется сброс регистра, названного SIGN, а при отрицательной температуре все биты этого регистра устанавливаются в 1. Модуль значения температуры помещается в регистр TEMPERATURE.

    clrf SIGN ; Обнуляем регистр признака знака и копируем

    movf f PORTC,TEMPERATURE ; принятое значение температуры

    movf TEMPERATURE,f ; Проверим на ноль или отрицательное число

    bnn NEXT ; Если > 0, ТО обходим инвертирование

     negf TEMPERATURE,f ; ИНАЧЕ меняем знак числа

     setf SIGN ; и устанавливаем биты регистра признака знака

NEXT …..; Продолжаем

Команда bnn NEXT выполняет переход к метке NEXT при положительном значении. В противном случае меняется знак числа и с помощью команды setf в регистр SIGN заносится число h’FF’.

На стр. 139 мы с вами видели, что для сравнения двух беззнаковых чисел, скажем W и содержимого регистра данных, необходимо вычесть одно число из другого с последующей проверкой состояния флагов С и Z. Три новые команды сравнения/пропуска выполняют эту проверку автоматически. Команда cpfseq пропускает следующую команду, если беззнаковое число в заданном регистре равно числу в рабочем регистре, cpfsgt — если оно больше, чем число в W, a cpfsit — если это число меньше находящегося в W.

Возьмем для примера код системы контроля уровня топлива, приведенный на стр. 140. Если в баке остается менее 20 л, то включается сигнальная лампочка, а если менее 5 л — звуковой сигнал. Теперь этот же код будет выглядеть следующим образом:

ALARM bcf DISPLAY,BUZZER ; Выключаем пищалку

           bcf DISPLAY,LAMP ; Выключаем лампочку

            movlw 4 ; Проверим уровень 5 литров

            cpfsgt FUEL ; Пропускаем, ЕСЛИ > 4 литров

              bsf DISPLAY,BUZZER ; ИНАЧЕ включаем звуковой сигнал

            movlw d’19’ ; Проверим уровень 20 литров

            cpfsgt FUEL ; Пропускаем, ЕСЛИ >19 литров

               bsf DISPLAY,LAMP ; ИНАЧЕ включаем лампочку

NEXT …..; Продолжаем

Не забывайте, что данные команды сравнения корректно работают только с беззнаковыми числами. Для сравнения чисел, представленных в дополнительном коде, необходимо по-прежнему выполнять операцию вычитания и проверки флагов N, OV и Z.

И последняя новая команда в этой группе, tstfsz, проверяет содержимое заданного регистра (не изменяя его) и пропускает следующую команду, если оно равно нулю. К командам incfsz и decfsz прибавились команды infsnz и dcfsnz, выполняющие пропуск команды, если результат инкрементирования/декрементирования не равен нулю.

 

Глава 17

Учебный пример

До настоящего момента вся информация носила в какой-то степени фрагментарный характер. Чтобы придать процессу изучения логическую завершенность, давайте попробуем применить большую часть полученных знаний и разработать реальное устройство (как схему, так и программу). Может показаться, что в одной короткой главе это сделать не так уж легко. Однако на данном этапе нам придется изучить совсем мало нового материала, большей же частью мы будем просто применять полученные знания.

Всякая подобная работа начинается с составления подробных технических требований. Все без исключения студенты во время устных докладов говорят очень долго. Чтобы хоть как-то их ограничить, предполагается создать специализированное устройство на базе микроконтроллера, которое бы отслеживало заданный интервал времени. По умолчанию рабочий период этого устройства (назовем его таймером) равен 10 мин, однако следует предусмотреть возможность изменения этого значения в пределах 1…99 мин.

После запуска таймер должен выполнить следующие операции:

1. При нажатии кнопки СБРОС включается зеленый СИД, а на сдвоенном 7-сегментном индикаторе начинается обратный отсчет от заданного значения до числа  с интервалом в одну минуту,

2. Еще через минуту включается желтый СИД, на дисплее высвечивается число  и на одну секунду включается звуковой излучатель.

3. Еще через минуту включается красный СИД, на дисплее высвечивается число , а звуковой излучатель включается на две секунды.

4. И наконец, по истечении последней минуты на дисплее высвечивается число  и включается звуковой излучатель (красный СИД продолжает светиться) до тех пор, пока не будет нажата кнопка СТОП. При этом таймер сбрасывается, выключив все индикаторы, светодиоды и звук. В принципе нажатие кнопки СТОП в любой момент времени приведет к останову таймера. Перезапуск системы с заданным значением тайм-аута может быть осуществлен сбросом процессора.

5. В любой момент времени работу таймера можно приостановить нажатием и удерживанием кнопки ПАУЗА. При отпускании этой кнопки процесс счета продолжается с места остановки.

6. Чтобы изменить величину интервала, заданную по умолчанию (), при перезапуске системы необходимо держать нажатой кнопку УСТ. В результате на дисплее появится значение , которое начнет медленно уменьшаться. Значение, которое будет находиться на дисплее в момент отпускания кнопки УСТ., станет новой длительностью интервала и будет сохранено до следующей операции установки временного интервала таймера.

Итак, прежде всего нам необходимо выбрать подходящий микроконтроллер. В данном случае мы вынуждены будем ограничить наш выбор моделями, рассматриваемыми в книге, т. е. PIC12F675/29, PIC16F627/8 и PIC16F87X. Поскольку модуль АЦП нам не требуется, то для реализации описанного устройства можно спокойно выбрать любой из указанных микроконтроллеров. Использование 18-выводного PIC16F627/8 вместо 40-выводного PIC16F874/7 потребует дополнительных ухищрений для реализации необходимого количества линий ввода/вывода, однако в то же время на его примере можно будет проиллюстрировать выбор оптимального решения при разработке более сложных систем. Альтернативный вариант таймера, в котором используется последний из указанных микроконтроллеров, можно найти на Web-сайте книги. В первой редакции книги был использован микроконтроллер PIC16F84, и, поскольку он по выводам совместим с PIC16F627, решено было использовать последний. Единственное изменение, которое потребовалось внести в программу, было связано с модулем аналогового компаратора, отсутствовавшего в исходной модели.

Окончательная схема на базе выбранного микроконтроллера приведена на Рис. 17.1. Ниже описывается, как используются выводы микроконтроллера.

Рис. 17.1. Схема таймера со звуковой и световой сигнализацией

Органы управления

Пять кнопок S2…S6, управляющие операциями ПУСК, УСТАНОВКА, СТОП, ДИАГНОСТИКА, ПАУЗА, подключены к линиям RB[4:0] порта В. Используя внутреннюю подтяжку (см. Рис. 11.9 на стр. 342), мы можем отказаться от внешних подтягивающих резисторов.

Кнопка S1 вместе с подтягивающим резистором R1 используется для ручного сброса системы, вызывающего перезапуск отсчета времени. Этот же сигнал  используется в качестве сигнала сброса для остальных микросхем. В микроконтроллере PIC16F627/8 4-й вывод может быть задействован в качестве дополнительной линии порта А. Хотя этот вывод используется в качестве входа внешнего сброса  по умолчанию, в Программе 17.3 такое функционирование вывода задается явно посредством указания значения бита конфигурации MCLRE.

В данной схеме можно использовать любые кнопки без фиксации с нормально разомкнутыми контактами.

Световая индикация

Для формирования световых сигналов используются 10-мм светодиоды D3…D1 подходящих цветов с большой яркостью, подключенные к выводами RB[7:5] порта В. Резисторы сопротивлением 330 Ом, включенные последовательно с СИД, ограничивают их ток на уровне 10 мА.

Звуковая индикация

Для звуковой индикации мы применим миниатюрный твердотельный излучатель. Типичный пьезоэлектрический излучатель может работать в диапазоне напряжений от 3 до 16 В, потребляя при напряжении 5 В чуть больше 1 мА. Управление звуковым излучателем осуществляется с вывода RA2.

Цифровой дисплей

Цифровой дисплей, позволяющий отображать значения от 00 до 99, образован двумя 7-сегментными светодиодными индикаторами. Поскольку в нашем распоряжении осталось всего 4 линии ввода/вывода, мы реализовали последовательный интерфейс. Этот интерфейс похож на применяющийся в схеме на Рис. 12.2 (стр. 370), однако в данном случае для каждого 8-битного сдвигового регистра с последовательным входом и параллельным выходом 74НС164 используется отдельная линия данных (RA0 — для десятков и RA3 — для единиц). Поэтому изображение на обоих индикаторах можно будет обновлять одновременно (за восемь тактов).

Цоколевка 7-сегментных индикаторов, показанная на схеме, соответствует индикаторам с общим анодом, выпускающимся в 16-выводном DIP-корпусе и имеющим десятичные точки с обеих сторон знакоместа — lhdp и rhdp. В нашем устройстве используется только правая точка для индикации паузы. Широко распространены индикаторы в других 16-выводных и 14-выводных корпусах, в том числе и сдвоенные (с двумя знакоместами в одном корпусе). Однако даже для индикаторов в 16-выводных корпусах цоколевка не стандартизирована.

В малогабаритных индикаторах (высотой менее 20 мм/0.8 дюйма) для каждого сегмента используется один СИД, прямое падение напряжения на котором составляет около 2 В. Две резисторные сборки R5 и R6 сопротивлением 330 Ом ограничивают ток через сегменты на уровне 10 мА. Общие аноды подключены непосредственно к линии +5 В и должны быть развязаны с помощью танталового конденсатора небольшой емкости. Хотя яркость свечения индикаторов обычно нормируется при токе 20 мА, она будет вполне достаточной даже при выбранном нами токе. Кроме того, при этом исключается необходимость подключения буферов к выходу сдвиговых регистров 74НС164.

Кварцевый резонатор

В качестве времязадающего элемента тактового генератора используется кварцевый резонатор частотой 3.2768 МГц, в результате частота выполнения команд составляет 819.21 кГц. Типичный резонатор с такой частотой имеет точность ±300 ppm и температурный коэффициент ±50 ppm в диапазоне рабочих температур. Такой необычный выбор частоты обусловлен тем, что при использовании Таймера 0 с коэффициентом деления предделителя, равным 64, мы сможем формировать прерывания ровно 50 раз в секунду (см., далее). В принципе можно было бы снизить потребление микроконтроллера, взяв резонатор частотой 32.768 кГц и генерируя прерывание каждые две секунды с использованием 16-битного Таймера 1. Однако мощность, потребляемая микроконтроллером, в любом случае будет мизерной по сравнению с потреблением светодиодных индикаторов.

В моделях PIC16F627/8 выводы OSC1 и OSC2 могут использоваться в качестве дополнительных линий порта А, при этом микроконтроллер будет работать от внутреннего ЛС-генератора с номинальной частотой 4 МГц (см. Табл. 10.2 на стр. 309). На то, что в нашей схеме используется внешний кварцевый резонатор, указывает наличие в Программе 17.3 опции конфигурации _XT_OSC.

* * *

Теперь, когда схема разработана, мы можем сосредоточить все свои усилия на разработке программы.

В общем виде модульная структура нашей системы изображена на Рис. 17.2. Прямоугольники с двойными линиями по краям соответствуют подпрограмме или процедуре обработки прерывания. На данном этапе можно выделить три обособленных процесса и две основных вспомогательных задачи.

Рис. 17.2. Модульная структура программы таймера

Задача формирования временных отсчетов

Все процессы привязаны ко времени. Временная привязка реализована аппаратно, генерацией прерывания с периодичностью 50 раз в секунду. Изменение значений секунд и минут происходит после накопления определенного количества тиков. Эти значения в дальнейшем используются для выполнения требуемых процессов.

При обнаружении нажатия на кнопку ПАУЗА декрементирование этих счетчиков приостанавливается, за счет чего обратный отсчет времени можно «заморозить» на сколь угодно длительный временной интервал.

Задача отображения времени

Состояние счетчика, а также вспомогательную информацию необходимо выводить на двухразрядный дисплей. Поскольку в процессе вывода осуществляется преобразование параллельного кода в последовательный, а также его передача по последовательному каналу, эту задачу лучше выделить в отдельный модуль.

Фоновый (основной) процесс

Фоновый процесс осуществляет в цикле вывод на индикатор значение счетчика минут до тех пор, пока он не станет равным нулю. При замыкании кнопки СТОП происходит преждевременный выход из цикла.

Процесс установки интервала

Если в момент сброса микроконтроллера кнопка УСТ. находится в замкнутом состоянии, то вызывается подпрограмма SETT. Эта подпрограмма постепенно уменьшает выводимое на дисплей число до тех пор, пока кнопка не будет отпущена. Последнее показанное число сохраняется в EEPROM и используется всеми запускаемыми впоследствии фоновыми процессами в качестве начального значения для отсчета интервала.

Процесс самодиагностики

Если при сбросе кнопка ДИАГ. находится в замкнутом состоянии, то управление передается в подпрограмму диагностики. Основной задачей этой подпрограммы является проверка всех периферийных устройств, чтобы облегчить нахождение неисправного узла.

Все процессы зависят от задачи формирования временных отсчетов, которая предоставляет базовую информацию о реальном времени. Как показано в Программе 17.1, эта задача реализована в виде обработчика прерывания от Таймера 0. Предделитель таймера сконфигурирован таким образом, чтобы при системной тактовой частоте 3.2763 МГц переполнение таймера происходило бы каждые 1/50 с. Поскольку прерывание от Таймера 0 разрешено (см. Программу 17.3), микроконтроллер будет переходить к обработчику прерывания при каждом переполнении таймера — каждые 256 импульсов с выхода предделителя. Учитывая, что частота внутреннего тактового сигнала составляет 1/4 от частоты резонатора, коэффициент деления предцелителя, равный 64, позволит нам формировать отсчеты времени 50 раз в секунду, т. е. 3.2763x106/4x64x256 = 50

Итак, обработчик прерывания будет выполнять следующие задачи:

1. ЕСЛИ кнопка ПАУЗА отпущена, ТО

а) Декрементировать счетчики на один тик.

б) Если прошла секунда, то установить соответствующий флаг.

2. ИНАЧЕ

а) Переключить флаг состояния ПАУЗА.

б) ЕСЛИ он установлен, ТО показать, что работа таймера приостановлена.

в) ИНАЧЕ отобразить время (нормальная работа).

г) Ждать, пока не будет отпущена кнопка УСТ.

3. Выйти из прерывания.

Программа 17.1. Обработка времени

; *********************

; * При каждом вызове обработчика прерывания внутренний *

; * счетчик увеличивается на 20-мс дискрет *

; * Каждую секунду в NEW_SEC заносится ненулевое значение *

; *********************

; Сначала сохраним контекст

ISR movwf _work; Сохраняем W

      swapf STATUS,w ; и регистр STATUS

      movwf _status

; ================

; Основной код

      btfss INTCON,T0IF ; Произошло переполнение Таймера 0?

         goto ISR_EXIT ; ЕСЛИ нет, ТО ложная тревога

      btfsc Pause,0 ; Проверяем флаг паузы

         goto ISR_EXIT ; ЕСЛИ нажата, не инкрементируем

      incf JIFFY,f ; Регистрируем очередные 1/50 с

      movlw d’50’ ; Досчитали до 50?

      subwf JIFFY,w

      btfss STATUS,Z

         goto ISR_EXIT ; ЕСЛИ нет, ТО выходим

      clrf JIFFY ; ИНАЧЕ обнуляем счетчик дискретов

      movf SECOND,f ; Счетчик секунд равен нулю?

      btfsc STATUS,Z

         goto NEW_MIN ; ЕСЛИ да, ТО смотрим минуты

      decf SECOND,f ; ИНАЧЕ декрементируем счетчик секунд,

      incf NEW_SEC,f ; извещаем основную программу о прохождении секунды

         goto ISR_EXIT ; и выходим

NEW_MIN movlw d’59’ ;Реинициализируем счетчик секунд

        movwf SECOND

        movf MINUTE,f ; Счетчик минут равен нулю?

        btfsc STATUS,Z

          goto ISR_EXIT ; ЕСЛИ да, ТО делать больше нечего

       decf MINUTE,f ; ИНАЧЕ декрементируем счетчик минут

; *************************

ISR_EXIT btfss PORTB,PAUSE ; Проверяем кнопку ПАУЗА

       call FREEZE ; ЕСЛИ нажата, ТО обновляем флаг паузы

       bcf INTCON,T0IF ; Сбрасываем флаг прерывания

       swapf _status,w ; Восстанавливаем регистр STATUS

       movwf STATUS

       swapf _work,f ; Восстанавливаем W,

       swapf _work,w ; не меняя состояния STATUS,

       retfie ; и возвращаемся из прерывания

; ***********************

; * ФУНКЦИЯ: Инкрементирует флаг паузы. *

; * ЕСЛИ 1, ТО отображает десятичные точки *

; * ЕСЛИ 0, ТО отображает нормальный отсчет минут *

; * РЕСУРСЫ П/п SPI_WRITE, переменная Pause *

; * ВХОД;Кнопка ПАУЗА нажата *

; * ВЫХОД Кнопка ПАУЗА отжата; соответствующая индикация *

*************************

FREEZE incf Pause,f ; Обновляем 0-й бит флага паузы

            btfss Pause,0 ; Проверяем его состояние

              goto UNFREEZE ; Переход 1 —> 0, разблокируем

; Дисплей заблокирован

            movlw b’01111111’ ;Код для десятичной точки

            movwf DATA_OUT_L

            movwf DATA_OUT_H

            call SPI_WRITE

               goto FREEZE EXIT

UNFREEZE ; Сюда переходим, если 0-й бит флага изменился 1 —> 0.

            movf MINUTE,w ; Отображаем оставшееся количество минут

            call OUTPUT

FREEZE_EXIT

            btfss PORTB,PAUSE ; Ждем отпускания кнопки

               goto FREEZE_EXIT ; Сбрасываем таймер/предделитель

            clrf TMR0

           return

Из Программы 17.1 видно, что время хранится в виде 3-байтного числа в регистрах MINUTES, SECOND и JIFFY. Полагая, что 0-й бит регистра PAUSE сброшен в 0, значение счетчика тиков JIFFY увеличивается на единицу. Обычно после этого выполняется выход из обработчика прерывания, однако если JIFFY становится равным 50, то он обнуляется и декрементируется счетчик секунд SECOND. При этом в регистр NEW_SEC заносится ненулевое значение, извещающее фоновый процесс о том, что прошла секунда. Когда счетчик секунд становится равным нулю, в него снова загружается константа 59, а счетчик минут MINUTES декрементируется. Описанная процедура похожа на операцию инкрементирования счетчика, которую мы реализовали в Примере 7.3.

Задача формирования временных отсчетов также поддерживает функцию приостановки счета. Самым простым решением был бы пропуск декрементирования счетчиков при нажатой кнопке ПАУЗА. Однако если потребуется удерживать кнопку нажатой дольше нескольких минут, то эта операция может оказаться довольно утомительной.

Формирование останова/пуска по последовательным нажатиям кнопки является более эргономичным и может быть достаточно легко реализовано программно. И уж точно, это гораздо лучше, чем использовать кнопку какого-либо другого типа (скажем, кнопку с фиксацией). В Программе 17.1 код обработки нажатия кнопки ПАУЗА вынесен в отдельную подпрограмму FREEZE. Мы вполне можем вызвать подпрограмму из обработчика прерывания точно так же, как и из другой подпрограммы. Аппаратный стек позволяет использовать до 8 уровней вложенности. В нашем случае будет использовано только два уровня стека.

Подпрограмма FREEZE вызывается только в случае замыкания кнопки ПАУЗА. При каждом входе в подпрограмму изменяется состояние 0-го бита регистра PAUSE, что реализуется простым инкрементированием регистра.

После переключения PAUSE[0] проверяется его состояние и, если он равен 1, то вызывается подпрограмма SPI_WRITE для вывода на индикаторы только двух десятичных точек. Разумеется, это только один из множества возможных способов отображения состояния паузы. Можно, например, выводить на индикатор символы . Если же PAUSE[0] равен 0, то вызывается подпрограмма OUTPUT, в которую передается значение счетчика минут и убирается индикация паузы.

Выход из подпрограммы осуществляется только после отпускания кнопки ПАУЗА. Это очень важно, так как в противном случае при входе в обработчик прерывания по следующему переполнению Таймера 0 могло бы произойти повторное (ложное) переключение флага паузы. Чтобы предотвратить влияние дребезга контактов кнопки, после ее отпускания сначала обнуляется Таймер 0 со своим предделителем и только после этого сбрасывается флаг прерывания T0IF, благодаря чему повторная проверка состояния кнопки будет произведена только через 1/50 секунды.

Вывод на индикаторы содержимого рабочего регистра в виде десятичного числа осуществляется подпрограммой OUTPUT, код которой приведен в Программе 17.2. Эта подпрограмма выполняет следующие операции:

1. Преобразует двоичное число в 2-разрядное BCD-число.

2. Преобразует значения обоих разрядов в коды 7-сегментного индикатора.

3. Пересылает каждый байт в соответствующий индикатор по последовательному каналу.

Программа 17.2. Функция вывода на индикаторы

; *************************

; * ФУНКЦИЯ: Выводит на индикаторы 2-разрядное десят. число *

; * РЕСУРСЫ: П/п BIN_2_BCD, SPI_WRITE, SVN_SEG *

; * РЕСУРСЫ: Перем. DATA_OUT_L, DATA_OUT_H, NEW_SEC, NUMBER *

; * ВХОД: Число в W (от 0 до 99) *

; * ВЫХОД: Число выводится, NEW_SEC обнуляется *

; *************************

OUTPUT bcf PORTA,SCK ; Инициализируем линию тактового сигнала

             call BIN_2_BCD ; Преобразовываем в BCD

             movwf NUMBER ; Сохраняем результат в NUMBER

             movf NUMBER,w ; Берем число, которое нужно вывести

             andlw b’00001111’ ; Выделяем число единиц

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf DATA_OUT_L ; Копируем в младший регистр

             swapf NUMBER,w ; Перегружаем число десятков в младший полубайт

             andlw b’00001111’ ; Выделяем число десятков

             call SVN_SEG ; Преобразуем в 7-сегментный код

             movwf DATA_OUT_H ; Копируем в старший регистр

             call SPI_WRITE ; Передаем значение обоих разрядов

             clrf NEW_SEC ; Обнуляем флаг NEW_SEC

             return

; *************************

; * ФУНКЦИЯ: Одновременно передает два байта по последовательному каналу *

; * ВХОД: Значения в DATA_OUT_L (младший разряд) *

; * ВХОД: и DATA_OUT_H (старший разряд) *

; * ВЫХОД DATA_OUT_L и DATA_OUT_H изменяются *

; *************************

SPI_WRITE

             bcf PORTA,SCK ; Выставляем НИЗКИЙ уровень на SCK

             movlw 8 ; Инициализируем счетчик цикла

             movwf COUNT

LOOP     bcf PORTA,SDOH ; Выставляем 0 на линию данных старшего разряда

             rlf DATA_OUT_H,f ; Выдвигаем младший бит в бит переноса

             btfsc STATUS,С ; ЕСЛИ С == 0, ТО пропускаем

               bsf PORTA,SDOH ; ИНАЧЕ выставляем на линию данных 1

             bcf PORTA,SDOL ; Выставляем 0 на линию данных младшего разряда

             rlf DATA_OUT_L,f ; Выдвигаем младший бит в бит переноса

             btfsc STATUS,С ; ЕСЛИ С == 0, ТО пропускаем

             bsf PORTA,SDOL ; ИНАЧЕ выставляем на линию данных 1

             bsf PORTA,SCK ; Формируем тактовый импульс

             bcf PORTA,SCK

Преобразование двоичного кода в код 7-сегментного индикатора

Подпрограмма SVN_SEG преобразует младший полубайт содержимого регистра W в соответствующий код 7-сегментного индикатора. Код подпрограммы полностью эквивалентен приведенному в Программе 6.6 (стр. 184).

Вывод по SPI

Подпрограмма SPI_WRITE похожа на свою тезку, реализованную в Программе 12.1 на стр. 371, но формирует два потока последовательных данных. Число, находящееся в регистре DATA_OUT_L, передается по линии RA3, тогда как число, находящееся в регистре DATA_OUT_H, — по линии RA0. Оба канала используют общий тактовый сигнал.

Прежде чем перейти к кодированию процессов, составляющих программу, вкратце рассмотрим используемую конфигурацию микроконтроллера, задаваемую при его программировании, а также инициализационный код, выполняемый после сброса микроконтроллера (см. Программу 17.3).

Программа 17.3. Инициализационный код

        include "p16f627a.inc

SDOH equ 0

SCK equ 1

BUZ equ 2

SDOL equ 3

GREEN equ 5

YELLOW equ 6

RED equ 7

PAUSE equ 0

DIAG equ 1

STOP equ 2

SETT equ 3

GO equ 4

       cblock 20h

         MINUTE:1, SECOND:1, JIFFY:1, NUMBER:1, NEW_SEC:1

         DATA_OUT_L:1, DATA_OUT_H, COUNT:1, TEMP:1, TIME_OUT:1

         Pause:1, _work:1, _status:1

       endc

       __config _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _LVP_OFF & _MCLRE_ON

       org 2100h ; Область EEPROM

       de d’10’ ; Значение по умолчанию — 10 мин

RESET org 0 ; Вектор сброса

Преобразование двоичного кода в код 7-сегментного индикатора

Подпрограмма SVN_SEG преобразует младший полубайт содержимого регистра W в соответствующий код 7-сегментного индикатора. Код подпрограммы полностью эквивалентен приведенному в Программе 6.6 (стр. 184).

Вывод по SPI

Подпрограмма SPI_WRITE похожа на свою тезку, реализованную в Программе 12.1 на стр. 371, но формирует два потока последовательных данных. Число, находящееся в регистре DATA_OUT_L, передается по линии RA3, тогда как число, находящееся в регистре DATA_OUT_H, — по линии RA0. Оба канала используют общий тактовый сигнал.

Прежде чем перейти к кодированию процессов, составляющих программу, вкратце рассмотрим используемую конфигурацию микроконтроллера, задаваемую при его программировании, а также инициализационный код, выполняемый после сброса микроконтроллера (см. Программу 17.3).

Программа 17.3. Инициализационный код

        include "p16f627a.inc

SDOH equ 0

SCK equ 1

BUZ equ 2

SDOL equ 3

GREEN equ 5

YELLOW equ 6

RED equ 7

PAUSE equ 0

DIAG equ 1

STOP equ 2

SETT equ 3

GO equ 4

        cblock 20h

          MINUTE:1, SECOND:1, JIFFY:1, NUMBER:1, NEW_SEC:1

          DATA_OUT_L:1, DATA_OUT_H, COUNT:1, TEMP:1, TIME_OUT:1

          Pause:1, _work:1, _status:1

          endc

          __config _XT_OSC & _WDT_OFF & _PWRTE_ON & _CP_OFF & _LVP_OFF & _MCLRE_ON

          org 2100h; Область EEPROM

          de d’10’ ; Значение по умолчанию — 10 мин

RESET org 0 ; Вектор сброса

          goto MAIN

          org 4 ; Вектор прерывания

           goto ISR

MAIN  bsf STATUS,RP0 ; Переключаемся в 1-й банк

          movlw b’11100000’ ; RA4:0 — выходы

          movwf TRISA

          movlw Ь’11100000’ ; RB7:5 — выходы; RB4:0 — входы

          movwf TRISB

          movlw b’00000101’ ; Таймер 0: внутр. такт. сигнал,

          movwf OPTION_REG ; предделитель 1:64. Подтяжка вкл.

          bcf STATUS,RP0 ; Возвращаемся в 0-й банк

          clrf Pause ; Обнуляем флаги паузы

          clrf NEW_SEC ; и секунды

          clrf TMR0

          bcf INTCON,T0IF

          bsf INTCON,T0IE ;Разрешаем прерывание от Таймера 0

          bsf INTCON,GIE ;Разрешаем все прерывания

          btfss PORTB,SETT ;Проверяем кнопку УСТ.

            call SET_TIME ;ЕСЛИ нажата, ТО устанавливаем интервал

          btfss PORTB,DIAG ;Проверяем кнопку ДИАГ.

            call DIAGNOSTIC ;ЕСЛИ нажата, ТО выполняем самодиагностику

Конфигурирование кристалла

С помощью директивы __config задается состояние конфигурационных ячеек в слове конфигурации кристалла. Сторожевой таймер отключен, генератор работает с внешним кварцевым резонатором, также задействован вход внешнего сброса . Запрещение низковольтного программирования высвобождает вывод RB3 для нужд ввода/вывода (см. стр. 312).

При прошивке микроконтроллера в ячейку EEPROM с адресом h’00’ заносится число 10. Это означает, что интервал счета только что запрограммированного микроконтроллера составляет 10 мин. Данное значение впоследствии можно изменять посредством процедуры установки интервала.

Эта ячейка расположена в адресном пространстве специальной области памяти по адресу h’2100’, а для указания значения, заносимого в эту область памяти на этапе программирования, используется директива de, как было описано на стр. 549.

Выполнение программы

Код, выполняемый при каждом сбросе микроконтроллера, используется для инициализации рабочего окружения.

Вектора

По адресу вектора сброса (h’000’) расположен переход к основной программе MAIN, а по адресу вектора прерывания (h’004’) — команда перехода к процедуре обработки прерывания.

Конфигурирование портов

Линии PORTA[4:0] и PORTB[7:5] конфигурируются как выходы, а остальные линии портов используются как входы.

Конфигурирование Таймера 0

Коэффициент деления предделителя задается равным 64, в качестве источника тактовых импульсов Таймера 0 используется системный тактовый сигнал. Также разрешается прерывание от Таймера 0.

Выбор текущего процесса

Для выбора процесса, которому следует передать управление, проверяется состояние кнопок ДИАГ. и УСТ. Если ни одна из кнопок не нажата, то осуществляется переход к процессу MAIN.

Если в момент сброса микроконтроллера нажата кнопка ДИАГ., то управление передается в подпрограмму DIAGNOSTIC, код которой приведен в Программе 17.4. Задачей процесса диагностики является тестирование различных периферийных устройств, подключенных к процессору, с целью проверки целостности цепей и исправности собственно устройств.

Программа 17.4. Процедура самодиагностики

; *****************

; * ФУНКЦИЯ: Проверяет состояние кнопок и включает соотв. *

; * ФУНКЦИЯ: СИД или звуковой излучатель. Поочередно включает*

; * ФУНКЦИЯ: по одному сегменту на каждом индикаторе *

; * РЕСУРСЫ: Подпрограмма SPI_WRITE *

; * РЕСУРСЫ: Переменные TEMP, DATA_OUT_H, DATA_OUT_L *

; * ВХОД: Кнопка ДИАГ. нажата *

; * ВЫХОД: Кнопка ДИАГ. отпущена *

; *****************

DIAGNOSTIC

            movlw b’11111110’ ; Формируем начальное значение

            movwf TEMP ; маски управления индикаторами

D_LOOP movlw b’11111111’; Выключаем все СИД и пищалку

             movwf PORTB

             bsf PORTA,BUZ

; Сканируем кнопки

             btfss PORTB,PAUSE ; ЕСЛИ нажата кнопка ПАУЗА,

               bcf PORTB,GREEN ; TO включаем зеленый СИД

             btfss PORTB,STOP ; ЕСЛИ нажата кнопка СТОП,

               bcf PORTB,YELLOW ; ТО включаем желтый СИД

             btfss PORTB,SETT ; ЕСЛИ нажата кнопка УСТ.,

               bcf PORTB,RED ; ТО включаем красный СИД

             btfss PORTB,GO ; ЕСЛИ нажата кнопка ПУСК,

                bcf PORTA,BUZ ; ТО включаем пищалку

; Теперь по очереди включаем все сегменты на обоих индикаторах

             movf TEMP,w ; Берем маску

             movwf DATA_OUT_L ; Копируем в регистры последовательной

             movwf DATA_OUT_H ; передачи

             call SPI_WRITE ; Передаем ее

             btfsc PORTB,DIAG ; ЕСЛИ кнопка ПАУЗА отпущена,

             return ; ТО выходим из процедуры самодиагностики

             clrf NEW_SEC ; Сбрасываем флаг секунды

; Теперь сдвигаем маску для индикаторов и ждем 1 секунду

             bcf STATUS,С ; Сбрасываем бит переноса

             btfsc TEMP,7 ; Проверяем старший бит маски

               bsf STATUS,С ; ЕСЛИ 1, ТО устанавливаем флаг переноса

             rlf TEMP,f ; Вдвигаем его в регистр

D_LOOP2 movf NEW_SEC,f ; Ждем секунду

               btfsc STATUS,Z ; ЕСЛИ флаг не равен нулю, ТО пропускаем

                  goto D_LOOP2 ; ИНАЧЕ пробуем снова

               goto D_LOOP ; Повторяем процедуру

Кнопки

Поочередно проверяются все пять кнопок, подключенных к порту В. При замыкании кнопки включается либо один из СИД, либо звуковой излучатель. Таким образом, проверяется состояние кнопок и соответствующих органов индикации. Разумеется, работоспособность кнопки ДИАГ. проверяется по переходу системы в режим диагностики, а работоспособность кнопки СБРОС — по запуску процесса инициализации.

Если число органов управления в устройстве больше числа органов индикации, то можно либо включать определенные комбинации индикаторов, либо задействовать для этой цели один или более сегментов 7-сегментного индикатора.

СИД и звуковой излучатель

Устройства вывода статической информации проверяются совместно с кнопками, как описано выше. Разумеется, отсутствие свечения у СИД или звука у излучателя может быть обусловлено неисправностью как входного, так и выходного узла. Какой именно из узлов неисправен, достаточно легко выясняется при помощи вольтметра или логического пробника. Помните также, что светодиоды должны светиться во время установки временного интервала.

Дисплей

Каждый из индикаторов дисплея проверяется путем поочередного включения одного из сегментов с периодом в одну секунду. Это реализуется формированием значения с «бегущим нулем» (b’11111110’ —> Ь’11111101’ —> … —> b’01111111’), которое передается подпрограммой SPI_WRITE при каждом ненулевом значени регистра NEW_SEC. Данный регистр инкрементируется в обработчике прерывания от Таймера 0 при каждом инкрементировании счетчика секунд и сбрасывается в процедуре диагностики. То есть он играет роль храпового механизма, обеспечивая вывод каждого нового символа не ранее чем через секунду.

Процесс установки интервала запускается в том случае, если при выходе микроконтроллера из состояния сброса кнопка УСТ. оказывается замкнутой. Данный процесс предназначен для того, чтобы оператор мог изменить содержимое ячейки EEPROM с адресом h’00’ на любое значение от 1 до 99. В этой ячейке хранится начальное значение, используемое основным процессом для определения длительности процедуры счета.

В этой подпрограмме, код которой приведен в Программе 17.5, сначала в счетчик секунд записывается число 99, которое затем декрементируется с периодом в одну секунду в соответствии с логикой работы обработчика прерывания. Содержимое регистра SECOND передается в подпрограмму вывода на дисплей каждый раз, когда обработчик прерывания записывает в регистр-флаг NEW_SEC ненулевое значение, т. е. каждую секунду. В подпрограмме DISPLAY регистр NEW_SEC обнуляется, благодаря чему обновление дисплея происходит опять же только раз в секунду. Кроме того, каждую секунду проверяется состояние кнопки УСТ. — при ее размыкании состояние счетчика секунд сохраняется в EEPROM в секции UPDATE вызовом подпрограммы низкого уровня EE_PUT из Программы 15.2 (стр.547).

Программа 17.5. Процедура установки временного интервала

; *****************

; * ФУНКЦИЯ: Медленно считает от 99 до 00. При отпускании

; * кнопки УСТ. в EEPROM заносится новое значение интервала

; * РЕСУРСЫ: П\п DISPLAY, EE_PUT, ISR; переменная TIME_OUT *

; * ВХОД: Кнопка УСТ. нажата *

; * ВЫХОД: Обновляется содержимое EEPROM по адресу 00 *

; *****************

SET_TIME movlw d’99’ ; Начинаем счет со значения 99

               movwf SECOND

               movlw b’00000000’ ; Включаем все СИД

               movwf PORTB

SET_LOOP movf SECOND,w ; Берем значение счетчика секунд

               call OUTPUT ; и выводим его на дисплей

               btfsc PORTB,SETT ; Проверяем, не надо ли прекратить счет?

                  goto UPDATE ; ЕСЛИ да, ТО обновляем EEPROM и выходим

               movf SECOND,w ; Берем отображаемое число

               movwf TIME_OUT ; Делаем временную копию

SLOOP movf NEW_SEC,f ; Проверяем флаг секунды

               btfsc STATUS,Z ; ЕСЛИ не ноль, ТО пропускаем

               goto S_LOOP ; ИНАЧЕ проверяем снова

               goto SET_LOOP ; Повторяем

UPDATE movf TIME_OUT,w ; Берем значение

             movwf EEDATA ; Инициализируем EEPROM

             clrf EEADR

             call EE_PUT ; Пишем в EEPROM

             return ; и возвращаемся в основную программу

Полная блок-схема алгоритма работы фоновой программы приведена на Рис. 17.3. На данной схеме показана процедура выбора требуемого процесса после сброса, а также подробная структура основной фоновой процедуры. Хотя эта блок-схема кажется довольно сложной, ее можно разбить на пять фаз, код которых приведен в Программе 17.6.

Рис. 17.3. Блок-схема основной фоновой процедуры

Программа 17.6 . Основная фоновая процедура

     movlw b’11000000’ ; Включаем зеленый СИД

     movwf PORTB

     bsf PORTA,BUZ ; Выключаем звук

; Считываем начальное значение из EEPROM

     clrf EEADR ; Адрес в EEPROM — 00

     call EE_GET ; Считываем начальное значение

     movwf MINUTE

     movlw d’59’ ; Начальное значение секунд

     movwf SECOND ; равно 59

     clrf JIPPY

DISPLAY movf MINUTE,w ; Берем значение минут

             call OUTPUT ; Выводим его на дисплей

; Фаза 2-минутной готовности ------------

; За две минуты до конца включаем звук на одну секунду и включаем

; желтый светодиод

TWO movf MINUTE,w ; Счетчик минут =2?

        addlw -2

        btfss STATUS,Z

           goto ONE ; ЕСЛИ нет, ТО проверим след, фазу

        movlw b’10100000’ ; Включаем желтый СИД

        movwf PORTB

        bcf PORTA,BUZ ; Включаем пищалку

TWO_LOOP movf NEW_SEC,f ; Проверяем флаг NEW_SEC

        btfsc STATUS,Z ; ЕСЛИ не ноль, ТО пропускаем

           goto TWO_LOOP ; ИНАЧЕ проверяем снова

        bsf PORTA,BUZ ; Выключаем пищалку через 1 секунду

          goto REPEAT ; Выводим интервал на дисплей

; Фаза 1-минутной готовности —

; За одну минуту до конца включаем звук на две секунды и включаем красный светодиод

ONE movf MINUTE,w ; Счетчик минут = 1?

       addlw -1

       btfss STATUS,Z

          goto ZERO ; ЕСЛИ нет, ТО проверим след, фазу

       movlw b’01100000’ ; Включаем красный СИД

       movwf PORTB

       bcf PORTA,BUZ ; Включаем пищалку

ONE_LOOP movf NEW_SEC,f ; Проверяем флаг NEW_SEC

       btfsc STATUS,Z ; ЕСЛИ не ноль, ТО пропускаем

          goto ONE_LOOP ; ИНАЧЕ проверяем снова

       clrf NEW_SEC ; Сбрасываем флаг NEW_SEC

UN_LOOP movf NEW_SEC,f ; Проверяем флаг NEW_SEC

       btfsc STATUS,Z ; ЕСЛИ не ноль, ТО пропускаем

          goto UN_LOOP ; ИНАЧЕ проверяем снова

       bsf PORTA,BUZ ; Выключаем пищалку через 2 секунды

          goto REPEAT ; Выводим интервал на дисплей

; Фаза тайм-аута —

; Когда счетчик минут становится равным нулю, включаем пищалку

; до тех пор, пока не будет нажата кнопка СТОП

ZERO movf MINUTE,f ; Счетчик минут =0?

        btfss STATUS,Z

           goto REPEAT ; ЕСЛИ нет, ТО повторим проверку через

        bcf PORTA,BUZ ; Включаем пищалку

ZERO_LOOP

        btfsc PORTB,STOP ; Проверяем кнопку СТОП

           goto ZERO_LOOP ; и продолжаем, пока не будет нажата

FINI movlw b’11100000’ ; Выключаем индикаторы

        movwf PORTB

        bsf PORTA,BUZ ; и пищалку

        movlw b’11111111’ ; Код для очистки индикаторов

        movwf DATA_OUT_L

        movwf DATA_OUT_H

        call SPI_WRITE ; Очищаем оба индикатора

        sleep ; и ждем следующего сброса

REPEAT btfss PORTB,STOP ; Проверяем кнопку СТОП

            goto FINI ; ЕСЛИ нажата, ТО прекращаем работу

        movf SECOND,f ; Ждем обнуления счетчика секунд,

        btfss STATUS,Z ; т. е. наступления следующей минуты

            goto REPEAT ; ЕСЛИ нет, ТО ждем дальше

        clrf NEW_SEC ; ИНАЧЕ ждем еще секунду

R_LOOP movf NEW_SEC,f ; Проверяем флаг NEW_SEC

        btfsc STATUS,Z; ЕСЛИ не ноль, ТО пропускаем

           goto R_LOOP; ИНАЧЕ проверяем снова

        goto DISPLAY; Повторяем вывод на дисплей

Преамбула

Если в момент сброса не нажата ни кнопка УСТ., ни кнопка ДИАГ., то управление переходит к основной программе, обозначенной меткой MAIN_PROC. В этой секции осуществляется считывание значения отсчитываемого периода из ячейки EEPROM с адресом h’00’ и инициализация счетных регистров. Зеленый СИД включается, а остальные световые индикаторы и звуковой излучатель выключаются.

Обратный отсчет

В фазе обратного отсчета осуществляется периодический ВЫВОД на дисплей значения счетчика минут — обновление дисплея осуществляется в прерывании. Зеленый СИД остается во включенном состоянии до тех пор, пока на дисплее не появится число . Эта фаза завершается, когда до конца отсчитываемого интервала остается меньше 3 мин либо при нажатии на кнопку СТОП. В последнем случае все органы индикации выключаются, и микроконтроллер переходит в «спящий» режим.

Всегда, за исключением времени обработки нажатия кнопки СТОП, на дисплей выводится значение счетчика минут. В секции REPEAT проверяется значение счетчика секунд, и, если он равен нулю, цикл повторяется, т. е. период повторения равен одной минуте. Использование более простого решения, заключающегося в непрерывном обновлении 7-сегментных индикаторов, привело бы к ухудшению изображения, поскольку данные, постоянно передаваемые по последовательному интерфейсу, могли бы вызывать кратковременную засветку лишних сегментов, которые должны быть выключены. Кроме того, период повторения цикла, равный одной минуте, упрощает кратковременное включение звукового излучателя при равенстве счетчика минут двум и единице.

Две минуты до конца

Когда на дисплее появляется число , включается желтый СИД и подается звуковой сигнал длительностью в одну секунду. Длительность последнего контролируется с помощью регистра NEW_SEC. И опять же, цикл можно преждевременно завершить, нажав кнопку СТОП.

Одна минута до конца

Когда на дисплее высвечивается , включается красный СИД и подается звуковой сигнал длительностью в две секунды (в программе это реализовано в виде двух включений излучателя по 1 с каждое).

Тайм-аут

Когда счетчик минут становится равным нулю, на дисплей выводится , а излучатель начинает непрерывно генерировать звуковой сигнал. Эта какофония может быть прекращена только нажатием кнопки СТОП либо сбросом и повторным запуском таймера. Как и прежде, при нажатии на кнопку СТОП все органы индикации выключаются, и микроконтроллер переводится в «спящий» режим.

После ассемблирования исходного кода программы и, быть может, даже после симуляции (см. Рис. 8.7 на стр. 265) полученный шестнадцатеричный код можно загрузить в память программ. На первом этапе достаточно реализовать только программу диагностики и связанные с ней подпрограммы, чтобы проверить правильность монтажа устройства. Подробности процесса программирования в значительной степени зависят от используемого программатора и его программного обеспечения.

На снимке экрана, показанном на Рис. 17.4, изображено окно ИСР MPLAB при работе с программатором PICSTART Plus® компании Microchip (см. Рис. 17.5). К персональному компьютеру программатор подключается через последовательный порт RS-232, а установление соединения осуществляется из пункта меню Pic s tart Plus . Окно, показанное в правой части экрана, позволяет оператору задать требуемые значения битов конфигурации (см. левое нижнее окно). После этого оператор может выполнить команды B lank out (очистить), R ead from (считать), P rogram (запрограммировать) или V erify (верифицировать). Последняя команда позволяет убедиться, что содержимое EEPROM или памяти программ идентично коду, сгенерированному в текущем проекте. Обращаю ваше внимание, что верификацию можно выполнить только в том случае, если защита кода выключена. Если же при программировании контроллера была включена защита кода, то после завершения программирования нельзя будет выполнить ни команду V erify , ни последующие команды Program.

На среднем окне отображается состояние процесса программирования или верификации. В данном случае сообщается, что была запрограммирована память программ до ячейки с адресом h’03FF’ и что верификация прошла успешно. Весь этот процесс занимает меньше минуты, а размер нашей программы оказался равен 254 словам памяти программ.

При использовании микроконтроллеров PIC, имеющих в обозначении букву «F», процесс программирования можно повторить несколько тысяч раз без ухудшения параметров FLASH-памяти программ. Микроконтроллеры с индексом «С», такие как PIC16C74, имеют память программ EPROM-типа. Если в корпусе микросхемы имеется кварцевое окошко (см. фотографию на стр. 15), то перед повторным программированием содержимое памяти программ необходимо стереть, подвергая кристалл действию ультрафиолетового излучения в течение примерно 20 мин. Хотя модели с кварцевым окошком необходимы для разработки устройств на базе микроконтроллеров С-серии, они все-таки достаточно дороги.

Рис. 17.4. Программирование микроконтроллера из ИСР MPLAB 5-й версии

Рис. 17.5. Фирменный программатор PICSTART Plus компании Microchip

Поэтому в производстве используются более дешевые варианты, называемые однократно-программируемыми (One-Time Programmable — OTP), поскольку их стереть невозможно. Изделия с кварцевым окошком в корпусе отличаются суффиксом «JW» в обозначении. Например, PIC16C74B—20/JW является микроконтроллером PIC16C74B в керамическом корпусе с окошком, a PIC16C74B—4/Р — однократно-программируемым исполнением того же микроконтроллера с максимальной частотой 4 МГц в 40-выводном корпусе типа DIP. Удостоверьтесь, что вы заказываете правильное устройство!

Устройство, схему и программное обеспечение которого мы только что разработали, представляет собой достаточно простой пример, в котором мы попытались собрать воедино различные методики, изучавшиеся нами на протяжении всей книги. Если вы решите изготовить это устройство, то на Web-сайте книги к вашим услугам имеются исходные файлы (в том числе и вариант на языке Си), сравнение с аналогичной конструкцией на микроконтроллере 68000 фирмы Motorola, а также много других идей для экспериментирования. Удачи!