- Introduction
- Hardware Requirements
- Prerequisites
- Console (Minicom)
- Zephyr
- Linux
- Ping Pong
- Blinky!
- Conclusion
This document describes, in some detail, the steps required to use Linux workstation and the Greybus protocol, over a Bluetooth Low Energy wireless link, to blink an LED on a Zephyr device.
Good question. Blinking an LED is kind of the Hello, World of the hardware community. In this case, we're less interested in the mechanics of switching a GPIO to drive some current through an LED and more interested in how that happens with the Internet of Things (IoT).
There are several existing network and application layers that are driven by corporate heavyweights and industry consortiums, but relatively few that are community driven and, more specifically, even fewer that have the ability to integrate so tightly with the Linux kernel.
The goal here is to provide a community-maintained, developer-friendly, and open-source protocol for the Internet of Things using the Greybus Protocol, and blinking an LED using Greybus is the simplest proof-of-concept for that. All that is required is a reliable transport.
There are a few technologies at the core of this demonstration, and far too much background information to describe adequately here, so they are simply listed below for brevity
- Project Ara
- IPv6 (via 6LowPAN)
- Bluetooth Internet Protocol Support Profile (IPSP)
- Zephyr support for Bluetooth IPSP
- Greybus originally from Project Ara
- Using Greybus for IoT
In short, Greybus is a kind of "bus transport" in that it conveys bus-specific messages back and forth between Linux and a connected device. The physical bus is attached to the connected device, which could be running Linux or a variety of Real-Time Operating Systems. Meanwhile, on the Linux side, a virtual bus is created corresponding to the physical bus on the connected device. To the user, this virtual bus (be it /dev/gpiochip0, /dev/i2c5, etc) appears and functions exactly the same. Greybus is the protocol used to exchange bus-specific messages and data between Linux and the connected device.
The major advantage there is that drivers can be well maintained in Linux rather than buried in microcontroller firmware.
Greybus currently supports several busses, including:
- USB
- I2C
- GPIO
- PWM
- SPI
- UART
- SDIO
- Camera (V4L)
- LED (with various programmability)
- AUDIO (I2S)
- a Linux workstation running Ubuntu Bionic
- Only x86_64 is supported at this time
- BLE support is required
- a board that is supported by Zephyr with a BLE Split-Stack Link-Layer Implementation
- In this example, we use the nrf52840dk_nrf52840
- Zephyr environment is set up according to the Getting Started Guide
- Please use the Zephyr SDK when installing a toolchain above
- Zephyr SDK is installed at ~/zephyr-sdk-0.11.2 (any later version should be fine as well)
- Zephyr board is connected via USB
In order to see diagnostic messages or to run certain commands on the Zephyr device we will require a terminal open to the device console. In this case, we use minicom. We will run it twice; the first time for setup using root privileges, and the a second time as a regular user.
sudo minicom -s
You should see the options shown below:
+-----[configuration]------+
| Filenames and paths |
| File transfer protocols |
| Serial port setup |
| Modem and dialing |
| Screen and keyboard |
| Save setup as dfl |
| Save setup as.. |
| Exit |
| Exit from Minicom |
+--------------------------+
- Select Serial port setup, hit Enter
- Press 'A' for Serial Device, type in
/dev/ttyACM0
, and hit Enter again - Press 'E' for Bps/Par/Bits, press 'E' for
115200
, and 'Q' for 8-N-1, and hit Enter again - Press 'F' to set Hardware Flow Control: No
- Press Down to Save setup as.., and then enter
ttyACM0
when prompted, and hit Enter - Press Down to Exit from Minicom and finally hit Enter again to exit setup
Now, we'll open a terminal to Zephyr using the newly created setup with the command below.
minicom ttyACM0
Enter the following key combinations
Ctrl+A, U
->Add carriage return ON
Ctrl+A, W
->Linewrap ON
Ctrl+A, C
-> clear the screen
To exit minicom (later), enter Ctrl+A, X
.
For the time being, Greybus must remain outside of the main Zephyr repository. Currently, it is just in a Zephyr fork, but it should be converted to a proper Module (External Project). This is for a number of reasons, but mainly there must be:
- specifications for authentication and encryption
- specifications for joining and rejoining wireless networks
- specifications for discovery
Therefore, in order to reproduce this example, please run the following in your zephyr directory.
git remote add greybus https://github.com/cfriedt/zephyr.git
git fetch greybus
git checkout -b greybus-sockets greybus/greybus-sockets
west update
Here, we will build and flash the Zephyr greybus_net sample to our device.
- Open a separate terminal window (
Ctrl+Shift+N
) or simply create a new tab in your existing terminal (Ctrl+Shift+T
) so that you can see both or quickly switch betweenminicom
and the shell. - Now in the shell, change to the
zephyrproject/zephyr
directory - Edit the file
~/.zephyrrc
and place the following text inside of itexport ZEPHYR_TOOLCHAIN_VARIANT=zephyr export ZEPHYR_SDK_INSTALL_DIR=~/zephyr-sdk-0.11.2 export BOARD=nrf52840dk_nrf52840 export ZEPHYR_PROJECT=samples/subsys/greybus/net
- Set up the required Zephyr environment variables via
source zephyr-env.sh
- Build the project
west build ${ZEPHYR_PROJECT} -- -DCONF_FILE="prj.conf overlay-bt.conf"
- Ensure that the last part of the build process looks somewhat like this:
... Memory region Used Size Region Size %age Used FLASH: 261996 B 1 MB 24.99% SRAM: 50879 B 256 KB 19.41% IDT_LIST: 152 B 2 KB 7.42% [245/245] Linking C executable zephyr/zephyr.elf
- Flash the firmware to your device using
west flash
- Go to your Bluetooth preferences and pair with a device named
Zephyr Greybus Demo
.
After flashing, you should observe the something matching the following output in minicom
.
*** Booting Zephyr OS build v2.3.0-rc1-391-g3852e0812618 ***
[00:00:00.008,789] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
[00:00:00.008,819] <inf> bt_hci_core: HW Variant: nRF52x (0x0002)
[00:00:00.008,819] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 2.3 Build 99
[00:00:00.009,490] <inf> bt_hci_core: Identity: ca:27:c1:e9:6f:d0 (random)
[00:00:00.009,490] <inf> bt_hci_core: HCI: version 5.2 (0x0b) revision 0x0000, manufacturer 0x05f1
[00:00:00.009,490] <inf> bt_hci_core: LMP: version 5.2 (0x0b) subver 0xffff
[00:00:00.381,500] <inf> net_config: Initializing network
[00:00:00.381,500] <inf> net_config: Waiting interface 0x200011c0 to be up...
uart:~$
The line beginning with ***
is the Zephyr boot banner.
Lines beginning with a timestamp of the form [H:m:s.us]
are Zephyr kernel messages.
Lines beginning with uart:~$
indicates that the Zephyr shell is prompting you to enter a command.
sudo modprobe bluetooth_6lowpan
echo 1 | sudo tee /sys/kernel/debug/bluetooth/6lowpan_enable
echo "connect CA:27:C1:E9:6F:D0 2" | sudo tee /sys/kernel/debug/bluetooth/6lowpan_control
We should now see a bt0
network interface.
$ ip a show bt0
7: bt0: <MULTICAST,UP,LOWER_UP> mtu 1280 qdisc fq_codel state UNKNOWN group default qlen 1000
link/6lowpan 5c:f3:70:92:5c:06 brd 00:00:00:00:00:00
inet6 2001:470:1d:8e9:64b6:d3b1:5376:aac9/64 scope global temporary dynamic
valid_lft 85992sec preferred_lft 13992sec
inet6 2001:470:1d:8e9:5cf3:70ff:fe92:5c06/64 scope global dynamic mngtmpaddr
valid_lft 85992sec preferred_lft 13992sec
inet6 fe80::5cf3:70ff:fe92:5c06/64 scope link
valid_lft forever preferred_lft forever
Once the 6LowPAN device is associated on Linux, the Zephyr network interface should come up automatically.
...
[00:00:59.766,021] <inf> net_config: Interface 0x200014e0 coming up
[00:00:59.865,966] <inf> net_config: IPv6 address: fe80::ca27:c1ff:fee9:6fd0
[00:00:59.867,034] <inf> net_config: IPv6 address: fe80::ca27:c1ff:fee9:6fd0
[00:01:17.953,552] <inf> net_config: IPv6 address: fe80::ca27:c1ff:fee9:6fd0
uart:~$
GB: D: identify_descriptor():298: cport_id = 0
GB: D: identify_descriptor():298: cport_id = 1
Now, perform a broadcast ping to see what else is listening on bt0
.
$ ping6 -I bt0 ff02::1
PING ff02::1(ff02::1) from fe80::5cf3:70ff:fe92:5c06%bt0 bt0: 56 data bytes
64 bytes from fe80::5cf3:70ff:fe92:5c06%bt0: icmp_seq=1 ttl=64 time=0.102 ms
64 bytes from fe80::ca27:c1ff:fee9:6fd0%bt0: icmp_seq=1 ttl=64 time=171 ms (DUP!)
64 bytes from fe80::5cf3:70ff:fe92:5c06%bt0: icmp_seq=2 ttl=64 time=0.150 ms
64 bytes from fe80::ca27:c1ff:fee9:6fd0%bt0: icmp_seq=2 ttl=64 time=144 ms (DUP!)
64 bytes from fe80::5cf3:70ff:fe92:5c06%bt0: icmp_seq=3 ttl=64 time=0.091 ms
64 bytes from fe80::ca27:c1ff:fee9:6fd0%bt0: icmp_seq=3 ttl=64 time=118 ms (DUP!)
Yay! We have pinged (pung?) the Zephyr device over Bluetooth using 6LowPAN!
We can ping the Zephyr device directly without a broadcast ping too, of course.
$ ping6 -I bt0 fe80::ca27:c1ff:fee9:6fd0
PING fe80::ca27:c1ff:fee9:6fd0(fe80::ca27:c1ff:fee9:6fd0) from fe80::5cf3:70ff:fe92:5c06%bt0 bt0: 56 data bytes
64 bytes from fe80::ca27:c1ff:fee9:6fd0%bt0: icmp_seq=1 ttl=64 time=244 ms
64 bytes from fe80::ca27:c1ff:fee9:6fd0%bt0: icmp_seq=2 ttl=64 time=120 ms
64 bytes from fe80::ca27:c1ff:fee9:6fd0%bt0: icmp_seq=3 ttl=64 time=142 ms
64 bytes from fe80::ca27:c1ff:fee9:6fd0%bt0: icmp_seq=4 ttl=64 time=115 ms
Similarly, we can ping the Linux host from the Zephyr shell.
uart:~$ net ping --help
ping - Ping a network host.
Subcommands:
--help :'net ping [-c count] [-i interval ms] <host>' Send ICMPv4 or ICMPv6
Echo-Request to a network host.
uart:~$ net ping -c 5 fe80::5cf3:70ff:fe92:5c06
PING fe80::5cf3:70ff:fe92:5c06
8 bytes from fe80::5cf3:70ff:fe92:5c06 to fe80::ca27:c1ff:fee9:6fd0: icmp_seq=0 ttl=64 time=97 ms
8 bytes from fe80::5cf3:70ff:fe92:5c06 to fe80::ca27:c1ff:fee9:6fd0: icmp_seq=1 ttl=64 time=71 ms
8 bytes from fe80::5cf3:70ff:fe92:5c06 to fe80::ca27:c1ff:fee9:6fd0: icmp_seq=2 ttl=64 time=95 ms
8 bytes from fe80::5cf3:70ff:fe92:5c06 to fe80::ca27:c1ff:fee9:6fd0: icmp_seq=3 ttl=64 time=70 ms
8 bytes from fe80::5cf3:70ff:fe92:5c06 to fe80::ca27:c1ff:fee9:6fd0: icmp_seq=4 ttl=64 time=93 ms
So far, we have been using IPv6 Link-Local addressing. However, the Zephyr application is configured to use a statically configured IPv6 address as well which is, namely 2001:db8::1
.
If we add a similar static IPv6 address to our Linux Bluetooth network interface, bt0
, then we should expect to be able to reach that as well.
In Linux, run the following
sudo ip -6 addr add 2001:db8::2/64 dev bt0
We can verify that the address has been set by examining the bt0
network interface again.
$ ip a show bt0
7: bt0: <MULTICAST,UP,LOWER_UP> mtu 1280 qdisc fq_codel state UNKNOWN group default qlen 1000
link/6lowpan 5c:f3:70:92:5c:06 brd 00:00:00:00:00:00
inet6 2001:db8::2/64 scope global
valid_lft forever preferred_lft forever
inet6 2001:470:1d:8e9:64b6:d3b1:5376:aac9/64 scope global temporary dynamic
valid_lft 86400sec preferred_lft 14400sec
inet6 2001:470:1d:8e9:5cf3:70ff:fe92:5c06/64 scope global dynamic mngtmpaddr
valid_lft 86400sec preferred_lft 14400sec
inet6 fe80::5cf3:70ff:fe92:5c06/64 scope link
valid_lft forever preferred_lft forever
Lastly, ping to the statically configured IPv6 address of the Zephyr device.
$ ping6 2001:db8::1
PING 2001:db8::1(2001:db8::1) 56 data bytes
64 bytes from 2001:db8::1: icmp_seq=1 ttl=64 time=384 ms
64 bytes from 2001:db8::1: icmp_seq=2 ttl=64 time=213 ms
64 bytes from 2001:db8::1: icmp_seq=3 ttl=64 time=186 ms
64 bytes from 2001:db8::1: icmp_seq=4 ttl=64 time=161 ms
Now that we have set up a reliable transport, let's move on to the application layer.
Hopefully the videos listed earlier provide a sufficient foundation to understand what will happen shortly. However, there is still a bit more preparation required.
Greybus was originally intended to work exclusively on the UniPro physical layer. However, we're using RF as our physical layer and TCP/IP as our transport. As such, there was need to be able to communicate with the Linux Greybus facilities through userspace, and out of that need arose gb-netlink. The Netlink Greybus module actually does not care about the physical layer, but is happy to usher Greybus messages back and forth between the kernel and userspace.
Build and probe the gb-netlink modules (as well as the other Greybus modules) with the following:
cd ${WORKSPACE}
git clone https://github.com/friedtco/greybus.git
cd greybus
make -j`nproc --all`
./gbprobe.sh
The gbridge utility was created as a proof of concept to abstract the Greybus Netlink datapath among several reliable transports. For the purposes of this tutorial, we'll be using it as a TCP/IP bridge.
To download and run gbridge
, perform the following:
cd ${WORKSPACE}
git clone https://github.com/friedtco/gbridge.git
cd gbridge
autoreconf -vfi
GBDIR=${PWD}/../greybus \
./configure --enable-uart --enable-tcpip --disable-gbsim --enable-netlink --disable-bluetooth
make -j`nproc --all`
./gbridge
Now that we have set up a reliable TCP transport, and set up the Greybus modules in the Linux kernel, and used Gbridge to connect a Greybus node to the Linux kernel via TCP/IP, we can now get to the heart of the demonstration!
First, gain root privileges using sudo -s
. Then, you can copy & paste the following into your terminal and observe led0
blinking.
CHIP=`gpiodetect | grep "greybus_gpio" | awk '{print $1}'`
VAL=0; for ((;;)); do VAL=$((VAL^1)); echo $VAL | gpioset ${CHIP} 0=${VAL}; done
The output of your minicom session should resemble the following.
*** Booting Zephyr OS build zephyr-v2.3.0-12-g833930e6fecb ***
uart:~$ [00:00:00.009,033] <inf> bt_hci_core: HW Platform: Nordic Semiconductor (0x0002)
uart:~$ [00:00:00.009,033] <inf> bt_hci_core: HW Variant: nRF52x (0x0002)
uart:~$ [00:00:00.009,063] <inf> bt_hci_core: Firmware: Standard Bluetooth controller (0x00) Version 2.3 Build 99
uart:~$ [00:00:00.009,735] <inf> bt_hci_core: Identity: ca:27:c1:e9:6f:d0 (random)
uart:~$ [00:00:00.009,735] <inf> bt_hci_core: HCI: version 5.2 (0x0b) revision 0x0000, manufacturer 0x05f1
uart:~$ [00:00:00.009,765] <inf> bt_hci_core: LMP: version 5.2 (0x0b) subver 0xffff
uart:~$ [00:00:00.375,488] <inf> net_config: Initializing network
uart:~$ [00:00:00.375,488] <inf> net_config: Waiting interface 0x20001700 to be up...
uart:~$ uart:~$ [00:00:37.629,608] <inf> net_config: Interface 0x20001700 coming up
uart:~$ [00:00:37.729,553] <inf> net_config: IPv6 address: fe80::ca27:c1ff:fee9:6fd0
uart:~$ [00:00:37.730,621] <inf> net_config: IPv6 address: fe80::ca27:c1ff:fee9:6fd0
uart:~$ uart:~$ gbsetup(): 584: Registering platform drivers..
gbsetup(): 587: Getting static manifest blob..
gbsetup(): 591: Parsing manifest..
GB: D: identify_descriptor():298: cport_id = 0
GB: D: identify_descriptor():298: cport_id = 1
gbsetup(): 597: Parsed manifest
gbsetup(): 599: Updating manifest blob..
gbsetup(): 602: Initializing Greybus..
gbsetup(): 609: Enabling Cports..
GB: I: enable_cports():129: Registering CONTROL greybus driver.
GB: D: _gb_register_driver():544: Registering Greybus driver on CP0
GB: I: enable_cports():136: Registering GPIO greybus driver.
GB: D: _gb_register_driver():544: Registering Greybus driver on CP1
gbsetup(): 612: Greybus is active.
netsetup(): 156: initializing control_thread stack
netsetup(): 169: initializing gpio_thread stack
netsetup(): 186: creating control server socket
netsetup(): 192: creating gpio server socket
netsetup(): 199: setting socket options for control server
netsetup(): 207: setting socket options for gpio server
netsetup(): 215: binding control server socket
netsetup(): 222: binding gpio server socket
netsetup(): 230: listening on control server socket
netsetup(): 236: listening on gpio server socket
accept_loop(): 259: preparing pollfds
accept_loop(): 266: calling poll
[00:00:43.093,536] <inf> net_config: IPv6 address: fe80::ca27:c1ff:fee9:6fd0
uart:~$ uart:~$ accept_loop(): 272: returned from poll
accept_loop(): 274: control socket has a traffic
accept_loop(): 290: accepted connection from [2001:470:1d:8e9:c5c7:a42e:bab5:ab8d]:52560 as fd 2
accept_loop(): 292: spawning control thread..
accept_loop(): 259: preparing pollfds
accept_loop(): 266: calling poll
GB: D: gb_process_request():251: gb_control_protocol_version: 0
gb_xport_send(): 545: control: cport: 0, buf: 0x2000ee08, len: 10
GB: D: gb_process_request():251: gb_control_get_manifest_size: 0
gb_xport_send(): 545: control: cport: 0, buf: 0x2000eda0, len: 10
GB: D: gb_process_request():251: gb_control_get_manifest: 0
gb_xport_send(): 545: control: cport: 0, buf: 0x2000ee18, len: 100
GB: D: gb_process_request():251: gb_control_bundle_activate: 0
gb_xport_send(): 545: control: cport: 0, buf: 0x2000edf8, len: 9
accept_loop(): 272: returned from poll
accept_loop(): 302: gpio service has traffic
accept_loop(): 318: accepted connection from [2001:470:1d:8e9:c5c7:a42e:bab5:ab8d]:46808 as fd 3
accept_loop(): 320: spawning gpio thread..
pthread_create: Success
GB: D: gb_process_request():251: gb_control_connected: 0
GB: D: gb_process_request():251: gb_gpio_line_count: 0
GB: D: gb_process_request():251: gb_gpio_get_direction: 0
GB: D: gb_process_request():251: gb_gpio_activate: 0
GB: D: gb_process_request():251: gb_gpio_get_direction: 0
GB: D: gb_process_request():251: gb_gpio_direction_out: 0
GB: D: gb_process_request():251: gb_gpio_deactivate: 0
GB: D: gb_process_request():251: gb_gpio_activate: 0
GB: D: gb_process_request():251: gb_gpio_get_direction: 0
GB: D: gb_process_request():251: gb_gpio_direction_out: 0
GB: D: gb_process_request():251: gb_gpio_deactivate: 0
GB: D: gb_process_request():251: gb_gpio_activate: 0
GB: D: gb_process_request():251: gb_gpio_get_direction: 0
GB: D: gb_process_request():251: gb_gpio_direction_out: 0
GB: D: gb_process_request():251: gb_gpio_deactivate: 0
GB: D: gb_process_request():251: gb_gpio_activate: 0
The blinking LED can be a somewhat anticlimactic, but hopefully it illustrates the potential for Greybus as an IoT application layer protocol.
The proof-of-concept involving Linux, Zephyr, and Bluetooth was actually fairly straight forward and was accomplished with mostly already-upstream source.
For Greybus, there is still a considerable amount of integration work to be done, including
- converting the fork to a proper Zephyr module
- integrating seamlessly with Zephyr's I/O APIs (e.g. for i2c, spi, etc)
- automated testing
- adding security and authentication into the Zephyr
- automatic detection, joining, and rejoining of devices
Thanks for reading, and we hope you've enjoyed this tutorial.