воскресенье, 12 мая 2019 г.

Программы для управления светодиодным индикатором на параллельном порту

Введение

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

1. Прямое обращение к портам ввода-вывода

Итак, как я уже писал, первым работающим способом управления светодиодами на параллельном порту оказалась работа с портами ввода-вывода через специальный файл /dev/ports. Я доработал тестовую программу, превратив её в утилиту командной строки, которая позволяет узнавать текущее состояние светодиодов на указанном параллельном порту и выставлять новое состояние светодиодов.

При запуске без аргументов, программа выводит справку:
Usage: parled12a [--port <port>] --set <value>
       parled12a [--port <port>] --get
       parled12a [--port <port>] --help
Options:
       --port <port>  - IO port numberic address or one of special values:
                        LPT1 - 0x3BC (default),
                        LPT2 - 0x378,
                        LPT3 - 0x278.
Modes:
       --set <value> - set activity of 12 leds. Value - is number, where each of
                       12 bits represent activity of corresponding led.
                       Special values:
                           all - turn on all leds,
                           none - turn off all leds.
       --get - get activity of 12 leds. Value - is number, where each of 12 bits
               represent activity of corresponding led.
       --help - show this help
Программе можно указать как числовое значение адреса порта, так и одну из трёх символьных констант - LPT1, LPT2, LPT3. По умолчанию используется значение LPT1, но мне каждый раз приходилось указывать константу LPT2, т.к. единственный параллельный порт, имеющийся на моём компьютере, оказался расположен по адресам 0x378-0x37A. DOS ищет параллельные порты на портах ввода-вывода в указанном порядке, но при нумерации параллельных портов не использует жёсткого соответствия, а нумерует по порядку только найденные порты. Таким образом, этот единственный параллельный порт, имеющийся в моём компьютере, в DOS получил бы обозначение LPT1.

При установке состояния светодиодов можно указать число в десятичном (без ведущих нолей), восьмеричном (указав ведущий ноль) или шестнадцатеричном виде (с префиксом 0x). Каждый из 12 бит этого числа соответствует одному из светодиодов, единица означает включенный светодиод, а ноль - выключенный. Также вместо числового значения можно указать одну из предопределённых констант: all - включить все светодиоды, none - выключить все светодиоды.

Исходный текст программы можно увидеть по ссылке parled12a.c.

Программа прекрасно справляется со своей задачей, однако прямая работа с портами ввода-вывода не учитывает специфику реализации конкретного параллельного порта. При попытке воспользоваться каким-нибудь адаптером USB-LPT, даже если он поддерживается Linux'ом, программа скорее всего не будет работать.

2. Работа с драйвером параллельного порта ppdev

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

Подсистема параллельного порта Linux 2.4

Пользуясь переведённой документацией, я написал программу, аналогичную предыдущей. При запуске без аргументов она выводит вот такую справку:
Usage: parled12b [--devpath <devpath>] --set <value>
       parled12b [--devpath <devpath>] --get
       parled12b --help
Options:
       --devpath <devpath>  - path to parport device, default - /dev/parport0
Modes:
       --set <value> - set activity of 12 leds. Value - is number, where each of
                       12 bits represent activity of corresponding led.
                       Special values:
                           all - turn on all leds,
                           none - turn off all leds.
       --get - get activity of 12 leds. Value - is number, where each of 12 bits
               represent activity of corresponding led.
       --help - show this help
Known bugs:
       Option --get cannot return correct value of high 4 bits, because linux
       driver of parallel port resets the control bits when port opened.
Вместо адреса порта ввода-вывода этой программе нужно указать путь к специальному файлу параллельного порта. Впрочем, единственный имеющийся на моём компьютере параллельный порт доступен через специальный файл /dev/parport0, поэтому на этот раз указывать его явным образом мне не пришлось.

Исходный текст программы можно увидеть по ссылке parled12b.c.

Эта программа лишена недостатков предыдущей и сможет работать с любой реализацией параллельного порта, которая поддерживается Linux. Для примера я попробовал воспользоваться адаптером USB2LPT Orient ULB-225, который приобрёл в интернет-магазине e2e4 за 290 рублей:
.
До конца не был уверен, что программа заработает с этим адаптером, поэтому не особо разочаровался, когда понял, что адаптер не реализует полноценный параллельный порт. В системе он определяется не как ещё одно устройство /dev/parport*, а как устройство /dev/usb/lp0. Разница заключается в том, что в первом случае имеется полный доступ к каждому отдельному выводу и различным режимам работы параллельного порта, а во втором случае реализуется лишь символьное устройство, в которое можно отправлять символы на принтер и считывать состояние принтера при помощи системных вызовов ioctl. При помощи этого устройства невозможно зафиксировать в одном состоянии даже выводы, используемые для передачи данных. Устройство дожидается сигнала готовности принтера, выставляет на линиях данных логические уровни, соответствующие передаваемому байту, активирует сигнал передачи данных, а когда принтер деактивирует сигнал готовности, состояние линий данных сбрасывается. Когда принтер обработает полученный байт, он снова активирует сигнал готовности и цикл передачи данных повторяется. Однако управлять выводами параллельного порта на этом адаптере произвольным образом уже невозможно.

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

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

3. Демон для управления индикаторами

Итак, работающая программа для управления светодиодами на параллельном порту уже имеется, теперь осталось реализовать на её базе демон.

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

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

Первым делом я реализовал приём входящих подключений к Unix-сокету и выполнение поступающих команд get и set.

