Доступ к внешним устройствам
С точки зрения центрального процессора и исполняющейся на нем программы, внешние устройства представляют собой наборы специализированных ячеек памяти или, если угодно, регистров. У микроконтроллеров эти ячейки памяти представляют собой регистры центрального процессора. У процессоров общего назначения регистры устройств обычно подключаются к шинам адреса и данных. Устройство имеет адресный дешифратор. Если выставленный на шине адрес соответствует адресу одного из регистров устройства, дешифратор подключает соответствующий регистр к шине данных. Таким образом, регистры устройства получают адреса в физическом адресном пространстве процессора. Схема подключения приведена на рисунке.
Два основных подхода к адресации этих регистров – это отдельное адресное пространство ввода-вывода и отображенный в память ввод-вывод (memory-mapped I/O), когда память и регистры внешних устройств размещаются в одном адресном пространстве. Такая архитектура называется Гарвардской. В первом случае для обращения к регистрам устройств используются специальные команды класса IN и OUT. Во втором случае могут использоваться любые команды, способные работать с операндами в памяти. Как правило, даже в случае раздельных адресных пространств, для обмена данными с памятью и внешними устройствами процессор использует одни и те же шины адреса и данных, но имеет дополнительный сигнал адресной шины, указывающий, какое из адресных пространств используется в данном конкретном цикле. Данная архитектура называется Принстонской.
Любопытный гибридный подход, сочетающий преимущества обоих вышеназванных, предоставляют микропроцессоры с системой команд SPARC v9. У этих процессоров команды имеют поле, служащее селектором адресного пространства. Этот селектор, в частности, может использоваться для выбора адресного пространства памяти или ввода-вывода. Благодаря этому, с одной стороны, можно применять для работы с регистрами портов любые команды работы с памятью, как при отображенном в память вводе-выводе, и в то же время полностью задействовать адресное пространство памяти.
Два основных подхода к выделению адресов внешним устройствам – это фиксированная адресация, когда одно и то же устройство всегда имеет одни и те же адреса регистров, и географическая адресация, когда каждому разъему периферийной (или системной, если внешние устройства подключаются непосредственно к ней) шины соответствует свой диапазон адресов. Географически можно распределять не только адреса регистров, но и другие ресурсы – линии запроса прерывания, каналы ПДП. Географическая адресация обладает свойством, которое на первый взгляд кажется противоестественным: перемещение платы устройства в другой разъем приводит к необходимости переконфигурации ОС (а в некоторых случаях, например, если перемещенная плата была контроллером загрузочного диска, а вторичный загрузчик или процедура инициализации ядра недостаточно сообразительны, может даже привести к ошибкам при загрузке). Однако этот способ распределения адресного пространства удобен тем, что исключает возможность конфликта адресов между устройствами разных производителей или между двумя однотипными устройствами (с этой проблемой должен быть знаком каждый, кто пытался одновременно установить в компьютер сетевую и звуковую карты конструктива ISA). Большинство периферийных шин современных мини- и микрокомпьютеров, такие, как PCI, S-Bus и др., реализуют географическую адресацию.
Многие современные конструктивы требуют, чтобы кроме регистров управления и данных устройства имели также конфигурационные регистры, через обращение к которым ОС может получить информацию об устройстве: фирму-изготовителя, модель, версию, количество регистров и т. д. Наличие таких регистров позволяет ОС без вмешательства (или с минимальным вмешательством) со стороны администратора определить установленное в системе оборудование и автоматически подгрузить соответствующие управляющие модули.
Простые внешние устройства
По-видимому, самым простым из мыслимых (а также и из используемых) внешних устройств является порт вывода. Такие устройства являются стандартным компонентом большинства микропроцессорных систем. У микропроцессоров первых поколений порты реализовались отдельными микросхемами, у современных микроконтроллеров они обычно интегрированы в один кристалл с процессором.
Порт вывода представляет собой регистр и несколько выходных контактов. В литературе входные и выходные контакты микросхем обычно называют просто входами и выходами. Количество выходов порта, как правило, соответствует, и никогда не превосходит количества битов в регистре. Если в бит регистра записан ноль, напряжение на выходе порта будет низким, а если единица, то, соответственно, высоким. Большинство современных микропроцессорных комплектов используют так называемые ТТЛ-совместимые напряжения, когда нулю соответствует напряжение 0 В, а единице – 5 В или, при работе от источника питания с более низким напряжением, напряжение этого самого источника.
Поскольку и внутри процессора для представления нулей и единиц также используется высокое и низкое напряжение, порт полезен прежде всего тем, что имеет регистр – чтобы удержать напряжение на выходе, процессору достаточно один раз установить значение регистра. Кроме того, польза от порта состоит в том, что поддержание напряжения на выходе может потребовать пропускания через этот выход тока. Порты современных микроконтроллеров имеют внутреннее сопротивление около 200 Ом и способны без вреда для себя пропустить через вывод ток до 25 mA. Выводы шин адреса и данных микропроцессоров, как правило, рассчитаны на гораздо меньшие токи. Кроме того, порты часто имеют встроенные механизмы защиты от короткого замыкания, статического электричества и т. д. Применения порта вывода многообразны. Например, к нему можно присоединить светодиод и получить лампочку, миганием которой можно программно управлять (такие диоды часто используются при отладке программ для микроконтроллеров – вместо диагностической печати). Присоединив к выводу порта динамик, можно издавать различные звуки. Кроме того, к выводу порта можно присоединить внешнюю цифровую, аналоговую или электромеханическую схему (например, шаговый электродвигатель), которая будет выполнять какую-то полезную работу. Таким образом, порт вывода, как правило, не является внешним устройством сам по себе, а служит интерфейсом между микропроцессором и собственно внешним устройством.
Другое столь же простое устройство – это порт ввода. Порт ввода также состоит из регистра и нескольких входных линий, соответствующих битам регистра. Бит регистра имеет значение 0, если на вход подано низкое напряжение и, наоборот, 1 – если высокое. Понятно, что напряжение практически никогда не соответствует в точности 0 или 5 В, поэтому в спецификациях портов ввода всегда указывают диапазон напряжений, которые считаются нулем (например, от 0 до 0.5 В) и единицей (например, от 4 до 5 В), для промежуточных же напряжений значение соответствующего бита не определено (а на практике определяется случайными факторами). Регистр порта ввода часто называют регистром-защелкой (latch register), потому что основная его функция – зафиксировать напряжения на входах в определенный момент времени и передать их центральному процессору в виде однозначно (пусть и негарантированно правильно) определенных значений.
Разработчики микросхем часто совмещают входы портов ввода и выходы портов вывода, создавая таким образом комбинированное устройство – порт ввода-вывода. Такое устройство должно быть существенно сложнее, чем простая комбинация порта ввода и порта вывода.
Порты передачи данных
Порты ввода-вывода преимущественно используются для управления простыми внешними устройствами: если бит установлен, мотор крутится (заслонка открыта, нагреватель включен и т. д.), и наоборот. Если же устройство более сложное, и работа с ним предполагает обмен последовательностями команд и ответов, или просто большими объемами данных, простой порт оказывается не очень удобен. Основная проблема при использовании простого порта в качестве средства обмена данными состоит в том, что принимающему устройству необходимо знать, выставило ли передающее устройство на своих выходах новую порцию данных, или еще нет. Три основных подхода к решению этой проблемы называются синхронной, асинхронной и изохронной передачами данных.
При синхронной передаче мы либо предоставляем дополнительный сигнал, строб, либо тем или иным способом передаем синхросигналы по тем же проводам, что и данные. Например, можно установить, что каждая следующая порция данных должна хотя бы одним битом отличаться от предыдущей. При этом необходимо предусмотреть протокол, посредством которого передатчик будет кодировать, а приемник декодировать повторяющиеся последовательности символов. Например, второй символ из пары одинаковых последовательных символов можно заменять на специальный символ повторения, впрочем, в этом случае нам необходимо предусмотреть и способ кодирования символа, совпадающего по значению с символом повторения. Передача стробирующего сигнала требует прокладки дополнительных проводов, но с реализационной точки зрения гораздо проще совмещения синхросигнала и данных и поэтому широко применяется в самых разнообразных ситуациях. Большинство стробируемых портов асимметричны: одно из устройств, ведущий (master – хозяин), генерирует стробовый сигнал, а второе, ведомый (slave – раб), пользуется этим сигналом для приема или передачи. Нередко вместо одиночного строба используется несколько различных сигналов – например, один сигнал выставляется передатчиком и сообщает, что следующая порция данных готова, а второй сигнал – приемником и сообщает, что приемник принял эти данные и готов принять следующие. Дополнительные сигналы могут также решать вопрос о том, какое из устройств в данном цикле будет приемником, а какое – передатчиком. В качестве примера синхронной передачи может служить интерфейс I2C.
При асинхронном обмене данными передающее устройство посылает специальный стартовый символ, сигнализирующий о том, что сейчас пойдут данные, и с фиксированным интервалом выставляет на своих выходах символы данных. Передаваемый за один прием блок данных обычно невелик по объему – ведь необходимо считаться с опасностью того, что часы приемника и передатчика, посредством которых они отмеряют интервал между последовательными порциями данных, недостаточно точны и могут разойтись. Обычно блок данных состоит из фиксированного количества символов и называется кадром или фреймом (frame). Кадр обычно завершается одним или несколькими стоповыми символами. Не обнаружив этих символов (или обнаружив на месте этих символов неверные значения), приемник может понять, что его часы все-таки разошлись с часами передатчика. Асинхронная передача позволяет сэкономить на проводах (не требуется стробирующих сигналов) и при этом избежать сложных способов кодирования, характерных для совмещенной синхронной передачи, однако стартовые и стоповые символы составляют значительную часть потока передаваемых данных и создают ощутимые накладные расходы. Кроме того, при передаче большого объема данных в виде плотно следующих друг за другом кадров велика опасность, что приемник потеряет заголовок очередного кадра и не сможет восстановить структуру потока, поэтому многие асинхронные протоколы требуют паузы между последовательными кадрами. Асинхронная передача данных удобна в ситуациях, когда объем передаваемых данных невелик, а потребность в их передаче возникает в труднопредсказуемые моменты. Обычно асинхронные порты работают на небольших скоростях, не более нескольких килобит в секунду. В качестве примера асинхронной передачи может служить интерфейс RS-232.
Изохронная передача данных по идее напоминает асинхронную, с тем лишь отличием, что при обмене данными приемник и передатчик пользуются высокостабильными, но независимыми – при использовании одного тактового генератора получится синхронная передача – тактовыми генераторами, и благодаря этому могут обмениваться кадрами большого размера. В идеале, изохронная передача сочетает преимущества синхронной и асинхронной, но на практике сложности обеспечения стабильности и калибровки тактовых генераторов достаточно велики и в чистом виде изохронная передача используется очень редко.
Как синхронные, так и асинхронные порты бывают следующих типов:
1. симплексные (simplex) – передавать данные может только одно устройство);
2. полудуплексные (half-duplex) – оба устройства могут принимать и передавать данные, но не способны делать это одновременно, например, потому, что прием и передача идут по одному проводу);
3. полнодуплексные (full-duplex) или просто дуплексные (оба устройства способны одновременно передавать и принимать данные, чаще всего по различным проводам).
Еще одна практически важная классификация портов передачи данных – это деление их на последовательные и параллельные порты. Последовательный порт состоит из одного провода, по которому, как следует из названия, последовательно передаются биты данных, а также, возможно, синхронизационные или стартовые и стоповые биты. Параллельный порт имеет несколько линий передачи данных. Как правило, последовательные порты – асинхронные или синхронные с совмещенной передачей синхросигнала, но исключения из этого правила – стробируемые последовательные порты – также нередки. Напротив, если уж мы проложили восемь проводов для данных, то экономить на девятом – синхронизующем проводе – было бы совсем уж глупо, поэтому подавляющее большинство практически используемых параллельных портов – синхронные стробируемые.
Шины передачи данных
Описанные в предыдущем разделе порты передачи данных соединяют друг с другом два устройства. Однако часто оказывается целесообразно подключить к одному порту передачи данных несколько устройств, причем необязательно однотипных. Каждая передаваемая по такому порту порция данных обязана сопровождаться указанием, какому из подключенных устройств она предназначена – адресом или селектором устройства. Такие многоточечные порты называются шинами (bus). Как и двухточечные порты передачи данных, шины бывают синхронные и асинхронные, а также последовательные и параллельные. При описании шин термины “синхронный” и “асинхронный” используют в ином значении, чем при описании портов. Асинхронными называют шины, в которых ведомое устройство не выставляет (или не обязано выставлять) сигнал завершения операции, а синхронными, соответственно, шины, где ведомый обязан это делать. Подключение N устройств двухточечными портами требует, чтобы центральный процессор имел N приемопередатчиков, использование же многоточечного порта позволяет обойтись одним. В частности, за счет этого удается уменьшить количество выводов микросхемы процессора или периферийного контроллера. Кроме того, при удачном размещении устройств можно получить значительный выигрыш в общей длине проводов и, таким образом, например, уменьшить количество проводников на печатной плате. Во многих случаях это приводит к столь значительному снижению общей стоимости системы, что оказывается целесообразным смириться с усложнением протокола передачи данных и другими недостатками, присущими шинной архитектуре. Основной недостаток шин состоит в том, что в каждый момент времени только одно устройство на шине может передавать данные. Если у двух точечных портов часто оказывается целесообразным реализовать полнодуплексный обмен данными . при помощи двух комплектов линий (один на прием, другой на передачу, как в RS232), то в случае шинной топологии это невозможно. Поэтому шины бывают только полудуплексные или симплексные. Невозможность параллельно осуществлять обмен с двумя устройствами может привести к падению производительности по сравнению с собственным портом обмена данными у каждого устройства. Если устройства не занимают пропускную способность каналов передачи полностью, проигрыш оказывается не так уж велик. Благодаря этому шины широко используются даже в ситуациях, когда только одно устройство имеет возможность инициировать обмены данными. Если передачу данных могут инициировать несколько устройств (как, например, в случае системной шины с несколькими процессорами и/или контроллерами ПДП), шинная топология оказывается наиболее естественным выбором. Такая конфигурация требует решения еще одной проблемы: обеспечения арбитража доступа к шине со стороны возможных инициаторов обмена – задатчиков шины. Методы арбитража отличаются большим разнообразием. Необходимость обнаружения коллизий и арбитража накладывает ограничения на физический размер шины: сигналы распространяются по шине с конечной скоростью, а любые два конфликтующих устройства должны узнать о коллизии за время, меньшее чем минимальный цикл передачи данных (цикл шины). Например, шина PC предназначена для низкоскоростной связи в пределах одной печатной платы, поэтому для нее эта проблема не актуальна, однако для локальных сетей, протяженность которых составляет сотни метров или даже километры, и для системных шин компьютеров с большим количеством процессоров и банков памяти это превращается в серьезную проблему. Кроме того, большое количество коллизий само по себе снижает производительность системы. Один из основных путей решения этой проблемы – замена шины центральным коммутатором, либо более или менее сложной сетью коммутаторов. Устройства соединяются с ближайшим коммутатором полнодуплексными двухточечными каналами, такие же каналы используются для соединения коммутаторов друг с другом, а все коллизии возникают и разрешаются только внутри коммутаторов. Внутренняя шина коммутатора, как правило, имеет большую пропускную способность, чем внешние соединения – у многих практически используемых коммутаторов несколько внутренних шин, поэтому разрешение коллизии внутри коммутатора часто состоит в отправке данных другим путем.