Qt5 GUI on Intel Edison

If you want to create a graphical user interface (GUI) for the Intel Edison, it’s better to use the popular Qt cross-platform framework.
Then you will be able to develop and debug a program on your powerful desktop computer and then deploy it to your Edison and test it there.

My hardware and Linux kernel configuration

I used my shield that has a SSD1322 based display connected to the SPI bus and 4 push buttons connected to GPIOs.
The display is controlled through the Linux framebuffer kernel module fbtft.
The buttons are controlled through the gpio-keys module.

QT5 Yocto Client package installation

My Edison source packages are located in the folder ~/edison.

I increased the size of my root Edison partition in the file ~/edison/poky/meta-intel-edison/meta-intel-edison-distro/recipes-core/images/edison-image.bb

IMAGE_ROOTFS_SIZE = "1048576"

I downloaded a new meta package meta-qt5.
At the time of writing, the Intel Edison packages used the openembedded branch called “dizzy”.

cd ~/edison/poky
git clone -b dizzy https://github.com/meta-qt5/meta-qt5.git

I modified the configuration file ~/edison/build_edison/conf/auto.conf
I added some Qt packages and enabled some configuration options. I may select something else in the future.

The Intel Edison has no GPU. So, the Qt OpenGL based modules don’t work. Among them are Qt Quick and Qt Declarative and the packages that depend on these two.

DISTRO = "poky-edison"
MACHINE = "edison"
DISTRO_FEATURES_append = " alsa bluetooth x11"
PACKAGE_CLASSES = "package_ipk"
BB_DANGLINGAPPENDS_WARNONLY = "1"
EDISONREPO_TOP_DIR = "${TOPDIR}/../poky"

IMAGE_INSTALL_append = " tinyb"
IMAGE_INSTALL_append = " tinyb-dev"
#IMAGE_INSTALL_append = " openzwave"
IMAGE_INSTALL_append = " bacnet-stack"
IMAGE_INSTALL_append = " libmodbus"
IMAGE_INSTALL_append = " mc"
IMAGE_INSTALL_append = " qtbase qtbase-fonts \
    qtbase-plugins \
    qtbase-tools \
    qtimageformats-plugins"
IMAGE_INSTALL_append_core2-32 = " libft4222"
IMAGE_INSTALL_append_edison = " libft4222"
LICENSE_FLAGS_WHITELIST_append = " ftdi"
GCCVERSION = "4.9%"

PACKAGECONFIG_DISTRO_append_pn-qtbase = " linuxfb icu alsa pulseaudio sql-sqlite"

I modified the file ~/edison/build_edison/conf/bblayers.conf and added the meta-qt5 layer at the end of BBLAYERS.

 
LCONF_VERSION = "6"

BBPATH = "${TOPDIR}"
BBFILES ?= ""

BBLAYERS ?= " \
  ${TOPDIR}/../poky/meta \
  ${TOPDIR}/../poky/meta-intel-edison/meta-intel-arduino \
  ${TOPDIR}/../poky/meta-intel-edison/meta-intel-edison-bsp \
  ${TOPDIR}/../poky/meta-intel-edison/meta-intel-edison-distro \
  ${TOPDIR}/../poky/meta-intel-iot-devkit \
  ${TOPDIR}/../poky/meta-intel-iot-middleware \
  ${TOPDIR}/../poky/meta-java \
  ${TOPDIR}/../poky/meta-oic \
  ${TOPDIR}/../poky/meta-openembedded/meta-filesystems \
  ${TOPDIR}/../poky/meta-openembedded/meta-networking \
  ${TOPDIR}/../poky/meta-openembedded/meta-oe \
  ${TOPDIR}/../poky/meta-openembedded/meta-python \
  ${TOPDIR}/../poky/meta-openembedded/meta-ruby \
  ${TOPDIR}/../poky/meta-openembedded/meta-webserver \
  ${TOPDIR}/../poky/meta-yocto \
  ${TOPDIR}/../poky/meta-yocto-bsp \
  ${TOPDIR}/../poky/meta-qt5 \
  "
BBLAYERS_NON_REMOVABLE ?= " \
  ${TOPDIR}/../poky/meta \
  ${TOPDIR}/../poky/meta-yocto \
  "