Первый вариант этой программы одновременно мог обслуживать только одного клиента, причём следующий клиент не начинал обслуживаться, пока не отключится предыдущий. Хотелось сделать программу, которая могла бы обслуживать подключения от нескольких клиентов одновременно. Классический подход с порождением дочернего процесса на каждого подключившегося клиента тут, на мой взгляд, не годился, т.к. был очевидно избыточен для такой простой задачи. Многопоточный вариант не намного менее избыточен, поэтому тоже был отклонён. На самом деле, мне давно было интересно попробовать механизм epoll и испытать свои силы в мультиплексированном обслуживании запросов от нескольких клиентов в рамках одного однопоточного процесса. При таком подходе в основном цикле при помощи epoll проверяется готовность сокетов к чтению или записи, а с каждым клиентом ассоциирована структура данных, содержащая входной и выходной буферы и дополнительные поля, отображающие текущее состояние клиента. По идее, здесь принято использовать конечные автоматы, чтобы отслеживать переходы клиента из одного состояния в другое, но в моём случае таблица переходов оказалась тривиальной, поэтому я не оформлял конечный автомат в явном виде. Реализация всего этого заняла довольно много времени. Сначала это была специализированная программа, но потом я поделил её на ряд универсальных подсистем:
  1. evloop - обработчик событий в сокетах. Принимает события и информирует о них подписчиков,
  2. server - подписчик на события в слушающем сокете, который принимает входящие подключения. Этот подписчик создаёт подписчика client и добавляет его в evloop,
  3. client - подписчик на события в сокете подключения. Когда получает сообщение о готовности сокета к чтению, читает из сокета все данные, складывает в буфер и вызывает обработчик. Аналогично, если получено сообщение о готовности сокета к записи, отправляет в сокет данные, имеющиеся в буфере данных для отправки.
Ни один из сокетов я не переключал в неблокирующий режим, но на практике блокировок не происходило, т.к. я постарался реализовать обработку событий максимально аккуратно, так чтобы операции ввода-вывода с сокетом происходили только тогда, когда сокет готов к соответствующей операции.

Потом я реализовал собственно демонизацию. Т.к. демонизировавшийся процесс найти становится непросто, я последовал сложившейся практике и предусмотрел создание PID-файла.

Для такой простой функции, как управление светодиодами, привилегии root избыточны. Поэтому следующим этапом я реализовал сброс привилегий root, настройку прав доступа к Unix-сокету и переход в chroot-среду.

Однако, после сброса привилегий, демон при завершении работы уже не мог удалить за собой Unix-сокет и PID-файл. Чтобы демон мог удалять их при завершении, я поделил его на два процесса - ведущий и ведомый. Ведущий не сбрасывает привилегий, ждёт завершения ведомого, считывает код его завершения и удаляет Unix-сокет и PID-файл. Ведомый выполняет всю остальную работу.

Когда появился ведущий процесс, который анализирует код завершения ведомого процесса, я подумал, что в эту схему хорошо вписывается функционал аварийного перезапуска ведомого процесса, если тот внезапно завершится. И такой функционал я тоже реализовал. А чтобы завершить работу и ведущего и ведомого демонов корректно, ведущий процесс, при получении сигнала на завершение работы, сам ретранслирует сигнал ведомому процессу, дожидается его завершения, а потом удаляет Unix-сокет и PID-файл.

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

Конечно, программа получилась неоправданно сложной. Оправдание тут только одно - это учебная программа, реализуя которую я приобрёл навыки, полезные для реализации демонов:
  1. использование системного вызова epoll, неблокирующий ввод-вывод и обработку нескольких входящих подключений в одном потоке,
  2. собственно демонизацию - отключение программы от терминала, отпочкование от родительского процесса,
  3. приёмы управления привилегиями демона - переход в chroot-среду, смену эффективного идентификатора пользователя и группы процесса,
  4. работу с сигналами и аварийный перезапуск ведомого процесса,
  5. работу с syslog,
  6. макросы для формирования исчерпывающих отладочных сообщений.
На сей раз для получения справки по использованию демона, нужно при запуске указать ему опцию --help:
Usage: parled12 <options>
       parled12 --help
Options:
       --parport <parport>    - path to parport device, default - /dev/parport0
                                The option can be specified multiple times.
       --socket <socket>      - path to listen unix-socket, default - 
                                /run/parled.sock
       --socket-owner <user>  - owner of socket
       --socket-group <group> - group of socket
       --socket-mode <mode>   - access mode for socket
       --daemon               - run as daemon
       --user <user>          - switch to specified user after open all sockets
                                and devices
       --group <group>        - switch to specified group after open all sockets
                                and devices
       --chroot <path>        - change root path of process to specified path
       --pidfile <PID-file>   - path to file, where will be saved PID, default -
                                none
Modes:
       <default> - listen commands on socket and work with leds on parallel
                   port.
       --help    - show this help
При помощи опции --parport можно указать специальные файлы параллельных портов, светодиодами на которых должен управлять демон. Порты нумеруются, начиная с нуля, клиент в командах демону может указывать номер порта явным образом.

Опции --socket, --socket-owner, --socket-group, --socket-mode позволяют указать путь к Unix-сокету, владельца, группу владельца, режим доступа. Через этот Unix-сокет будут приниматься подключения клиентов.

Опция --daemon позволяет переключить программу из интерактивного режима в режим демона. В интерактивном режиме программа не отделяется от консоли и пишет отладочные сообщения на стандартный вывод. В режиме демона программа отделяется от консоли, от родительского процесса, от группы процессов и делится на два процесса - ведущий и ведомый. Ведущий процесс ловит сигналы: при внезапном завершении ведомого, ведущий процесс перезапускает ведомого, а при получении сигнала завершения работы - завершает ведомого и удаляет PID-файл и Unix-сокет. Ведомый открывает необходимые специальные файлы параллельных портов и Unix-сокет, после чего сбрасывает привилегии и начинает принимать входящие подключения и обслуживать запросы.

Опции --user, --group, --chroot позволяют настроить сброс привилегий ведомым процессом. После открытия необходимых специальных файлов параллельных портов и Unix-сокета, ведомый процесс может перейти в указанную chroot-среду и сменить свой эффективный идентификатор пользователя и группы.

