Single board computer projects

Device tree overlay with beaglebone black

My old BeagleBone had problems to get data from the uarts after an update. I decided to use a new BeagleBone black (bbb) with new kernel (4.4) to use eMMC instead the SD-card.
The old GPIO lib did not work with the new kernel, so I needed the the adafruit gpio lib.

Unfortunately this change was not easy and extremely time consuming. The used GPIOs are not free with the new device tree overlays and I had to dig deep to get it work.

A good help was the page from Derek Molloy. Another useful link is the introduction to device tree by adafruit.

Device tree

The device tree is a data structure for describing hardware. So there is no further need to do this in the kernel for all the different arm controller boards. This data structure is passed to the operating system at boot time.

A dts-file (device tree source) is describing the data structure in a human readable format. A compiler (dtc; decvice tree compiler) converts the dts file to compact device tree blob file used by the kernel. This files have the ending dtb (device tree blob). The dtb files can be found on bbb in /boot/dtbs.

It is possible to change these dtb files in /boot/dtbs, but we have to reboot every time after we change the device tree. A more flexible way is to use device tree overlays in user space to change the device tree in runtime.

Device tree overlay

An important file is /boot/uEnv.txt. In this file we find the kernel version: uname_r=4.4.9-ti-r25 and we are able to change the loading of device tree overlays.

I don't want to change my cape so I need the head pins P0915 (PINS Nr 16), P0923 (PINS Nr 17), P0925 (PINS Nr 107) and P0846 (PINS Nr 41). These pins are blocked by hdmi audio (mcasp0) and nxphdmibonelt.

In /boot/uEnv.txt I have to uncomment the following dtb line:

    ##BeagleBone Black: HDMI (Audio/Video) disabled:
    dtb=am335x-boneblack-emmc-overlay.dtb

and to change the following line

    cmdline=coherent_pool=1M quiet cape_universal=enable

to:

    cmdline=coherent_pool=1M quiet

After a reboot I want to check the pins and loaded capes. The simplest way is to create two shell variables as environment variables. Add the two following lines to ~/.profile:

    export SLOTS=/sys/devices/platform/bone_capemgr/slots
    export PINS=/sys/kernel/debug/pinctrl/44e10800.pinmux/pins

The command cat $SLOTS shows that there are no capes or overlays loaded (the first slots are assigned by EEPROM IDs on the capes):

    weigu@beaglebone:~$ cat $SLOTS    
     0: PF----  -1 
     1: PF----  -1 
     2: PF----  -1 
     3: PF----  -1 

With the command sudo cat $PINS i can check the state of the pins:

    weigu@beaglebone:~$ sudo cat $PINS
    registered pins: 142
    pin 16 (44e10840.0) 00000008 pinctrl-single 
    pin 17 (44e10844.0) 00000027 pinctrl-single 
    pin 41 (44e108a4.0) 0000002f pinctrl-single 
    pin 107 (44e109ac.0) 00000027 pinctrl-single 

Pin number is not the GPIO number! See in Dereks PDFs for GPIO number!

Now its time to write the device tree source file by changing Dereks example file. First I change the part number to part-number = "WEIGU1". I want my 4 Pins set as output, so the code has to by 0x07. The offset is found in Dereks PDFs.

    /*
    * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com/
    *
    * This program is free software; you can redistribute it and/or modify
    * it under the terms of the GNU General Purpose License Version 2 as
    * published by the Free Software Foundation
    *
    * Original from: github.com/jadonk/validation-scripts/blob/master/test-capemgr/
    *
    * Modified by Derek Molloy for the example on www.derekmolloy.ie
    * that maps GPIO pins for the example
    */

    /dts-v1/;
    /plugin/;

    /{
        compatible = "ti,beaglebone", "ti,beaglebone-black";
        part-number = "WEIGU1";
        version = "00A0";
        fragment@0 {
            target = <&am33xx_pinmux>;
            __overlay__ {
                pinctrl_test: DM_GPIO_Test_Pins {
                    pinctrl-single,pins = <
                        0x044 0x07  /* P9_23 49  OUTPUT MODE7 - MUXA */
                        0x040 0x07  /* P9_15 48  OUTPUT MODE7 - MUXB */
                        0x1ac 0x07  /* P9_25 117 OUTPUT MODE7 - MUXT conflict mcasp0! */
                        0x0a4 0x07  /* P8_46 71  OUTPUT MODE7 - RTS conflict hdmi_bonelt! */
                        /* OUTPUT  GPIO(mode7) 0x07 pulldown, 0x17 pullup, 0x?f no pullup/down */
                        /* INPUT   GPIO(mode7) 0x27 pulldown, 0x37 pullup, 0x?f no pullup/down */
                    >;
                };
            };
        };

        fragment@1 {
            target = <&ocp>;
            __overlay__ {
                test_helper: helper {
                    compatible = "bone-pinmux-helper";
                    pinctrl-names = "default";
                    pinctrl-0 = <&pinctrl_test>;
                    status = "okay";
                };
            };
        };
    };

