Проектирование устройств сопряжения (1083567), страница 16
Текст из файла (страница 16)
Рис. 2.54. Функциональная схема узла последовательной передачи данных и временные диаграммы его работы.
Обмен по сети без буферного ОЗУ имеет довольно ограниченное применение. Он чаще используется тогда, когда допускается побайтная или пословная передача, например, в оптоволоконных сетях. Однако обычно требуется неразрывная передача целого массива (называемого также пакетом или кадром) со своей структурой и, соответственно, прием этого массива. В этом случае необходимо применять буферное ОЗУ и, конечно же, обеспечить максимальную скорость обмена компьютера с ним. Как уже отмечалось в 2.1.7, наибольшее быстродействие достигается при применении разделяемой памяти или (что то же самое) при параллельном доступе к буферному ОЗУ (то есть каждому адресу буферного ОЗУ соответствует свой адрес в адресном пространстве памяти компьютера). Рассмотрим практическую реализацию этого подхода при проектировании контроллера локальной сети.
Рис. 2.55. Структура кадра локальной сети Ethernet.
Прежде всего отметим, что особенностью обмена по сети является использование пакетов (кадров) самой различной Длины (обмен пакетами стандартной длины тоже встречается, но гораздо реже). Помимо информации о сетевых адресах приемника и передатчика пакета, начальной и конечной комбинаций пакета, а также управляющей информации в пакет могут входить или не входить данные, причем объем этих данных может быть самым различным. Примером может служить структура пакета (кадра) широко распространенной локальной сети Ethernet (рис. 2.55). Поэтому важно обеспечить, чтобы буферное ОЗУ контроллера сети могло выдавать в сеть и принимать из сети пакеты различной длины.
Особых требований по быстродействию микросхем буферного ОЗУ здесь не предъявляется, так как при скорости обмена по сети 10 Мбит/с и 16-разрядной организации буферного ОЗУ период смены его адресов будет 1,6 мкс (при 8-разрядной организации — 0,8 мкс). Такие характеристики обеспечивают многие микросхемы памяти.
Пример функциональной схемы включения буферного ОЗУ в контроллере сети показан на рис. 2.56. Часть интерфейсной части УС для наглядности приведена здесь подробнее. Селектор адреса AS вырабатывает сигнал ADR в случае обращения компьютера в заданную 4К-байтную зону адресов памяти. В случае такого обращения счетчик переходит в режим параллельной записи входных данных и передает 12 младших разрядов адреса на адресные входы буферного ОЗУ. По стробу обмена -SMEMW данные записываются в память. Аналогично обрабатывается строб чтения из памяти -SMEMR, управляющий буфером данных для ОЗУ (на схеме не показано).
Передаваемый пакет данных формируется в ОЗУ, начиная с нулевого адреса. После окончания записи в ОЗУ дается команда старта передачи, перебрасывающая триггер разрешения передачи. В случае возможности передачи (сеть свободна) информация из ОЗУ через регистр сдвига выдается в сеть. При этом каждый строб передачи в сеть уменьшает состояние счетчика на единицу. После выдачи в сеть всего сформированного пакета вырабатывается сигнал переноса счетчика, перебрасывающий триггер разрешения передачи в исходное состояние.
При приеме пакета из сети каждый строб приема из сети также уменьшает состояние счетчика на единицу. Поэтому хотя в сеть пакет поступает как бы задом наперед (байт, записанный последним, выдается первым, что, кстати, надо учитывать при записи стартовой комбинации), но в ОЗУ приемника он располагается так же, как и в ОЗУ передатчика (в порядке возрастания адресов ОЗУ). Это иллюстрируется рис. 2.57. Таким образом производится автоматический учет длины передаваемого пакета, и все байты, записанные в ОЗУ компьютером, передаются в сеть.
Схема рис. 2.56 восьмиразрядная, что позволяет несколько упростить аппаратуру. Но для достижения большего быстродействия можно перейти на 16-разрядную схему. И в том, и в другом случае компьютеру не требуется дополнительного времени для перекачки содержимого системного ОЗУ в буферное ОЗУ контроллера и наоборот. В то же время следует отметить такой недостаток предложенного подхода, как невозможность приема пакета из сети во время обмена буферного ОЗУ с компьютером, что ограничивает применение приведенной схемы. Устранение этого недостатка требует существенного усложнения аппаратуры и здесь не рассматривается.
Рис. 2.56. Функциональная схема включения буферного ОЗУ в контроллере локальной сети.
Рис. 2.57. Порядок обмена с буферным ОЗУ контроллера локальной сети.
2.2. Разработка программного обеспечения устройств сопряжения для ISA
2.2.1. Особенности проектирования программного обеспечения для устройств сопряжения
Специфика программирования аппаратуры и, в частности, устройств сопряжения для компьютера, заключается в повышенных требованиях к быстродействию программного обеспечения и, в ряде случаев, в необходимости получения программы минимального размера. Для этого приходится выбирать подходящие языки программирования, а также использовать специальные методы и алгоритмы.
Наиболее очевидный выбор языка программирования — ассемблер. Действительно, на ассемблере можно написать любую программу, и, при условии минимальной грамотности программиста, она получится самой быстрой, а код — самым коротким (причем это относится ко всем областям программирования). Однако трудоемкость программирования на ассемблере и требования к квалификации программиста несравненно выше, чем для языков высокого уровня.
Кроме того, редко программа управления устройством нужна сама по себе: как правило, она является частью системы (с интерфейсом пользователя, математикой и другими блоками, писать которые на ассемблере, мягко говоря, нецелесообразно). И хотя вопросы сопряжения написанных на разных языках программ во многих случаях решены, это еще одна возможность потратить массу усилий.
В то же время большинство современных реализаций языков программирования высокого уровня (Бейсик, Си, Паскаль и другие) имеют более или менее эффективные средства программирования устройств сопряжения. Рассматривая пригодность того или иного языка, следует проанализировать с точки зрения полноты, оптимальности и удобства программирования такие его возможности, как:
- программный доступ к устройствам ввода/вывода и к памяти,
- обработка прерываний,
- битовые логические операции,
- управление системным таймером
и, возможно, какие-то другие в зависимости от конкретной задачи.
Написанные на языках высокого уровня программы, как правило, оказываются не намного медленнее и длиннее аналогичных программ, написанных на ассемблере. Многие эксперты считают, что наиболее удобен для программирования аппаратуры язык Си. Многие с ними не согласны. Во многом это дело вкуса и привычки программиста. Именно этим, по-видимому, и определен выбор авторами книги языка Си для реализации примеров программ.
Есть, тем не менее, достаточно узкие области, в которых ассемблер оказывается действительно самым эффективным, а иногда и единственным средством программирования. Эти области лежат на самых крайних пределах требований к размерам и быстродействию — там, где важен каждый байт (резидентные программы; программы, записываемые в ПЗУ) и каждая микросекунда (драйверы некоторых быстрых устройств реального времени).
Наряду с использованием стандартных методов и правил программирования, при программировании аппаратуры приходится учитывать особенности конкретной задачи и применять специфические приемы, часто идущие вразрез с принципами "хорошего стиля". Например, часто неэффективным оказывается принцип модульного программирования, так как сами процедуры могут быть очень короткими и быстрыми, а на вызов функций и передачу параметров может тратиться половина времени.
Как правило, собственно взаимодействие с устройством связано с подачей на него и приемом от него определенных сигналов в определенном порядке и представляет собой последовательность операций ввода/вывода, обильно усыпанную битовыми логическими операциями над принимаемыми и передаваемыми данными. Большинство предлагаемых в данной книге примеров программирования устройств сопряжения написано в едином стиле, опирающемся на такие правила, как:
- устройства сопряжения выполняют определенные функции, каждая из которых реализуется в виде отдельного драйвера; на более низком уровне (управление отдельными разрядами отдельных регистров) разделения на программные модули не производится;
- большинство устройств имеют регистры управления или состояния, в которых каждый бит или группа битов соответствуют определенным режимам работы устройства; маски этих битов (позиции в байте или слове) определяются перед драйверами и используются в битовых операциях для установки или проверки;
- в качестве глобальной переменной определяется первый ("базовый") адрес устройства в адресном пространстве, а его остальные адреса вычисляются добавлением к базовому адресу смещения.
Следует помнить, что приведенные здесь программы служат только для иллюстрации особенностей программирования устройств, поэтому не следует механически переносить куски этих программ в свои системы — работоспособность таких систем авторами не гарантируется.
2.2.2. Программирование универсального контроллера параллельного обмена
Описанный в п.2.1.9 универсальный контроллер параллельного обмена (УКПО) содержит 7 программно-доступных байтовых двунаправленных портов и регистр управляющего слова. Таким образом, на нижнем уровне программирования УКПО необходимо реализовать три функции: запись управляющего слова, запись данных в любой порт и чтение данных из любого порта. На языке Си они выглядят так:
II *** Функция записи управляющего слова
// CW_ADDR — адрес регистра управляющего слова
// cw — байт управляющего слова
outportb (CW_ADDR, cw);
II *** Функция записи данных в порт
// BASE_ADDR — базовый адрес УКПО (адрес порта 0)
// port — номер порта (0...6)
// data — байт данных
outportb (BASE_ADDR + port, data);
// *** Функция чтения данных из порта
// BASE_ADDR — базовый адрес УКПО (адрес порта 0)
// port — номер порта (0...6)
// data — байт данных
data = inportb (BASE_ADDR + port);
Напишем для примера простейшую программу тестирования УКПО. Будем последовательно записывать во все порты код 55Н и контролировать правильность записи. При этом все порты, кроме тестируемого, будем переводить в режим чтения.
// *** Программа тестирования УКПО ***
#include <STDIO.H> #include <DOS.H>
#define NPORT 7 // Число портов УКПО
// Глобальные переменные
unsigned BASE_ADDR; // Базовый адрес УКПО unsigned CW_ADDR;
// Адрес регистра управляющего
// слова
void main (void)
{
unsigned i;
unsigned char data = 0x55;
for (i=0;i<NPORT;i++)
{
outportb (CW_ADDR, 1 << i); // Установка
// конфигурации:
// i—ый порт
//на вывод,
// остальные
//на ввод
outportb (BASE + i, data); // Запись данных
if ( inportb (BASE + i) != data )
printf ("\n\tПорт %d неисправен");
else
printf ("\n\tПорт %d исправен");
}
} //*** Конец программы
Как уже отмечалось, каждому порту УКПО соответствует один бит управляющего слова. Единица в этом бите устанавливает порт в режим передачи данных, а ноль — в режим приема. Естественно, что когда порт находится в режиме передачи, ничто не мешает прочитать его содержимое, контролируя таким образом правильность вывода. Именно это свойство УКПО использовано в программе его тестирования. Поэтому, хотя мы и будем называть состояния портов "ввод" и "вывод", будем иметь в виду, что "вывод" на самом деле является "вводом/выводом". В нашем примере для последовательной установки портов в режим вывода использовалась операция сдвига единицы на число разрядов, равное номеру порта. Очевидно, что при этом данный порт устанавливается на вывод, а все остальные — на ввод.
Используя описанные выше функции программирования УКПО, можно реализовать самые разные режимы его работы и алгоритмы взаимодействия с внешними устройствами.
Например, на основе УКПО может быть построен простейший тактовый генератор. Для этого будет использован один из битов порта 0 УКПО. Ниже приведен пример программы тактового генератора, работающего с частотой 1 кГц.
// *** Программа тактового генератора на 1 кГц
// *** на базе УКПО.
// *** выход генератора — бит 0 порта 0.
#include <DOS.H>
#include <CONIO.H>
#define MASK_BIT 1 // Маска бита 0