Опция --pidfile позволяет указать путь к файлу, в котором будет храниться идентификатор ведущего процесса.

Архив с исходными текстами программы можно взять по ссылке stupin.su/git/stupin/parled12/archive/master.zip.

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

В качестве клиента можно использовать программу socat, которая позволяет подключиться к Unix-сокету и соединить их со стандартным вводом и выводом:
$ socat UNIX:/run/parled.sock STDIO
Подключившись к демону таким образом, можно отправлять ему одну из следующих команд:
КомандаОписание
get leds [from port <port>]Возвращает шестнадцатеричное число, младшие 3 цифры которого соответствую состоянию 12 светодиодов. Единица соответствует светящемуся светодиоду, а ноль - погашенному.
set <bits> leds [on port <port>]Задаёт состояние 12 светодиодов. В качестве аргумента bits выступает десятичное, восьмеричное или шестнадцатеричное число, 12 младших бит которого соответствуют состоянию 12 светодиодов. Вместо числа можно указать константу all или none. Константа all соответствует 12 единичным битам, а константа none - 12 нулевым битам.
not leds [on port <port>]Обращает состояние всех светодиодов на противоположное: светящиеся светодиоды гасятся, а погашенные светодиоды включаются.
or <bits> leds [on port <port>]Выполняет со светодиодами логическую побитовую операцию ИЛИ: в дополнение к уже светящимся светодиодам включатся указанные единичными битами аргумента.
and <bits> leds [on port <port>]Выполняет со светодиодами логическую побитовую операцию И: из уже светящихся светодиодов останутся включенными только указанные единичными битами аргумента.
xor <bits> leds [on port <port>]Выполняет со светодиодами логическую побитовую операцию ИСКЛЮЧАЮЩЕЕ ИЛИ: состояние светодиодов, указанных единичными битами аргумента, будет обращено на обратное.
add <bits> leds [on port <port>]Добавляет к состоянию светодиодов, выраженному в виде числа, число, указанное в аргументе. От результата сложения берутся только 12 младших бит, которые задают новое состояние светодиодов.
sub <bits> leds [on port <port>]Вычитает из состояния светодиодов, выраженного в виде числа, число, указанное в аргументе. От результата вычитания берутся только 12 младших бит, которые задают новое состояние светодиодов.
inc leds [on port <port>]Добавляет к состоянию светодиодов, выраженному в виде числа, единицу. От результата сложения берутся только 12 младших бит, которые задают новое состояние светодиодов.
dec leds [on port <port>]Вычитает из состояния светодиодов, выраженного в виде числа, единицу. От результата вычитания берутся только 12 младших бит, которые задают новое состояние светодиодов.
rs <shift> leds [on port <port>]Сдвиг битов, соответствующих состоянию светодиодов, вправо на указанное количество позиций. Лишние биты отбрасываются, а новые биты слева принимают нулевое значение. Аргумент может принимать любое значение, однако сдвиг на 0 битов и на более 11 битов не имеют особого смысла: в первом случае состояние светодиодов не меняется, а во втором случае все светодиоды будут погашены.
ls <shift> leds [on port <port>]Сдвиг битов, соответствующих состоянию светодиодов, влево на указанное количество позиций. Лишние биты отбрасываются, а новые биты справа принимают нулевое значение. Аргумент может принимать любое значение, однако сдвиг на 0 битов и на более 11 битов не имеют особого смысла: в первом случае состояние светодиодов не меняется, а во втором случае все светодиоды будут погашены.
rcs <shift> leds [on port <port>]Циклический сдвиг битов вправо: вытесненные вправо биты будут добавлены слева. Сдвиг на 0 битов и на количество, кратное 12, не меняет состояния светодиодов. Сдвиг на более чем 12 битов имеет такой же эффект, как сдвиг на остаток от деления на 12.
lcs <shift> leds [on port <port>]Циклический сдвиг битов влево: вытесненные влево биты будут добавлены справа. Сдвиг на 0 битов и на количество, кратное 12, не меняет состояния светодиодов. Сдвиг на более чем 12 битов имеет такой же эффект, как сдвиг на остаток от деления на 12.
exitЗавершение работы: по этой команде демон разрывает соединение с клиентом.
quitЗавершение работы: по этой команде демон разрывает соединение с клиентом.
closeЗавершение работы: по этой команде демон разрывает соединение с клиентом.
logoutЗавершение работы: по этой команде демон разрывает соединение с клиентом.
Если демону были указаны несколько специальных файлов параллельных портов, то они нумеруются с 0. Если необходимо выполнить команду на нулевом порту или на единственном указанном порту, то окончание команды с указанием номера порта можно не указывать.

Для управления светодиодами из скриптов, можно использовать перенаправление ввода-вывода, следующим образом:
$ echo -n "set 0x801 leds" | socat UNIX:/run/parled12.sock STDIO
Например, можно написать вот такой shell-скрипт, который будет последовательно переключать светодиоды, начиная с крайних до центральных, а потом наоборот:
#!/bin/sh

while true
do
  echo "set 0x801 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x402 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x204 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x108 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x090 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x060 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x090 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x108 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x204 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
  echo "set 0x402 leds" | socat UNIX:/run/parled12.sock STDIO ; sleep 0.1
done
Прервать выполнение этого скрипта можно нажатиями сочетания клавиш Ctrl-C.

4. Сценарий инициализации демона для sysvinit

Я подготовил сценарий для запуска демона при помощи системы инициализации System V init: parled12-sysvinit. Для использования его нужно скачать и поместить в каталог /etc/init.d/, а затем прописать в автозапуск, например, вот так:
# cd /etc/init.d/
# wget https://stupin.su/git/stupin/parled12/raw/branch/master/init/parled12-sysvinit -O parled12
# insserv parled12
Обратите внимание, что в сценарии инициализации имеется переменная окружения DAEMON_SBIN, значением которой является путь к исполняемому файлу демона. В файле по ссылке это путь /home/stupin/parled12/parled12 Скорее всего на вашем компьютере исполняемый файл будет располагаться в другом месте, поэтому эту переменную нужно будет отредактировать соответствующим образом.

