Bobbin CLI: Getting to Blinky

Anyone that’s dabbled in open-source embedded software development in C, C++ or Rust knows how rough it is around the edges. For beginners, it’s a huge barrier - there’s a lot of research, installation, and configuration that has to happen just to get a working toolchain that you can use to build and load software onto your chosen development board. For more experienced developers, it makes you wonder if it’s all worth the effort.

Unfortunately, individual tools from the command line can be clumsy and unergonomic. Starting a development session may require opening up multiple terminal windows and then starting up tools in a specific sequence. Often, required parameters such as device paths (needed to start up a screen session, for instance) will vary depending on exactly which USB port you decided to plug your device into, and there may be no obvious way to tell which TTY device is associated with which development board.

It can also be surprisingly difficult to do some types of things we would otherwise consider routine. One example: building a program, loading it onto a device, executing it and capturing the serial output to a text file, all in one step. Another that we take for granted in the Rust community: building and running a series of unit tests and getting back a success or failure result (the cornerstone of Continous Integration).

All of this is a barrier for anyone that wants to do embedded development with Rust - beginners and experienced developers alike.

Chip vendors don’t care - in fact, they have a real incentive to keep the system as it is. They manage the complexity by creating their own custom IDEs, often based on Eclipse, GCC and GDB. Once you start a project using those tools, it’s hard to move to another vendor’s IDE.

At the opposite end of the spectrum, there’s Arduino, which makes it incredibly easy to install the Arduino IDE, plug in one of a variety of boards running Arduino-compatible firmware, and create applications using the Arduino APIs and libraries. But the APIs are designed around what a 16 bit AVR MCU can do, even if they’ve been ported to a number of other platforms. And if your specific board isn’t supported or you are designing your own hardware, you’re out of luck.

Also, both of these approaches are IDE-centric. There’s nothing wrong with IDEs, but there are many reasons why one might prefer a CLI approach. Maybe you already use a different editor or IDE and want to keep your existing development tooling and workflow, maybe you want to be able to easily automate parts of your development workflow (perhaps integrating into a CI system), maybe you want to be able to work remotely without having to set up remote desktop sharing, maybe you simply want something you can use without having to download hundreds of megabytes of code.

Bobbin CLI is a Rust-inspired approach to tackling this problem. In the Rust ecosystem, rustup installs and upgrades Rust and the components you need; rustc doesn’t just give you a bunch of object files; it knows how to call the linker to generate an executable. cargo adds the ability to cargo build, cargo run, cargo test and cargo install, among many other things. xargo (an essential tool for Rust embedded development) builds and caches your sysroot so that you don’t need to understand the details of how to build the core libraries for your device. Each tool provides a well-thought-out command line interface and takes responsibility for a job that makes a developer’s life easier and lowers the barriers to using Rust.

Bobbin CLI’s job is to make this possible:

