Используем LCD-индикатор в Debian

Материал из Linux Wiki
Перейти к навигацииПерейти к поиску

Для одной из своих самодельных конструкций - а именно аудиоплеера на базе 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'ом вариант, попутно отсеяны совсем уж экзотические символы:

wh1602.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 режима:

  1. Верхняя строка - текущее время трека, потом режим плеера (Play, Pause, Stop), потом пусть будет режим работы карты (например, 24/192). Нижняя строка - бегущая; исполнитель и название трека, взятые из Music On Console. Соответственно, обновление всего этого дела должно быть минимум раз в полсекунды
  2. При регулировке громкости верхняя строка должна заменяться на "Громкость" и уровень громкости в процентах. Нижняя строка - шкала громкости
  3. При перемотке трека сверху отображать текущую позицию, снизу - шкалу позиции на треке
  • Пункты 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 "" > "/tmp/nextsong"
                                                }
                                }' - "${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 раза - при проверке "уровня" переменной таймера и перед удалением маркера, а блок проверки наличия маркера снова вернуть на первое место.

Ссылки