Підключення символьного РКІ до плати від WD MyBook Live на AppliedMicro APM82181. Закінчення

Добрий день!
Продожим роботу з платою від NAS WesternDigital MyBook Live і підключеним до неї РК індикатором.
Отже, у попередній частині ми знайшли на платі місце для підключення до шини I2C, підключили розширювач портів з індикатором, переконалися що все працює. Сьогодні виведемо на індикатор стан системи.
image
image

Початок був тут: Підключення символьного РКІ до плати від 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 можна виявити підключені на шину пристрою і визначити їх адресу.
У нашому випадку зайнятий тільки адресу 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 це ні трохи не завадить.
Результат:
image
Трохи пояснень щодо її реалізації.
Процедури 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/i2/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/i2/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/i2/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/i2/devices/i2c-0/0-0027/eeprom
Все правильно, висновок без адреси комірки, тільки те, що нам треба:
image
Тепер можна переробити тестову програму, прибравши звідти виклик утиліти i2cset:
#!/bin/sh

i2c_adres=0x27
i2c_dev=/sys/bus/i2/devices/i2c-0/0-0027/eeprom

led=8
ansi=0

to_octal () {
hh3=$(($hh / 64))
hh1=$(($x $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/i2/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 'i2'
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() loop delay
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 16 Nov 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 16 Nov 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 конфігураційного меню

Драйвер з'явиться в модулях ядра (kmod-hd44780), і його можна додати в конфігурацію:
LEDE Configurationimage

Після компіляції, оновлення і перезавантаження, якщо не включена запуск модуля, то пробуємо завантажити, дивимося результат:
root@lede: insmod hd44780
root@lede: lsmod |grep 44780
hd44780 5450 0

Пробуємо додати пристрій:
root@lede: echo hd44780 0x27 > /sys/bus/i2/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/i2/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-ю позицію (нумерація з нуля):image

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.
Все як у керівництві:
  1. З файлу drv_Sample.c drv робимо копію drv_vt100.c
  2. Редагуємо drv_vt100.c, видаляємо все пов'язане з графічним режимом, з GPIO
  3. Додаємо новий драйвер drv.c
  4. Додаємо в Makefile.am
  5. Додаємо в drivers.m4
    if test "$VT100" = "так"; then
    TEXT="так"
    I2="так"
    DRIVERS="$DRIVERS drv_vt100.o"
    AC_DEFINE(WITH_VT100,1,[vt100 driver])
    fi
  6. Додаємо в 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, "Порту", 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="так"
USBLCD="так"
VNC="так"
+ VT100="так"
WINCORNIXDORF="так"
X11="так"
;;
@@ -279,6 +280,9 @@
VNC)
VNC=$val
;;
+ vt100)
+ VT100=$val
+ ;;
WincorNixdorf)
WINCORNIXDORF=$val
;;
@@ -869,6 +873,13 @@
fi
fi

+if test "$VT100" = "так"; then
+ TEXT="так"
+ I2="так"
+ DRIVERS="$DRIVERS drv_vt100.o"
+ AC_DEFINE(WITH_VT100,1,[vt100 driver])
+fi
+
if test "$WINCORNIXDORF" = "так"; then
TEXT="так"
SERIAL="так"

Створюється один патч для всіх файлів. Якщо подивитися в латках, які вже є в папці feeds/packages/utils/lcd4linux/patches, всередині файлів відсутні рядки, які показують виконувану команду «diff -Naur...». Наводимо наш патч у такий же стан і копіюємо в папку.
Заходимо в конфігуратор LEDE.
Бачимо появу нашого драйвера дисплея в LCD4Linuc-custom:image

Включаємо його в проект, зберігаємо налаштування, компілюємо, оновлюємо, перезавантажуємо.
Підключаємо наш драйвер у файлі конфігурації:
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, Рядок 1, Col 0 (to 1,16)


На індикаторі ми бачимо, як і планували в конфіги, цифри.
image

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, Рядок 1, Col 0 (to 1,9)
widget 'RAM': Class 'text', Parent '<root>', Layer 1, Рядок 1, Col 10 (to 1,21)

На екрані бачимо час роботи плати з останнього завантаження, завантаження системи, вільної пам'ять і символ анімації у вигляді заповнюються і очищающегося диска.
image
Додавши виправлення конфига 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 передаються дуже маленькими блоками, а конкретно по одному байту. А кожному блоку додається адресу веденого пристрою. Логічно припустити, що передача адреси пристрою з блоком більшого розміру збільшить утилізацію шини та зменшить час передачі.
Так виглядає передача окремими байтамиimage
Видно що кожен другий байт адреси (0x4E).

Проведемо часткову оптимізацію. Для цього згадаємо як передається один байт даних на індикатор. РКІ працює в 4-бітному режимі, тобто отримує за раз по полбайта. Ці полбайта повинні підтверджуватися видачею сигналу «Enable». У підсумку для передачі одного символу з процесора на індикатор по шині I2C йде 6 байт:
  1. Старші полбайта без сигналу «Enable»
  2. Старші полбайта c сигналом «Enable»
  3. Старші полбайта без сигналу «Enable»
  4. Молодші полбайта без сигналу «Enable»
  5. Молодші полбайта з сигналом «Enable»
  6. Молодші полбайта без сигналу «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);
}

Ось так виглядає тепер передача байта 0x1Fimage

І займає 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 кгц) 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 кгц) 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 мс:
image
Що майже на порядок краще вихідної.

В якості висновку можна сказати, що в результаті проведеної роботи ми отримали можливість використовувати плату з слабодокументированным у відкритих джерелах процесором в проектах, де застосуємо Linux (LEDE). Основний інтерфейс Ethernet, зберігання на SATA, управління через I2C і кілька портів дають широкі можливості для розробників.
Ну і наостанок, для дублювання всього вищесказаного, файли з LEDE, згідно структури каталогів (начебто все згадав) доступні тут.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.