Vorstellung des nRF52840 ProMicro/SuperMini Board (nice!nano kompatibel)

Die Firma Nordic Semiconductor baut System On Chip (SoCs) als Mikrocontroller, die sich durch ihren geringen Stromverbrauch und günstigen Preis auszeichnen.

Aufmerksam bin ich auf den nRF52840 geworden, als ich ihn in zahlreichen Meshtastic-Geräten als Prozessor vorgefunden habe. Besonders in jenen, die einen niedrigen Stromverbrauch als Fokus hatten (solche mit ePaper-Display).

Nach meinen Deep-Sleep-Experimenten mit dem ESP32 habe ich herausgefunden, dass die ESP32-Boards, die ich bisher eingesetzt habe, nicht so richtig auf Low Power zu trimmen sind. Darum war ich neugierig, wie sich hier nRF-Boards schlagen. Vielleicht sind diese ja eher zu bändigen, um lange Batterielaufzeiten zu bekommen, so mein Gedanke.

Die SoCs von Nordic sind 3.3 Volt Mikrocontroller, die im Vergleich zum ESP32 nicht ganz so viel Rechenpower haben, dafür im Deep Sleep Modus noch ein Quäntchen mehr Strom sparen können. Sie kommen deswegen häufig in Wearables, Smart Watches, GPS-Empfänger oder LoRa / Meshtastic Geräte zum Einsatz.

Im Gegensatz zum ESP32 haben die nRF-Mikrocontroller allerdings keine vollständig implementierte WLAN-Funktionalität. Außerdem sind die nRF-Chips auf Low-Power-Funkprotokolle spezialisiert. Die Funkprotokolle ANT, Thread oder Zigbee sind oft schon an Bord verbaut und der nRF5340 kann auch LE Audio.

nRF-Chips sind zusammen mit ESP32 und Raspberry Pi Pico derzeit (2025) im Mikrocontroller-Segment die erste Wahl in Sachen Preis-/Leistungsverhältnis und Library-Unterstützung für Maker.

Als ich auf AliExpress dann nRF52840-Boards für ein paar Euro gesehen habe, konnte ich nicht länger widerstehen und habe ich mir ein paar zum Experimentieren bestellt.

Mein Wahl fiel auf das nRF52840 "ProMicro" Board, wie es auch von Tenstar Robot auf AliExpress angeboten wird. Dieses ist als Keyboard-Controller im Nice! Nano-Projekt populär geworden. Das wollen wir uns heute näher anschauen.

Das nRF52840 SoC

nRF-Mikrocontroller gibt es seit 2012. Die nRF51-Serie begründete die nRF5-Serie. Die nRF5er-Serie ist auch heute noch aktuell. Besonders interessant für Maker ist das Flaggschiff nRF52840, für das es eine gute Library-Unterstützung im Arduino-Framework gibt.

Hier die Features des nRF52840-ProMicro-Baords (weitere nRF Varianten siehe Nordic Semiconductor nRF-Familien / Varianten):

Vergleich: nRF52840 vs. ESP32-WROOM32

Was der ESP32 (classic, WROOM32) hat, was dem nRF52840 fehlt:

Was der nRF52840 hat, was dem ESP32 fehlt:

Wenn man WLAN benötigt, dann nimmt man den ESP32, oder auch den neuen Raspberry Pi Pico W. Der ESP32 bietet auch mehr Rechenpower als der nRF52840. Wenn man allerdings besonders stromsparend unterwegs sein will für kleine Geräte wie Wearables, Smartwatches, IoT- und LoRa-Sensoren, dann bietet sich ein nRF an. Auch weil ein nRF gleich die dort üblichen Funkstandards wie BLE/Zigbee/Thread unterstützt.

Ist der Stromverbrauch nicht das Wichtigste, dann kann man auch einen der neuen ESP32-Varianten nehmen, die ebenfalls Bluetooth 5.x, ZigBee, Thread und Matter unterstützen.

Oder ganz kurz zusammengefasst als Faustformel:

Internet und Power → ESP32
Batterie und Funk   → nRF52840