The file is saved as WEIGU1.dts. The compiler will help to create my overlay file:

    dtc -O dtb -o WEIGU1-00A0.dtbo -b 0 -@ WEIGU1.dts

Now we have to copy the file to /lib/firmware.

    sudo cp WEIGU1-00A0.dtbo /lib/firmware/

To enable the overlay sudois not enough. Use the following commands:

    sudo su
    echo WEIGU1 > /sys/devices/platform/bone_capemgr/slots

Exit with Ctrl+D.

After this the command cat $SLOTS shows my new overlay:

    weigu@beaglebone:~$ cat $SLOTS    
     0: PF----  -1 
     1: PF----  -1 
     2: PF----  -1 
     3: PF----  -1 
     4: P-O-L-   0 Override Board Name,00A0,Override Manuf,WEIGU1

sudo cat $PINS shows now that my pins are configures as output:

    weigu@beaglebone:~$ sudo cat $PINS
    registered pins: 142
    pin 16 (44e10840.0) 00000007 pinctrl-single 
    pin 17 (44e10844.0) 00000007 pinctrl-single 
    pin 41 (44e108a4.0) 00000007 pinctrl-single 
    pin 107 (44e109ac.0) 00000007 pinctrl-single 

There is a simple way to force the overlay to be loaded at boot. In /boot/uEnv.txt we have to add this line:

    cape_enable=capemgr.enable_partno=WEIGU1

With cape_enable=capemgr.enable_partno= you can tell the kernel to load any overlay found in /lib/firmware. As I want to use all 4 UARTs, I complete the line with 4 part numbers:

    cape_enable=bone_capemgr.enable_partno=BB-UART1,BB-UART2,BB-UART4,BB-UART5,WEIGU1

Same it is also possible to disable parts:

    cape_disable=bone_capemgr.disable_partno=BB-BONELT-HDMI,BB-BONELT-HDMIN

The source code (with the part numbers) of the BB-UART overlays can be found in /opt/source/bb.org-overlays/src/arm.

Testing GPIOs and UARTS with python3

Now we are able to test our GPIOs:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    # test_gpio.py testing gpios 
    # www.weigu.lu

    import Adafruit_BBIO.GPIO as GPIO
    from time import sleep

    A = "P9_23" # GPIO1_17 Header P9 Pin 23
    B = "P9_15" # GPIO1_16 Header P9 Pin 15
    T = "P9_25" # GPIO3_21 Header P9 Pin 25
    R = "P8_46" # GPIO2_7  Header P8 Pin 46

    GPIO.setup(A, GPIO.OUT)
    GPIO.setup(B, GPIO.OUT)
    GPIO.setup(T, GPIO.OUT)
    GPIO.setup(R, GPIO.OUT)

    try:
        while True:   
            print('0')
            GPIO.output(A,GPIO.LOW)  
            GPIO.output(B,GPIO.LOW)
            GPIO.output(T,GPIO.LOW)
            GPIO.output(R,GPIO.LOW)
            sleep(1)  
            print('1')
            GPIO.output(A,GPIO.HIGH) 
            GPIO.output(B,GPIO.HIGH)
            GPIO.output(T,GPIO.HIGH)
            GPIO.output(R,GPIO.HIGH)
            sleep(1)
    except KeyboardInterrupt:
        pass

    GPIO.cleanup()

The program has to be called as superuser:

    sudo python3 test_gpio.py    

And here the program for testing the UARTs:

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    # test_uart.py testing 4 uarts
    # www.weigu.lu

    import Adafruit_BBIO.UART as UART
    import serial
    from time import sleep

    UART.setup("UART1") 
    UART.setup("UART2") 
    UART.setup("UART4") 
    UART.setup("UART5") 

    ser1 = serial.Serial(port = "/dev/ttyO1", baudrate=9600)
    ser1.close()
    ser1.open() 
    if ser1.isOpen():     
        print('UART1 opened')
    ser2 = serial.Serial(port = "/dev/ttyO2", baudrate=9600)
    ser2.close()
    ser2.open()      
    if ser2.isOpen():
        print('UART2 opened')
    ser3 = serial.Serial(port = "/dev/ttyO4", baudrate=9600)
    ser3.close()
    ser3.open()      
    if ser3.isOpen():
        print('UART4 opened')
    ser4 = serial.Serial(port = "/dev/ttyO5", baudrate=9600)
    ser4.close()
    ser4.open()      
    if ser4.isOpen():
        print('UART5 opened')

    try:
        while True:
            ser1.write('A'.encode())
            ser2.write('B'.encode())
            ser3.write('C'.encode())
            ser4.write('D'.encode())
            sleep(0.01)

    except KeyboardInterrupt:
        pass

    ser1.close()
    ser2.close()
    ser3.close()
    ser4.close()

    UART.cleanup()
    sudo python3 test_uart.py