$ bobbin run
   Compiling blue-pill v0.1.0 (file:///home/bobbin/bobbin-hello/blue-pill)
    Finished dev [optimized + debuginfo] target(s) in 0.13 secs
   text	   data	    bss	    dec	    hex	filename
    152	      0	      4	    156	     9c	target/thumbv7em-none-eabihf/debug/blue-pill
     Loading target/thumbv7em-none-eabihf/debug/blue-pill.hex
    Complete Successfully flashed device
      Loader Load Complete
     Console Opening Console
Hello World 1
Hello World 2
Hello World 3
^C
$

and this:

$ bobbin test
   Compiling frdm-k64f v0.1.0 (file:///home/bobbin/bobbin-boards/frdm-k64f)
    Finished dev [optimized + debuginfo] target(s) in 0.61 secs
   text	   data	    bss	    dec	    hex	filename
   6252	    428	    408	   7088	   1bb0	target/thumbv7em-none-eabihf/debug/frdm-k64f
     Loading target/thumbv7em-none-eabihf/debug/frdm-k64f
    Complete Successfully flashed device
      Loader Load Complete
     Console Opening Console
[start] Running tests for frdm-k64f
[pass] 0
[pass] 1
[pass] 2
[pass] 3
[pass] 4
[done] All tests passed
$ echo $?
0
$

Bobbin CLI handles USB device selection and enumeration, making it easy to work with many devices at the same time:

$ bobbin list
ID        VID:PID  Vendor / Product                         Serial Number
8d071081 1366:0101 SEGGER / J-Link                          000260101567
4c01a4ad 1366:0105 SEGGER / J-Link                          000621000000
189c7be3 0483:3748 STMicroelectronics / STM32 STLink        HÿnPuIUAG‡
8c6bbec5 0d28:0204 ARM / DAPLink CMSIS-DAP                  0260000025414e4500495012
a3ef65e3 0483:374b STMicroelectronics / STM32 STLink        0667FF555654725187073723
4cade99c 0483:374b STMicroelectronics / STM32 STLink        066CFF565251887067024851
14a7f5da 03eb:2157 Atmel Corp. / EDBG CMSIS-DAP             00000000EZE000005574
c2f3dc42 0483:374b STMicroelectronics / STM32 STLink        0670FF484957847167071621
b7e67550 0483:374b STMicroelectronics / STM32 STLink        0673FF485550755187121723
$

It also knows what tools are used by each type of device and can locate paths to serial ports and mass storage devices based on device ID:

$ bobbin -d 4c01 info
ID               c2f3dc42b4aadc58b6dfa98ce527dd436e3e4fa5
Vendor ID        0483
Product ID       374b
Vendor           STMicroelectronics
Product          STM32 STLink
Serial Number    0670FF484957847167071621
Type             STLinkV21
Loader Type      OpenOCD
Debugger Type    OpenOCD
CDC Device       /dev/cu.usbmodem141413
OpenOCD Serial   hla_serial 0670FF484957847167071621
$

Bobbin CLI also checks for and displays the version of all tools being used.

$ bobbin check
      Rust 1.20.0-nightly (83c659ef6 2017-07-18)
     Cargo 0.21.0-nightly (f709c35a3 2017-07-13)
     Xargo 0.3.8
       GCC 5.4.1 20160919 (release) [ARM/embedded-5-branch revision 240496]
   OpenOCD 0.10.0+dev-00092-g77189db (2017-03-01-20:42)
     JLink V6.15c (Compiled Apr 24 2017 19:07:08)
     Bossa 1.7.0
    Teensy 2.1
  dfu-util 0.9

In the future, Bobbin CLI may check for and warn about outdated tools and debug probe firmware and potentially download and install up-to-date versions in a manner similar to rustup.

You can read more about Bobbin CLI on Github.

Currently, Bobbin CLI detects and works with

  • ST-Link/V2 and ST-Link/V2-1 (STM32 Discovery and Nucleo development boards, including optional SWO trace output)
  • J-Link (Standalone debug probes and included in many development boards)
  • DAPLINK / CMSIS-DAP (Used in many development bards)
  • Blackmagic Probe (popular open source debug probe)
  • TI ICDI
  • Bossa (Firmware uploader for Arduino-compatible boards including the Arduino Zero and Feather-M0)
  • Teensy Loader (Firmware uploader for Teensy boards)
  • DFU-UTIL (USB DFU firmware uploader)

At the moment, only macOS and Linux are supported, but Windows support is planned and under development.

Similar to Rust itself, Bobbin is MIT / Apache 2.0 licensed.

Acknowledgements

Bobbin CLI is certainly not the only tool in this space.

The PlatformIO CLI is the closest in spirit, and shares many of the same concepts. I developed most of Bobbin before becoming aware of PlatformIO CLI and was pleasantly surprised that we came up with similar solutions to similar problems.

Adafruit Adalink was a more direct inspiration - it’s simpler than Bobbin CLI but also does more, in particular automatic board detection.

I previously spent a lot of time using with Docker and Kubernetes, which made me wonder if it was possible to make dealing with embedded devices as easy as dealing with containers.

Ripgrep was tremendously helpful as a model for how a good Rust CLI is built, as well as Cargo.

And of course, Jorge Aparicio’s work on Xargo and with the Rust team has made embedded programming using Rust possible in the first place.

In my next post, the question I haven’t answered: How exactly do you get to Blinky?