nRF52840 Dongle - PlatformIO + Zephyr + SSD1306 OLED

With NordicSemi adopting Zephyr with the nRF Connect SDK and phasing out the overly complicated nRF52 SDK, I’ve moved to using PlatformIO+Zephyr as my core nRF52840 dev environment. I’m also using the Arduino IDE & CircuitPython (with the Adafruit Bootloader) for quick experimentation. This approach has panned out well so far.

I have a SSD1306 OLED running the nRF52840 dongle using PIO+Arduino+DFU serial, and the Adafruit SSD1306 library. The next challenge is to run the SSD1306 under PIO+Zephyr.

Currently, I’m on a learning curve with respect to the Zephyr Platform and would appreciate any assistance or direction with the following.

Does anyone have pointers to or an example of how to add support for SSD1306 OLED displays in PIO-Zephyr for the nRF52840 Dongle? Thanks.

The critical things to know are

Thanks. I’ll follow up on the links provided. Much appreciated!

I’ve built the Zephyr SSD1306 example on both nRF52840 dongle and a Black Pill (stm32f411ce), after adding the I2C overlays for each of the boards. Since I haven’t yet got my nRF52840 dongle setup for a breadboard and J-Link debug, I debugged the sample application on the stm32f411ce.

This is a rather lengthy post but I wanted to share more details and solicit feedback.

Here are my environment and Zephyr settings plus the serial debug output from the stm32f411ce.

Environment: Mac OS Mojave 10.14.6
PlatformIO Core: 5.0.3
Platform: ststm32
Framework: Zephyr
Target: stm32f411ce - Black Pill
Display: SSD1306 OLED
Debug: ST-Link V2

SSD1306 Sample: Stock example from “/.platformio/packages/framework-zephyr/samples/drivers/display”


default_envs = stm32

platform = ststm32
framework = zephyr
board = blackpill_f411ce
build_flags = -DSHIELD=ssd1306_128x64
upload_protocol = stlink
debug_tool = stlink
debug_init_break = tbreak main
monitor_speed = 115200

Zephyr CMakeLists.txt (standard boilerplate):

cmake_minimum_required(VERSION 3.13.1)
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)