Перед запуском демона нужно ещё задать настройки демона. Для этого создадим файл /etc/default/praled12 со следующим содержимым:
PIDFILE=/run/praled12.pid
DAEMON_OPTS="--parport /dev/parport0 --socket /run/parled12.sock --socket-owner root --socket-group root --socket-mode 0666 --user nobody --group nogroup --chroot /var/spool/parled12"
В файле с настройками указано, что демон после запуска должен сменить корневой каталог, сделав таковым для себя каталог /var/spool/parled12. Создадим этот каталог:
# cd /var/spool
# mkdir parled12
# chmod o= parled12
Теперь демона можно запустить:
# /etc/init.d/parled12 start
Unix-сокет для взаимодействия клиентов с демоном имеет полное имя /run/parled12.sock.

5. service-файл для системы инициализации systemd

В случае с systemd всё оказывается даже немного проще. Нужно создать файл /etc/systemd/system/parled12.service со следующим содержимым:
[Unit]
Description=Manage dozen leds on parallel ports

[Service]
Type=simple
EnvironmentFile=/etc/default/parled12
ExecStart=/home/stupin/parled12/parled12 $DAEMON_OPTS

[Install]
WantedBy=multi-user.target
Файл /etc/default/parled12 с настройками демона полностью совпадает с вариантом для sysvinit:
PIDFILE=/run/praled12.pid
DAEMON_OPTS="--parport /dev/parport0 --socket /run/parled12.sock --socket-owner root --socket-group root --socket-mode 0666 --user nobody --group nogroup --chroot /var/spool/parled12"
В отличие от sysvinit, в случае systemd нельзя указать исполнимый файл через переменную окружения, настроенную в файле /etc/default/parled12.

В файле с настройками указано, что демон после запуска должен сменить корневой каталог, сделав таковым для себя каталог /var/spool/parled12. Создадим этот каталог:
# cd /var/spool
# mkdir parled12
# chmod o= parled12
Теперь service-файл можно подключить и запустить сервис:
# systemctl enable parled12
# systemctl start parled12
Состояние сервиса и последние выведенные им сообщения можно увидеть при помощи такой команды:
# systemctl status parled12
В случае systemd я настроил запуск программы без создания PID-файла и в интерактивном режиме. systemd запускает демон в отдельной контрольной группе процессов, поэтому нет необходимости сохранять куда-то PID-файл с идентификаторами процессов. Все процессы, порождённые в рамках этой контрольной группы, systemd будет считать относящимися к одному сервису. Благодаря контрольным группам systemd может найти и завершить все процессы, порождённые в рамках контрольной группы.

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

Как и в случае с sysvinit, Unix-сокет для взаимодействия клиентов с демоном имеет полное имя /run/parled12.sock.

6. service-файл и облегчённая версия parled12

systemd позволяет выполнять часть работы, которую делает ведущий процесс parled12. systemd мог бы самостоятельно отслеживать активность запущенного процесса и при необходимости перезапускать его. Кроме того, systemd не нужен PID-файл для отправки сигнала на завершение работы процесса. Кроме того, systemd может сделать демона даже из процесса, не предпринимающего никаких действий для перехода в фоновый режим. Поэтому я решил сделать облегчённую версию демона, который не поддерживает опции --daemon и --pidfile, не запускает процесс master и не использует отправку сообщений в syslog.

Поначалу я хотел отделить ядро программы и сформировать две ветки программы - heavy и lite. Однако в процессе переделки я понял, что проще будет обойтись введением макроопределения и условной компиляцией: слишком уж малыми оказались различия. Макрос называется LITE и если он определён, то собирается облегчённая версия программы. В файле make.sh собираются обе версии, вторая - под именем parled12-lite.

Файл /etc/default/parled12, в отличие от предыдущих вариантов, немного похудел и теперь не содержит переменной окружения PIDFILE:
DAEMON_OPTS="--parport /dev/parport0 --socket /run/parled12.sock --socket-owner root --socket-group root --socket-mode 0666 --user nobody --group nogroup --chroot /var/spool/parled12"
Файл /etc/systemd/system/parled12.service несколько видоизменился. Изменился путь к исполняемому файлу в настройке ExecStart и добавилась опция Restart. После изменений он принял следующий вид:
[Unit]
Description=Manage dozen leds on parallel ports

[Service]
Type=simple
EnvironmentFile=/etc/default/parled12
ExecStart=/home/stupin/parled12/parled12-lite $DAEMON_OPTS
Restart=always

[Install]
WantedBy=multi-user.target
В service-файле можно было бы указать пользователя и группу, от имени которых нужно запускать процесс, однако перед тем, как процесс сбросит привилегии, он должен сначала получить доступ к файлам устройств, поэтому никаких изменений в этой части сделано не было.

Как и в прошлых случаях, нужно создать chroot-каталог, включить service-файл и запустить службу:
# cd /var/spool
# mkdir parled12
# chmod o= parled12
# systemctl enable parled12
# systemctl start parled12
Unix-сокет для взаимодействия клиентов с демоном остался прежним и имеет полное имя /run/parled12.sock.

7. Демонстрация работы устройства

На видео ниже продемонстрирована работа устройства:

Скрипт plctl.sh содержит всего одну команду:
#!/bin/sh

socat UNIX-CONNECT:/run/parled12.sock STDIO
Репозиторий проекта со всеми исходными текстами и файлами можно найти по ссылке: https://stupin.su/git/stupin/parled12

воскресенье, 5 мая 2019 г.

Светодиодный индикатор для подключения к параллельному порту

Введение

В детстве я интересовался электроникой, но интерес этот был чуть более чем чисто теоретическим. А заинтересовался я ей, когда увидел в ларьке с газетами книгу "Электронные самоделки" Б. С. Иванова.