Then the usual Edison image compilation stage.
You must insert yourself into the group “dialout” on Linux Ubuntu to have permissions to run the script “flashall.sh”.

cd ~/edison/poky/
source oe-init-build-env ../build_edison/
bitbake edison-image u-boot
../poky/meta-intel-edison/utils/flash/postBuild.sh .
./toFlash/flashall.sh
Qt5 Host cross-platform SDK compilation

I modified the file ~/edison/poky/meta-qt5/recipes-qt/packagegroups/packagegroup-qt5-toolchain-target.bb
and deleted all packages from the list RDEPENDS_${PN} that depend on Qt Quick and Qt Declarative. My list may change in the future.

RDEPENDS_${PN} += " \
    packagegroup-core-standalone-sdk-target \
    libsqlite3-dev \
    qtbase-dev \
    qtbase-fonts \
    qtbase-mkspecs \
    qtbase-plugins \
    qtbase-staticdev \
    qtconnectivity-dev \
    qtconnectivity-mkspecs \
    qtimageformats-dev \
    qtimageformats-plugins \
    qtserialport-dev \
    qtserialport-mkspecs \
    qtsvg-dev \
    qtsvg-mkspecs \
    qtsvg-plugins \
    qtsystems-dev \
    qtsystems-mkspecs \
    qttools-dev \
    qttools-mkspecs \
    qttools-staticdev \
    qttools-tools \
    qtxmlpatterns-dev \
    qtxmlpatterns-mkspecs \
"

I compiled the SDK.

cd ~/edison/poky/
source oe-init-build-env ../build_edison/
bitbake meta-toolchain-qt5

I ran the new SDK installer in ~/edison/build_edison/tmp/deploy/sdk

sh poky-edison-glibc-x86_64-meta-toolchain-qt5-core2-32-toolchain-1.7.3.sh

It installed the SDK into the default folder /opt/poky-edison/1.7.3
I opened the directory /opt/poky-edison/1.7.3/sysroots/x86_64-pokysdk-linux/usr/bin/i586-poky-linux
and created symlinks. Without them, my system compiler and linker in /usr/bin are called instead.

ln -s i586-poky-linux-g++ g++
ln -s i586-poky-linux-cpp cpp
ln -s i586-poky-linux-ld ld
ln -s i586-poky-linux-gdb gdb
Qt Creator IDE installation

I installed Qt Creator from https://download.qt.io/archive/qt/5.3/5.3.2/, as the version of Qt in the Yocto package is 5.3.2.
But, probably, the one from the Linux distribution should work as well.
I installed it in the directory ~/Qt.

I modified the script ~/Qt/Tools/QtCreator/bin/qtcreator.sh, so it will load the environment variables for the SDK. I put the new line at the very top of the script.

source /opt/poky-edison/1.7.3/environment-setup-core2-32-poky-linux
#! /bin/sh

On my Edison I created the user “farit” and added him to the group “video” that has permissions to write to the framebuffer and read input buttons. His home directory on the Edison is “/home/farit”.

usermod -a -G video farit

I modified the menu link on my desktop to run the script ~/Qt/Tools/QtCreator/bin/qtcreator.sh to open Qt Creator.

In Qt Creator, I opened the menu Tools -> Options -> Devices and added my Edison device as “Generic Linux Device”.
I named it “edison” and provided the name and password combination for the user “farit”. Then tested the connection.

I opened the menu Tools > Options > Build & Run.
I opened the tab “Qt Versions”, clicked on the button “Add” and browsed to select qmake

/opt/poky-edison/1.7.3/sysroots/x86_64-pokysdk-linux/usr/bin/qt5/qmake

I opened the tab “Compilers”, clicked on the button “Add”, selected “GCC” and browsed to select gcc

/opt/poky-edison/1.7.3/sysroots/x86_64-pokysdk-linux/usr/bin/i586-poky-linux/i586-poky-linux-gcc

I opened the tab “Debuggers”, clicked on the button “Add” and browsed to select gdb. I named it GDB.

/opt/poky-edison/1.7.3/sysroots/x86_64-pokysdk-linux/usr/bin/i586-poky-linux/i586-poky-linux-gdb

