Подключение символьного ЖКИ к плате от WD MyBook Live на AppliedMicro APM82181. Окончание
Продожим работу с платой от NAS WesternDigital MyBook Live и подключенным к ней ЖК индикатором.
Итак, в предыдущей части мы нашли на плате место для подключения к шине I2C, подключили расширитель портов с индикатором, убедились что все работает. Сегодня выведем на индикатор состояние системы.
Начало было тут: Подключение символьного ЖКИ к плате от WD MyBook Live на AppliedMicro APM82181
Содержание первой части:
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 можно обнаружить подключенные на шину устройства и определить их адрес.
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
то на шине мы увидим следующую последовательность байт:
0×4e-0×00–0×31 0×4e-0×01–0×31 0×4e-0×02–0×31 0×4e-0×03–0×31 0×4e-0×04–0×0a.
Первый байт в каждой тройке — это адрес устройства, умноженный на 2 (0×27 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
Все правильно, вывод без адреса ячейки, только того что нам надо: #!/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
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:
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), и его можно добавить в конфигурацию:
После компиляции, обновления и перезагрузки, если не включена автозагрузка модуля, то пробуем загрузить, смотрим результат:
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
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.
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;
}
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);
}
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);
}
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
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.
Включаем его в проект, сохраняем настройки, компилируем, обновляем, перезагружаем.
Подключаем наш драйвер в файле конфигурации:
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 '', 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 байт и отправить с командой в знакогенератор, в другом их отправить.
Снова исправляем
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).
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 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:
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.
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 '', Layer 1, Row 0, Col 0 (to 0,16)
Creating new timer group (83 ms)
widget 'Timer': Class 'icon', Parent '', Layer 1, Row 0, Col 15 (to 1,16)
Creating new timer group (500 ms)
widget 'Busy': Class 'text', Parent '', Layer 1, Row 1, Col 0 (to 1,9)
widget 'RAM': Class 'text', Parent '', Layer 1, Row 1, Col 10 (to 1,21)
На экране видим время работы платы с последней загрузки, загрузку системы, свободной память и символ анимации в виде заполняющегося и очищающегося диска.
Добавив исправление конфига LCD4Linux в патч 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 передаются очень маленькими блоками, а конкретно по одному байту. А каждому блоку добавляется адрес ведомого устройства. Логично предположить, что передача адреса устройства с блоком бОльшего размера увеличит утилизацию шины и уменьшит время передачи.
Видно что каждый второй — байт адреса (0×4E).
Проведем частичную оптимизацию. Для этого вспомним как передается один байт данных на индикатор. ЖКИ работает в 4-битном режиме, т.е. получает за раз по полбайта. Эти полбайта должны подтверждаться выдачей сигнала «Enable». В итоге для передачи одного байте из процессора на индикатор по шине I2C идет 6 байт:
- Старшие полбайта без сигнала «Enable»
- Старшие полбайта c сигналом «Enable»
- Старшие полбайта без сигнала «Enable»
- Младшие полбайта без сигнала «Enable»
- Младшие полбайта с сигналом «Enable»
- Младшие полбайта без сигнала «Enable»
И так как каждый сопровождается адресом, то реально это составляет 12 байт, при скорости шины 100 КГц это 1.2 мс.
Предлагается передавать те же 6 байт, но одним блоком, с одним байтом адреса, т.е. 7 байт вместо 12.
Оригинальные процедуры передачи данных из драйвера HD44780.
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, исправленная для пакетной передачи данных.
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);
}
И занимает 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, согласно структуры каталогов (вроде все вспомнил) доступны тут.