Für ESP32 im Arduino-Framework auf PlatformIO gibt es einen Top Support, eine riesige Community, und fast alles läuft "out of the box". Ideal für schnelle Maker-Projekte, Internet-of-Things mit Wi-Fi, Webservern, MQTT.

Pinout des nRF52840 ProMicro-Boards


Download als PDF

Anmerkungen: Die Pins für SPI, I2C, UART und ADC sind willkürlich so von mir festgelegt und orientieren sich höchstens an anderen Pin-Definitionen, die bereits existieren. Änderungen vorbehalten.

Unterstützung von nRF52840 im Arduino-Framework auf PlatformIO

Die Unterstützung von nRF52840 im Arduino-Framework auf PlatformIO ist okay, aber begrenzt. BLE/USB funktioniert zwar mit Adafruit-Libs, aber es ist nicht alles abgedeckt. Für ernsthafte IoT-Protokolle (Thread, Zigbee, Matter) sollte man eher zum nRF Connect SDK (Zephyr) wechseln.

Wie gesagt, wird das nRF52840 "ProMicro" Board von Tenstar eher als Keyboard-Controller verkauft, wie es im Nice! Nano-Projekt verwendet wird.

Ich würde gerne meine gewohnten Ardunio-Programmier-Befehle und auch weiterhin Visual Studio Code mit Platform IO als Entwicklungsumgebung behalten. Wie sich herausstellen soll, ist das gar nicht so einfach.

In der PlatformIO ist nämlich kein Board "NiceNano" oder "ProMicro" für den nRF52840 zu finden.

Es gibt wohl zwar das Projekt Adafruit_nRF52_Arduino, aber das scheint nicht ganz kompatibel zu sein.

Ich habe einige Board-Definitionen ausprobiert: Keine Board-Definition war perfekt. Entweder ging das eine, oder das andere nicht. Zum Schluss bin ich bei folgender Konfiguration für meine platformio.ini herausgekommen:
[env:adafruit_feather_nrf52840] platform = nordicnrf52 board = adafruit_feather_nrf52840 framework = arduino monitor_speed = 115200 lib_deps = https://github.com/adafruit/Adafruit_nRF52_Arduino
Damit ging das komfortable Hochladen mit dem Bootloader im Auslieferungszustand und auch das Serial-Interface war vorhanden, was ja beim Debuggen am Anfang sehr wichtig ist, weil man sonst blind ist.

Allerdings weigerte sich das System, meine Befehle mittels pinMode() und digitalWrite() richtig auszuführen. Es tat sich einfach nichts bei meiner RGB-LED, die ich in einer Testschaltung angeschlossen habe. Irgendwas war noch falsch. Erst hatte ich meine Pin-Definitionen in Verdacht, aber später kam ich darauf, dass die pinMode(), digitalWrite() einfach nicht richtig programmiert waren und irgendwie "daneben gingen".

Und bist du nicht willig, so gebrauche ich die LowLevel-API

Ein erster Versuch, direkt die nRF-Register direkt zu manipulieren, um die GPIO-Pins anzusteuern, brachte dann Erfolg und so hielt ich mich im weiteren Verlauf an die Dokumentation von Nordic Semiconductor, um meine eigenen Befehle zu programmieren, die ich digiMode(), digiWrite() und digiRead() nannte.

Unter Verwendung von digiMode(), digiWrite() und digiRead() statt pinMode() und digitalWrite() und digitalRead() funktionierte nach ein bisschen Nordic-Dokumentation lesen und testen dann auch meine Schaltung wie gewünscht: Anzeige (Output) einer von drei Farben auf der RGB-LED, weiterschalten mit einem Taster (Input).

Der nRF52840 hat zwei 32 bit breite Register, um den High/Low-Status von bis zu 64 GPIOs zu verwalten. Die 64 Bits speichern den Zustand der Pins, 1 für High und 0 für Low.

Die erste Bank wird P0 genannt und ist für GPIO-Pin 0 bis 31 zuständig. Und die zweite Bank wird P1 genannt und ist für GPIO-Pin 32 bis 63 zuständig.