Родители купили мне эту книгу, я с интересом читал описания схем и запоминал обозначения элементов. Потом дедушка, который сам был радиолюбителем, подарил мне паяльник на 40 Ватт. К сожалению, продолжения не последовало, т.к. дедушка жил в деревне, а для того, чтобы собрать что-нибудь из приведённых в книге схем, одного паяльника было недостаточно: нужны были электронные компоненты, платы, корпусы, крепёж, разнообразные инструменты. Нельзя сказать, что от этого весьма поверхностного увлечения не было вообще никакой пользы, потому что я мог разобрать на принципиальных схемах электронные компоненты, найти их выводы (анод, катод, базу, эмиттер, коллектор), опознать на плате по внешнему виду. При необходимости я мог сделать мелкий ремонт какой-нибудь электроники: перепаять провод питания, какую-нибудь лампочку или динамик. На этом мои знания и навыки исчерпывались. Примерно такой же поверхностный интерес к электронике я привил и сыну, подарив ему несколько электронных конструкторов "Знаток" и конструктор "Микроник", с которыми он периодически любит повозиться и пособирать схемы из приложенных к ним инструкций.


Теперь, когда повсюду имеются радиоэлектронные магазины, имеется изобилие информации и видеороликов, самостоятельные занятия электроникой стали вполне доступны для любого достаточно заинтересованного человека - было бы желание, достаточно денег и свободного времени. Захотелось мне попробовать развить это детское увлечение и сделать какую-нибудь самоделку самостоятельно. С чего начинает большинство нынешних любителей электроники? Сейчас модно осваивать микроконтроллеры, а первое электронное устройство на его основе обычно просто мигает светодиодами. Для микроконтроллера нужен программатор и нужно питание. Программаторы бывают с разъёмами USB, с разъёмами COM, а наиболее распространены программаторы, работающие через порт LPT. Однако, для того, чтобы мигать светодиодами, достаточно одного только порта LPT. Не нужно выбирать микроконтроллер, делать для него программатор и придумывать источник питания. Вместо написания программы для микроконтроллера понадобится написать управляющую программу для Linux, но это - лишь дополнительный повод улучшить уже имеющиеся навыки программирования. Для первого проекта - то, что нужно.

1. Разработка идеи

1.1. Расчёт

У классического параллельного порта имеется 8 линий для передачи данных на устройство, 4 управляющих линии для управления им и 5 сигнальных линий, при помощи которых устройство может сообщать компьютеру о своём состоянии. Таким образом, 8 линий данных и 4 управляющие линии можно использовать для управления 12 светодиодами.

Подключить светодиод непосредственно к выводам цифровых микросхем можно по одной из нескольких схем:

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

Параллельный порт использует напряжения логических уровней, принятых в микросхемах, изготавливавшихся по технологии ТТЛ - транзистор-транзисторная логика. Эти микросхемы используют напряжение питания +5 Вольт, напряжение от 2 до 5 Вольт на входе микросхемы распознаётся как логическая единица, а за логический ноль принимается напряжение от 0 до 0,8 Вольт. При этом на выходах микросхем ТТЛ для логической единицы используется напряжение от 2,4 до 5 Вольт, а для логического ноля - от 0 до 0,4 Вольт.

Сила тока, протекающего через светодиод, должна быть ограничена как возможностями порта, так и возможностями светодиода. После недолгих поисков я нашёл описание параллельного порта, в котором были указаны следующие параметры: на выводах данных при уровне логической единицы порт может выдавать ток до 15 мА, а на управляющих выводах при уровне логической единицы вытекающий ток ограничен всего-лишь 0,55 мА.

Как-то читая книгу "Цифровые устройства и микропроцессоры" авторского коллектива Микушина, Сажнева и Сединина, на странице 194 наткнулся на такой пассаж:
"В радиолюбительских схемах часто встречаются решения, где коммутирующий ток берётся непосредственно с выхода дешифратора, который не может выдать ток больше 20 мА. Возникает вопрос - где смотреть такой индикатор? В полной темноте? Получается "прибор ночного видения", т.е. прибор, показания которого видны только в полной темноте." Хотя сказанное относится к динамической индикации, когда ток с одного вывода попеременно подаётся на каждый из нескольких индикаторов, я тогда запомнил этот момент, поэтому выбирая светодиоды для своей поделки, я искал так называемые "сверхъяркие светодиоды". Мой выбор пал на "Светодиод круглый матовый 3мм LED DIP 3mm DFL-3014URD-6". В описании этого светодиода указаны следующие характеристики:
Светодиод LED DIP 3mm DFL-3014URD-6 красный, 6000mcd, 60°, 1.8V

Корпус: круглый 3 мм;
Цвет свечения: красный;
Напряжение питания: 2 В;
Угол обзора: 60°;
Сила света: 6000 мкд
Как видно, характеристики по падению напряжения немного противоречивы: в названии светодиода написано, что оно составляет 1,8 Вольт, а в описании написано, что оно составляет 2 Вольта. Для расчётов возьмем напряжение 1,8 Вольт, т.к. чем ниже расчётное напряжение, тем ниже и расчётная рассеиваемая мощность. Выбирая меньшую расчётную рассеиваемую светодиодом мощность мы страхуемся от перегрева и перегорания светодиода.

Мне нужно было знать максимальную допустимую силу тока через светодиод, поэтому я поискал эту информацию в интернете по названию модели светодиода "DFL-3014URD-6". Нашёл такое описание:
Светодиод DFL-3014URD-6
Диаметр 3 мм
Цвет свечения красный
Цвет линзы красный матовый
Сила света 6000 mcd
Угол обзора 60 градусов
Напряжение питания 2в
Ток потребления 20 мА
Как видно из этого описания, максимальная сила тока через светодиод должна быть не выше 20 мА, что значительно больше силы тока 0,55 мА, которую может обеспечить управляющий вывод. Поэтому для расчётов ограничим силу тока значением 0,55 мА. Посчитаем номинал резистора, который будет ограничивать ток, протекающий через светодиод:
    U - dU   2,4 - 1,8