I opened the tab “Kits”, clicked on the button “Add” and filled in the form.
I selected the values for Qt, Compiler, Debugger that I just defined in the previous steps.

Name: edison
Device type: Generic Linux Device
Device: edison
Sysroot: /opt/poky-edison/1.7.3/sysroots/x86_64-pokysdk-linux
Compiler: GCC
Debugger: GDB
Qt Version: Qt 5.3.2

Sample Qt program

I clicked on “New Project” and selected Applications -> Qt Widgets Application.
I named it “testik”.

In testik.pro, I added the path “/home/farit”, where Qt Creator will copy the executable on the Edison.

#-------------------------------------------------
#
# Project created by QtCreator 2016-06-02T14:32:35
#
#-------------------------------------------------

QT       += core gui

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = testik
TEMPLATE = app


SOURCES += main.cpp\
        mainwindow.cpp

HEADERS  += mainwindow.h

FORMS    += mainwindow.ui

target.path = /home/farit
INSTALLS += target

In mainwindow.h, I only added the prototype for the function “keyPressEvent”.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

protected:
    void keyPressEvent(QKeyEvent *);

private:
    Ui::MainWindow *ui;
};

#endif // MAINWINDOW_H

main.cpp is the default one

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

In mainwindow.cpp, I implemented the function “keyPressEvent”.

#include <QKeyEvent>
#include <QDebug>
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::keyPressEvent(QKeyEvent *event)
{
    if(event->key() == Qt::Key_Up)
    {
        if (event->isAutoRepeat()) {
             ui->myLabel->setText("You auto pressed Up");
        }
        else {
            ui->myLabel->setText("You pressed Up");
        }
    }
    else if(event->key() == Qt::Key_Down)
    {
        ui->myLabel->setText("You pressed Down");
    }
}

In mainwindow.ui, I created a label “myLabel”.
In the MainWindow properties, I clicked on the “palette” button and made the background transparent by setting the opacity to 0 for “Window”. I changed “WindowText” to white by selecting the color #FFFFFF.

In the “Projects” tab in the “Run” section, I added the arguments for the executable, so it can write to the default framebuffer /dev/fb0 and read input from /dev/input/event0.

-platform linuxfb -plugin EvdevKeyboard
Qt test program running
Qt test program running

Connecting Buttons to Intel Edison

The Intel Edison has multiple GPIO pins, which can be connected to push buttons.

I use the gpio-keys Linux driver. Each button emits an event, which can be read in the same way as a keyboard event.

Schematic

Here is the schematic of my shield with the push buttons. The buttons are SW2, SW3, SW4, SW5.

Shiled with push buttons for Intel Edison
Shield with Push Buttons for Intel Edison

GPIO pullup code

As you can see, one side of a button is connected to the ground and another to a GPIO pin. I could use external pullup resistors, but the Intel Edison has built-in pullup resistors, which I activated instead. The current kernel code doesn’t have a function for setting a pullup resitor, so I had to write my own.

I added the function lnw_gpio_set_pull_alt into the Linux kernel file drivers/gpio/gpio-langwell.c

void lnw_gpio_set_pull_alt(unsigned gpio, int value, int pullup_value)
{
	struct lnw_gpio *lnw;
	u32 flis_offset;
	u32 flis_value;
	unsigned long flags;

	/* use this trick to get memio */
	lnw = irq_get_chip_data(gpio_to_irq(gpio));
	if (!lnw) {
		pr_err("langwell_gpio: can not find pin %d\n", gpio);
		return;
	}
	if (gpio < lnw->chip.base || gpio >= lnw->chip.base + lnw->chip.ngpio) {
		dev_err(lnw->chip.dev, "langwell_gpio: wrong pin %d to config alt\n", gpio);
		return;
	}
	gpio -= lnw->chip.base;

	if (lnw->type != TANGIER_GPIO) {
		return;
    }

	flis_offset = lnw->get_flis_offset(gpio);
	if (WARN(flis_offset == -EINVAL, "invalid pin %d\n", gpio))
		return -EINVAL;
	if (is_merr_i2c_flis(flis_offset))
		return;

	spin_lock_irqsave(&lnw->lock, flags);
	flis_value = get_flis_value(flis_offset);
	if (value) {
		flis_value |= PULLUP_ENABLE;
		flis_value &= ~PULLDOWN_ENABLE;
	} else {
		flis_value |= PULLDOWN_ENABLE;
		flis_value &= ~PULLUP_ENABLE;
	}
	//flis_value |= PUPD_VAL_50K;
    flis_value |= pullup_value;

	set_flis_value(flis_value, flis_offset);
	spin_unlock_irqrestore(&lnw->lock, flags);
}
EXPORT_SYMBOL_GPL(lnw_gpio_set_pull_alt);