Außerdem gibt es 64 32-bit-Register, über den der Modus für den entsprechenden Pin eingestellt werden kann. Diese sind über NRF_Px->PIN_CNF[0...31] erreichbar. Zur Konfiguration eines Pins wird ein 32-bit-Wort übergeben.

NRF_Px->PIN_CNF[0...31]=mode ist also der Befehl, um den Modus (Input, Output, Pullup, Pulldown) zu konfigurieren.
NRF_Px->OUTSET(pinbit) setzt ein oder mehrere Pin-Bits auf HIGH.
NRF_Px->OUTCLR(pinbit) setzt ein oder mehrere Pin-Bits auf LOW.

In Code gegossen für die Funktionen pinMode() und digitalWrite() und digitalRead() sieht das dann so aus:
///////////////////////////////////////////////////////////////////////////////////////////////////////// // Source Code (C) 2025 Oliver Kuhlemann, cool-web.de (aka Doc Cool) // see https://cool-web.de/nrf/nrf52840-promicro-nicenano-board-platformio-vorstellung.htm for more info // Please provide Source and Link, if you use this code ///////////////////////////////////////////////////////////////////////////////////////////////////////// void pinMode(int pin, uint32_t pin_mode) { uint32_t mode=0; if (pin_mode == OUTPUT ) mode = 3; else if (pin_mode == INPUT ) mode = 0; else if (pin_mode == INPUT_PULLUP ) mode = 12; else if (pin_mode == INPUT_PULLDOWN) mode = 4; if (pin <= 31) { // P0 NRF_P0->PIN_CNF[pin]=mode; } else { // P1 NRF_P1->PIN_CNF[pin-32]=mode; } } void digitalWrite(int pin_out, int pin_state) { if (pin_out <= 31) { // P0 if (pin_state) { // HIGH NRF_P0->OUTSET |= (1 << pin_out); } else { // LOW NRF_P0->OUTCLR = (1 << pin_out); } } else { // P1 if (pin_state) { // HIGH NRF_P1->OUTSET |= (1 << (pin_out-32)); } else { // LOW NRF_P1->OUTCLR = (1 << (pin_out-32)); } } } int digitalRead(int pin_in) { unsigned long state = 0; if (pin_in <= 31) { // P0 state = NRF_P0->IN ^ (1 << pin_in); } else { // P1 state = NRF_P1->IN ^ (1 << (pin_in-32)); } if (state == 0) return 1; // XOR Pin-Bit, wenn 1, dann "1 XOR 1" = 0 return 0; }
Vornweg habe ich noch meine Pin-Definitionen geschrieben und das ganze als nanonice.h gespeichert, um das Programmieren komfortabler zu machen. Die Umrechnung ist aber eigentlich ganz einfach, wenn man weiß, dass es um zwei 32-bit breite Register geht: Man teilt die Pin-Beschriftung auf dem Silk-Screen auf dem Board in zwei Teile: erster Ziffer und zweite/dritte Ziffer, so wird z. B. 020 zu 0.20 und 115 zu 1.15. Alles, was dann vorne eine 0 hat, bleibt so. Alles was vorne eine 1 hat, wird um 32 erhöht, also 020 → 0.20 → 20 bzw. 115 → 1.15 → 32+15 → 47, um an den Hardware-Pin zu kommen. Und wenn man ein P davor schreibt (P0.20 bzw. P1.15) hat man die nRF-Notation.