R = ------ = --------- = 1090,(90) Ом
      I       0,00055
Наиболее близки к этом значению сопротивления резисторы с номиналом 1100 Ом.

Посчитаем рассеиваемую резистором мощность:
W = (U - dU) * I = (2,4 - 1,8) * 0,00055  = 0,00033 Вт
Можно брать резисторы самой минимальной доступной мощности. Я выбрал резисторы с рассеиваемой мощностью 0,125 Ватт.

Итак, нам понадобятся светодиоды DFL-3014URD-6, а также резисторы с сопротивлением 1100 Ом и максимальной рассеиваемой мощностью 0,125 Ватт.

Для проверки идеи достаточно скрутить резистор и светодиод друг с другом и воткнуть их в разъём параллельного порта. Для пробы я воспользовался выводами 2 (вывод данных DATA0) и 25 (общий провод). Чтобы зажигать и гасить светодиод, понадобится управляющая программа.

1.2. Тестовая программа

Первый пример программы для работы с параллельным портом я взял в разделе 9. Пример кода в документе Linux I/O port programming mini-HOWTO.

Для того, чтобы этот пример собрался в моей системе, довёл его до следующего вида:
/*
 * example.c: очень простой пример для порта ввода/вывода
 *
 * Этот код не делает ничего полезного, только запись в порт, пауза,
 * и чтение из порта. Откомпилируйте `gcc -O2 -o example example.c',
 * и запустите под root `./example'.
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/io.h>

#define BASEPORT 0x378 /* lp1 */

int main()
{
  /* Получить доступ к порту */
  if (ioperm(BASEPORT, 3, 1)) {perror("ioperm"); exit(1);}

  /* Вывод в порт (0) */
  outb(0, BASEPORT);

  /* Задержка (1 с) */
  sleep(1);

  /* Чтение из порта (BASEPORT+1) и вывод результатов на экран */
  printf("статус: %d\n", inb(BASEPORT + 1));

  /* Мы больше не нуждаемся больше в порту */
  if (ioperm(BASEPORT, 3, 0)) {perror("ioperm"); exit(1);}

  exit(0);
}

/* конец example.c */
Собирал следующим образом:
$ gcc -O2 -std=c99 -Wall -pedantic -o parled12 parled12.c
Результат выполнения - ошибка сегментации:
# ./parled12
Ошибка сегментирования
Второй пример написал сам, руководствуясь рекомендацией из раздела 5. Другие языки программирования всё из того же документа Linux I/O port programming mini-HOWTO. В этом разделе для прямого доступа к портам ввода-вывода используется специальное устройство /dev/ports, на которое отображаются все порты ввода-вывода. Открыв его как файл, можно считывать из этого файла и записывать в него байты по смещениям, соответствующим адресам портов.

Этот вариант сработал.

1.3. Тестирование

Удалось включать светодиод, подключенный к выводу данных:

И выключать светодиод, подключенный к инверсному выводу управления:

Теперь, когда идея успешно протестирована, можно было продолжать её более глубокую проработку.

2. Макет устройства

Следующим этапом я решил спаять устройство на макетной плате. Я хотел написать программу, которая использовала бы для управления светодиодами штатную подсистему параллельного порта Linux, а не прямой доступ к портам ввода-вывода. Использование штатной подсистемы позволило бы управлять светодиодами, например, через сквозной LPT-порт сканера или ZIP-привода. Попутно я решил перевести на русский язык документацию Подсистема параллельного порта Linux 2.4, хотя, в принципе, мог бы ограничиться использованием оригинальной документации на английском языке.

2.1. Принципиальная схема

Нарисовал принципиальную схему устройства в программе gschem из программного комплекса geda:

Файл со схемой для gschem можно взять по ссылке: bread-board.sch.

2.2. Смета

№ позицииНаименование позицииКоличествоЦена за штукуЦена
1Разъем DB-25M120.00р.20.00р.
2Корпус разъема DP-25C124.00р.24.00р.
3Светодиод круглый матовый 3мм LED DIP 3mm DFL-3014URD-61212.00р.144.00р.
4Резистор 1,1 кОм, 0,125/0,25 Вт122.00р.24.00р.
5Кабель коммут. 25 пр. экр. (серый) (CCC25G) 1 м1120.00р.120.00р.
6Плата макетная 50x50-1 о/с175.00р.75.00р.
7Трубка термоусадочная d 1,6 мм * 1 м, белая128.00р.28.00р.
Итого435.00р.

2.3. Готовое устройство

Для отладки программ, управляющих светодиодами, использовал вот это устройство:

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

3. Устройство

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

3.1. Принципиальная схема

Поначалу я попробовал развести печатную плату по предыдущей схеме. Чтобы точно воспроизвести её, было нужно делать двустороннюю печатную плату или пользоваться перемычками. Делать ни первое, ни второе мне не хотелось, поэтому я упростил схему, объединив все общие провода. Как можно будет увидеть ниже, в таком виде схему удалось уместить на односторонней печатной плате.

Файл со схемой для gschem можно взять по ссылке: printed_circuit_board.sch.

Ко времени проектирования устройства на печатной плате мне на глаза попался документ с описанием стандартизованного параллельного порта - Стандарт IEEE 1284. Изначально на параллельный порт не было никакого стандарта, IBM выпускала компьютеры и принтеры к ним, которые работали по этому интерфейсу. Производители клонов компьютеров IBM PC использовали для реализации параллельного порта такие же микросхемы, как IBM, а другие производители принтеров ориентировались на возможности микросхемы. Потом микросхемы для реализации параллельного порта стали выпускать другие производители, дополняя их новыми возможностями. Когда различных реализаций и дополнений накопилось слишком много, комитет IEEE выпустил стандарт на параллельный порт, который в той или иной мере вобрал в себя сложившиеся дополнения и расширения параллельного порта.

