Чтение/запись в LPT порт (Часть 1) Итак, настало время написать простую программу, иллюстрирующую приемы чтения и записи данных в LPT порт. Пока напишем ее в консольном варианте, дабы на этапе понимания и разбора этой программы не пришлось копаться в дебрях кода под Windows (не переживайте, следующая статья будет посвящена как раз приложению по Win).
Краткая характеристика проекта:
* работает под любой ОС (9x, NT)
* позволяет читать и записывать данные в любой регистр lpt порта (если он эту возможность предусматривает)
* приложение консольное, для доступа к портам используем библиотеку inpout32.dll
Прежде чем двигаться дальше и писать программу, необходимо разобраться с LPT портом, посмотреть из чего он состоит и как нам воспользоваться им в своих целях.
Внутренности LPT порта
Если говорить на бытовом уровне, то можно сказать, что LPT порт это набор контактов, на которых мы можем установить напряжение 0 или +5 В (логическая 0 и 1) из программы или это может сделать внешнее устройство снаружи. Давайте разберемся, какими контактами мы можем оперировать, а какими нет. В этом нам поможет рис. ниже (его рисовал не я, автор мне неизвестен. Но он уж больно хорош, я и сам им постоянно пользуюсь)
Из рис. видно, что выводы порта можно разделить на четыре группы: это 'земляные' выводы (не понятно чем руководствовались разработчики интерфейса LPT, сделав этих выводов аж 8 штук). Они обозначены черным цветом (контакты 18-25). Все они соединены между собой, поэтому для своих разработок в качестве земли можно использовать любой из них.
Красным цветом обозначены выводы так называемого регистра Data (контакты 2-9). Под регистром будем понимать (на бытовом уровне) объединение группы контактов LPT порта. В регистре Data их 8 штук. Это самый толковый регистр - он позволяет нам как из программы, так и из внешнего устройства установить на его контактах логическую 0 или 1, т.е. он двунаправленный. Именно его мы и использовали в нашей первой программе Port.exe - подключали светодиод ко 2-му выводу порта (как теперь понятно, этот вывод принадлежит регистру Data, является его нулевым битом) и 25 выводу (земля), и с помощью программы управляли подачей напряжения на вывод 2 относительно земли. Чтобы обращаться к этому регистру, надо знать его адрес: 0x378 - в 16-ричной системе или 888 в десятичной (на рис. написано &H378 - это тоже самое что и 0x378, просто первое обозначение присуще языку Pasсal и ему подобным, мы же пишем на Си).
Опять вспоминая программу Port.exe, заметим, что обращались мы к регистру с помощью следующей функции _outp(Address, 0);, где переменная Address была предварительно определена как 888. Теперь понятно, что этим мы указывали функции _outp, что мы хотим работать именно с регистром Data.
Продолжим рассмотрение порта. Осталось еще два регистра. Следующим будет регистр Status (контакты 10-13, 15). Это однонаправленный регистр. Управлять им можно только из снаружи, через внешнее устройство (имеется в виду изменять данные на нем, читать можно из любого регистра в любую строну). Он имеет адрес 0x379 - в 16-ричной системе или 889 в десятичной. И регистр Control (контакты 1, 14, 16-17). Он имеет всего 4 контакта и может управляться только программой. Его адрес: 890 в десятичной системе.
В итоге мы получили:
* 8 двунаправленых контактов (регистр Data) - данные туда может записать и программа и внешнее устройство
* 5 однонаправленных контактов (регистр Status) - данные туда может записать только внешнее устройство
* 4 однонаправленных контакта (регистр Control) - данные туда может записать только программа
Вывод: у нас есть 17 выводов которыми мы можем управлять по своему усмотрению.
Теперь рассмотрим, а как происходит запись и чтение данных в регистры LPT порта, т.е. как нам установить на нужных выводах 0 или 1.
Запись/чтение данных в регистр Data
Итак, рассмотрим сразу практическую задачу. Хочу чтобы на выводе регистра Data под номером 3 (3 - это номера вывода LPT порта) была установлена логическая 1 (т.е. чтоб между ним и землей было +5 В) и на остальных выводах этого регистра (2,4-9 выводы порта) были нули. Пишем код:
int Address=888;
int data=2;
Out32(Address, data);
Я использовал функцию Out32 библиотеки inpout32.dll, будем привыкать к ней, т.к. дальнейшие примеры будем разбирать именно на этой библиотеке. Если этот код выполнить, то получится что на выводе порта 3 есть +5 В, а на 2,4-9 висит ноль. Как это получилось?
Начнем разбираться: первым параметром функции Out32 мы передаем число 888. Как Вы уже знаете, это адрес регистра Data LPT порта. Теперь функция знает куда ей писать данные. Далее вторым параметром мы передаем число 2. Прошу обратить внимание, что двойка в десятичной системе счисления. Что дальше делает функция? Ей надо эту двойку запихнуть в регистр Data, но вот проблема: регистр совершенно не понимает что такое 2. Он знает 0 или 1. Больше ничего. Тогда функция как бы "переводит эту двойку в двоичную систему счисления" (это не совсем верно, но для объяснения на пальцах сгодится) и каждый разряд двоичного числа с право налево записывает по порядку в регистр начиная с младшего разряда D0 (вывод 2 порта) и заканчивая старшим D7 (вывод 9). Если Вы переведете число 2 из десятичной в двоичную систему счисления то получите 10. Функция берет первый разряд двоичного числа - это 0 (самую правую) и пишет ее в D0, далее берет 1 и пишет ее в D1. Т.к. регистр 8-ми разрядный (у него есть 8 контактов), функция продолжает брать данные с право налево и писать в следующий бит регистра. Т.к. наше число закончилось, то функция как бы дописывает нулями наше двоичное число слева, чтоб оно стало 8-ми разрядным. Эта операция смыслу не противоречит, т.к. например, что 23 руб. так и 00023 руб. - одно и тоже.
Ну что, мозг опух пока прочитали? Сейчас станет понятнее. Давайте в регистр Data запишем число 245. Пишем код:
int Address=888;
int data=245;
Out32(Address, data);
Опять переводим 245 в двоичную систему счисления и с право налево записываем разряды числа в соответствующие биты регистра. В итоге получим, что на выводах LPT порта под номерами 2,4,6-9 присутствует напряжение +5 В, на выводах 3,5 ноль.
Ну что, теперь я думаю, с записью данных в регистр Data мы разобрались. Надо отметить, что диапазон десятичных чисел, которые можно записать в регистр Data лежит в пределах от 0 до 255. Регистр он у нас 8-ми разрядный, значит максимальное число комбинации 0 и 1 на его выводах составляет 28-1=256-1=255.
Чтение данных
Теперь давайте получим данные из порта, а именно из его регистра Data, к нам в программу. Мы хотим узнать, на каких выводах регистра Data сейчас высокий уровень напряжения, а на каких низкий. Помните, выше мы записали в порт число 245? Давайте его сейчас получим из порта. Пишем код:
int Address=888;
int data;
data = Inp32(Address);
Inp32 это функция для чтения данных из порта библиотеки inpout32.dll. Единственным параметром для нее является адрес того регистра, откуда мы хотим прочесть данные. На выходе она возвращает десятичное число, соответствующее текущему содержимому регистра. Выполнив этот код, переменная data будет содержать число 245. Что это значит? Чтобы разобраться, переводим число 245 из десятичной в двоичную и смело можем сказать что на выводах порта 2,4,6-9 сейчас +5 В а на выводах 3,5 0 В. (см. рис. выше)
Как я уже упомянул выше, в регистр Data данные записать может и внешнее устройство. Однако рассмотрение этого вопроса пока оставим, т.к. это потребует внешних источников питания. Сначала, давайте полностью разберемся с базовыми операциями.
Запись/чтение данных в регистр Control
Теперь по управляем регистром Control. Он однонаправленный, данные в него может записать только наша программа. Обратите внимание на несколько особенностей этого регистра. Во-первых, о содержит всего четыре рабочих вывода. Значит в него можно записать число в диапазоне от 0 до 24-1=16-1=15. Во-вторых, он имеет очень неприятную особенность: некоторые из его выводов инвертированы, т.е. если Вы на этот вывод пишете 1, то на ней устанавливается 0. И наоборот, читаете 1, а на самом деле там 0. Поэтому, значение записываемых данных и читаемых данные не совсем очевидны. Для разбора необходимо просто попробовать писать в регистр разные данные и смотреть, что получается. Приведу пример записи числа в регистр Control. Пишем код:
int Address=890; //адрес регистра Control
int data=10;
Out32(Address, data);
И пример чтения:
int Address=890; //адрес регистра Control
int data;
data = Inp32(Address);
Запись/чтение данных в регистр Status
Наконец, добрались до регистра Status. Он однонаправленный, данные в него может записать только внешнее устройство, т.е. мы в программе можем только читать содержимое этого регистра. Прочитав данные из Status, и переведя их в двоичное число, сразу довольно трудно понять что же реально творится с напряжениями на выходах этого регистра. Во-первых, он тоже имеет инвертированные выводы, а во-вторых рабочими являются биты под номерами 4-7, а 0-3 не используются, и следовательно число записывается довольно хитро. Чтобы разобраться, лучше просто несколько раз делать чтение при разных данных в регистре.
Возникает вопрос, а как эти данные на нем установить? Довольно просто. В качестве внешнего устройства, пока, будете выступать Вы. Выполните такой код.
int Address=889; //адрес регистра Status
int data;
data = Inp32(Address);
Вы получите некоторое число. Теперь возьмите проводник и соедините им любой из земляных выводов порта (18-25) с каким-нибудь выводом регистра Status (10-13, 15), например с десятым. И снова выполните чтение. Вы получите другое число. Уберите проводник. Прочитав, получите исходное число. Как это работает? Исходно, на всех выводах этого регистра находится высокий уровень напряжения +5 В. Когда мы соединили один из его выводов с землей, то на нем, соответственно, напряжение стало равным нулю, т.е. логический ноль. Теперь двоичным данным в регистре соответствует другое десятичное число. Можно попробовать замыкать и другие выводы регистра Status на землю, замыкать сразу несколько. Каждый раз при чтении получится разный результат.
Следует заметить, что при таких опытах с регистром Status возникает не совсем понятная ситуация с другими выводами порта LPT. После первого замыкания выводов Status, начинают мигать выводы Data и Control. Это связано с тем, что порт LPT предназначен для подключения принтера, а выводы Status он использует, для того чтобы сообщить компьютеру некоторую служебную информацию. Изменения на выводах Status регистрирует системный драйвер операционной системы. Он же проводит и ответные действия, для нас наблюдаемые в виде периодического изменения состояния других выводов. Тут уж ни чего не поделаешь. Я обычно, просто в начале работы с портом делаю замыкание какой-нибудь линии регистра Status на землю и жду примерно минуту, пока драйвер не утихомирится. После этого порт свободен, и новые операции над регистром Status не приводят к неконтролируемым процессам в порту.
Ну вот мы и познакомились поближе с содержимым порта LPT. Теперь можно писать программу. См. Часть №2
PCPORTS.RU Иванов Д.В.