Also added its declaration and the constants for the pullup values into the Linux kernel file include/linux/lnw_gpio.h

#define PUPD_VAL_2K	(0 << 4)
#define PUPD_VAL_20K	(1 << 4)
#define PUPD_VAL_50K	(2 << 4)
#define PUPD_VAL_910	(3 << 4)
void lnw_gpio_set_pull_alt(unsigned gpio, int value, int pullup_value);

I added the definitions for my 4 push buttons into the Linux kernel file arch/x86/platform/intel-mid/device_libs/platform_gpio_keys.c
The buttons are for GPIO12, GPIO13, GPIO182, GPIO183.
Each button configured as a general purpose pin by using the multiplexor code: lnw_gpio_set_alt(gb[i].gpio, LNW_GPIO);
The pullup value is set to 50K using the previously written function: lnw_gpio_set_pull_alt(gb[i].gpio, 1, PUPD_VAL_50K);

/*
 * platform_gpio_keys.c: gpio_keys platform data initilization file
 *
 * (C) Copyright 2008 Intel Corporation
 * Author:
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License.
 */

#include <linux/input.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/gpio.h>
#include <linux/gpio_keys.h>
#include <linux/platform_device.h>
#include <asm/intel-mid.h>
#include "platform_gpio_keys.h"
#include <linux/lnw_gpio.h>

/*
 * we will search these buttons in SFI GPIO table (by name)
 * and register them dynamically. Please add all possible
 * buttons here, we will shrink them if no GPIO found.
 */
static struct gpio_keys_button gpio_button[] = {
        {
                .code = KEY_POWER,
                .gpio = -1, /* GPIO number */
                .active_low = 1,
                .desc = "power_btn",/*Button description*/
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 3000,
        },
        {
                .code = KEY_UP,
                .gpio = 182,
                .active_low = 1,
                .desc = "up_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,
        },
        {
                .code = KEY_DOWN,
                .gpio = 12,
                .active_low = 1,
                .desc = "down_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,
        },
        {
                .code = KEY_ESC,
                .gpio = 13,
                .active_low = 1,
                .desc = "back_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,
        },
        {
                .code = KEY_ENTER,
                .gpio = 183,
                .active_low = 1,
                .desc = "down_btn",
                .type = EV_KEY,
                .wakeup = 0,
                .debounce_interval = 100,
        },
};

static struct gpio_keys_platform_data gpio_keys = {
	.buttons	= gpio_button,
	.rep		= 1,
	.nbuttons	= -1, /* will fill it after search */
};

static struct platform_device pb_device = {
	.name		= DEVICE_NAME,
	.id		= -1,
	.dev		= {
		.platform_data	= &gpio_keys,
	},
};

/*
 * Shrink the non-existent buttons, register the gpio button
 * device if there is some
 */
static int __init pb_keys_init(void)
{
	struct gpio_keys_button *gb = gpio_button;
	int i, num, good = 0;

	num = sizeof(gpio_button) / sizeof(struct gpio_keys_button);
	for (i = 0; i < num; i++) {
		pr_info("info[%2d]: name = %s, gpio = %d\n",
			 i, gb[i].desc, gb[i].gpio);
		if (gb[i].gpio == -1)
			continue;

                if (gb[i].gpio > 0) {
                    lnw_gpio_set_alt(gb[i].gpio, LNW_GPIO);
                    lnw_gpio_set_pull_alt(gb[i].gpio, 1, PUPD_VAL_50K);
                }

		if (i != good)
			gb[good] = gb[i];
		good++;
	}

	if (good) {
		gpio_keys.nbuttons = good;
		return platform_device_register(&pb_device);
	}
	return 0;
}
late_initcall(pb_keys_init);
Testing Buttons