Если верить этому документу, то нагрузочные возможности параллельного порта по максимальной силе тока, вытекающей с выходов при уровне напряжения логической единицы, оказываются выше. В стандарте написано, что напряжение логической единицы на выходах составляет 2,4 Вольт, а сила вытекающего тока может достигать максимум 14 мА. Выходное напряжение низкого уровня на выходах составляет 0,4 Вольта, а сила тока, втекающего в вывод может достигать тех же 14 мА. Это значительно больше прежних 0,55 мА, но всё ещё меньше тока, допустимого для выбранных светодиодов - 20 мА.

Исходя из этих уточнённых данных, я пересчитал сопротивления резисторов, ограничивающих силу тока, протекающего через светодиоды и выводы порта, а также мощность, рассеиваемую на резисторе:
    U - dU   2.4 - 1.8
R = ------ = --------- = 43 Ом
      I       0.014

W = (U - dU) * I = (2.4 - 1.8) * 0.014 = 0.0084 Вт
На сей раз наиболее подходящими резисторами оказались резисторы с сопротивлением 47 Ом. Рассеиваемая мощность хоть и увеличилась, но не настолько, чтобы резисторы мощностью 0,125 Ватт не смогли справиться с ней.

3.2. Печатная плата и корпус

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

При разводке печатной платы я старался уместить её на минимально возможную площадь. В то же время, светодиоды не должны были располагаться слишком близко друг к другу, т.к. я собирался подобрать для устройства корпус, а светодиоды вывести через отверстия в корпусе. Т.к. диаметр одного светодиода составляет 3 миллиметра, то между отверстиями должно оставаться 1-2 миллиметра. Когда получилась плата минимального размера и с расстоянием между центрами светодиодов в 5 миллиметров, стало примерно понятно, какого размера корпус мне нужно подобрать для устройства. Самая длинная размерность корпуса должна была иметь размер не менее 75 миллиметров - 12 светодиодов на расстоянии 5 миллиметров друг от друга и по 10 миллиметров по краям.

Наиболее подходящим мне показался корпус размерами 90x65x35. После переделки печатной платы под размеры корпуса, она приняла вот такой вид:

Со стороны зрителя располагаются компоненты, а с обратной стороны нанесены дорожки.

Для изготовления печатной платы при помощи лазерно-утюжной технологии, использовалось вот такое изображение:

Зеркальное отображение не понадобилось, поскольку на изображении обратная сторона видна как бы "на просвет" платы. Зритель смотрит на ту сторону платы, где находятся компоненты и сквозь плату видит обратную сторону уже в зеркальном виде.

3.3. Смета

Только детали, без материалов и инструментов:
№ позицииНаименование позицииКоличествоЦена за штукуЦена
1Светодиод круглый матовый 3мм LED DIP 3mm DFL-3014URD-61212.00р.144.00р.
2Резистор 47,0 Ом, 0,125/0,25 Вт122.00р.24.00р.
3Разъем DRB-25MA133.00р33.00р.
4Стеклотекстолит односторонний СФ1-35Г, 100*100*1,5мм135.00р.35.00р.
5Корпус для РЭА, МАСТЕР КИТ BOX-KA12 (90x65x35)1130.00р.130.00р.
6Шнур XYC008 (1.8м), удлинитель LPT DB25F-DB25M1160.00р.160.00р.
Итого526.00р.

3.4. Технология изготовления печатной платы

Дополнительно понадобятся:
  1. мини-дрель для выпиливания платы нужных размеров, выпиливания в корпусе места под разъём и для сверления отверстий, а также свёрла диаметрами от 0,5 мм до 3 мм и абразивные насадки различной формы - дисковой, роликовой, грибовидной или сферической. При сверлении отверстий большого диаметра мою мини-дрель часто заклинивало, поэтому их я сверлил шуруповёртом,
  2. маркер для печатных плат - дорисовать на плате плохо пропечатавшиеся фрагменты дорожек,
  3. хлорное железо для травления печатной платы,
  4. ёмкость для травления платы. Я использовал лоток для проявки фотографий,
  5. воронка для слива хлорного железа из лотка в пластиковую бутылку для дальнейшего использования (можно включить в список и саму пластиковую бутылку),
  6. ацетон для смыва тонера после травления печатной платы и удаления канифоли/флюса с печатной платы после её лужения. Знающие люди советуют использовать для обеих целей изопропиловый спирт,
  7. резиновые перчатки для защиты рук. Можно ещё дополнительно приобрести защитные очки. Поскольку у меня есть обычные очки для коррекции зрения, я заморачиваться с этим не стал.
Естественно, для пайки понадобятся также паяльник, припой и канифоль (или флюс). Для сборки готового изделия понадобится крестовая отвёртка :)