FILE(GLOB app_sources ../src/*.c*)
target_sources(app PRIVATE ${app_sources})

Zephyr prj.conf:


Zephyr Overlay: blackpill_f411ce.overlay

&arduino_i2c {
	status = "okay";
	ssd1306@3c {
		compatible = "solomon,ssd1306fb";
		reg = <0x3c>;
		label = "SSD1306";
		width = <128>;
		height = <64>;
		segment-offset = <0>;
		page-offset = <0>;
		display-offset = <0>;
		multiplex-ratio = <63>;
		prechargep = <0x22>;

zephyr.dts (from .pio/build/stm32/zephyr): i2c1 entry

	i2c1: arduino_i2c: i2c@40005400 {
		compatible = "st,stm32-i2c-v1";
		clock-frequency = < 0x61a80 >;
		#address-cells = < 0x1 >;
		#size-cells = < 0x0 >;
		reg = < 0x40005400 0x400 >;
		clocks = < &rcc 0x2 0x200000 >;
		interrupts = < 0x1f 0x0 >, < 0x20 0x0 >;
		interrupt-names = "event", "error";
		status = "okay";
		label = "I2C_1";
		ssd1306@3c {
			compatible = "solomon,ssd1306fb";
			reg = < 0x3c >;
			label = "SSD1306";
			width = < 0x80 >;
			height = < 0x40 >;
			segment-offset = < 0x0 >;
			page-offset = < 0x0 >;
			display-offset = < 0x0 >;
			multiplex-ratio = < 0x3f >;
			prechargep = < 0x22 >;

main.c code snippet:

#if DT_NODE_HAS_STATUS(DT_INST(0, solomon_ssd1306fb), okay)
#define DISPLAY_DEV_NAME DT_LABEL(DT_INST(0, solomon_ssd1306fb))

void main(void)

printk(“Target: STM32f411ce - Black Pill\n”);

LOG_INF("Display sample for %s", DISPLAY_DEV_NAME);
display_dev = device_get_binding(DISPLAY_DEV_NAME);
if (display_dev == NULL) {
	LOG_ERR("Device %s not found. Aborting sample.",
display_get_capabilities(display_dev, &capabilities);


stm32f411ce target serial output:

12:54:05.135 -> *** Booting Zephyr OS build zephyr-v20400 ***
12:54:05.135 -> Target: STM32f411ce - Black Pill
12:54:05.135 -> [00:00:00.007,000] e[0m sample: Display sample for SSD1306e[0m
12:54:05.135 -> [00:00:00.007,000] e[1;31m sample: Device SSD1306 not found. Aborting sample.e[0m

Under debug, device_get_binding() returns NULL, and therefore never invokes display_get_capabilities().
The SSD1306 overlay was automatically added the generated zephyr.dts.

Unlike the nrf52840_dongle, I noticed that arduino_i2c entry for the stm32f411ce doesn’t
have the SCL/SDA pin settings.


	i2c0: arduino_i2c: i2c@40003000 {
		#address-cells = < 0x1 >;
		#size-cells = < 0x0 >;
		reg = < 0x40003000 0x1000 >;
		clock-frequency = < 0x186a0 >;
		interrupts = < 0x3 0x1 >;
		status = "okay";
		label = "I2C_0";
		compatible = "nordic,nrf-twi";
		sda-pin = < 0x1f >;
		scl-pin = < 0x1d >;
		ssd1306@3c {
			compatible = "solomon,ssd1306fb";
			reg = < 0x3c >;
			label = "SSD1306";
			width = < 0x80 >;
			height = < 0x40 >;
			segment-offset = < 0x0 >;
			page-offset = < 0x0 >;
			display-offset = < 0x0 >;
			multiplex-ratio = < 0x3f >;
			prechargep = < 0x22 >;

I use the P0.29 (0x1d) & P0.31(0x1f) pins on the nrf52840_dongle for the SSD1306’s SCL/SCDA
and expected similar entries for the stm32f411ce devicetree.

Pins P0.29/P.31 on the nrf52840_dongle and pins PB6 (SCL1) & PB7 (SDA1) on the stm32f411ce
work fine with the SSD1306 OLED when using the Arduino framework under PlatformIO.
I suspect that there may be an issue with the Zephyr devicetree configuration, that is the cause.

What am I missing or doing incorrectly to configure the Zephyr display sample?

Do I need to include the “solomon,ssd1306fb.yaml” explicitly in PlatformIO Zephyr folder?

If anyone has managed to get the SSD1306 display running successfully with the Zephyr framework under PlatformIO, please share info.

Any suggestions would be greatly welcomed. Thanks!

Is the device’s I2C address 0x3C or 0x3D though? What does the Arduino I2C scanner sketch say when it’s being run on the device?

Thanks for the quick response.

I just ran the Arduino I2C scanner. The SSD1306 OLED is on address 0x3c, as per Arduino serial output below.

16:14:06.304 -> Scanning…
16:14:06.304 -> I2C device found at address 0x3C !
16:14:06.340 -> done
16:14:06.375 ->

Btw - I would have expected the stm32f411ce devicetree overlay, like the nrf52840_dongle, to have the entries for SCL/SDA, unless it defaults to the hardware SCL/SDA.

Does anyone have any further suggestions what I may be doing wrong in trying to get the SSD1306 display to work with nRF52840 dongle or the stm32f411ce under Zephyr/PlatformIO?

I have reviewed the Zephyr docs and SSD1306 example again but didn’t find anything different. A misconfiguration of the I2C may still be the culprit.

I have a Particle Xenon with a J-Link due shortly, so will be able to debug further.

Can you upload your full current project to e.g. Github for reproduction? I have that OLED and a STM32F1 and also F4 chippies here so I can try. These also have a debugger built-in so painless and fast debugging is possible.

Thanks maxgerhardt.

You can clone the PlatformIO project for stm32f411ce (blackball_f411ce) from

I’ve added a few notes from my earlier attempts & debug session.

I’ve received my Particle Xenon dev kit today and will try flashing the Zephyr OLED display on it and test with the OLED. The Xenon has the 10 pin J-Link connector. It saves me time adding a connector to the nRF52840 Dongle, so debugging should be easier with the Segger J-Link Edu Mini.

Thank you for the follow up. It’s much appreciated.

Hi maxgerhardt.

I have resolved my issues with getting the SSD1306 OLED to work with STM32 blackpill_f411ce. It turned out to be a silly error. I should have checked the Zephyr docs for the blackpill_f411ce peripheral mapping.

The code I uploaded to my Github for you to review works fine, I had used the wrong mapping for I2C1 SCL/SDA. The SSD1306 was not connected to the correct pins! With the Arduino framework and Adafruit CircuitPython, the peripheral mapping of SCL1 (PB6) & SDA (PB7) worked fine but not with Zephyr. I should have used the mapping of SCL1 (PB8) & SDA (PB9).

I have also tested the SSD1306 with the Particle Xenon board. So, once I get around to a bit soldering, the code should also work with the nRF52840 Dongle. The debugging with the Segger J-Link Edu Mini and ST-Link is a breeze and invaluable! Highly recommend both.

There were a couple of minor changes I had to make to the stock sample so that it worked correctly for white on black display. My SSD1306 OLED is a Yellow (top 128x16 or 2 rows) / Blue (128 x 48 or 6 rows).

The project with the updated readme is on Github at

I have also built the character frame buffer example, as well as one with custom fonts, for both nRF52840 and STM32 devices. I’ll upload them to my Github in the next few days. It may assist others.

A quick question, Is there a way in PlatformIO to conditionally select a {board}.overlay depending on the board? At the moment, I have to separate PlatformIO projects for Particle Xenon & blackpill_f411ce that include their respective Zephyr {board}.overlay file. It would be good to have a single project where I can conditionally include the {board}.overlay.

Thank you for your feedback.

1 Like