Die Pin-Definitionen sind nicht endgültig. Eventuell ändere und erweitere ich die noch. Zum Beispiel, wenn ich herausfinde, über welche Pins man die internen LEDs anspricht.
///////////////////////////////////////////////////////////////////////////////////////////////////////// // Source Code (C) 2025 Oliver Kuhlemann, cool-web.de (aka Doc Cool) // see https://cool-web.de/nrf/nrf52840-promicro-nicenano-board-platformio-vorstellung.htm for more info // Please provide Source and Link, if you use this code ///////////////////////////////////////////////////////////////////////////////////////////////////////// #define PIN_002 2 // P0.02 //#define PIN_003 3 // P0.03 //#define PIN_004 4 // P0.04 //#define PIN_005 5 // P0.05 #define PIN_006 6 // P0.06 #define PIN_UART1_TX 6 // P0.06 //#define PIN_007 7 // P0.07 #define PIN_008 8 // P0.08 #define PIN_UART1_RX 8 // P0.08 #define PIN_009 9 // P0.09 #define PIN_010 10 // P0.10 #define PIN_011 11 // P0.11 //#define PIN_012 12 // P0.12 #define PIN_013 13 // P0.13 #define PIN_VCC_OFF 13 // P0.13 // auf LOW setzen, um 3.3V-Rail abzuschalten //#define PIN_014 14 // P0.14 //#define PIN_015 15 // P0.15 LED? //#define PIN_016 16 // P0.16 #define PIN_017 17 // P0.17 //#define PIN_018 18 // P0.18 //#define PIN_019 19 // P0.19 #define PIN_020 20 // P0.20 //#define PIN_021 21 // P0.21 #define PIN_022 22 // P0.22 //#define PIN_023 23 // P0.23 #define PIN_024 24 // P0.24 //#define PIN_025 25 // P0.25 //#define PIN_026 26 // P0.26 //#define PIN_027 27 // P0.27 //#define PIN_028 28 // P0.28 #define PIN_029 29 // P0.29 //#define PIN_030 30 // P0.30 #define PIN_031 31 // P0.31 #define PIN_100 32 // P1.00 #define PIN_101 33 // P1.01 #define PIN_102 34 // P1.02 //#define PIN_103 35 // P1.03 #define PIN_104 36 // P1.04 (SDA) (SPECIFY &Wire) #define PIN_I2C_SDA 36 // P1.04 (SDA) (SPECIFY &Wire) //#define PIN_105 37 // P1.05 #define PIN_106 38 // P1.06 (SCL) (SPECIFY &Wire) #define PIN_I2C_SCL 38 // P1.06 (SCL) (SPECIFY &Wire) #define PIN_107 39 // P1.07 //#define PIN_108 40 // P1.08 //#define PIN_109 41 // P1.09 //#define PIN_110 42 // P1.10 #define PIN_111 43 // P1.11 (SCK) #define PIN_SPI_SCK 43 // P1.11 (SCK) //#define PIN_112 44 // P1.12 #define PIN_113 45 // P1.13 (MOSI) #define PIN_SPI_MOSI 45 // P1.13 (MOSI) //#define PIN_114 46 // P1.14 #define PIN_115 47 // P1.15 (MISO) #define PIN_SPI_MISO 47 // P1.15 (MISO) #include <Arduino.h>
Natürlich war es ein bisschen doof, das ich nicht die gewohnten Befehle verwenden und somit fertigen Arduino-Code übernehmen konnte. Nach dem Motto "Versuch macht kluch" habe ich dann einfach meine Routinen in pinMode() und digitalWrite() und digitalRead() umbenannt und Kompatibilität bei den Parametern und der Funktion eingehalten. Und zu meiner Überraschung meckerte der Compiler nicht an, dass es diese Funktionen schon gäbe, was ich eigentlich erwartet hätte. Nach dem Motto "Wenn es läuft, dann war es Absicht" wollte ich dem "geschenktem Gaul dann doch nicht tiefer ins Maul" schauen - wer weiß, vielleicht hatte der Mundgeruch ;)

Weitere Aussichten

Ob jetzt mit diesem Konstrukt I2C und SPI und alles andere Mögliche funktionieren wird - ich bin mir (noch) nicht sicher. Da muss ich erst tiefer forschen und wahrscheinlich noch das eine oder andere programmieren.

Auf jeden Fall wundert mich nicht, dass ich so gut wie nichts zur Programmnierung des nRF52840 ProMicro Boards im Internet gefunden habe. Dafür ist das wohl eher nicht vorgesehen, sondern eher dafür, um darauf dieses Nice-Nano-Keyboard-Projekt laufen zu lassen.

Wie dem auch sei: Der Stromverbrauch scheint beim nRF52840 ProMicro Board sehr niedrig auszufallen, auch schon so ohne Deep Sleep. Das heißt, dass es auch mit der bisher möglich gemachten Funktionalität ein brauchbarer Kandidat für stromsparende Projekte ist.

