前回はディスプレイやキーボードを接続してコンパクトなLinux端末として動かしました.
ディスプレイにタッチパネルが付いているのでタッチ対応のGUIアプリケーションを動かしてみます.
GUIアプリケーションを作るためのフレームワークとして探してみましたが
代表的なQtからFlutter,uGFXなどがありました.
今回は比較的導入しやすそうなLVGLを使って実現しようと思います.
タッチパネル用のデバイスを増やし,そのデバイスを用いてLVGLでアプリケーションを動かします.
タッチパネル用のデバイスを導入する
まずはLinux側でタッチパネルコントローラを制御できるようにします.
ディスプレイにはXPT2046という型番のタッチパネルコントローラICが載っています.
これはSPI通信による制御を行うものですがSPI通信は
ディスプレイで既に使用してしまっていることから増やす必要があります.
以下のサイトを参考に増やします.
そこでデバイスツリーを書き換えます.
345行目付近にSPIに関する記述があるのでnum-csとcs-gpiosを増やします.
CSピンとして使用するのは32番ピンで内部では77番となっています.
~/workspace/yocto/kernel/arch/arm/boot/dts/qcom/mdm9607-wp76xx.dtsi
&spi_1 {
status = "ok";
num-cs = <2>;
cs-gpios = <0>, <&tlmm_pinmux 77 0>;
};
また,タッチパネルコントローラ用のファイルが必要なのでダウンロードして配置します.
XPT2046とADS7846はコンパチらしいのでADS7846のドライバを使用します.
$ cd ~/workspace/yocto/kernel/drivers/input/touchscreen/
$ curl -O https://raw.githubusercontent.com/notro/fbtft_tools/master/ads7846_device/ads7846_device.c
$ echo "obj-$(CONFIG_TOUCHSCREEN_ADS7846) += ads7846_device.o" >> Makefile
書き換えが終わったらカーネルコンフィグを変更してビルドします.
カーネルの内容を書き換えるとカーネルコンフィグが初期化されるようなので
前回設定した内容のうち必要な部分とタッチパネルについての設定を追加します.
Device Drivers -> Graphic Support -> Frame buffer Devices
<*> Support for frame buffer devices
[*] Framebuffer foreign endianness support
[*] Enable Video Mode Handling Helpers
[*] Enable Tile Blitting Support
[*] Simple framebuffer support
<*> Support for small TFT LCD display modules
-> Support for small TFT LCD display modules
<*> FB driver for the ILI9341 LCD Controller
<*> Generic FB driver for TFT LCD displays
<M> Module to for adding FBTFT devices
Device Drivers -> Input device support
[*] Touchscreens
-> Touchscreens
<M> ADS7846/TSC2046/AD7873 and AD(S)7843 based touchscreens
ビルドします.
$ bitbake -C compile -f linux-quic
$ bitbake linux-quic
$ bitbake -c cleansstate mdm9x28-image-minimal
$ bitbake mdm9x28-image-minimal
これでカーネルの構築は完了です.
LVGLをビルドする
ソースコードを眺めてみるとディスプレイやタッチパネル向けのドライバを
使用したいデバイスに応じて追加することで対応する形であることがわかります.
/dev/input/event1としてデバイスが生成される予定です.
このデバイスをLVGLで使用するにはlibinputが必要になるのですが,
WP7605用のビルド環境には含まれていません.
自分でlibinputをビルドし追加します.
libinputを導入するにあたってmtdevとlibevdevも必要なのでこれもビルドします.
$ . /opt/swi/SWI9X07Y_02.28.03.05/environment-setup-armv7a-neon-poky-linux-gnueabi
$ mkdir ~/workspace/wp7605_libbuild
まずはlibevdevのビルド
$ cd ~/workspace/wp7605_libbuild
$ git clone https://github.com/freedesktop/libevdev.git
$ cd libevdev
$ ./autogen.sh $CONFIGURE_FLAGS --prefix=$SDKTARGETSYSROOT/usr
$ make
$ sudo su
# . /opt/swi/SWI9X07Y_02.28.03.05/environment-setup-armv7a-neon-poky-linux-gnueabi
# make install
# ln -s /opt/swi/SWI9X07Y_02.28.03.05/sysroots/armv7a-neon-poky-linux-gnueabi/usr/include/libevdev-1.0/libevdev/ /opt/swi/SWI9X07Y_02.28.03.05/sysroots/armv7a-neon-poky-linux-gnueabi/usr/include/libevdev
# exit
$
次にmtdevのビルド
$ cd ~/workspace/wp7605_libbuild
$ wget https://bitmath.org/code/mtdev/mtdev-1.1.6.tar.gz
$ tar xvfz mtdev-1.1.6.tar.gz
$ cd mtdev-1.1.6
$ ./configure $CONFIGURE_FLAGS --with-sysroot=$SDKTARGETSYSROOT --prefix=$SDKTARGETSYSROOT/usr
$ make
$ sudo su
# . /opt/swi/SWI9X07Y_02.28.03.05/environment-setup-armv7a-neon-poky-linux-gnueabi
# make install
# exit
$
次にlibinputのビルドですが最新版を導入するにはmesonというビルド環境が必要なのですが
うまく構築することができなかったので少し古いバージョンを用いて使用しないビルドを行いました.
$ cd ~/workspace/wp7605_libbuild
$ wget https://www.freedesktop.org/software/libinput/libinput-1.8.0.tar.xz
$ tar xvfz libinput-1.8.0.tar.xz
$ cd libinput-1.8.0
$ ./configure $CONFIGURE_FLAGS --with-sysroot=$SDKTARGETSYSROOT --prefix=$SDKTARGETSYSROOT/usr --enable-debug-gui=no --enable-tests=no --enable-libwacom=no
$ make
$ sudo su
# . /opt/swi/SWI9X07Y_02.28.03.05/environment-setup-armv7a-neon-poky-linux-gnueabi
# make install
# exit
$
サンプルアプリケーションのビルド
LVGLをフレームバッファで動作させる方法を参考に導入します.
libinputの最新版を導入することができなかったので別途自分でドライバを作成して対応します.
$ cd ~/workspace/
$ git clone --recursive https://github.com/lvgl/lv_port_linux_frame_buffer
必要な変更を加えていきます.
main.cには画面サイズの変更や新しく追加するタッチパネル用のドライバなどを追加します.
main.c
#include "lvgl/lvgl.h"
#include "lv_drivers/display/fbdev.h"
#include "lv_drivers/indev/eventinput.h"
#include "lv_demos/lv_demo.h"
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#include <sys/time.h>
#define DISP_BUF_SIZE (128 * 1024)
int main(void)
{
/*LittlevGL init*/
lv_init();
/*Linux frame buffer device init*/
fbdev_init();
/*A small buffer for LittlevGL to draw the screen's content*/
static lv_color_t buf[DISP_BUF_SIZE];
/*Initialize a descriptor for the buffer*/
static lv_disp_draw_buf_t disp_buf;
lv_disp_draw_buf_init(&disp_buf, buf, NULL, DISP_BUF_SIZE);
/*Initialize and register a display driver*/
static lv_disp_drv_t disp_drv;
lv_disp_drv_init(&disp_drv);
disp_drv.draw_buf = &disp_buf;
disp_drv.flush_cb = fbdev_flush;
disp_drv.hor_res = 240;
disp_drv.ver_res = 320;
lv_disp_drv_register(&disp_drv);
/* Linux input device init */
eventinput_init();
/* Set up touchpad input device interface */
lv_indev_drv_t indev_drv;
lv_indev_drv_init(&indev_drv);
indev_drv.type = LV_INDEV_TYPE_POINTER;
indev_drv.read_cb = eventinput_read;
lv_indev_drv_register(&indev_drv);
/*Create a Demo*/
lv_demo_widgets();
/*Handle LitlevGL tasks (tickless mode)*/
while(1) {
lv_tick_inc(5);
lv_task_handler();
usleep(5000);
}
return 0;
}
/*Set in lv_conf.h as `LV_TICK_CUSTOM_SYS_TIME_EXPR`*/
uint32_t custom_tick_get(void)
{
static uint64_t start_ms = 0;
if(start_ms == 0) {
struct timeval tv_start;
gettimeofday(&tv_start, NULL);
start_ms = (tv_start.tv_sec * 1000000 + tv_start.tv_usec) / 1000;
}
struct timeval tv_now;
gettimeofday(&tv_now, NULL);
uint64_t now_ms;
now_ms = (tv_now.tv_sec * 1000000 + tv_now.tv_usec) / 1000;
uint32_t time_ms = now_ms - start_ms;
return time_ms;
}
lv_conf.hでは色深度の変更のみ行います.
lv_conf.h
#define LV_COLOR_DEPTH 16
新しく追加するタッチパネルドライバのヘッダーを追加します.
libinputの宣言の後ろに配置しました.
lv_drv_conf.h
/*-------------------------------------------------
* Touchscreen as libinput interface (for Linux based systems)
*------------------------------------------------*/
#ifndef USE_LIBINPUT
# define USE_LIBINPUT 0
#endif
#if USE_LIBINPUT
# define LIBINPUT_NAME "/dev/input/event0" /*You can use the "evtest" Linux tool to get the list of devices and test them*/
#endif /*USE_LIBINPUT*/
//Add Start
/*-------------------------------------------------
* Touchscreen as libinput interface (for Linux based systems)
*------------------------------------------------*/
#ifndef USE_EVENTINPUT
# define USE_EVENTINPUT 1
#endif
#if USE_EVENTINPUT
# define LIBINPUT_NAME "/dev/input/event1" /*You can use the "evtest" Linux tool to get the list of devices and test them*/
# define EVENTINPUT_HOR_RES LV_HOR_RES
# define EVENTINPUT_VER_RES LV_VER_RES
# define EVENTINPUT_X_MIN 400
# define EVENTINPUT_Y_MIN 400
# define EVENTINPUT_X_MAX 3800
# define EVENTINPUT_Y_MAX 3800
#define EVENTINPUT_X_FLIP 0
#define EVENTINPUT_Y_FLIP 1
#endif /*USE_EVENTINPUT*/
//Add End
FBIOBLANKを設定することができないのでコメントアウトします.
lv_drivers/display/fbdev.c
// Make sure that the display is on.
//if (ioctl(fbfd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) {
// perror("ioctl(FBIOBLANK)");
// return;
//}
新しく追加したドライバのヘッダーとソースファイルです.
lv_drivers/indev/eventinput.h
/**
* @file eventinput.h
*
*/
#ifndef EVENTINPUT_H
#define EVENTINPUT_H
#ifdef __cplusplus
extern "C" {
#endif
/*********************
* INCLUDES
*********************/
#ifndef LV_DRV_NO_CONF
#ifdef LV_CONF_INCLUDE_SIMPLE
#include "lv_drv_conf.h"
#else
#include "../../lv_drv_conf.h"
#endif
#endif
#if USE_EVENTINPUT
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/input.h>
#include <unistd.h>
#ifdef LV_LVGL_H_INCLUDE_SIMPLE
#include "lvgl.h"
#else
#include "lvgl/lvgl.h"
#endif
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* GLOBAL PROTOTYPES
**********************/
/**
* Initialize the mouse
*/
void eventinput_init(void);
/**
* Get the current position and state of the mouse
* @param indev_drv pointer to the related input device driver
* @param data store the mouse data here
*/
bool eventinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data);
/**********************
* MACROS
**********************/
#endif /* USE_EVENTINPUT */
#ifdef __cplusplus
} /* extern "C" */
#endif
#endif /* EVENTINPUT_H */
lv_drivers/indev/eventinput.c
/**
* @file eventinput.c
*
*/
/*********************
* INCLUDES
*********************/
#include "eventinput.h"
#if USE_EVENTINPUT != 0
/*********************
* DEFINES
*********************/
/**********************
* TYPEDEFS
**********************/
/**********************
* STATIC PROTOTYPES
**********************/
int16_t calcX(int16_t raw);
int16_t calcY(int16_t raw);
/**********************
* STATIC VARIABLES
**********************/
static bool left_button_down = false;
static int16_t last_x = 0;
static int16_t last_y = 0;
struct input_event event;
int eventfd;
/**********************
* MACROS
**********************/
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
/**********************
* GLOBAL FUNCTIONS
**********************/
/**
* Initialize the mouse
*/
void eventinput_init(void)
{
eventfd = open(LIBINPUT_NAME, O_RDWR | O_NONBLOCK);
if(eventfd < 0)
{
perror("mouse open error\n");
}
}
/**
* Get the current position and state of the mouse
* @param indev_drv pointer to the related input device driver
* @param data store the mouse data here
*/
bool eventinput_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
{
(void) indev_drv; /*Unused*/
int len;
while((len = read(eventfd, &event, sizeof(event)))>0)
{
switch(event.type) {
case EV_KEY:
switch (event.code)
{
case BTN_TOUCH:
left_button_down = event.value;
break;
}
break;
case EV_ABS:
switch(event.code) {
case ABS_X:
last_x = calcX(event.value);
break;
case ABS_Y:
last_y = calcY(event.value);
break;
case ABS_PRESSURE:
break;
default:
break;
}
break;
default:
break;
}
}
/*Store the collected data*/
data->point.x = last_x;
data->point.y = last_y;
data->state = left_button_down ? LV_INDEV_STATE_PRESSED : LV_INDEV_STATE_RELEASED;
return false;
}
/**********************
* STATIC FUNCTIONS
**********************/
int16_t calcX(int16_t raw)
{
double tmp = 1.0 * MIN(MAX(raw,EVENTINPUT_X_MIN),EVENTINPUT_X_MAX) - EVENTINPUT_X_MIN;
tmp = tmp / (EVENTINPUT_X_MAX-EVENTINPUT_X_MIN) * EVENTINPUT_HOR_RES;
return (EVENTINPUT_X_FLIP)?(EVENTINPUT_HOR_RES-tmp):tmp;
}
int16_t calcY(int16_t raw)
{
double tmp = 1.0 * MIN(MAX(raw,EVENTINPUT_Y_MIN),EVENTINPUT_Y_MAX) - EVENTINPUT_Y_MIN;
tmp= tmp / (EVENTINPUT_Y_MAX-EVENTINPUT_Y_MIN) * EVENTINPUT_VER_RES;
return (EVENTINPUT_Y_FLIP)?(EVENTINPUT_VER_RES-tmp):tmp;
}
#endif
ビルドするにあたりlibinputを含めるためにMakefileに変更を加えます.
元々は宣言されていなければ定義するところを追記の形に変更します.
Makefile
#CFLAGS ?= -O3 -g0 -I$(LVGL_DIR)/ -Wall -Wshadow -Wundef -Wmissing-prototypes -Wno-discarded-qualifiers -Wall -Wextra -Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wtype-limits -Wshift-negative-value -Wstack-usage=2048 -Wno-unused-value -Wno-unused-parameter -Wno-missing-field-initializers -Wuninitialized -Wmaybe-uninitialized -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wpointer-arith -Wno-cast-qual -Wmissing-prototypes -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wno-discarded-qualifiers -Wformat-security -Wno-ignored-qualifiers -Wno-sign-compare
#LDFLAGS ?= -lm
CFLAGS += -O3 -g0 -I$(LVGL_DIR)/ -Wall -Wshadow -Wundef -Wmissing-prototypes -Wno-discarded-qualifiers -Wall -Wextra -Wno-unused-function -Wno-error=strict-prototypes -Wpointer-arith -fno-strict-aliasing -Wno-error=cpp -Wuninitialized -Wmaybe-uninitialized -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wno-cast-qual -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wformat-security -Wno-ignored-qualifiers -Wno-error=pedantic -Wno-sign-compare -Wno-error=missing-prototypes -Wdouble-promotion -Wclobbered -Wdeprecated -Wempty-body -Wtype-limits -Wshift-negative-value -Wstack-usage=2048 -Wno-unused-value -Wno-unused-parameter -Wno-missing-field-initializers -Wuninitialized -Wmaybe-uninitialized -Wall -Wextra -Wno-unused-parameter -Wno-missing-field-initializers -Wtype-limits -Wsizeof-pointer-memaccess -Wno-format-nonliteral -Wpointer-arith -Wno-cast-qual -Wmissing-prototypes -Wunreachable-code -Wno-switch-default -Wreturn-type -Wmultichar -Wno-discarded-qualifiers -Wformat-security -Wno-ignored-qualifiers -Wno-sign-compare
LDFLAGS += -lm -linput
ここまでくれば後はクロスコンパイルをするだけです.
$ . /opt/swi/SWI9X07Y_02.28.03.05/environment-setup-armv7a-neon-poky-linux-gnueabi
$ make
実際に動かす
タッチパネル用の配線を接続します.
SPIに必要なSDI,SDO,CSの他に割り込みのIRQを結線します.
LCDは特にプルアップする必要がなかったのですがXPT2046は3.3V入出力なので
SDI,SDO,CSをIOREFの3.3Vに10kΩでプルアップします.
配線が終われば新しくビルドしたカーネルを転送します.
$ DEST_IP=192.168.2.2 ~/workspace/legatoAF/legato-19.02.0/bin/fwupdate download ~/workspace/yocto/build_bin/tmp/deploy/images/swi-mdm9x28-wp/yocto_wp76xx.4k.cwe
転送が終わりLinuxの起動が終わったらビルドしたサンプルGUIアプリケーションを転送します.
$ scp ~/workspace/lv_port_linux_frame_buffer/demo root@192.168.2.2:~/
現状SPIのCSピンを出力に設定する必要があるのでそれも含めて
タッチパネルのドライバを導入します.
デバイスツリーを変更すればこの処理も不要になるとは思うのですが
とりあえずはこの方法で動かします.
$ scp root@192.168.2.2
# echo 32 > /sys/class/gpio/export
# echo "out" > /sys/class/gpio/gpio32/direction
# insmod /lib/modules/3.18.131/kernel/drivers/input/touchscreen/ads7846.ko
# insmod /lib/modules/3.18.131/kernel/drivers/input/touchscreen/ads7846_device.ko verbose=2 busnum=1 cs=1 gpio_pendown=79
これで準備は整いました.
ホームディレクトリにおいてあるdemoを実行すればサンプルアプリケーションが動作します.
サンプルがよくできているのでタブやスクロール,ソフトウェアキーボードなど色々試せて
フレームワークの幅が広いことがわかります.
# ./demo
まとめ
まだ完璧ではないところがありますが
タッチパネルを含めたディスプレイによるGUIアプリケーションを動かしました.
表示だけでなく入力ができるのでWP7605の活用の幅を広げることができると思います.
コメント