Добрый день!
Продожим работу с платой от 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
то на шине мы увидим следующую последовательность байт:
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
Все правильно, вывод без адреса ячейки, только того что нам надо:
#!/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 '<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 байт и отправить с командой в знакогенератор, в другом их отправить.
Снова исправляем
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 '<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, мы получим такой же вид индикатора сразу при загрузке:
--- 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).
Проведем частичную оптимизацию. Для этого вспомним как передается один байт данных на индикатор. ЖКИ работает в 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, согласно структуры каталогов (вроде все вспомнил) доступны тут.
ссылка на оригинал статьи https://habrahabr.ru/post/316100/
Добавить комментарий