Graeme Winter

Basic Zephyr

Been meaning to look at Zephyr for a while and was nudged by reading an article in Elektor magazine to have a go. Had a BBC Micro:bit v1.3 lying around to play with which was handy.

Getting started easy enough, following instructions (had already done this for pico) but the magazine also talked about device trees which was something I had an interest in. Pulled a copy of the standard blinky example and added pad2 as an ersatz LED for output:

/ {
    leds {
        compatible = "gpio-leds";
        led0: led_0 {
            gpios = <&gpio0 1 GPIO_ACTIVE_HIGH>;
        };
    };

    aliases {
        led0 = &led0;
    };
};

This gets added as boards/bbc_microbit.overlay in the project source repo. Rather than building with west decided to figure out out-of-tree building: easy enough as it turns out:

BOARD=bbc_microbit cmake ..
make

Then

cp zephyr/zephyr.hex /Volumes/MICROBIT/

to flash the device. Code simple enough:

#include <stdio.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>

#define LED0_NODE DT_ALIAS(led0)

static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

int main(void) {
  int ret;

  if (!gpio_is_ready_dt(&led)) {
    return 0;
  }

  ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
  if (ret < 0) {
    return 0;
  }

  while (1) {
    ret = gpio_pin_set_dt(&led,1);
    if (ret < 0) {
        return 0;
    }
    ret = gpio_pin_set_dt(&led,0);
    if (ret < 0) {
        return 0;
    }
  }

  return 0;
}

which did indeed toggle the output:

Oscilloscope trace

but this seems very slow. Wondering if this is just the fact that the CPU is really slow, tried with basic register pokes:

#include <stdio.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/kernel.h>

#define LED0_NODE DT_ALIAS(led0)

static const struct gpio_dt_spec led = GPIO_DT_SPEC_GET(LED0_NODE, gpios);

int main(void) {
  int ret;

  volatile uint32_t *gpio_set = (uint32_t *)(0x50000000 | 0x508);
  volatile uint32_t *gpio_clr = (uint32_t *)(0x50000000 | 0x50c);

  if (!gpio_is_ready_dt(&led)) {
    return 0;
  }

  ret = gpio_pin_configure_dt(&led, GPIO_OUTPUT_ACTIVE);
  if (ret < 0) {
    return 0;
  }

  while (1) {
    *gpio_set = (0x1 << 1);
    *gpio_clr = (0x1 << 1);
  }

  return 0;
}

N.B. volatile here is critical: this worked correctly and was much faster:

Oscilloscope trace: faster

The timing here is consistent with Cortex M0: load and store are two cycles, branch is three because three stage pipeline. Adding some nops with __asm__("nop") can pad this nicely to give 1MHz-ish output with 8 cycles high and low.

Noteworthy things:

I expect for less trivial use cases this is probably worthwhile e.g. making USB devices and such.