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

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

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

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

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

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

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

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

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

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

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

Вообще говоря, параллельный порт ввода/вывода может рассматриваться как обычный регистр, содержимое которого доступно остальным элементам схемы. Именно такое несколько упрощенное представление показано на Рис. 11.1. На рисунке изображена небольшая область памяти данных микроконтроллера PIC16F84, структура которой полностью была показана на Рис. 4.7 (стр. 97). В микроконтроллерах PIC16XXXX среднего уровня в обязательном порядке имеется, как минимум, 13 линий ввода/вывода. В микроконтроллерах группы PIC16F87X есть дополнительный вывод RA5 (портА), в то время как в микроконтроллерах группы PIC16F62X имеется уже три дополнительных линии, показанные на Рис. 11.1 пунктиром (если пожертвовать выводами OSC1, OSC2 и [141]. В «миниатюрных» микроконтроллерах 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)[142]. В большинстве микроконтроллеров других производителей такие регистры называются регистрами направления передачи данных (Data Direction Register — DDR), однако компания Microchip использует аббревиатуру TRIS, образованную от словосочетания TRI-State (тристабильный). Причину, по — которой регистры называются именно так, вы узнаете чуть позже в данной главе.

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

bsf STATUSfRP0; Переключаемся на 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, б, триггер данных оказывается напрямую подключенным к остальным узлам схемы.

• При чтении этого регистра данных порта включается буфер данных, в результате чего состояние защелки[143] выдается на линию шины данных микроконтроллера. Если чтения порта не происходит, то эта 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 В (падение напряжения на переходе база-эмиттер типового транзистора)[144].

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

Ситуация, изображенная на Рис. 11.5, б, аналогична предыдущей, только в этом случае ток втекает в порт микроконтроллера[145] через светодиод (СИД), в результате чего напряжение на входе буфера 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-выводных корпусах, имеет схожую функциональность[146]. Причем для большей гибкости реакция на изменение состояния может быть разрешена или запрещена индивидуально для каждого из выводов порта.

При использовании этой возможности необходимо быть очень аккуратным. Например, при изменении младших битов порта В (скажем, командой 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 выходов). Вместо того чтобы устанавливать в каждой зоне по микроконтроллеру, каждый из которых отсылал бы информацию центральному контроллеру[147], было принято решение расширить возможности ввода/вывода одного-единственного микроконтроллера 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. Зависимость выходного напряжения ВЫСОКОГО уровня оттока

Крутизна нагрузочной линии ΔVI представляет собой сопротивление в кОм (так как ток выражается в мА) и получается равной 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°[148]. Так, обмотка А формирует поле в направлении север, А + В — в направлении северо-восток, В — восток и т. д. Соответственно ротор вращается вслед за изменением направления магнитного поля, при условии, что конструкция двигателя обеспечивает стабилизацию положения ротора при разгоне и торможении.

Рис. 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 раз в секунду), то из-за инерционности системы зрения будет казаться, что индикаторы не мерцают[149]. Разумеется, ток, протекающий через каждый сегмент, следует увеличить, чтобы скомпенсировать снижение яркости, вызванное импульсным режимом работы. А поскольку в таком режиме СИД работают более эффективно, зависимость между сопротивлением последовательных резисторов и коэффициентом заполнения управляющего сигнала будет не прямо пропорциональной.

Рис. 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. Зависимость выходного напряжения низкого уровня от тока

Более 800 000 книг и аудиокниг! 📚

Получи 2 месяца Литрес Подписки в подарок и наслаждайся неограниченным чтением

ПОЛУЧИТЬ ПОДАРОК