Connecting a character LCD to a board from WD MyBook Live on AppliedMicro APM82181. Ending

    Good afternoon! We continue working with the board from NAS WesternDigital MyBook Live and the LCD indicator connected to it. So, in the previous part, we found a place on the board for connecting to the I2C bus, connected a port expander with an indicator, made sure that everything worked. Today we display the status of the system on the indicator.
    image
    image
    The beginning was here: Connecting a character LCD to a board from WD MyBook Live on AppliedMicro APM82181

    Contents of the first part:

    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

    As before, additions and comments are welcome.

    So, at this point, a port expander is connected to the system, which we can control. A symbol LCD indicator on the clone of the HD44780 controller is connected to the expander. Theoretically, we can control it by including all the ports for output and knowing their purpose. It was already possible to blink a backlight, yanking the third port.

    7. Initialization of the HD44780 via i2cset


    The connection between the HD44780 controller and the port expander is organized as follows:

    RS - P0
    R / W - P1
    E - P2
    BL - P3
    D4 - P4
    D5 - P5
    D6 - P6
    D7 - P7

    This is one of the options found. The controller is translated and operates in 4-bit mode, and the byte is transmitted in parts.

    Having at its disposal all the ports of the expander, it is possible to issue data on it bit by bit and thus control the display. I think you will agree that this is not very convenient.

    Let's try to directly control via the I2C bus. A simple option to test this possibility is to use the I2C-tools utility suite. In LEDE they are in the Utilites section. The kit includes i2cdetect, i2cdump, i2cget, i2cset. We are interested in the latter and the first one (for diagnostics).

    Using i2cdetect, you can detect devices connected to the bus and determine their address.

    In our case, only the address 0x27 is busy:
    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: -- -- -- -- -- -- -- --
    


    The i2cset utility is used to output data to a device with a given address on the I2C bus.
    Knowing the initialization sequence for your LCD, you can execute it without any problems and display the characters on the screen.

    In order not to reinvent the wheel, I recommend downloading from here: I2C hd44780 module on the PCF8574 expander “i2c lcd testilik”. Here is a direct link . Inside the shell archive is a script that works with the indicator through the i2cset command and displays characters alternately on the screen. The only thing before use is to comment out or delete lines at the beginning of the file:

    insmod i2c-dev
    insmod i2c-gpio-custom bus0=0,$sda_gpio,$scl_gpio
    

    They create an I2C software port on any free I / O ports, and we already have a hardware one. Well, and besides, it is designed for an indicator of 4 * 40 dimension, but it doesn’t hurt to check the performance and understand the use of the i2cset utility.

    Result:

    image

    A little explanation on its implementation. The write_CMD and print_LCD procedures display a command or data respectively on the indicator. It depends on the RS signal, in our case being on the zero bit.

    The init_LCD procedure sequentially issues commands to initialize the indicator according to its datasheet, widely distributed on the Internet. For example, here . Next, various characters are displayed sequentially on the screen.

    8. Character device for writing data to the I2C bus


    Everything is fine, but I would like to avoid using the utilities, and have a character device, displaying which bytes, they would go directly to the I2C bus, of course, with the given address.

    Unfortunately, I could not find such a driver for the I2C bus in LEDE. Therefore, with experimental goals, it was decided to redo one of the existing ones. It is clear that if you wanted to use it further, you did not have to redo it, but at least create a new one on its basis.

    The EEPROM driver for the I2C bus turned out to be suitable for experiments. The kmod-eeprom-at24 driver was connected in the LEDE core, after updating the system, an attempt was made to add a device:

    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
    

    Successfully connected. Now if you output something to the device:

    root@lede: echo "1111" > /sys/bus/i2c/devices/i2c-0/0-0027/eeprom

    then on the bus we will see the following sequence of bytes:

    0x4e-0x00-0x31 0x4e-0x01-0x31 0x4e-0x02-0x31 0x4e-0x03-0x31 0x4e-0x04-0x0a

    The first byte in each triple is the device address multiplied by 2 (0x27 x 2). The second is the cell address in the EEPROM, the third is the data. The driver is quite suitable for transmitting data to the LCD, with the exception of issuing a cell address.

    To remove this, we fix the driver file build_dir / target-powerpc_464fp_musl-1.1.15 / linux-apm821xx_sata / linux-4.4.21 / drivers / misc / eeprom / at24.c. Let's comment out a few lines in the at24_eeprom_write (335-337) procedure:

    //if (at24->chip.flags & AT24_FLAG_ADDR16)
    //  msg.buf[i++] = offset >> 8;
    //msg.buf[i++] = offset;

    Compile-update, add the device, see the output

    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
    

    Everything is correct, output without a cell address, only what we need:

    image

    Now you can redo the test program by removing the i2cset utility call from there:
    #!/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
    


    At the same time, now the program is designed only for our screen geometry - 2x16 characters. It is clear that changing the source in the build_dir directory, we should expect that in the near future the file will be restored from the original packages during assembly. To create permanent fixes, you should use the ability to apply patches at the build stage.

    9. Adding the HD44780 driver to the kernel


    After studying the issue of operability of this LCD connection option, it was decided to try to assign some functionality to the indicator. For example, displaying some part of the state of the operating system.

    Such a package already exists and is even included in LEDE. This is LCD4Linux . It allows you to obtain the necessary information about the components of the OS and place it on the indicator in the right place. Naturally, real-time update.

    However, using it with our indicator on the I2C bus caused some difficulties.
    Connecting a display on the HD44780-I2C from the LCD4Linux kit

    in the file \ etc \ lcd4linux.conf
    Display HD44780-I2C {
        Driver 'HD44780'
        Model 'generic'
        Bus 'i2c'
        Port '/dev/i2c-0'
        Device '0x27'
        Bits '4'
        Size '16x2'
        asc255bug 0
        Icons 1
        Wire {
            RW     'DB1'
            RS     'DB0'
            ENABLE 'DB2'
            GPO    'GND'
        }
    }
    
    caused an error:
    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


    Almost all possible displays from the package were also tested, including the use of a character device based on the EEPROM driver made in the previous chapter. It didn’t work.

    Then it was decided to go the other way. Add the driver of this particular indicator to the system, which accepts symbols and control commands for display, and then add a new display using this driver to LCD4Linux, since it has a manual for that.

    So, we take the ready driver for HD44780 on I2C from here: Linux driver for Hitachi HD44780 LCD attached to I2C bus via PCF8574 I / O expander. The driver was tested on the Raspberry Pi, it understands a couple of VT100 terminal control commands, it is configured for different indicator geometries, it can display, extinguish and blink the cursor. It remains to integrate it into LEDE and refine it a bit.
    Download, unpack to the package / hd44780 / src folder.

    Tree
    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


    We leave only this in the Makefile:

    obj-m := hd44780.o
    hd44780-y := hd44780-i2c.o hd44780-dev.o

    And create a new Makefile, just the folder above, in package / hd44780, similar to the files in other LEDE packages:

    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))


    The line with autoload (AUTOLOAD: = $ (call AutoLoad, 70, hd44780)) can be added later when the driver is tested. Now when calling the LEDE configurator

    make menuconfig

    The driver will appear in the kernel modules (kmod-hd44780), and it can be added to the configuration:

    LEDE Configuration
    image

    After compilation, updating and rebooting, if the autoload of the module is not enabled, then we try to load it, see the result:

    root@lede: insmod hd44780
    root@lede: lsmod |grep 44780
    hd44780                 5450  0
    

    Trying to add a device:

    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

    On the indicator, as laid down in the driver, during initialization, the address of the created device is displayed: "/ dev / lcd0", with a blinking cursor at the end.

    You can send characters to this device that will be displayed on the indicator:

    root@lede: echo -n 123 > /dev/lcd0

    You can also control the operating modes through sysfs (/ sys / class / hd44780 / lcd0). There are the following file names along this path: backlight, cursor_display, geometry, cursor_blink. Through them, you can adjust the screen geometry, control the cursor modes and backlight. For example, to turn off the blinking cursor just give the command:

    root@lede: echo -n 0 > /sys/class/hd44780/lcd0/cursor_blink

    In addition, two VT100 terminal commands are supported, this is cleaning the screen and setting the cursor to the starting position. You can submit them as follows:

    root@lede: echo -n -e '\x1b'[2J > /dev/lcd0
    root@lede: echo -n -e '\x1b'[H > /dev/lcd0

    Setting the necessary modes can also be done when loading the OS. To do this, add the target / linux / apm821xx / base-files / etc / board.d / 03_lcd file to the LEDE with the contents:

    #!/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
    

    Now the board will greet you every time you boot the system.

    10. Adding processing of necessary VT100 commands to the HD44780 driver


    So, the driver works, but for use in LCD4Linux it must be able to place characters in any position on the screen. According to the list of terminal commands, select the desired one:

    Esc [Line; ColumnH - Move cursor to screen location v, h

    Find the package / hd44780 / src / hd44780-dev.c file and add detection and execution of a new command. It is necessary to refine the procedure for processing esc sequences:

    Original:
    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);
    	}
    }

    Modified version:
    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);
    	}
    }

    And you need to change the buffer length to accumulate and analyze esc sequences. We find the line in hd44780.h file:

    #define ESC_SEQ_BUF_SIZE   4

    And we correct the value from 4 to 6. You can compile and check. Only one package from LEDE can be compiled.

    root@debian:/apm82181-lede-master# make package/hd44780/compile
     make[1] package/hd44780/compile
     make[2] -C package/hd44780 compile

    If there are no errors, then compile the entire project, update, reboot. We check:

    root@lede:/ echo -n -e '\x1b[1;6H' > /dev/lcd0

    The cursor moves to the second line and 7th position (numbering from scratch):
    image


    11. Adding a display with some VT100 commands to 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" = "yes"; then
         TEXT="yes"
         I2C="yes"
         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, "Port", NULL);
        if (s == NULL || *s == '\0' || strlen(s) > 80) {
    	error("%s: no '%s.Port' entry from %s", Name, section, cfg_source());
    	return -1;
        }
        strcpy(Port, s);
        f = open(Port, O_WRONLY);
        if (f == -1) {
    	error("open(%s) failed: %s", Port, strerror(errno));
    	return -1;
        }
        close (f);
        return 0;
    }
    


    drv_vt100_send:
    static void drv_vt100_send(const char *data, const unsigned int len)
    {
        unsigned int i;
        int f;
        f = open(Port, O_WRONLY);
        write (f, data, len);
        close (f);
    }

    drv_vt100_clear:
    static void drv_vt100_clear(void)
    {
        char cmd[4];
        cmd[0] = 0x1B; // ESC
        cmd[1] = '[';  // [
        cmd[2] = '2';  // 2
        cmd[3] = 'J';  // J
        drv_vt100_send(cmd, 4);
        cmd[2] = 'H';  // H
        drv_vt100_send(cmd, 3);
    }

    drv_vt100_write:
    static void drv_vt100_write(const int row, const int col, const char *data, int len)
    {
        char cmd[6];
        cmd[0] = 0x1B; 		// ESC
        cmd[1] = '[';  		// [
        cmd[2] = row & 0xff;	// Line
        cmd[3] = ';';		// ;
        cmd[4] = col & 0xff;	// Column
        cmd[5] = 'H';		// H
        drv_vt100_send(cmd, 6);
    }

    drv_vt100_close leave empty.

    We edit and create files in a separate folder from the LEDE project. Then, since the LCD4linux files are updated from the archive when compiling the project, it makes no sense to change them in the build_dir / ... folder. You must take advantage of the opportunity to apply patches. Patches for LCD4Linux are located in the feeds / packages / utils / lcd4linux / patches folder. Own, adding a new display driver VT100 must be placed right there.

    To create a patch, we make two folders side by side. In one (let 1 /) we place the original files, in the other (let 2 /) the same, but modified. Then run the diff command:

    diff -Naur ./1 ./2 > 180-vt100.patch

    As a result, we have the 180-vt100.patch file with approximately the following contents:
    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"
    

    One patch is created for all files. If you look at the patches that are already in the feeds / packages / utils / lcd4linux / patches folder, there are no lines inside the files that show the diff -Naur ... command that is being executed. We bring our patch to the same state and copy it to the folder.
    We go into the LEDE configurator.

    We see the appearance of our display driver in LCD4Linuc-custom:
    image

    We include it in the project, save the settings, compile, update, reboot. We include our driver in the configuration file:

    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'
    


    We check:
    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)


    On the indicator we see, as planned in the config, numbers.

    image

    12. Adding character generator programming commands to the HD44780 and VT100 drivers


    The indicator based on the HD44780 controller has a wired Latin alphabet with numbers and signs and freely 8 programmable characters (sometimes there are Russian characters, but not in this case). By changing the display of programmable characters, you can get a simple animation. Its options are presented in the LCD4Linux config example, but while our drivers do not support this function, we cannot use them.

    There is nothing complicated about this; you need to accept 8 bytes in one driver and send it to the character generator with the command, and send them in another.

    Correcting again
    procedure hd44780_handle_esc_seq_char in 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);
    	    }
    	}
    }

    Add to it the mode of receiving 8 bytes of the character generator and writing them to CGRAM (character generator). Since I did not find the ESC character programming sequence in VT100, I had to come up with something. Let it be Esc (S 8 bytes, that is, the ESC code, then the opening parenthesis, the Latin letter S and 8 bytes of the matrix. In our case, with the familiarity size 8 * 5, only the 5 least significant bits of each byte will be used.

    In the procedure hd44780_write, add resetting the reception mode of the character generator (line 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);
    ...
    

    And we describe this structure field (is_in_set_char) in the header file 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;
    };

    Now add this functionality to the LCD4Linux display driver. The drv_vt100_defchar function of the drv_vt100.c file:

    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);
    }
    

    Compile, update, reboot. Change the LCD4linux config again.

    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'
    

    We check:

    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)
    

    On the screen we see the board’s operating time from the last boot, the system boot, free memory and the animation symbol in the form of a filling and cleaning disk.

    image

    Adding the LCD4Linux config fix to patch 180-vt100.patch, we get the same kind of indicator right at boot time:

    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. Optimization of data transmission on the I2C bus


    Now that everything is working as planned, a little about the data transfer speed. I would like to draw attention to two points.

    Firstly, the data on the I2C bus are transmitted in very small blocks, and specifically by one byte. And to each block, the address of the slave device is added. It is logical to assume that transferring the address of a device with a larger block will increase bus utilization and reduce transmission time.

    This is how the transmission looks in separate bytes
    image
    It can be seen that every second is an address byte (0x4E).

    We will carry out partial optimization. To do this, remember how one byte of data is transmitted to the indicator. LCD works in 4-bit mode, i.e. gets half a byte at a time. These half-bytes should be confirmed by issuing an “Enable” signal. As a result, to transfer one byte from the processor to the indicator on the I2C bus, there are 6 bytes:

    1. High half byte without “Enable” signal
    2. High half bytes with the “Enable” signal
    3. High half byte without “Enable” signal
    4. Low half bytes without the “Enable” signal
    5. Low half bytes with the signal "Enable"
    6. Low half bytes without the “Enable” signal

    And since each is accompanied by an address, then in reality it is 12 bytes, with a bus speed of 100 KHz it is 1.2 ms.

    It is proposed to transmit the same 6 bytes, but in one block, with one address byte, i.e. 7 bytes instead of 12. Original procedures for transferring data from the HD44780 driver.

    hd44780_write_data, hd44780_write_nibble, pcf8574_raw_write from 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);
    }

    Procedure from driver HD44780, corrected for packet data transfer.

    hd44780_write_data of 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);
    }

    This is how the transfer of byte 0x1F now looks
    image

    And it takes 0.67 ms.

    Secondly, the default bus speed is 100 KHz, and this is not the maximum. Of course, such a speed is recommended for the port expander on the LCD. But at the same time, many developers are talking about smooth operation at 400 KHz. Of course, the use of a non-standard mode is justified, if necessary, and thorough testing for the absence of failures, but I can only say how to do this and what happens.

    Information on the inclusion of the mode could not be found on the Internet, I had to revise the source code of LEDE. As a result, there are two options for enabling fast mode, i.e. 400 kHz.

    The first is to pass the parameter to the kernel module. The module is i2c-ibm_iic. The parameter is iic_force_fast. As a result, you need to add i2c-ibm_iic.iic_force_fast = 1 to the kernel parameters at startup. This can be done in the U-boot loader, for example like this:

    setenv addmisc 'setenv bootargs ${bootargs} i2c-ibm_iic.iic_force_fast=1'

    After loading the system, we have:

     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
    

    The second is to specify the bus operating mode in the device tree (apollo3g.dtsi, fast-mode parameter):

    IIC0: i2c@ef600700 {
    	compatible = "ibm,iic";
    	reg = <0xef600700 0x00000014>;
    	interrupt-parent = <&UIC0>;
    	interrupts = <0x2 0x4>;
    	fast-mode;
    	#address-cells = <1>;
    	#size-cells = <0>;
    };

    After compilation, do not forget to update the device tree on the TFTP server. And the result:

    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
    

    And the byte transfer rate is 0.19 ms:

    image

    Which is almost an order of magnitude better than the original.

    As a conclusion, we can say that as a result of the work done, we got the opportunity to use the board with a processor that is poorly documented in open sources in projects where Linux (LEDE) is applicable. The main Ethernet interface, storage on SATA, management via I2C and several ports provide ample opportunities for developers.

    And finally, to duplicate all of the above, files from LEDE, according to the directory structure (like I remembered everything) are available here .

    Also popular now: