Archiv der Kategorie: Debian GNU/Linux

Setting up a GPIO-Button „keyboard“ on a Raspberry Pi

 
Update: If you need more than a hand full of buttons you might be better of using a matrix keyboard instead.

Back in late 2013, when I wrote the first Version of a raspberry-pi based software controlling a HD44780 based 4×20 characters LCD and 4 input buttons I started querying the buttons using the generic GPIO driver included in Raspbian and its sysfs interface.

However, this has a couple of drawbacks. First of all it is hardly portable to other Linux based hardware and one has to do a lot of stuff like debouncing on the application level.

Fast forward to early 2017. Raspbian now uses a device-tree based approach for system setup and a driver called gpio-keys is readily available in its standard kernel.

However, as it is often the case in the Free Software world, the documentation of this driver is limited to some README files included in the Linux kernel and some discussions scattered all around the web.

Linux already has drivers for almost all of the common low level peripheral interfaces like I2C, SPI, OneWire, hardware PWM and generic GPIO. It is usually the better approach to use them instead of constantly re-inventing the wheel.

So here is my quick guide for setting up a „keyboard“ made up from a couple of buttons connected via GPIO ports as shown in the image.

gpio-keys

While this has currently only been tested on Raspberry Pi, it will likely also work on other Linux based boards with device tree enabled (e.g Beaglebone and others).

Keyboards in modern Linux Kernels are presented to userland as a so called input event device. To inspect them I would recommend the installation of the evtest and input-utils packages on Debian/Ubuntu based distributions. The lsinput command (run as root) shows which ones are available on a system.

So, what do we need to do to make a keyboard from our GPIO connected push-buttons?

The missing link between the gpio-keys driver and the setup of the actual GPIO ports, where the buttons are connected to, is a so called device-tree (DT) overlay.

While DT itself is a data structure for describing hardware, a DT overlay is something a user can put in place to change such a hardware description in a way which matches the actual application scenario (like buttons, buses etc. connected to the device).

So let’s build such an overlay for the four buttons shown in our schematic above.
The Documentation available at raspberrypi.org provides some clues about device tree overlays as well.

Here is the final result which works, so let’s go into the details:

    /dts-v1/;
    /plugin/;
    / {
       compatible = "brcm,bcm2835", "brcm,bcm2708", "brcm,bcm2709";
       
       fragment@0 {
          target-path = "/";
          __overlay__ {
             keypad: breadboard_keys {
                compatible = "gpio-keys";
                #address-cells = <1>;
                #size-cells = <0>;
		autorepeat;
                button@22 {
                   label = "breadboard Menu";
                   linux,code = <28>;
                   gpios = <&gpio 22 1>;
                };
                button@10 {
                   label = "breadboard down";
                   linux,code = <108>;
                   gpios = <&gpio 10 1>;
                };
                button@9 {
                   label = "breadboard up";
                   linux,code = <103>;
                   gpios = <&gpio 9 1>;
                };
                button@11 {
                   label = "breadboard enter";
                   linux,code = <14>;
                   gpios = <&gpio 11 1>;
                };
             };
          };
       };
    };

Our overlay fragment contains a keypad called breadboard_keys. This is actually the string which lsinput will show as the actual name of our input device. 22, 10, 9 and 11 are the GPIO port numbers corresponding to the green wires in our schematic.

The file gpio-keys.txt from the Linux Kernel source-tree will show us what our four button definitions need to look like. We need a label, which is arbitrary text, a linux,code which is actually a keycode as defined in /usr/include/linux/input-event-codes.h and we need a gpio definition with two options, the number of the GPIO to use and a boolean value indicating if the button is active low (1, as in our case) or active high (0).

Another thing I would like to point at is the autorepeat keyword. If given this will activate a key-press repeat behavior known from ordinary keyboards. The production of key-press-events will be repeated as long as the button is pressed.

Now how to enable this overlay on Raspberry Pi?
Very simple, once you know how 🙂

First put the above code in a file e.g. breadboard.dts.

Then compile a binary version and put it into the right place:
dtc -I dts -O dtb -o /boot/overlays/breadboard.dtbo breadboard.dts

Finally the following line must be added to /boot/config.txt:
dtoverlay=breadboard

Now we are done.

Here is how this looks like on the software side without any other input devices like keyboards connected:

root@raspberrypi:~# lsinput
/dev/input/event0
   bustype : BUS_HOST
   vendor  : 0x1
   product : 0x1
   version : 256
   name    : "breadboard_keys"
   phys    : "gpio-keys/input0"
   bits ev : EV_SYN EV_KEY EV_REP

root@raspberrypi:~# input-events 0
/dev/input/event0
   bustype : BUS_HOST
   vendor  : 0x1
   product : 0x1
   version : 256
   name    : "breadboard_keys"
   phys    : "gpio-keys/input0"
   bits ev : EV_SYN EV_KEY EV_REP

waiting for events
20:00:23.629190: EV_KEY KEY_BACKSPACE (0xe) pressed
20:00:23.629190: EV_SYN code=0 value=0
20:00:23.749163: EV_KEY KEY_BACKSPACE (0xe) released
20:00:23.749163: EV_SYN code=0 value=0
20:00:23.969176: EV_KEY KEY_DOWN (0x6c) pressed
20:00:23.969176: EV_SYN code=0 value=0
20:00:24.099151: EV_KEY KEY_DOWN (0x6c) released
20:00:24.099151: EV_SYN code=0 value=0
20:00:24.329158: EV_KEY KEY_UP (0x67) pressed
20:00:24.329158: EV_SYN code=0 value=0
20:00:24.439154: EV_KEY KEY_UP (0x67) released
20:00:24.439154: EV_SYN code=0 value=0
20:00:24.669157: EV_KEY KEY_ENTER (0x1c) pressed
20:00:24.669157: EV_SYN code=0 value=0
20:00:24.759176: EV_KEY KEY_ENTER (0x1c) released
20:00:24.759176: EV_SYN code=0 value=0
root@raspberrypi:~# grep breadboard /sys/kernel/debug/gpio
 gpio-9   (                    |breadboard up       ) in  hi    
 gpio-10  (                    |breadboard down     ) in  hi    
 gpio-11  (                    |breadboard enter    ) in  hi    
 gpio-22  (                    |breadboard Menu     ) in  hi    

Finally something which is not strictly on-topic concerning this post. There is something one should know about keyboard like input event devices like this. Pressing a button will send events to all applications normally consuming them (e.g. applications running on Linux console or X-Window system).

This might be an unwanted behavior. If so, your application software needs to issue a EVIOCGRAB ioctl after opening the input device.

A Raspbian read-only root-fs HOWTO

In embedded applications it is often a requirement, that the device must be able to sustain a power cycle almost any time.

Unfortunately this is not something which modern operating systems (including GNU/Linux) like very much.

Fortunately in Linux there are workarounds. While there are specialized filesystems like f2fs, the most simple approach is still to just run the OS from a read-only root filesystem.

So here is the solution I made for my brewing hardware.

We bootup our fresh raspbian image install available at http://downloads.raspberrypi.org.

On the HDMI console expand the filesystem and setup i18n (german keyboard in my case).

All steps starting from here can now be done via ssh as well as the HDMI console.

  • Remove some stuff which is not needed or unsuitable for readonly operation:
  • apt-get remove --purge wolfram-engine triggerhappy
    apt-get remove --purge cron anacron logrotate dbus dphys-swapfile
    
  • Remove X-Server and related stuff:
  • apt-get remove --purge xserver-common lightdm
    insserv -r x11-common
    
  • auto-remove some X11 related libs
  • apt-get autoremove --purge
    
  • Install busybox syslog instead of rsyslog
  • The reason for doing this is because we do not want logfiles, but we want to be able to do some debugging (read logfiles). busybox-syslogd does not write logfiles but logs to a ring-buffer in memory which can be displayed using the logread command:

    apt-get install busybox-syslogd
    dpkg --purge rsyslog
    

The following steps are important, because we do not want any filesystem checks on our headless system at all!

  • Comment do_start in /etc/init.d/checkroot.sh
  • Comment do_start in /etc/init.d/checkfs.sh
  • ...
    case "$1" in
      start|"")
            #do_start
            ;;       
      restart|reload|force-reload)
            echo "Error: argument '$1' not supported" >&2
            exit 3
            ;;    
      stop)
            # No-op
            ;;     
      *)
            echo "Usage: checkfs.sh [start|stop]" >&2
            exit 3
            ;;    
    esac
    ...
    
  • Comment Operations in /etc/init.d/checkroot-bootclean.sh
  • ...
    case "$1" in
      start|"") 
            # Clean /tmp, /lib/init/rw, /run and /run/lock.  Remove the
            # .clean files to force initial cleaning.  This is intended
            # to
            # allow cleaning of directories masked by mounts while the
            # system was previously running, which would otherwise
            # prevent
            # them being cleaned.
            #rm -f /tmp/.clean /lib/init/rw/.clean /run/.clean /run/lock/.clean
    
            #clean_all
            exit $?   
            ;;
      restart|reload|force-reload)
            echo "Error: argument '$1' not supported" >&2
            exit 3
            ;;
      stop)   
            # No-op
            ;;
      *)
            echo "Usage: checkroot-bootclean.sh [start|stop]" >&2
            exit 3
            ;;
    esac
    ...
    
  • Comment swaponagain ’swapfile‘ in /etc/init.d/mountall.sh
  • Remove a couple of startup scripts:
  • insserv -r bootlogs
    insserv -r sudo
    insserv -r alsa-utils
    insserv -r console-setup
    insserv -r fake-hwclock 
    
  • Change /etc/fstab as follows:
  • proc              /proc           proc    defaults     0       0
    /dev/mmcblk0p1    /boot           vfat    defaults,ro  0       2
    /dev/mmcblk0p2    /               ext4    defaults,ro  0       1
    tmpfs             /tmp            tmpfs   defaults     0       0
    
  • append ro in /boot/cmdline.txt:
  • ...  elevator=deadline rootwait ro
    
  • Make dhclient write its leases file to /tmp instead of /var/lib/dhcp/:
  • rm -rf /var/lib/dhcp/
    ln -s /tmp /var/lib/dhcp
    

That’s it, have fun with your read-only Raspbian. As far as my brewing software is concerned, there is automated remount-rw/ro support included (see sample configfile).

Backporting stuff to Debian stable (6.0.x squeeze)

Being a Debian User for a long time now, I can live quite well running Debian stable, because official backports and backports I build on my own, work around the problem of the long release cycles quite nicely.

This is arguably a little bit more work than updating to a recent Version of Ubuntu twice a year, but fortunately Debian does not tend to break on updates 😉

However, with Debian’s recent move to Multiarch support coming in the next major release (wheezy) building backports is currently not that easy as it used to be anymore.

Fortunately the Multiarch conversion howto gives some hints but naturally in a somewhat inverted way.

As I did not find a DE-multi-arch-HOWTO on the web, here is a 4-step mini HOWTO:

  1. Remove the following line from debian/rules (if present):
    DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH)
  2. Remove all occurrences of $(DEB_HOST_MULTIARCH) in debian/rules (only the pattern itself, keep the surrounding stuff)
  3. Replace occurrences of /*/ in all files debian/*.install by a single /
    This can be done using the following command:
    for i in debian/*.install; do sed -i -e 's;/\*/;/;g' $i; done
  4. Remove all the lines starting with Multi-Arch: in debian/control

Hopefully this will be useful to other people crawling the web.