Добрый день! Продожим работу с платой от NAS WesternDigital MyBook Live и подключенным к ней ЖК индикатором. Итак, в предыдущей части мы нашли на плате место для подключения к шине I2C, подключили расширитель портов с индикатором, убедились что все работает. Сегодня выведем на индикатор состояние системы.
Начало было тут: Подключение символьного ЖКИ к плате от WD MyBook Live на AppliedMicro APM82181
Содержание первой части:
Сегодня рассмотрим:
Как и прежде, дополнения и замечания приветствуются.
Итак, к этому моменту подключен в систему расширитель портов, которыми мы можем управлять. К расширителю присоединен символьный ЖК индикатор на клоне контроллера HD44780. Теоретически мы можем им управлять, включив все порты на вывод и зная их назначение. Подсветкой помигать уже удалось, дергая третий порт.
Соединение между контроллером HD44780 и расширителем портов организовано так:
Это один из встречающихся вариантов. Контроллер при этом переводится и работает в 4-битном режиме, а байт передается по частям.
Имея в распоряжении все порты расширителя, можно выдавать на него данные побитно и таким образом управлять дисплеем. Думаю согласитесь, что это не очень удобно.
Попробуем напрямую управлять через шину I2C. Простой вариант для проверки такой возможности — использовать набор утилит I2C-tools. В LEDE они в разделе Utilites. В набор входит i2cdetect, i2cdump, i2cget, i2cset. Нас интересует последняя и немного первая (для диагностики).
С помощью i2cdetect можно обнаружить подключенные на шину устройства и определить их адрес.
Утилита i2cset используется для вывода данных устройству с заданным адресом на шине I2C.
Зная последовательность инициализации для вашего ЖКИ, можно без проблем ее выполнить и вывести символы на экран.
Чтобы не изобретать велосипед, рекомендую скачать отсюда: I2C hd44780 модуль на расширителе PCF8574 «тестилку i2c lcd». Вот прямая ссылка. Внутри архива shell скрипт, который работает с индикатором через команду i2cset и выдает на экран символы поочередно. Единственно перед использованием надо закоментировать или удалить строки в начале файла:
Они создают программный порт I2C на любых свободных портах ввода-вывода, а у нас уже есть аппаратный. Ну и кроме того она рассчитана на индикатор размерностью 4*40, но для проверки работоспособности и понимания использования утилиты i2cset это ни чуть не помешает.
Результат:
Немного пояснений по ее реализации. Процедуры write_CMD и print_LCD выводят соответственно на индикатор команду или данные. Это зависит от сигнала RS, в нашем случае находящегося на нулевой бите.
Процедура init_LCD последовательно выдает команды для инициализации индикатора согласно его datasheet'у, широко распространенному в интернет. Например вот. Далее последовательно выдаются различные символы на экран.
Все замечательно, однако хотелось бы уйти от использования утилит, и иметь символьное устройство, выводя на который байты, они бы попадали прямо на шину I2C, конечно с заданным адресом.
К сожалению мне не удалось найти такой драйвер для шины I2C в LEDE. Поэтому с экспериментальными целями было решено переделать один из существующих. Понятно что при желании его использовать и далее, надо было не переделывать, а хотя бы создать на его основе новый.
Подходящим для опытов оказался драйвер EEPROM под шину I2C. В ядре LEDE был подключен драйвер kmod-eeprom-at24, после обновления системы сделана попытка добавления устройства:
Успешно подключилось. Теперь если вывести что-то в устройство:
то на шине мы увидим следующую последовательность байт:
Первый байт в каждой тройке — это адрес устройства, умноженный на 2 (0x27 x 2). Второй — адрес ячейки в EEPROM, третий — данные. Драйвер вполне подходит для передачи данных на ЖКИ, за исключением выдачи адреса ячейки.
Чтобы убрать это исправим файл драйвера build_dir/target-powerpc_464fp_musl-1.1.15/linux-apm821xx_sata/linux-4.4.21/drivers/misc/eeprom/at24.c. Закомментируем несколько строк в процедуре at24_eeprom_write (335-337):
Компилируем-обновляем, добавляем устройство, смотрим вывод
Все правильно, вывод без адреса ячейки, только того что нам надо:
Заодно теперь программа рассчитана только на нашу геометрию экрана — 2х16 символов. Понятно, что изменяя исходник в каталоге build_dir, следует ожидать что в ближайшем времени файл будет восстановлен из оригинальных пакетов при сборке. Для создания постоянных исправлений следует использовать возможность применения патчей на этапе сборки.
После изучения вопроса работоспособности данного варианта подключения ЖКИ было решено попробовать возложить на индикатор некоторый функционал. Например отображение какой-то части состояния операционной системы.
Такой пакет уже существует и даже включен в состав LEDE. Это LCD4Linux. Он позволяет получать необходимую информацию о компонентах ОС и располагать ее на индикаторе в нужном месте. Естественно, обновление в реальном времени.
Однако использование его с нашим индикатором на шине I2C вызвало некоторые затруднения.
Подключение дисплея на HD44780-I2C из комплекта LCD4Linux
Было также опробованы почти все возможные варианты дисплеев из пакета, в том числе и для использования символьного устройства на базе драйвера EEPROM, сделанного в предыдущей главе. Не заработало.
Тогда было решено идти другим путем. Добавить в систему драйвер именно этого индикатора, принимающий для отображения символы и команды управления, а затем добавить новый дисплей, использующий этот драйвер, в LCD4Linux, благо в нем есть для этого руководство.
Итак, берем готовый драйвер для HD44780 на I2C отсюда: Linux driver for Hitachi HD44780 LCD attached to I2C bus via PCF8574 I/O expander. Драйвер испытывался на Raspberry Pi, понимает пару команд управления терминала VT100, настраивается под разную геометрию индикатора, умеет отображать, гасить и мигать курсором. Осталось его интегрировать в LEDE и немного доработать.
Скачиваем, распаковываем в папку package/hd44780/src.
Оставляем в Makefile только это:
И создаем новый Makefile, только папкой выше, в package/hd44780, по аналогии с файлами в других пакетах LEDE:
Строку с автозагрузкой (AUTOLOAD:=$(call AutoLoad,70,hd44780)) можно добавить позже, когда драйвер будет протестирован. Теперь при вызове конфигуратора LEDE
Драйвер появится в модулях ядра (kmod-hd44780), и его можно добавить в конфигурацию:
После компиляции, обновления и перезагрузки, если не включена автозагрузка модуля, то пробуем загрузить, смотрим результат:
Пробуем добавить устройство:
На индикаторе, как заложено в драйвере, при инициализации выдается адрес созданного устройства: "/dev/lcd0", с мигающим курсором в конце.
На это устройство можно отправлять символы, которые будут отображаться на индикаторе:
Также можно управлять режимами работы через sysfs (/sys/class/hd44780/lcd0). По этому пути есть следующие имена файлов: backlight, cursor_display, geometry, cursor_blink. Через них можно настраивать геометрию экрана, управлять режимами курсора и подсветкой. Например, для выключения мигания курсора достаточно дать команду:
Кроме того поддерживаются две команды терминала VT100, это очистка экрана и установка курсора в начальную позицию. Подать их можно так:
Установку необходимых режимов также можно сделать при загрузке ОС. Для этого добавляем файл target/linux/apm821xx/base-files/etc/board.d/03_lcd в LEDE с содержимым:
Теперь плата будет вас приветствовать каждый раз при загрузке системы.
Итак, драйвер работает, но для использования в LCD4Linux он должен уметь размещать символы в любой позиции экрана. Согласно списка команд терминала выбираем нужную:
Esc[Line;ColumnH — Move cursor to screen location v,h
Находим файл package/hd44780/src/hd44780-dev.c и добавляем обнаружение и выполнение новой команды. Надо доработать процедуру обработки esc-последовательностей:
И надо изменить длину буфера для накопления и анализа esc-последовательностей. Находим в файле hd44780.h строку:
И исправляем значение на с 4 на 6. Можно компилировать и проверять. Можно компилировать только один пакет из LEDE.
Если ошибок нет, то компилируем весь проект, обновляем, перезагружаемся. Проверяем:
Драйвер ЖКИ выполняет свой функционал. Теперь его можно задействовать в пакете LCD4Linux для отображения состояния системы. Однако я в нем не нашел дисплея, работающего с драйвером по протоколу терминала.
Значит пишем свой. Согласно инструкции How to write new display drivers.
Исходные файлы можно взять в каталоге build_dir/target-powerpc_464fp_musl-1.1.15/lcd4linux-custom/lcd4linux-r1203, либо из пакета dl/lcd4linux-r1203.tar.bz2.
Все как в руководстве:
Далее пишем свои процедуры в файл drv_vt100.c.
drv_vt100_close оставляем пустой.
Редактируем и создаем файлы в отдельной от проекта LEDE папке. Затем, так как при компиляции проекта файлы LCD4linux обновляются из архива, то изменять их в папке build_dir/… бессмысленно. Необходимо пользоваться возможность применять патчи. Патчи для LCD4Linux располагаются в папке feeds/packages/utils/lcd4linux/patches. Свой, добавляющий новый драйвер дисплея VT100 нужно разместить тут же.
Для создания патча делаем рядом две папки. В одной (пусть 1/) размещаем оригинальные файлы, в другой (пусть 2/) те же, но измененные. Затем выполняем команду diff:
Создается один патч для всех файлов. Если посмотреть в патчах, которые уже есть в папке feeds/packages/utils/lcd4linux/patches, внутри файлов отсутствуют строки, которые показывают выполняемую команду «diff -Naur...». Приводим наш патч в такое же состояние и копируем в папку.
Заходим в конфигуратор LEDE.
Включаем его в проект, сохраняем настройки, компилируем, обновляем, перезагружаем. Подключаем наш драйвер в файле конфигурации:
На индикаторе мы видим, как и планировали в конфиге, цифры.
Индикатор на базе контроллера HD44780 имеет зашитый латинский алфавит с цифрами и знаками и свободно 8 программируемых символов (бывает есть и русские символы, но не в этом случае). Меняя отображение программируемых символов можно получить несложную анимацию. Ее варианты представлены в примере конфига LCD4Linux, но пока наши драйверы не поддерживают эту функцию, использовать их мы не можем.
Ничего сложного в этом нет, надо в одном драйвере принять 8 байт и отправить с командой в знакогенератор, в другом их отправить.
Снова исправляем
Добавляем в нее режим приема 8-и байтов знакогенератора и запись их в CGRAM (знакогенератор). Так как в VT100 я не нашел ESC-последовательность программирования символа, то пришлось что-то придумать. Пусть это будет Esc(S 8 байт, то есть код ESC, затем открывающая круглая скобка, латинская буква S и 8 байт матрицы. В нашем случае, при размере знакоместа 8*5 будет использоваться только 5 младших бит каждого байта.
В процедуре hd44780_write добавляем сброс режима приема знакогенератора (строка lcd->is_in_set_char = 0).
И описываем это поле структуры (is_in_set_char) в файле заголовков hd44780.h.
Теперь добавим этот функционал в драйвер дисплея LCD4Linux. Функция drv_vt100_defchar файла drv_vt100.c:
Компилируем, обновляем, перезагружаем. Меняем снова конфиг LCD4linux.
Проверяем:
На экране видим время работы платы с последней загрузки, загрузку системы, свободной память и символ анимации в виде заполняющегося и очищающегося диска.
Добавив исправление конфига LCD4Linux в патч 180-vt100.patch, мы получим такой же вид индикатора сразу при загрузке:
Теперь, когда все работает как планировалось, немного о скорости передачи данных. Хочется обратить внимание на два момента.
Во-первых, данный по шине I2C передаются очень маленькими блоками, а конкретно по одному байту. А каждому блоку добавляется адрес ведомого устройства. Логично предположить, что передача адреса устройства с блоком бОльшего размера увеличит утилизацию шины и уменьшит время передачи.
Проведем частичную оптимизацию. Для этого вспомним как передается один байт данных на индикатор. ЖКИ работает в 4-битном режиме, т.е. получает за раз по полбайта. Эти полбайта должны подтверждаться выдачей сигнала «Enable». В итоге для передачи одного байте из процессора на индикатор по шине I2C идет 6 байт:
И так как каждый сопровождается адресом, то реально это составляет 12 байт, при скорости шины 100 КГц это 1.2 мс.
Предлагается передавать те же 6 байт, но одним блоком, с одним байтом адреса, т.е. 7 байт вместо 12. Оригинальные процедуры передачи данных из драйвера HD44780.
Процедура из драйвера HD44780, исправленная для пакетной передачи данных.
И занимает 0,67 мс.
Во-вторых, скорость шины по умолчанию 100 КГц, а это не максимум. Конечно именно такая скорость рекомендуется для расширителя портов на ЖКИ. Но в тоже время многие разработчики говорят о бесперебойной работе и на 400 КГц. Конечно, использование нестандартного режима оправдано при необходимости и тщательном тестирование на отсутствие сбоев, а я всего лишь могу сказать как это сделать и что получится.
Информации в интернете о включении режима найти не удалось, пришлось пересматривать исходники LEDE. В итоге есть два варианта включения режима fast, т.е. 400 КГц.
Первый, это передать параметр модулю ядра. Модуль это i2c-ibm_iic. Параметр — iic_force_fast. В итоге надо к параметрам ядра при запуске добавить i2c-ibm_iic.iic_force_fast=1. Это можно сделать в загрузчике U-boot например так:
После загрузки системы мы имеем:
Второй — указать режим работы шины в дереве устройств (apollo3g.dtsi, параметр fast-mode):
После компиляции не забудьте обновить дерево устройств на TFTP сервере. И результат:
И скорость передачи байта 0,19 мс:
Что почти на порядок лучше исходной.
В качестве вывода можно сказать, что в результате проделанной работы мы получили возможность использовать плату со слабодокументированным в открытых источниках процессором в проектах, где применим Linux (LEDE). Основной интерфейс Ethernet, хранение на SATA, управление через I2C и несколько портов дают широкие возможности для разработчиков.
Ну и напоследок, для дублирования всего вышесказанного, файлы из LEDE, согласно структуры каталогов (вроде все вспомнил) доступны тут.
Содержание первой части:
1. Подключение консоли
2. Загрузка без диска
3. Компиляция в LEDE
4. Управление портами (через LuCI и консоль)
5. Подключение к шине I2C
6. Подключение расширителя портов PCF8574
Сегодня рассмотрим:
7. Инициализация HD44780 через i2cset
8. Символьное устройство для записи данных в шину I2C
9. Добавление драйвера HD44780 в ядро
10. Добавление обработки необходимых команд VT100 в драйвер HD44780
11. Добавление дисплея с некоторыми командами VT100 в LCD4Linux
12. Добавление команды программирования знакогенератора в драйвер HD44780
13. Оптимизация передачи данных по шине I2C
Как и прежде, дополнения и замечания приветствуются.
Итак, к этому моменту подключен в систему расширитель портов, которыми мы можем управлять. К расширителю присоединен символьный ЖК индикатор на клоне контроллера HD44780. Теоретически мы можем им управлять, включив все порты на вывод и зная их назначение. Подсветкой помигать уже удалось, дергая третий порт.
7. Инициализация HD44780 через i2cset
Соединение между контроллером HD44780 и расширителем портов организовано так:
RS — P0
R/W — P1
E — P2
BL — P3
D4 — P4
D5 — P5
D6 — P6
D7 — P7
Это один из встречающихся вариантов. Контроллер при этом переводится и работает в 4-битном режиме, а байт передается по частям.
Имея в распоряжении все порты расширителя, можно выдавать на него данные побитно и таким образом управлять дисплеем. Думаю согласитесь, что это не очень удобно.
Попробуем напрямую управлять через шину I2C. Простой вариант для проверки такой возможности — использовать набор утилит I2C-tools. В LEDE они в разделе Utilites. В набор входит i2cdetect, i2cdump, i2cget, i2cset. Нас интересует последняя и немного первая (для диагностики).
С помощью i2cdetect можно обнаружить подключенные на шину устройства и определить их адрес.
В нашем случае занят только адрес 0x27:
root@lede: i2cdetect 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x03-0x77.
Continue? [Y/n] y
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
Утилита i2cset используется для вывода данных устройству с заданным адресом на шине I2C.
Зная последовательность инициализации для вашего ЖКИ, можно без проблем ее выполнить и вывести символы на экран.
Чтобы не изобретать велосипед, рекомендую скачать отсюда: I2C hd44780 модуль на расширителе PCF8574 «тестилку i2c lcd». Вот прямая ссылка. Внутри архива shell скрипт, который работает с индикатором через команду i2cset и выдает на экран символы поочередно. Единственно перед использованием надо закоментировать или удалить строки в начале файла:
insmod i2c-dev
insmod i2c-gpio-custom bus0=0,$sda_gpio,$scl_gpio
Они создают программный порт I2C на любых свободных портах ввода-вывода, а у нас уже есть аппаратный. Ну и кроме того она рассчитана на индикатор размерностью 4*40, но для проверки работоспособности и понимания использования утилиты i2cset это ни чуть не помешает.
Результат:
Немного пояснений по ее реализации. Процедуры write_CMD и print_LCD выводят соответственно на индикатор команду или данные. Это зависит от сигнала RS, в нашем случае находящегося на нулевой бите.
Процедура init_LCD последовательно выдает команды для инициализации индикатора согласно его datasheet'у, широко распространенному в интернет. Например вот. Далее последовательно выдаются различные символы на экран.
8. Символьное устройство для записи данных в шину I2C
Все замечательно, однако хотелось бы уйти от использования утилит, и иметь символьное устройство, выводя на который байты, они бы попадали прямо на шину I2C, конечно с заданным адресом.
К сожалению мне не удалось найти такой драйвер для шины I2C в LEDE. Поэтому с экспериментальными целями было решено переделать один из существующих. Понятно что при желании его использовать и далее, надо было не переделывать, а хотя бы создать на его основе новый.
Подходящим для опытов оказался драйвер EEPROM под шину I2C. В ядре LEDE был подключен драйвер kmod-eeprom-at24, после обновления системы сделана попытка добавления устройства:
root@lede: echo 24c00 0x27 > /sys/bus/i2c/devices/i2c-0/new_device
root@lede: echo 24c00 0x27 >
[ 33.335472] at24 0-0027: 16 byte 24c00 EEPROM, writable, 1 bytes/write
[ 33.342102] i2c i2c-0: new_device: Instantiated device 24c00 at 0x27
Успешно подключилось. Теперь если вывести что-то в устройство:
root@lede: echo "1111" > /sys/bus/i2c/devices/i2c-0/0-0027/eeprom
то на шине мы увидим следующую последовательность байт:
0x4e-0x00-0x31 0x4e-0x01-0x31 0x4e-0x02-0x31 0x4e-0x03-0x31 0x4e-0x04-0x0a
Первый байт в каждой тройке — это адрес устройства, умноженный на 2 (0x27 x 2). Второй — адрес ячейки в EEPROM, третий — данные. Драйвер вполне подходит для передачи данных на ЖКИ, за исключением выдачи адреса ячейки.
Чтобы убрать это исправим файл драйвера build_dir/target-powerpc_464fp_musl-1.1.15/linux-apm821xx_sata/linux-4.4.21/drivers/misc/eeprom/at24.c. Закомментируем несколько строк в процедуре at24_eeprom_write (335-337):
//if (at24->chip.flags & AT24_FLAG_ADDR16)
// msg.buf[i++] = offset >> 8;
//msg.buf[i++] = offset;
Компилируем-обновляем, добавляем устройство, смотрим вывод
root@lede: echo 24c00 0x27 > /sys/bus/i2c/devices/i2c-0/new_device
[ 2708.782356] at24 0-0027: 16 byte 24c00 EEPROM, writable, 1 bytes/write
[ 2708.788891] i2c i2c-0: new_device: Instantiated device 24c00 at 0x27
root@lede: echo "1111" > /sys/bus/i2c/devices/i2c-0/0-0027/eeprom
Все правильно, вывод без адреса ячейки, только того что нам надо:
Теперь можно переделать тестовую программу, убрав оттуда вызов утилиты i2cset:
#!/bin/sh
i2c_adres=0x27
i2c_dev=/sys/bus/i2c/devices/i2c-0/0-0027/eeprom
led=8
ansi=0
to_octal () {
hh3=$(($hh / 64))
hh1=$(($hh - $hh3 * 64))
hh2=$(($hh1 / 8))
hh1=$(($hh1 - $hh2 * 8))
}
write_CMD () {
: $((hb = $c & 240))
: $((lb = ($c << 4) & 240 ))
hh=$((4 + $hb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
hh=$((0 + $hb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
hh=$((4 + $lb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
hh=$((0 + $lb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
}
print_LCD () {
: $((hb = $c & 240))
: $((lb = ($c << 4) & 240 ))
hh=$((5 + $hb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
hh=$((1 + $hb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
hh=$((5 + $lb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
hh=$((1 + $lb + $led))
to_octal
echo -e -n \\$hh3$hh2$hh1 >> $i2c_dev
}
########## init LCD #####################
init_LCD () {
if [[ ! -w $i2c_dev ]]
then
echo 24c00 0x27 > /sys/bus/i2c/devices/i2c-0/new_device
sleep 0.5
fi
sleep 0.5
c=3
write_CMD
c=3
write_CMD
c=2
write_CMD
c=40 #28
write_CMD
c=44 #2C
write_CMD
c=44 #2C
write_CMD
c=12 #0C
write_CMD
c=1
write_CMD
sleep 0.2
c=6
write_CMD
c=2
write_CMD
}
###############################
init_LCD
c=0x80 # stroka - 1
write_CMD
for i in `seq 32 63`; do
if [ "$i" == 48 ]; then
c=0xC0 # stroka - 2
write_CMD
fi
c=$(($i + $ansi))
print_LCD
done
sleep 3
c=0x80 # stroka - 1
write_CMD
for i in `seq 64 95`; do
if [ "$i" == 80 ]; then
c=0xC0 # stroka - 2
write_CMD
fi
c=$(($i + $ansi))
print_LCD
done
sleep 3
c=0x80 # stroka - 1
write_CMD
for i in `seq 96 127`; do
if [ "$i" == 112 ]; then
c=0xC0 # stroka - 2
write_CMD
fi
c=$(($i + $ansi))
print_LCD
done
Заодно теперь программа рассчитана только на нашу геометрию экрана — 2х16 символов. Понятно, что изменяя исходник в каталоге build_dir, следует ожидать что в ближайшем времени файл будет восстановлен из оригинальных пакетов при сборке. Для создания постоянных исправлений следует использовать возможность применения патчей на этапе сборки.
9. Добавление драйвера HD44780 в ядро
После изучения вопроса работоспособности данного варианта подключения ЖКИ было решено попробовать возложить на индикатор некоторый функционал. Например отображение какой-то части состояния операционной системы.
Такой пакет уже существует и даже включен в состав LEDE. Это LCD4Linux. Он позволяет получать необходимую информацию о компонентах ОС и располагать ее на индикаторе в нужном месте. Естественно, обновление в реальном времени.
Однако использование его с нашим индикатором на шине I2C вызвало некоторые затруднения.
Подключение дисплея на HD44780-I2C из комплекта LCD4Linux
в файле \etc\lcd4linux.conf
Display HD44780-I2C {
Driver 'HD44780'
Model 'generic'
Bus 'i2c'
Port '/dev/i2c-0'
Device '0x27'
Bits '4'
Size '16x2'
asc255bug 0
Icons 1
Wire {
RW 'DB1'
RS 'DB0'
ENABLE 'DB2'
GPO 'GND'
}
}
вызывало ошибку:
root@lede: /usr/bin/lcd4linux -v -F
LCD4Linux 0.11.0-SVN-1193 starting
HD44780: $Rev: 1202 $
HD44780: using model 'generic'
HD44780: using I2C bus
HD44780: using 1 Controller(s)
HD44780: using 4 bit mode
udelay: using gettimeofday() delay loop
Segmentation fault
Было также опробованы почти все возможные варианты дисплеев из пакета, в том числе и для использования символьного устройства на базе драйвера EEPROM, сделанного в предыдущей главе. Не заработало.
Тогда было решено идти другим путем. Добавить в систему драйвер именно этого индикатора, принимающий для отображения символы и команды управления, а затем добавить новый дисплей, использующий этот драйвер, в LCD4Linux, благо в нем есть для этого руководство.
Итак, берем готовый драйвер для HD44780 на I2C отсюда: Linux driver for Hitachi HD44780 LCD attached to I2C bus via PCF8574 I/O expander. Драйвер испытывался на Raspberry Pi, понимает пару команд управления терминала VT100, настраивается под разную геометрию индикатора, умеет отображать, гасить и мигать курсором. Осталось его интегрировать в LEDE и немного доработать.
Скачиваем, распаковываем в папку package/hd44780/src.
Дерево
ls -l
-rw-r--r-- 1 root root 18092 Feb 21 2016 LICENSE
-rw-r--r-- 1 root root 60 Nov 9 06:17 Makefile
-rw-r--r-- 1 root root 1945 Feb 21 2016 README.md
-rw-r--r-- 1 root root 10316 Nov 16 04:33 hd44780-dev.c
-rw-r--r-- 1 root root 7756 Feb 21 2016 hd44780-i2c.c
-rw-r--r-- 1 root root 1122 Nov 16 03:28 hd44780.h
-rw-r--r-- 1 root root 235 Feb 21 2016 make.sh
Оставляем в Makefile только это:
obj-m := hd44780.o
hd44780-y := hd44780-i2c.o hd44780-dev.o
И создаем новый Makefile, только папкой выше, в package/hd44780, по аналогии с файлами в других пакетах LEDE:
package/hd44780/Makefile
include $(TOPDIR)/rules.mk
include $(INCLUDE_DIR)/kernel.mk
PKG_NAME:=hd44780
PKG_RELEASE:=1
PKG_BUILD_DIR := $(BUILD_DIR)/$(PKG_NAME)
include $(INCLUDE_DIR)/package.mk
define KernelPackage/hd44780
SUBMENU:=Other modules
TITLE:=I2C HD44780 driver
FILES:=$(PKG_BUILD_DIR)/hd44780.ko
AUTOLOAD:=$(call AutoLoad,70,hd44780)
KCONFIG:=
endef
define Package/hd44780/description
Big comments....
...
endef
MAKE_OPTS:= ARCH="$(LINUX_KARCH)" CROSS_COMPILE="$(TARGET_CROSS)" SUBDIRS="$(PKG_BUILD_DIR)" EXTRA_CFLAGS="$(EXTRA_CFLAGS)"
define Build/Prepare
mkdir -p $(PKG_BUILD_DIR)
$(CP) ./src/* $(PKG_BUILD_DIR)/
endef
define Build/Compile
$(MAKE) -C "$(LINUX_DIR)" $(MAKE_OPTS) modules
endef
$(eval $(call KernelPackage,hd44780))
Строку с автозагрузкой (AUTOLOAD:=$(call AutoLoad,70,hd44780)) можно добавить позже, когда драйвер будет протестирован. Теперь при вызове конфигуратора LEDE
make menuconfig
Драйвер появится в модулях ядра (kmod-hd44780), и его можно добавить в конфигурацию:
LEDE Configuration
После компиляции, обновления и перезагрузки, если не включена автозагрузка модуля, то пробуем загрузить, смотрим результат:
root@lede: insmod hd44780
root@lede: lsmod |grep 44780
hd44780 5450 0
Пробуем добавить устройство:
root@lede: echo hd44780 0x27 > /sys/bus/i2c/devices/i2c-0/new_device
[ 9463.913178] i2c i2c-0: new_device: Instantiated device hd44780 at 0x27
На индикаторе, как заложено в драйвере, при инициализации выдается адрес созданного устройства: "/dev/lcd0", с мигающим курсором в конце.
На это устройство можно отправлять символы, которые будут отображаться на индикаторе:
root@lede: echo -n 123 > /dev/lcd0
Также можно управлять режимами работы через sysfs (/sys/class/hd44780/lcd0). По этому пути есть следующие имена файлов: backlight, cursor_display, geometry, cursor_blink. Через них можно настраивать геометрию экрана, управлять режимами курсора и подсветкой. Например, для выключения мигания курсора достаточно дать команду:
root@lede: echo -n 0 > /sys/class/hd44780/lcd0/cursor_blink
Кроме того поддерживаются две команды терминала VT100, это очистка экрана и установка курсора в начальную позицию. Подать их можно так:
root@lede: echo -n -e '\x1b'[2J > /dev/lcd0
root@lede: echo -n -e '\x1b'[H > /dev/lcd0
Установку необходимых режимов также можно сделать при загрузке ОС. Для этого добавляем файл target/linux/apm821xx/base-files/etc/board.d/03_lcd в LEDE с содержимым:
#!/bin/sh
echo hd44780 0x27 > /sys/bus/i2c/devices/i2c-0/new_device
echo -n 16x2 > /sys/class/hd44780/lcd0/geometry
echo -n 0 > /sys/class/hd44780/lcd0/cursor_display
echo -n 0 > /sys/class/hd44780/lcd0/cursor_blink
echo -n -e '\x1b'[2JHello! > /dev/lcd0
exit 0
Теперь плата будет вас приветствовать каждый раз при загрузке системы.
10. Добавление обработки необходимых команд VT100 в драйвер HD44780
Итак, драйвер работает, но для использования в LCD4Linux он должен уметь размещать символы в любой позиции экрана. Согласно списка команд терминала выбираем нужную:
Esc[Line;ColumnH — Move cursor to screen location v,h
Находим файл package/hd44780/src/hd44780-dev.c и добавляем обнаружение и выполнение новой команды. Надо доработать процедуру обработки esc-последовательностей:
Оригинал:
static void hd44780_handle_esc_seq_char(struct hd44780 *lcd, char ch)
{
int prev_row, prev_col;
lcd->esc_seq_buf.buf[lcd->esc_seq_buf.length++] = ch;
if (!strcmp(lcd->esc_seq_buf.buf, "[2J")) {
prev_row = lcd->pos.row;
prev_col = lcd->pos.col;
hd44780_clear_display(lcd);
hd44780_write_instruction(lcd, HD44780_DDRAM_ADDR | (lcd->geometry->start_addrs[prev_row] + prev_col));
hd44780_leave_esc_seq(lcd);
} else if (!strcmp(lcd->esc_seq_buf.buf, "[H")) {
hd44780_write_instruction(lcd, HD44780_RETURN_HOME);
lcd->pos.row = 0;
lcd->pos.col = 0;
hd44780_leave_esc_seq(lcd);
} else if (lcd->esc_seq_buf.length == ESC_SEQ_BUF_SIZE) {
hd44780_flush_esc_seq(lcd);
}
}
Доработанный вариант:
static void hd44780_handle_esc_seq_char(struct hd44780 *lcd, char ch)
{
int prev_row, prev_col;
struct hd44780_geometry *geo = lcd->geometry;
lcd->esc_seq_buf.buf[lcd->esc_seq_buf.length++] = ch;
if (!strcmp(lcd->esc_seq_buf.buf, "[2J")) {
prev_row = lcd->pos.row;
prev_col = lcd->pos.col;
hd44780_clear_display(lcd);
hd44780_write_instruction(lcd, HD44780_DDRAM_ADDR | (lcd->geometry->start_addrs[prev_row] + prev_col));
hd44780_leave_esc_seq(lcd);
} else if (!strcmp(lcd->esc_seq_buf.buf, "[H")) {
hd44780_write_instruction(lcd, HD44780_RETURN_HOME);
lcd->pos.row = 0;
lcd->pos.col = 0;
hd44780_leave_esc_seq(lcd);
} else if ((lcd->esc_seq_buf.buf[0]=='[') && (lcd->esc_seq_buf.buf[4]=='H') && // Esc[ Line ; Column H
(lcd->esc_seq_buf.buf[2]==';' ) && (lcd->esc_seq_buf.length == 5)) {
lcd->pos.row = lcd->esc_seq_buf.buf[1] % geo->rows;
lcd->pos.col = lcd->esc_seq_buf.buf[3] % geo->cols;
hd44780_write_instruction(lcd, HD44780_DDRAM_ADDR
| (geo->start_addrs[lcd->pos.row] + lcd->pos.col));
hd44780_leave_esc_seq(lcd);
} else if (lcd->esc_seq_buf.length == ESC_SEQ_BUF_SIZE) {
hd44780_flush_esc_seq(lcd);
}
}
И надо изменить длину буфера для накопления и анализа esc-последовательностей. Находим в файле hd44780.h строку:
#define ESC_SEQ_BUF_SIZE 4
И исправляем значение на с 4 на 6. Можно компилировать и проверять. Можно компилировать только один пакет из LEDE.
root@debian:/apm82181-lede-master# make package/hd44780/compile
make[1] package/hd44780/compile
make[2] -C package/hd44780 compile
Если ошибок нет, то компилируем весь проект, обновляем, перезагружаемся. Проверяем:
root@lede:/ echo -n -e '\x1b[1;6H' > /dev/lcd0
Курсор перемещается во вторую строку и 7-ю позицию (нумерация с нуля):
11. Добавление дисплея с некоторыми командами VT100 в LCD4Linux
Драйвер ЖКИ выполняет свой функционал. Теперь его можно задействовать в пакете LCD4Linux для отображения состояния системы. Однако я в нем не нашел дисплея, работающего с драйвером по протоколу терминала.
Значит пишем свой. Согласно инструкции How to write new display drivers.
Исходные файлы можно взять в каталоге build_dir/target-powerpc_464fp_musl-1.1.15/lcd4linux-custom/lcd4linux-r1203, либо из пакета dl/lcd4linux-r1203.tar.bz2.
Все как в руководстве:
- Из файла drv_Sample.c drv делаем копию drv_vt100.c
- Редактируем drv_vt100.c, удаляем все связанное с графическим режимом, с GPIO
- Добавляем новый драйвер в drv.c
- Добавляем в Makefile.am
- Добавляем в drivers.m4
if test "$VT100" = "yes"; then TEXT="yes" I2C="yes" DRIVERS="$DRIVERS drv_vt100.o" AC_DEFINE(WITH_VT100,1,[vt100 driver]) fi
- Добавляем в Makefile.am
Далее пишем свои процедуры в файл drv_vt100.c.
drv_vt100_open:
static int drv_vt100_open(const char *section)
{
char *s;
int f = -1;
s = cfg_get(section, "Port", NULL);
if (s == NULL || *s == '\0' || strlen(s) > 80) {
error("%s: no '%s.Port' entry from %s", Name, section, cfg_source());
return -1;
}
strcpy(Port, s);
f = open(Port, O_WRONLY);
if (f == -1) {
error("open(%s) failed: %s", Port, strerror(errno));
return -1;
}
close (f);
return 0;
}
drv_vt100_send:
static void drv_vt100_send(const char *data, const unsigned int len)
{
unsigned int i;
int f;
f = open(Port, O_WRONLY);
write (f, data, len);
close (f);
}
drv_vt100_clear:
static void drv_vt100_clear(void)
{
char cmd[4];
cmd[0] = 0x1B; // ESC
cmd[1] = '['; // [
cmd[2] = '2'; // 2
cmd[3] = 'J'; // J
drv_vt100_send(cmd, 4);
cmd[2] = 'H'; // H
drv_vt100_send(cmd, 3);
}
drv_vt100_write:
static void drv_vt100_write(const int row, const int col, const char *data, int len)
{
char cmd[6];
cmd[0] = 0x1B; // ESC
cmd[1] = '['; // [
cmd[2] = row & 0xff; // Line
cmd[3] = ';'; // ;
cmd[4] = col & 0xff; // Column
cmd[5] = 'H'; // H
drv_vt100_send(cmd, 6);
}
drv_vt100_close оставляем пустой.
Редактируем и создаем файлы в отдельной от проекта LEDE папке. Затем, так как при компиляции проекта файлы LCD4linux обновляются из архива, то изменять их в папке build_dir/… бессмысленно. Необходимо пользоваться возможность применять патчи. Патчи для LCD4Linux располагаются в папке feeds/packages/utils/lcd4linux/patches. Свой, добавляющий новый драйвер дисплея VT100 нужно разместить тут же.
Для создания патча делаем рядом две папки. В одной (пусть 1/) размещаем оригинальные файлы, в другой (пусть 2/) те же, но измененные. Затем выполняем команду diff:
diff -Naur ./1 ./2 > 180-vt100.patch
В результате имеем файл 180-vt100.patch примерно с таким содержанием:
diff -Naur ./vt100/Makefile.am ./vt100-f/Makefile.am
--- ./vt100/Makefile.am 2016-11-28 11:01:56.000000000 +0000
+++ ./vt100-f/Makefile.am 2016-11-14 07:33:41.000000000 +0000
@@ -125,6 +125,7 @@
drv_USBHUB.c drv_USBLCD.c drv_vnc.c +drv_vt100.c drv_WincorNixdorf.c drv_X11.c diff -Naur ./vt100/drivers.m4 ./vt100-f/drivers.m4
--- ./vt100/drivers.m4 2016-11-14 11:54:41.000000000 +0000
+++ ./vt100-f/drivers.m4 2016-11-14 07:37:00.000000000 +0000
@@ -39,7 +39,7 @@
[ Newhaven, Noritake, NULL, Pertelian, PHAnderson,]
[ PICGraphic, picoLCD, picoLCDGraphic, PNG, PPM, RouterBoard,]
[ Sample, SamsungSPF, serdisplib, ShuttleVFD, SimpleLCD, st2205, T6963,]
- [ TeakLCM, TEW673GRU, Trefon, ULA200, USBHUB, USBLCD, VNC, WincorNixdorf, X11],
+ [ TeakLCM, TEW673GRU, Trefon, ULA200, USBHUB, USBLCD, VNC, vt100, WincorNixdorf, X11],
drivers=$withval,
drivers=all
)
@@ -114,6 +114,7 @@
USBHUB="yes"
USBLCD="yes"
VNC="yes"
+ VT100="yes"
WINCORNIXDORF="yes"
X11="yes"
;;
@@ -279,6 +280,9 @@
VNC)
VNC=$val
;;
+ vt100)
+ VT100=$val
+ ;;
WincorNixdorf)
WINCORNIXDORF=$val
;;
@@ -869,6 +873,13 @@
fi
fi
+if test "$VT100" = "yes"; then
+ TEXT="yes"
+ I2C="yes"
+ DRIVERS="$DRIVERS drv_vt100.o"
+ AC_DEFINE(WITH_VT100,1,[vt100 driver])
+fi
+
if test "$WINCORNIXDORF" = "yes"; then
TEXT="yes"
SERIAL="yes"
Создается один патч для всех файлов. Если посмотреть в патчах, которые уже есть в папке feeds/packages/utils/lcd4linux/patches, внутри файлов отсутствуют строки, которые показывают выполняемую команду «diff -Naur...». Приводим наш патч в такое же состояние и копируем в папку.
Заходим в конфигуратор LEDE.
Видим появление нашего драйвера дисплея в LCD4Linuc-custom:
Включаем его в проект, сохраняем настройки, компилируем, обновляем, перезагружаем. Подключаем наш драйвер в файле конфигурации:
lcd4linux.conf
Variables {
tick 500
tack 100
minute 60000
}
Display VT100 {
Driver 'vt100'
Size '16x2'
Port '/dev/lcd0'
}
Widget Test {
class 'Text'
expression '1234567890123456'
width 16
}
Layout Test {
Row01.Col1 'Test'
Row02.Col1 'Test'
}
Display 'VT100'
Layout 'Test'
Проверяем:
root@lede:/# /usr/bin/lcd4linux -v -F
LCD4Linux 0.11.0-SVN-1193 starting
vt100: $Rev: 001 $
initializing layout 'Test'
Creating new timer group (1000 ms)
widget 'Test': Class 'text', Parent '<root>', Layer 1, Row 0, Col 0 (to 0,16)
widget 'Test': Class 'text', Parent 'Test', Layer 1, Row 1, Col 0 (to 1,16)
На индикаторе мы видим, как и планировали в конфиге, цифры.
12. Добавление команд программирования знакогенератора в драйверы HD44780 и VT100
Индикатор на базе контроллера HD44780 имеет зашитый латинский алфавит с цифрами и знаками и свободно 8 программируемых символов (бывает есть и русские символы, но не в этом случае). Меняя отображение программируемых символов можно получить несложную анимацию. Ее варианты представлены в примере конфига LCD4Linux, но пока наши драйверы не поддерживают эту функцию, использовать их мы не можем.
Ничего сложного в этом нет, надо в одном драйвере принять 8 байт и отправить с командой в знакогенератор, в другом их отправить.
Снова исправляем
процедуру hd44780_handle_esc_seq_char в hd44780-dev.c
static void hd44780_handle_esc_seq_char(struct hd44780 *lcd, char ch)
{
int prev_row, prev_col;
struct hd44780_geometry *geo = lcd->geometry;
if (lcd->is_in_set_char == 0) {
lcd->esc_seq_buf.buf[lcd->esc_seq_buf.length++] = ch;
if (!strcmp(lcd->esc_seq_buf.buf, "[2J")) {
prev_row = lcd->pos.row;
prev_col = lcd->pos.col;
hd44780_clear_display(lcd);
hd44780_write_instruction(lcd, HD44780_DDRAM_ADDR | (lcd->geometry->start_addrs[prev_row] + prev_col));
hd44780_leave_esc_seq(lcd);
} else if (!strcmp(lcd->esc_seq_buf.buf, "[H")) {
hd44780_write_instruction(lcd, HD44780_RETURN_HOME);
lcd->pos.row = 0;
lcd->pos.col = 0;
hd44780_leave_esc_seq(lcd);
} else if ((lcd->esc_seq_buf.buf[0]=='[') && (lcd->esc_seq_buf.buf[4]=='H') && // Esc[ Line ; Column H
(lcd->esc_seq_buf.buf[2]==';' ) && (lcd->esc_seq_buf.length == 5)) {
lcd->pos.row = lcd->esc_seq_buf.buf[1] % geo->rows;
lcd->pos.col = lcd->esc_seq_buf.buf[3] % geo->cols;
hd44780_write_instruction(lcd, HD44780_DDRAM_ADDR
| (geo->start_addrs[lcd->pos.row] + lcd->pos.col));
hd44780_leave_esc_seq(lcd);
} else if (!strcmp(lcd->esc_seq_buf.buf, "(S")) { // Esc(S code matrix(8)
lcd->is_in_set_char = 1;
} else if (lcd->esc_seq_buf.length == ESC_SEQ_BUF_SIZE) {
hd44780_flush_esc_seq(lcd);
}
} else if (lcd->is_in_set_char == 1) { // start set CGRAM code
hd44780_write_instruction(lcd, HD44780_CGRAM_ADDR | 8 * (ch & 0x07));
lcd->is_in_set_char++;
} else {
hd44780_write_data(lcd, ch & 0x1f); // set 8 bytes CGRAM code
lcd->is_in_set_char++;
if (lcd->is_in_set_char == 10){ // go to DDRAM mode
hd44780_write_instruction(lcd, HD44780_DDRAM_ADDR
| (geo->start_addrs[lcd->pos.row] + lcd->pos.col));
hd44780_leave_esc_seq(lcd);
}
}
}
Добавляем в нее режим приема 8-и байтов знакогенератора и запись их в CGRAM (знакогенератор). Так как в VT100 я не нашел ESC-последовательность программирования символа, то пришлось что-то придумать. Пусть это будет Esc(S 8 байт, то есть код ESC, затем открывающая круглая скобка, латинская буква S и 8 байт матрицы. В нашем случае, при размере знакоместа 8*5 будет использоваться только 5 младших бит каждого байта.
В процедуре hd44780_write добавляем сброс режима приема знакогенератора (строка lcd->is_in_set_char = 0).
hd44780_write
void hd44780_write(struct hd44780 *lcd, const char *buf, size_t count)
...
case '\e':
lcd->is_in_esc_seq = true;
lcd->is_in_set_char = 0;
break;
default:
hd44780_write_char(lcd, ch);
...
И описываем это поле структуры (is_in_set_char) в файле заголовков hd44780.h.
struct hd44780
struct hd44780 {
struct cdev cdev;
struct device *device;
struct i2c_client *i2c_client;
struct hd44780_geometry *geometry;
struct {
int row;
int col;
} pos;
char buf[BUF_SIZE];
struct {
char buf[ESC_SEQ_BUF_SIZE];
int length;
} esc_seq_buf;
bool is_in_esc_seq;
int is_in_set_char;
bool backlight;
bool cursor_blink;
bool cursor_display;
bool dirty;
struct mutex lock;
struct list_head list;
};
Теперь добавим этот функционал в драйвер дисплея LCD4Linux. Функция drv_vt100_defchar файла drv_vt100.c:
drv_vt100_defchar
static void drv_vt100_defchar(const int ascii, const unsigned char *matrix)
{
char cmd[12];
int i;
/* call the 'define character' function */
cmd[0] = 0x1B; // ESC
cmd[1] = '('; // (
cmd[2] = 'S'; // S
cmd[3] = ascii & 0x07; // code
/* send bitmap to the display */
for (i = 0; i < 8; i++) {
cmd[i + 4] = (*matrix++) & 0x1f;
}
drv_vt100_send(cmd, 12);
}
Компилируем, обновляем, перезагружаем. Меняем снова конфиг LCD4linux.
lcd4linux.conf
Variables {
tick 500
tack 100
minute 60000
}
Display VT100 {
Driver 'vt100'
Size '16x2'
Port '/dev/lcd0'
Icons 1
}
Widget RAM {
class 'Text'
expression meminfo('MemFree')/1024
postfix ' MB RAM'
width 11
precision 0
align 'R'
update tick
}
Widget Busy {
class 'Text'
expression proc_stat::cpu('busy', 500)
prefix 'Busy'
postfix '%'
width 9
precision 1
align 'R'
update tick
}
Widget Uptime {
class 'Text'
expression uptime('%d days %H:%M:%S')
width 20
align 'R'
prefix 'Up '
update 1000
}
Widget Uptime {
class 'Text'
expression 'Up '.uptime('%d %H:%M:%S')
width 16
align 'L'
update 1000
}
# Icons
Widget Timer {
class 'Icon'
speed 83
Bitmap {
Row1 '.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|'
Row2 '.***.|.*+*.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.+++.|.+*+.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|'
Row3 '*****|**+**|**++*|**+++|**++.|**++.|**+++|**+++|**+++|**+++|**+++|+++++|+++++|++*++|++**+|++***|++**.|++**.|++***|++***|++***|++***|++***|*****|'
Row4 '*****|**+**|**+**|**+**|**+++|**+++|**+++|**+++|**+++|**+++|+++++|+++++|+++++|++*++|++*++|++*++|++***|++***|++***|++***|++***|++***|*****|*****|'
Row5 '*****|*****|*****|*****|*****|***++|***++|**+++|*++++|+++++|+++++|+++++|+++++|+++++|+++++|+++++|+++++|+++**|+++**|++***|+****|*****|*****|*****|'
Row6 '.***.|.***.|.***.|.***.|.***.|.***.|.**+.|.*++.|.+++.|.+++.|.+++.|.+++.|.+++.|.+++.|.+++.|.+++.|.+++.|.+++.|.++*.|.+**.|.***.|.***.|.***.|.***.|'
Row7 '.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|'
Row8 '.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|'
}
}
Layout L16x2 {
Row1 {
Col1 'Uptime'
col16 'Timer'
}
Row2 {
Col1 'Busy'
Col11 'RAM'
}
}
Display 'VT100'
Layout 'L16x2'
Проверяем:
root@lede: /usr/bin/lcd4linux -v -F
LCD4Linux 0.11.0-SVN-1193 starting
vt100: $Rev: 001 $
vt100: reserving 1 of 8 user-defined characters for icons
initializing layout 'L16x2'
Creating new timer group (1000 ms)
widget 'Uptime': Class 'text', Parent '<root>', Layer 1, Row 0, Col 0 (to 0,16)
Creating new timer group (83 ms)
widget 'Timer': Class 'icon', Parent '<root>', Layer 1, Row 0, Col 15 (to 1,16)
Creating new timer group (500 ms)
widget 'Busy': Class 'text', Parent '<root>', Layer 1, Row 1, Col 0 (to 1,9)
widget 'RAM': Class 'text', Parent '<root>', Layer 1, Row 1, Col 10 (to 1,21)
На экране видим время работы платы с последней загрузки, загрузку системы, свободной память и символ анимации в виде заполняющегося и очищающегося диска.
Добавив исправление конфига LCD4Linux в патч 180-vt100.patch, мы получим такой же вид индикатора сразу при загрузке:
180-vt100.patch
--- a/lcd4linux.conf.sample 2016-11-15 09:47:46.000000000 +0000
+++ a-f/lcd4linux.conf.sample 2016-11-18 03:18:22.000000000 +0000
@@ -567,7 +567,14 @@
HttpPort '5800'
}
-
+Display VT100 {
+ Driver 'vt100'
+ Size '16x2'
+ Port '/dev/lcd0'
+ Icons 1
+}
+
+
Display FutabaVFD {
Driver 'FutabaVFD'
Port '/dev/parport0'
@@ -674,7 +681,7 @@
Widget RAM {
class 'Text'
- expression meminfo('MemTotal')/1024
+ expression meminfo('MemFree')/1024
postfix ' MB RAM'
width 11
precision 0
@@ -828,6 +835,14 @@
update 1000
}
+Widget Uptime {
+ class 'Text'
+ expression 'Up '.uptime('%d %H:%M:%S')
+ width 16
+ align 'L'
+ update 1000
+}
+
Widget mpris_TrackPosition_bar {
class 'Bar'
expression mpris_dbus::method_PositionGet('org.kde.amarok')
@@ -1015,7 +1030,7 @@
Widget Timer {
class 'Icon'
- speed 50
+ speed 83
Bitmap {
Row1 '.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|.....|'
Row2 '.***.|.*+*.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.*++.|.+++.|.+*+.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|.+**.|'
@@ -1225,6 +1240,17 @@
}
}
+Layout L16x2-2 {
+ Row1 {
+ Col1 'Uptime'
+ col16 'Timer'
+ }
+ Row2 {
+ Col1 'Busy'
+ Col11 'RAM'
+ }
+}
+
Layout L20x2 {
Row1 {
Col1 'CPUinfo'
@@ -1323,7 +1349,7 @@
-Display 'ACool'
+#Display 'ACool'
#Display 'SerDispLib'
#Display 'LCD-Linux'
#Display 'LCD2041'
@@ -1354,7 +1380,7 @@
#Display 'IRLCD'
#Display 'USBLCD'
#Display 'BWCT'
-#Display 'Image'
+Display 'Image'
#Display 'TeakLCD'
#Display 'Trefon'
#Display 'LCD2USB'
@@ -1363,15 +1389,17 @@
#Display 'ctinclud'
#Display 'picoLCD'
#Display 'VNC'
+Display 'VT100'
#Display 'FutabaVFD'
#Display 'GLCD2USB'
-#Layout 'Default'
-Layout 'TestLayer'
+Layout 'Default'
+#Layout 'TestLayer'
#Layout 'TestImage'
#Layout 'L8x2'
#Layout 'L16x1'
#Layout 'L16x2'
+Layout 'L16x2-2'
#Layout 'L20x2'
#Layout 'L40x2'
#Layout 'Test'
13. Оптимизация передачи данных по шине I2C
Теперь, когда все работает как планировалось, немного о скорости передачи данных. Хочется обратить внимание на два момента.
Во-первых, данный по шине I2C передаются очень маленькими блоками, а конкретно по одному байту. А каждому блоку добавляется адрес ведомого устройства. Логично предположить, что передача адреса устройства с блоком бОльшего размера увеличит утилизацию шины и уменьшит время передачи.
Так выглядит передача отдельными байтами
Видно что каждый второй — байт адреса (0x4E).
Видно что каждый второй — байт адреса (0x4E).
Проведем частичную оптимизацию. Для этого вспомним как передается один байт данных на индикатор. ЖКИ работает в 4-битном режиме, т.е. получает за раз по полбайта. Эти полбайта должны подтверждаться выдачей сигнала «Enable». В итоге для передачи одного байте из процессора на индикатор по шине I2C идет 6 байт:
- Старшие полбайта без сигнала «Enable»
- Старшие полбайта c сигналом «Enable»
- Старшие полбайта без сигнала «Enable»
- Младшие полбайта без сигнала «Enable»
- Младшие полбайта с сигналом «Enable»
- Младшие полбайта без сигнала «Enable»
И так как каждый сопровождается адресом, то реально это составляет 12 байт, при скорости шины 100 КГц это 1.2 мс.
Предлагается передавать те же 6 байт, но одним блоком, с одним байтом адреса, т.е. 7 байт вместо 12. Оригинальные процедуры передачи данных из драйвера HD44780.
hd44780_write_data, hd44780_write_nibble, pcf8574_raw_write из hd44780-dev.c
static void pcf8574_raw_write(struct hd44780 *lcd, u8 data)
{
i2c_smbus_write_byte(lcd->i2c_client, data);
}
static void hd44780_write_nibble(struct hd44780 *lcd, dest_reg reg, u8 data)
{
data = (data << 4) & 0xF0;
if (reg == DR)
data |= RS;
data = data | (RW & 0x00);
if (lcd->backlight)
data |= BL;
pcf8574_raw_write(lcd, data);
pcf8574_raw_write(lcd, data | E);
pcf8574_raw_write(lcd, data);
}
static void hd44780_write_data(struct hd44780 *lcd, u8 data)
{
u8 h = (data >> 4) & 0x0F;
u8 l = data & 0x0F;
hd44780_write_nibble(lcd, DR, h);
hd44780_write_nibble(lcd, DR, l);
udelay(37 + 4);
}
Процедура из драйвера HD44780, исправленная для пакетной передачи данных.
hd44780_write_data из hd44780-dev.c
static void hd44780_write_data(struct hd44780 *lcd, u8 data)
{
u8 h = (data >> 4) & 0x0F;
u8 l = data & 0x0F;
u8 buf[5];
h = (h << 4) & 0xF0;
l = (l << 4) & 0xF0;
h |= RS;
l |= RS;
h = h | (RW & 0x00);
l = l | (RW & 0x00);
if (lcd->backlight){
h |= BL;
l |= BL;
}
buf[0] = h | E;
buf[1] = h;
buf[2] = l;
buf[3] = l | E;
buf[4] = l;
i2c_smbus_write_i2c_block_data(lcd->i2c_client, h, 5, (const u8 *)(&buf[0]));
udelay(37 + 4);
}
Вот так выглядит теперь передача байта 0x1F
И занимает 0,67 мс.
Во-вторых, скорость шины по умолчанию 100 КГц, а это не максимум. Конечно именно такая скорость рекомендуется для расширителя портов на ЖКИ. Но в тоже время многие разработчики говорят о бесперебойной работе и на 400 КГц. Конечно, использование нестандартного режима оправдано при необходимости и тщательном тестирование на отсутствие сбоев, а я всего лишь могу сказать как это сделать и что получится.
Информации в интернете о включении режима найти не удалось, пришлось пересматривать исходники LEDE. В итоге есть два варианта включения режима fast, т.е. 400 КГц.
Первый, это передать параметр модулю ядра. Модуль это i2c-ibm_iic. Параметр — iic_force_fast. В итоге надо к параметрам ядра при запуске добавить i2c-ibm_iic.iic_force_fast=1. Это можно сделать в загрузчике U-boot например так:
setenv addmisc 'setenv bootargs ${bootargs} i2c-ibm_iic.iic_force_fast=1'
После загрузки системы мы имеем:
root@lede: dmesg | grep i2c
[ 0.000000] Kernel command line: root=/dev_nfs rw nfsroot=192.168.1.10:/nfs/debian_ppc/rootfs ip=dhcp console=ttyS0,115200 i2c-ibm_iic.iic_force_fast=1
[ 4.770923] i2c /dev entries driver
[ 4.774742] ibm-iic 4ef600700.i2c: using fast (400 kHz) mode
[ 10.456041] i2c i2c-0: new_device: Instantiated device hd44780 at 0x27
Второй — указать режим работы шины в дереве устройств (apollo3g.dtsi, параметр fast-mode):
IIC0: i2c@ef600700 {
compatible = "ibm,iic";
reg = <0xef600700 0x00000014>;
interrupt-parent = <&UIC0>;
interrupts = <0x2 0x4>;
fast-mode;
#address-cells = <1>;
#size-cells = <0>;
};
После компиляции не забудьте обновить дерево устройств на TFTP сервере. И результат:
root@lede: dmesg | grep i2c
[ 4.774585] i2c /dev entries driver
[ 4.778396] ibm-iic 4ef600700.i2c: using fast (400 kHz) mode
[ 10.464396] i2c i2c-0: new_device: Instantiated device hd44780 at 0x27
root@lede: ls -al /proc/device-tree/plb/opb/i2c@ef600700
-r--r--r-- 1 root root 4 Nov 18 04:13 #address-cells
-r--r--r-- 1 root root 4 Nov 18 04:13 #size-cells
drwxr-xr-x 2 root root 0 Nov 18 04:13 .
drwxr-xr-x 12 root root 0 Nov 18 04:13 ..
-r--r--r-- 1 root root 8 Nov 18 04:13 compatible
-r--r--r-- 1 root root 0 Nov 18 04:13 fast-mode
-r--r--r-- 1 root root 4 Nov 18 04:13 interrupt-parent
-r--r--r-- 1 root root 8 Nov 18 04:13 interrupts
-r--r--r-- 1 root root 4 Nov 18 04:13 name
-r--r--r-- 1 root root 8 Nov 18 04:13 reg
И скорость передачи байта 0,19 мс:
Что почти на порядок лучше исходной.
В качестве вывода можно сказать, что в результате проделанной работы мы получили возможность использовать плату со слабодокументированным в открытых источниках процессором в проектах, где применим Linux (LEDE). Основной интерфейс Ethernet, хранение на SATA, управление через I2C и несколько портов дают широкие возможности для разработчиков.
Ну и напоследок, для дублирования всего вышесказанного, файлы из LEDE, согласно структуры каталогов (вроде все вспомнил) доступны тут.
Поделиться с друзьями