When the new Linux kernel was recompiled and flashed to the Intel Edison, I could verify the buttons.

I checked the files in the directory for the GPIO12: /sys/kernel/debug/gpio_debug/gpio12
current_pinmux was mode0
current_pullmode was pullup
current_pullstrength was 50k

I ran the command and tried to push on the buttons. I received some symbols. It meant that the buttons worked.

cat /dev/input/event0

I copied the file evtest.c from http://elinux.org/images/9/93/Evtest.c and compiled it on my Edison.

gcc evtest.c -o evtest

I ran the program evtest and got expected results.

# ./evtest /dev/input/event0
Input driver version is 1.0.1
Input device ID: bus 0x19 vendor 0x1 product 0x1 version 0x100
Input device name: "gpio-keys"
Supported events:
  Event type 0 (Sync)
  Event type 1 (Key)
    Event code 1 (Esc)
    Event code 28 (Enter)
    Event code 103 (Up)
    Event code 108 (Down)
  Event type 20 (Repeat)
Testing ... (interrupt to exit)
Event: time 1465107735.152126, type 1 (Key), code 108 (Down), value 1
Event: time 1465107735.152126, -------------- Report Sync ------------
Event: time 1465107735.344576, type 1 (Key), code 108 (Down), value 0
Event: time 1465107735.344576, -------------- Report Sync ------------
Event: time 1465107743.087594, type 1 (Key), code 103 (Up), value 1
Event: time 1465107743.087594, -------------- Report Sync ------------
Event: time 1465107743.332727, type 1 (Key), code 103 (Up), value 0
Event: time 1465107743.332727, -------------- Report Sync ------------
Event: time 1465107745.988003, type 1 (Key), code 28 (Enter), value 1
Event: time 1465107745.988003, -------------- Report Sync ------------
Event: time 1465107746.187692, type 1 (Key), code 28 (Enter), value 0
Event: time 1465107746.187692, -------------- Report Sync ------------
Event: time 1465107748.336925, type 1 (Key), code 1 (Esc), value 1
Event: time 1465107748.336925, -------------- Report Sync ------------
Event: time 1465107748.552085, type 1 (Key), code 1 (Esc), value 0
Event: time 1465107748.552085, -------------- Report Sync ------------

I may change the event key codes for the buttons. I can get the codes from the same file http://elinux.org/images/9/93/Evtest.c

Accessing /dev/input/event0 as a regular user

By default, the device /dev/input/event0 is accessible only by root.

I created the file /etc/udev/rules.d/input.rules
I chose the group “video” as I will use a user who should be able to access both the video device and the input device.

KERNEL=="event*", NAME="input/%k", MODE="660", GROUP="video"

I also created the file with the same contents poky/meta/recipes-core/systemd/systemd/input.rules
and added it into the recipe poky/meta/recipes-core/systemd/systemd_216.bb

SRC_URI = "git://anongit.freedesktop.org/systemd/systemd;branch=master;protocol=git \
           file://binfmt-install.patch \
           file://systemd-pam-configure-check-uclibc.patch \
           file://systemd-pam-fix-execvpe.patch \
           file://systemd-pam-fix-fallocate.patch \
           file://systemd-pam-fix-mkostemp.patch \
           file://optional_secure_getenv.patch \
           file://uclibc-sysinfo_h.patch \
           file://uclibc-get-physmem.patch \
           file://0001-add-support-for-executing-scripts-under-etc-rcS.d.patch \
           file://0001-missing.h-add-fake-__NR_memfd_create-for-MIPS.patch \
           file://0001-Make-root-s-home-directory-configurable.patch \
           file://0001-systemd-user-avoid-using-system-auth.patch \
           file://0001-journal-Fix-navigating-backwards-missing-entries.patch \
           file://0001-tmpfiles-make-resolv.conf-entry-conditional-on-resol.patch \
           file://0001-build-sys-do-not-install-tmpfiles-and-sysusers-files.patch \
           file://0001-build-sys-configure-the-list-of-system-users-files-a.patch \
           file://touchscreen.rules \
           file://input.rules \
           file://00-create-volatile.conf \
           file://init \
           file://run-ptest \
          "