Используем LCD-индикатор в Debian
Для одной из своих самодельных конструкций - а именно аудиоплеера на базе Linux - я решил использовать LCD-индикатор для отображения текущей информации. В этой статье я опишу подробности сборки конструкции и настройки софта для работы с индикатором в связке Debian + lcd4linux + lirc + Music On Console Player.
Изучение темы
Для начала залез в гугл в поисках информации о малогабаритных символьных LCD-индикаторах с USB-интерфейсом и поддерживающихся в Linux. Сходу нагуглились некие picoLCD и статьи по их настройке. Однако цена в 40-50$ - как-то многовато для маленького индикатора. Поиск альтернативных вариантов привел к статье на Википедии про HD44780 - фактический стандарт для подобных LCD-контроллеров. Следовательно, оставалось найти способ подключить его к компьютеру.
Дальнейший поиск показал, что контроллер прекрасно подключается напрямую на LPT-порт компьютера - а значит, при необходимости с использованием простого переходника USB-LPT за 10$ его можно подключить и к USB.
Обход магазинов города и рынка радиодеталей принес результат в виде индикатора Winstar WH1602B-NYG-CT, представляющего собой LCD-дисплей без подсветки, состоящий из двух строк по 16 символов и управляющийся аналогом упомянутого контроллера. Обошелся он мне в 7$. Были еще более дорогие варианты с подсветкой, а также пара графических дисплеев. В другом магазине нашел Seiko L2432 за 6$ (24 символа, 2 строки, без подсветки (хотя по даташиту вроде должна быть) и без русского (что я выяснил уже позже)).
Первый индикатор я успешно спалил, перепутав питание (попался на глаза вариант распайки под другой индикатор - читайте примечание на Википедии в статье выше), поэтому был куплен еще один WH1602B-NYG-CT и в дополнение - упомянутый L2432.
Подключение
На материнской плате плеера обнаружилась гребенка LPT-порта, поэтому задача упрощалась и покупать USB-LPT-переходник не пришлось. За основу была взята эта схема, исключая цепи подсветки (диод с резистором, транзистор и подстроечник на 16 контакте индикатора). Разводка индикатора соответствует той, что была показана в таблице на Википедии:
- шина данных LPT-порта цепляется один к одному на DB-контакты индикатора
- 1@LPT -> 6@LCD
- 14@LPT -> 5@LCD
- 16@LPT -> 4@LCD
- 1@LCD - общий, 2 - +5В, 3 - регулировка контраста.
Соединять кучу контактов на LPT на общий провод не обязательно. Для запитки можно взять, например, питание с внутреннего USB-порта (1 и 4 контакты, если кто не в курсе :) ).
Чем выше напряжение на входе контрастности - тем ниже контрастность. В финальной конструкции подстроечник был заменен на делитель из двух резисторов на 9,1 К (к +) и 1,5 К (к 0).
Софт
Готовыми конфигами под такой индикатор поделиться никто не захотел, поэтому поначалу пробовал все методом полунаучного тыка.
Поставил lcd4linux, создал более-менее пристойный конфиг на базе того, что нашел в интернете; попробовал прописывать разные доступные драйверы. Попутно пытался заставить работать параллельный порт. С портом помогла эта статья. В двух словах суть: нужно выгрузить модуль lp, а загрузить parport_pc и ppdev.
Однако индикатор в лучшем случае реагировал закрашиванием произвольных блоков.
Поставил LCDproc. При настройке помогла эта статья. Поначалу индикатор так же само отказывался работать, однако в конфиге /etc/LCDd.conf в разделе hd44780 мне не понравился ConnectionType - дефолтно он был, кажется, 4bit, а линий у меня 8 (с 8bit тоже не заработало). В общем, полез в ман в поисках других вариантов и в итоге перепробовал для hd4470 все, что не относилось к USB и последовательному подключению. И на типе "winamp" индикатор внезапно заработал. Команды из статьи вывели апплеты на индикатор - в общем, все ок.
Мне от индикатора требовалась возможность выводить произвольную строку в произвольный момент времени без переконфигурирования сервисов. В статье предлагалось собрать сторонний модуль ядра lcdmod (который не обновлялся уже много лет), что меня, естественно, не устраивало. Поэтому вернулся к попыткам запуска под lcd4linux, однако теперь уже знал, в какую сторону копать.
Изучение /usr/share/doc/lcd4linux/lcd4linux.conf.sample.gz показало, что там описан аналогичный пример winamp-типа. Правильнее сказать - разводки на LPT-порт, ведь, фактически, вся разница была в указании соответствия сигналов LCD-индикатора сигналам LPT-порта (иначе говоря, если сделать "правильную" разводку, соответствующую дефолтным настройкам lcd4linux - ничего и указывать не придется).
Составил простой конфиг по найденным в интернете примерам - все работает. Описание конфига lcd4linux.conf чем-то напоминает конфиг X-сервера: описываются дисплеи, виджеты, вид экрана и переменные, а потом это все собирается в одну кучу указанием нужного вида для нужного дисплея.
Для отображения произвольной строки было решено использовать fifo-сокеты - со стороны lcd4linux был сделан апплет, читающий информацию с сокета с некоторой периодичностью, а клиент должен был отправлять нужную строку в сокет. Так как строки 2, а нужно было независимо менять их содержимое (да и вывести одной командой информацию сразу на две строки я не знаю как - возможно, надо специальным образом описать виджет), то было сделано 2 виджета, каждый со своим сокетом.
Стоит заметить, что одним из основных режимов вывода на индикатор был вывод информации о проигрываемом треке. Так как индикатор небольшой, то логичным было использовать бегущую строку. lcd4linux имеет встроенные средства для организации бегущей строки (align "M" и указание скорости прокрутки через speed в настройке виджета), но тогда бы не получилось на той же строке выводить какую-то статичную информацию - например, шкалу громкости. В итоге пришлось использовать только статический вывод, а бегущую строку эмулировать в скрипте.
Итоговый конфиг lcd4linux.conf получился такой:
Display HD44780 {
Driver 'HD44780'
Model 'generic'
Port '/dev/parport0'
Size '16x2'
Wire {
RW 'AUTOFD'
RS 'INIT'
ENABLE 'STROBE'
}
}
Widget String1 {
class 'Text'
expression exec('[ ! -e /tmp/fifo1 ] && mkfifo -m 666 /tmp/fifo1 || cat /tmp/fifo1')
width 16
align 'L'
update tick
}
Widget String2 {
class 'Text'
expression exec('[ ! -e /tmp/fifo2 ] && mkfifo -m 666 /tmp/fifo2 || cat /tmp/fifo2')
width 16
align 'L'
update tick
}
Layout Default {
Row1 {
Col1 'String1'
}
Row2 {
Col1 'String2'
}
}
Variables {
tick 500
}
Display 'HD44780'
Layout 'Default'
Как видите, ничего сложного. Описывается дисплей, где указывается размер дисплея, драйвер и порт, а также та самая разводка проводов. Описываются 2 виджета с проверкой на существование fifo-сокета и создание его при необходимости (да, права 666 не сильно секьюрные, но плеер - однопользовательская система и там никто не будет печатать мне маты на дисплее или делать что-то подобное :) ), описывается размещение виджетов на дисплее, описываются переменные, после чего для заданного дисплея указывается заданное размещение виджетов.
Теперь отправляя на /tmp/fifo1 или /tmp/fifo2 нужную строку, можно увидеть ее на экране.
Не забываем прописать lcd4linux в автозапуск при старте системы, дефолтно этого нет.
Поддержка кириллицы
Все бы хорошо, однако при попытке отправить что-то, написанное на русском языке, на индикаторе я получал кракозяблики. Однако индикатор должен поддерживать русский (о чем свидетельствует суффикс CT в обозначении). При поиске решения были опробованы разные варианты, в первую очередь
expression iconv('UTF-8', 'KOI8-R', 'текст ')
увиденный в этой статье. Однако это был не KOI8-R, да и другие кодировки не подходили. Зато попался на глаза самописный транслятор, где русские буквы заменялись напрямую на коды. Попробовал отправить один из кодов - получил символ. Не тот, что в том трансляторе, но хоть что-то. Набросал простой скрипт вида
for i in {0..7}
do
for j in {0..7}
do
for k in {0..7}
do
echo -e "\\0$i$j$k $i$j$k" > /tmp/fifo1
echo 0$i$j$k
read var
echo 0$i$j$k $var >> wh1602
done
done
done
И таким вот "брутфорсом", перебирая все коды в восьмеричной системе и вводя то, что видел на дисплее, я получил полную таблицу символов.
Непрерывного списка русских букв там не было, были только отличающиеся и начинались они с 240-го кода; остальное надо было преобразовывать в выглядящие так же латинские буквы.
Полученный список был преобразован в читаемый sed'ом вариант, попутно отсеяны совсем уж экзотические символы:
s@А@A@g s@В@B@g s@С@C@g s@Е@E@g s@Н@H@g s@К@K@g s@М@M@g s@О@O@g s@Р@P@g s@Т@T@g s@Х@X@g s@а@a@g s@с@c@g s@е@e@g s@о@o@g s@р@p@g s@х@x@g s@Б@\\0240@g s@Г@\\0241@g s@Ё@\\0242@g s@Ж@\\0243@g s@З@\\0244@g s@И@\\0245@g s@Й@\\0246@g s@Л@\\0247@g s@П@\\0250@g s@У@\\0251@g s@Ф@\\0252@g s@Ч@\\0253@g s@Ш@\\0254@g s@Ъ@\\0255@g s@Ы@\\0256@g s@Э@\\0257@g s@Ю@\\0260@g s@Я@\\0261@g s@б@\\0262@g s@в@\\0263@g s@г@\\0264@g s@ё@\\0265@g s@ж@\\0266@g s@з@\\0267@g s@и@\\0270@g s@й@\\0271@g s@к@\\0272@g s@л@\\0273@g s@м@\\0274@g s@н@\\0275@g s@п@\\0276@g s@т@\\0277@g s@у@y@g s@ч@\\0300@g s@ш@\\0301@g s@ъ@\\0302@g s@ы@\\0303@g s@ь@\\0304@g s@э@\\0305@g s@ю@\\0306@g s@я@\\0307@g s@«@\\0310@g s@»@\\0311@g s@„@\\0312@g s@”@\\0313@g s@№@\\0314@g s@£@\\0317@g s@×@\\0325@g s@↑@\\0331@g s@↓@\\0332@g s@Д@\\0340@g s@Ц@\\0341@g s@Щ@\\0342@g s@д@\\0343@g s@ф@\\0344@g s@ц@\\0345@g s@щ@\\0346@g s@é@\\0352@g s@ç@\\0353@g s@å@a@g s@Ø@O@g s@ļ@l@g s@ģ@g@g s@Ļ@L@g s@ē@e@g s@ū@u@g s@ā@a@g s@š@s@g s@ī@i@g s@ņ@n@g s@є@e@g s@ї@i@g s@і@i@g
Для доработки остается еще замена украинских символов, некоторых юникодных (например, степени) а также диакритики.
Теперь командой вида
echo -e "$(echo "Текст" | sed -f /home/rain/wh1602.sed)" > /tmp/fifo1
можно было печатать и русские слова.
Кстати, упомянутый выше L2432 в знакогенераторе имеет немного больше различных юникодных символов, а вместо русского - китайские иероглифы.
Управляющий скрипт
От индикатора хотелось 3 режима:
- Верхняя строка - текущее время трека, потом режим плеера (Play, Pause, Stop), потом пусть будет режим работы карты (например, 24/192). Нижняя строка - бегущая; исполнитель и название трека, взятые из Music On Console. Соответственно, обновление всего этого дела должно быть минимум раз в полсекунды
- При регулировке громкости верхняя строка должна заменяться на "Громкость" и уровень громкости в процентах. Нижняя строка - шкала громкости
- При перемотке трека сверху отображать текущую позицию, снизу - шкалу позиции на треке
- Пункты 2 и 3 после отпускания кнопки должны возвращаться к пункту 1 секунд через 5.
Соответственно, был необходим некий сервис - связующее звено между компонентами плеера и lcd4linux. Через некоторое время такой скрипт был написан:
#!/bin/bash
# Установка начальных значений внутренних переменных
clock='0.5' # Скорость работы внутреннего цикла
l='16' # Длина индикатора
a='0' # Инициализация значения переменной бегущей строки
vtime='0' # Инициализация значение таймера показа громкости
rtime='0' # Инициализация значение таймера показа перемотки
showvol='20' # *clock sec - время показа экрана регулировки громкости
showtrack='20' # *clock sec - время показа экрана перемотки трека
# Файлы-маркеры
volfile='/tmp/volchange' # При наличии файла показывается экран регулировки громкости
rewfile='/tmp/rewind' # При наличии файла показывается экран
nextfile='/tmp/nextsong' # При наличии файла обновляется информация о треке и цикл бегущей строки начинается заново
# Название микшера
mixer='PCM,0'
# Номер карты
card='1'
# Файл состояния карты в procfs
cardstate='/proc/asound/card1/pcm0p/sub0/hw_params'
#cardstate='/proc/asound/card0/pcm0p/sub0/hw_params'
# Сокет первой строки
str1sock='/tmp/fifo1'
# Сокет второй строки
str2sock='/tmp/fifo2'
# Функция трансляции кириллицы и прочих символов
cyrconv() {
echo -e "$(cat | sed -f /home/rain/wh1602.sed)"
}
# Ждем, пока не запустится lcd4linux
while [ ! -p "${str1sock}" ] ; do sleep 0.1 ; done
# Пишем приветствие
echo "* Плеер готов *" | cyrconv > "${str1sock}"
echo "* к работе *" | cyrconv > "${str2sock}"
sleep 5
# Главный цикл
while sleep "${clock}"
do
# Проверяем значение переменной экрана громкости
if [ "${vtime}" != '0' ]
then
# Получаем текущее значение уровня громкости
vol="$(amixer -c "${card}" sget "${mixer}" | awk '/^ +Front/{gsub(/\[|\]|\%/, "", $5) ; print $5 ; exit}')"
# Выводим первую строку
echo "Громкость: ${vol}%" | cyrconv > "${str1sock}"
# Вычисляем и рисуем нужное число блоков во второй строке
for i in $(seq 1 $((${vol}*${l}/100)))
do
echo -n '\0777'
done | cyrconv > "${str2sock}"
let vtime-=1
# Проверяем значение переменной перемотки
elif [ "${rtime}" != '0' ]
then
# Получаем значения текущего времени и состояния плеера
times="$(mocp -i 2>/dev/null | grep -E 'Time|Sec|State')"
# Рисуем первую строку - текущую позицию и общее время звучания трека
mawk '
/^CurrentTime:/{
ct=$2
}
/^TotalTime:/{
tt=$2
} END {print ct, " ", tt}' <<< "${times}" | cyrconv > "${str1sock}"
# Вычисляем и рисуем шкалу прогресса
# Если не проверять состояние плеера и не печатать 0 для seq - можно загнать скрипт в бесконечный цикл с поеданием памяти
for i in $(seq 1 $(mawk '
/^CurrentSec:/{
cs=$2
}
/^TotalSec:/{
ts=$2
}
/^State:/{
state=$2
} END {
OFMT="%.0f"
if (state=="STOP") print 0
else print cs/ts*"'$l'"
}' <<< "${times}"))
do
echo -n '\0777'
done | cyrconv > "${str2sock}"
let rtime-=1
# Проверяем, не изменилась ли громкость
elif [ -e "${volfile}" ]
then
vtime="${showvol}"
rm -f "${volfile}"
# Проверяем, не перематывается ли трек
elif [ -e "${rewfile}" ]
then
rtime="${showtrack}"
rm -f "${rewfile}"
# Проверяем, нет ли переключения на следующий трек
elif [ -e "${nextfile}" ]
then
# Получаем теги трека
tags=" $(mocp -i 2>/dev/null | mawk '/^Title:/{gsub(/^Title: /, ""); gsub(/^([0-9]+)/, "&."); print}') "
a=0
rm -f "${nextfile}"
else
# Иногда из-за переходных процессов в переменной оказывалось непонятно что
# Проверяем наличие полезной информации, если нет - перезапускаем бегущую строку
[ "${#tags}" -lt '10' ] && touch "${nextfile}"
# Обрабатываем сразу 2 файла для уменьшения числа операций
# Обрабатываем состояние карты и информацию о состоянии и текущей позиции плеера
data="$(mocp -i 2>/dev/null | mawk '
/^State:/{
state=$2
}
/^CurrentTime:/{
time=$2
}
/^format:/{
gsub(/S|_|LE|BE/, "", $2)
format=$2
}
/^rate:/{
rate=$2/1000
}
END {
OFMT="%.0f"
OFS=""
if (state=="PLAY") print time, " > ", format, "/", rate
else if (state=="PAUSE") print time, " ПАУЗА"
else {
print "Плеер остановлен"
print "" > "'${nextfile}'"
}
}' - "${cardstate}")"
# Пришлось сделать так, чтобы индикатор не "мигал" верхней строкой в некоторые моменты времени
echo "${data}" | cyrconv > "${str1sock}"
# Бегущая строка
[[ "$a" -le "$((${#tags}-$l))" ]] && { echo -e "${tags:$a:$l}" | cyrconv > "${str2sock}" ; let a+=1 ; } || a='0'
fi
done
Для создания файлов-маркеров необходимо немного доработать конфиги Music On Console и lircrc (настройка последнего описывалась здесь).
- В MOCp надо в ~/.moc/config в OnSongChange задать команду touch /tmp/nextsong
- В lircrc надо в /etc/lirc/lircrc привести соответствующие блоки примерно к такому виду:
begin
prog = irexec
button = KEY_VOLUMEUP
repeat = 1
config = su -l rain -c 'touch /tmp/volchange ; mocp -v +2'
end
begin
prog = irexec
button = KEY_VOLUMEDOWN
repeat = 1
config = su -l rain -c 'touch /tmp/volchange ; mocp -v -2'
end
begin
prog = irexec
button = KEY_REWIND
repeat = 1
config = su -l rain -c 'touch /tmp/rewind ; mocp --seek -5'
end
begin
prog = irexec
button = KEY_FORWARD
repeat = 1
config = su -l rain -c 'touch /tmp/rewind ; mocp --seek +5'
end
Далее скрипт надо прописать в cron, чтобы он стартовал вместе с системой. У меня это сделано так:
rain@player:~$ crontab -l | grep lcd @reboot /home/rain/.lcd4linux/lcd.sh
Плюс в домашнем каталоге (или там, где это будет объявлено в функции в начале скрипта) должен находиться скрипт для sed с трансляцией символов.
Примечание:
- На данный момент время показа экранов регулировки громкости и перемотки не вполне корректно: после нажатия кнопки на пульте происходит удаление маркер-файла и начало отсчета, но при дальнейшем удержании кнопки файл создается снова. В итоге отсчет доходит до нуля, цикл проверок доходит до проверки наличия маркера, снова запускает цикл отсчета и удаляет маркер (теперь его уже никто не создает). В результате время отображения плавает от showvol*clock+clock до showvol*clock*2 (т.е., от 5 до 10 секунд, в зависимости от того, как долго удерживалась кнопка) - и аналогично для переменных перемотки. В изначальной версии проверки маркеров шли первыми, однако это блокировало обновление экрана во время удержания кнопки. Как вариант фикса - перенести рисование соответствующего экрана в функцию и вызывать ее 2 раза - при проверке "уровня" переменной таймера и перед удалением маркера, а блок проверки наличия маркера снова вернуть на первое место.
- В ESI Juli@ отображение разрядности в режиме работы карты не имеет смысла - карта всегда работает в 32-битном режиме, поэтому всегда будет отображаться "32/". Вместо этого можно более красиво выводить частоту дискретизации (44КГц, 192КГц) или просто упростить обработку (выводить 44100, 172400 и так далее).
- В lircrc можно объявить еще пару кнопок (что-нибудь вроде "Display"), которые будут создавать маркеры регулировки громкости и перемотки трека. Это позволит переключаться на соответствующие экраны без, собственно, регулировки громкости или перемотки трека.