Hier werde ich als nächsten mal einen Stromverbrauchsvergleich zwischen den einzelnen Mikrocontrollern, die ich sonst so habe, anstellen und schauen, wo sich dieses Board einordnet. Wenn es sich lohnt, werde ich weiter zu Deep Sleep forschen und dann hätte man ein tolles Board für langanhaltenden Batteriebetrieb - freundlicherweise ich ein LiIon-Ladeschaltung ja schon auf dem Board vorhanden.

Test-Aufbau

Als Test-Schaltung verwende ich einen einfachen Aufbau mit einer RGB-LED und einem Taster. Der Taster, damit ich auch einen Input habe, den ich testen kann.



Bilder anklicken, um sie größer darzustellen. Die RGB-LED ist common Ground und mit einem 220 Ω Widerstand an GND angeschlossen. Die 3 Anschlüsse für die Farben gehen auf die Pins 020, 022 und 024. Pin 100 geht zum Taster, der bei Betätigung mit GND kurzgeschlossen wird. Deshalb wird für diesen Pin der interne Pullup-Widerstand des nrf52840 aktiviert.

Beim Start soll das Programm die erste Farbe der LED anschalten, indem Pin 020 auf High gezogen wird. Nach Tastendruck sollen die Farben weitergeschaltet werden und wenn sie durch sind, wieder von vorne angefangen werden.

Der Sourcecode dazu sieht so aus:
#include "nicenano.h" // vor Arduino.h wegen SPI-Pins Arduino Mega #include <Arduino.h> int pin_in=PIN_100; int pin_out_1=PIN_020; int pin_out_2=PIN_022; int pin_out_3=PIN_024; void setup() { pinMode(pin_in, INPUT_PULLUP); pinMode(pin_out_1, OUTPUT); pinMode(pin_out_2, OUTPUT); pinMode(pin_out_3, OUTPUT); // NRF_P0->PIN_CNF[20] = 0x00000003; // Output // NRF_P0->PIN_CNF[22] = 0x00000003; // Output // NRF_P0->PIN_CNF[24] = 0x00000003; // Output // NRF_P1->PIN_CNF[0] = 0x0000000C; // Input mit Pullup // digiMode(pin_in, INPUT_PULLUP); // digiMode(pin_out_1, OUTPUT); // digiMode(pin_out_2, OUTPUT); // digiMode(pin_out_3, OUTPUT); Serial.begin(115200); Serial.println("Test gestartet!"); } void loop() { int led_an=1; while (1) { Serial.print("LED "); Serial.print(led_an); Serial.println(" an"); if (led_an == 1) { digitalWrite(pin_out_1, HIGH); digitalWrite(pin_out_2, LOW); digitalWrite(pin_out_3, LOW); } if (led_an == 2) { digitalWrite(pin_out_1, LOW); digitalWrite(pin_out_2, HIGH); digitalWrite(pin_out_3, LOW); } if (led_an == 3) { digitalWrite(pin_out_1, LOW); digitalWrite(pin_out_2, LOW); digitalWrite(pin_out_3, HIGH); } // while (1) { // rudimentärer Blink-Test nRF // NRF_P0->OUTSET |= (1 << 20); // Pin HIGH // delay(200); // NRF_P0->OUTCLR = (1 << 20); // Pin LOW // delay(200); // Serial.print("."); // } // while (1) { // rudimentärer Blink-Test nRF, eigene Routinen // digiWrite(pin_out_2, HIGH); // delay(200); // digiWrite(pin_out_2, LOW); // delay(200); // Serial.print("."); // } while (digitalRead(pin_in) == HIGH) { // warten bis Taster gedrückt delay (10); } Serial.println("Taster gedrückt"); while (digitalRead(pin_in) == LOW) { // warten bis Taster wieder losgelassen delay (10); } Serial.println("Taster losgelassen"); led_an++; if (led_an > 3) led_an = 1; } }

Video

Hier noch ein Demonstrationsvideo, um zu zeigen, dass der Code funktioniert und dem ich noch so das eine oder andere Wort rund um das Board verliere:





Quellen, Literaturverweise und weiterführende Links