По ходу процесса делал фотографии, но фотоаппарат использовал старенький. Иногда он фокусируется на задний план, так что объект съёмки получается замыленным. Отобрал те немногие фотографии, которые получились более-менее чёткими. Последовательность изготовления следующая:
  1. В программе pcbedit, в которой была подготовлена печатная плата, нужно отключить все слои, кроме слоя с дорожками. Оставшийся видимым слой с дорожками нужно экспортировать как PNG-картинку с разрешением, поддерживаемым принтером. В моём случае это было разрешение 600x600 точек на дюйм. Следите за тем, чтобы картинка была зеркальным отражением того рисунка, который должен остаться на печатной плате.
  2. Берём какой-нибудь рекламный каталог с тонкой лощёной бумагой, вырываем из него лист. Если лист меньше формата А4, можно наклеить его скотчем на лист обычной бумаги для принтеров.
  3. Теперь нужно распечатать на лазерном принтере на лощёной бумаге PNG-картинку с дорожками. Для этого я воспользовался графическим редактором GIMP, поменяв в нём разрешение изображения при печати на разрешение 600x600 пикселей на дюйм. При печати нужно выставить максимальную "яркость", чтобы на бумагу перенеслось побольше тонера. Проследите за тем, чтобы все дорожки хорошо пропечатались. Если тонер в картридже заканчивается, на изображении могут появиться светлые полосы. В таком случае лучше заменить или заправить картридж. Если на бумаге есть небольшие непропечатавшиеся фрагменты, их можно будет дорисовать на самой печатной плате при помощи маркера для печатных плат.
  4. Совмещаем отпечаток с фольгированной частью текстолита и аккуратно подворачиваем края бумаги так, чтобы лист и текстолит были неподвижны друг относительно друга.
  5. Разогреваем на максимум утюг и прижимаем его к бумаге, так чтобы отпечаток схватился с фольгой. Подошва утюга и кусок текстолита могут быть не идеально ровными, поэтому если просто прижать их друг к другу, некоторые места могут оказаться не пропечатанными. Поэтому дальше тщательно проглаживаем бумагу по всей площади, в том числе медленными движениями, уголком и рёбрами утюга. Не бойтесь перегреть бумагу или плату - белый лист бумаги после хорошего разглаживания должен слегка потемнеть и приобрести коричневатый оттенок.
  6. После тщательного проглаживания бросаем плату в воду и даём размякнуть бумаге. Достаточно 3-5 минут. После этого можно счищать прилипшую бумагу пальцами. Я воспользовался старой зубной щёткой. Если тонер был хорошо прогрет, то он должен крепко прилипнуть к фольге, поэтому можно счищать бумагу без опаски, довольно уверенными нажатиями. Подозреваю, что слишком сильными нажатиями можно счистить и сам тонер, поэтому излишне усердствовать тоже не стоит. После высыхания тёмный отпечаток тонера приобретёт белёсый вид из-за мелких, впаявшихся в него волокон бумаги.
  7. Теперь можно отпилить от куска текстолита тот кусок, где остался рисунок. Я делал это мини-дрелью, воспользовавшись абразивным диском.
  8. Надеваем резиновые перчатки, наливаем в ванночку тёплой воды и сыпем соль хлорного железа. Если уже имеется раствор, оставшийся после прошлого травления, можно использовать его. Опускаем в ванночку печатную плату и ждём около получаса, когда между дорожками растворится медь. Неплохо в процессе травления слегка покачивать ванночку или саму плату. Сначала слой меди начнёт менять оттенок до фиолетового, а потом у краёв платы и дорожек начнёт проступать текстолит. Продолжаем травить до полного пропадания видимой меди.
  9. Раствор хлорного железа аккуратно переливаем в пластиковую бутылку (пластиковая воронка облегчит этот процесс). Плату, ванночку, воронку и перчатки промываем в воде.
  10. Ватными тампонами при помощи ацетона отмываем тонер (и следы маркера для печатных плат) с протравленной платы.
  11. Покрываем плату канифолью при помощи разогретого паяльника или флюсом (я использовал флюс ЛТИ-120) при помощи кисточки.
  12. Покрываем припоем при помощи разогретого паяльника все дорожки, контактные площадки, металлизацию вокруг отверстий и надписи. Чтобы на дорожках не образовались наплывы, с припоем жадничаем - размазываем то, что прилипло к жалу паяльника по дорожкам до тех пор, пока дорожки не перестанут лудиться. После этого можно взять ещё немного припоя и продолжить лужение.
  13. После лужения платы смываем с неё канифоль или флюс при помощи ватного тампона и ацетона.
  14. Сверлим отверстия при помощи мини-дрели. Крупные отверстия лучше сверлить маломощной дрелью, я для этих целей использовал шуруповёрт.
  15. Плата готова, можно впаивать в неё электронные компоненты.
  16. Сверлим в корпусе отверстия под светодиоды и разъём параллельного порта. Вставляем плату, закручиваем шурупы, получаем готовое устройство.

3.5. Готовое устройство


Корпус устройства с просверленными в нём отверстиями под светодиоды и вырезом под разъём параллельного порта:

Печатная плата со впаянными в неё компонентами:

4. Ошибки

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

Первый экземпляр был сначала рассверлен по отпечатанному шаблону, а потом проутюжен уже по вновь отпечатанному шаблону. Отпечаток не совпал с отверстиями, причём отпечаток с отверстиями явно имеет чуть большие размеры, чем отпечаток с дорожками. С такими плавающими раз от раза размерам отпечатка, мне показалось что попробовать подогнать отпечаток под отверстия - занятие бесперспективное. Поэтому я решил сделать плату заново, сначала напечатав дорожки и протравив плату. А сверлить отверстия я решил уже после лужения дорожек.

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

В одном месте тонер не пропечатался и дорожка получилась с разрывом, который был устранён аккуратным её залуживанием. В другом месте в процессе лужения, видимо - из-за перегрева, от дорожки отслоился небольшой кусочек фольги. Лужением тут восстановить дорожку оказалось невозможно, поэтому был напаян небольшой кусочек проводка. И, наконец, одна маленькая, но самая важная деталь: на схеме я по ошибке отметил разъём DB25-мама, а впаивать собирался (и впаял) разъём DB25-папа. С виду их контактные площадке на плате невозможно отличить друг от друга, однако они являются зеркальным отражением друг друга из-за разной нумерации выводов. В результате, чтобы оживить эту плату, можно впаять в неё разъём DB25-мама, и она с соответствующим кабелем будет работать так, как и было задумано. Хорошо, что из-за этой ошибки не вышел из строя параллельный порт компьютера. Я, однако, предпочёл сделать новую плату, соответствующую первоначальной задумке.

В следующей заметке я напишу подробнее о программной части этого учебного проекта.