Libraries und Software für das Elecrow LoRaWAN LR1262 Dev.-Board mit Raspi-Pico 2040
In diesem Artikel hatte ich ja schon das LoRaWAN LR1262 Development Board mit RP2040 und 1.8 “ LCD für LoRa-Anwendungen von der Hardwareseite her vorgestellt.Heute soll es darum gehen, das Board softwaretechnisch auszustatten. Fangen wir mit der Auswahl der Libraries an. Die müssen zum einen zu den Hardware-Komponenten passen und zum anderen auf dem verbauten RP2040 Raspberry Pi Pico Chip kompatibel sein.
Die Integration richtig sich natürlich wieder nach der verwendeten Entwicklungsumgebung (auch IDE genannt). Ich setze wieder Visual Studio Code mit Platform IO ein, weil mir diese am besten gefällt und diese mich am besten unterstützt. Siehe dazu auch mein Artikel Taugt Visual Studio Code mit Platform IO als Ersatz für die die Arduino IDE?.
Umgebung in platform.io definieren
Zuerst muss unsere IDE mal wissen, mit welchem Board sie es zu tun hat, damit sie auch für die CPU passende Maschinensprachebefehle in die Firmware kompiliert. Das geschieht über die platformio.ini, die normalerweise so aussehen würde:Core: mbed
[env:pico]
platform = raspberrypi
board = pico
framework = arduino
upload_protocol = mbed
upload_port = G:
monitor_port = COM15
monitor_speed = 115200
monitor_dtr = 1
monitor_rts = 0upload_protocol = mbed bewirkt, dass die Firmware auf das dann auftauchende Laufwerk kopiert wird, wenn man links unter dem USB-Port BOOT hält und dann kurz RESET über dem USB-Port drückt. Und leider funktioniert auch das automatische Kopieren nicht richtig. Die Firmware wollte sich damit nicht bei mir aktualisieren. Ich musste immer per Hand kopieren, aber dafür habe ich mir ein kleines _upload_firmware.cmd geschrieben, das ich dann nur noch kurz anklicken muss:
copy .pio\build\pico\firmware.uf2 g:\Da die Schalter für BOOT und RESET auf dem Board wirklich winzig sind und ich bei dem Spagat BOOT zu halten, während man kurz RESET drückt, ständig von den Knöpfchen abrutsche - was extrem nervt - habe ich nach einer Lösung dafür gesucht. Und es gibt eine: Man kann auch nur den RESET-Taster benutzen: Erst einmal ein wenig länger drücken, vielleicht eine halbe Sekunde und dann ganz schnell danach noch einmal kurz - et voila: ebenfalls Boot-Modus. Klappt eigentlich zuverlässig nach ein bisschen Übung und man erkennt den Boot-Modus a) an dem USB-Einstöpsel-Sound von Windows und b) und einem kurzen Flackern der blauen LED auf dem Board.
Core: earlephilhower und maxgerhardt
Ich habe oben "normalerweise" geschrieben, weil ich feststellen musste, dass es mit der obigen Konfiguration nicht wirklich funktioniert und ich auf Board-Features verzichten müsste. Spätestens, wenn ich den I2C-Bus ansprechen will, was ich schon allein für die LED-Ampeln muss, scheitert der mbed-Core.Darum sieht meine platformio.ini nun so aus:
[env:pico]
;platform = raspberrypi
platform = https://github.com/maxgerhardt/platform-raspberrypi.git
board = pico
board_build.core = earlephilhower
framework = arduino
;upload_protocol = picotool
upload_port = COM13
;upload_speed = 230400
monitor_port = COM13
monitor_speed = 115200
monitor_dtr = 1
monitor_rts = 0Damit klappt es auch mit dem I2C-Bus und auch der Firmware-Upload gelingt (ab dem zweiten mal) ohne Boot- und Reset-Knöpgchen-Gefummel.
Die Hardware-Komponenten und die dazu passenden Libraries
Okay, wir brauchen Libraries, die auch für den RP2040 bzw. Pico geeignet sind. Um nach passendem zu suchen können wir im Library Manager (in PIO Home Libraries anklicken) unseren Suchterm durch platform:rp2040 ergänzen, also zum Beispiel nach ST7735 platform:rp2040 suchen. Zur Sicherheit gucken wir bei den einzelnen Libs noch nach, ob sie auch den Tag "Raspberry Pi RP2040" in den Tags stehen haben.Der Buzzer
Fangen wir mit etwas ganz einfachem an, für das wir keine Library brauchen: dem Buzzer. Auf der Platine ist die passive Ausführung verbaut. Das heißt, wir müssen das Signal am Buzzer-Pin mit der gewünschten Frequenz oszillieren lassen. Das erledigt aber der Befehl tone() zuverlässig für uns.Warum mit dem Buzzer anfangen? Nun, er bietet die wenigsten Fehlerquellen neben der eingebauten LED. So können wir schnell erkennen, ob das Hochladen der Firmware geklappt haben und hören zumindest etwas. Beim TFT könnte es schon schwerer werden, ihm ein Bild zu entlocken.
Hier der Source-Code für eine kleine Melodie, die wir beim Einschalten abspielen wollen:
#include
...
#define PIN_BUZZER 28
...
void playOnMelody() {
tone(PIN_BUZZER, 440); delay (200);
tone(PIN_BUZZER, 440); delay (200);
tone(PIN_BUZZER, 880); delay (600);
noTone(PIN_BUZZER);
}
void setup() {
playOnMelody();
} Weitere Code- und Anwendungsbeispiele und Melodien für den Buzzer findet ihr in meinen Artikeln
- Mit dem Raspberry Pi einen passiven Lautsprecher ansprechen und zur Sirene machen
- Mit dem Raspberry Pi Musik machen und "Alle meine Entchen" spielen
- Mit dem Raspberry Pi morsen / Tieferer Einstieg in Python
- Das Spiel Senso bzw. Simon Says auf dem Raspberry Pi
- Vom Breadboard-Prototyp zur Lochraster-Platine
- Countdown-Wecker mit dem STM32 für Schlafzeit-Experimente
- Spaß mit Melodien (Mein Start- und Ende- GCode für den Anycubic i3 Mega)
Das ST7735 128x160px Farb-Display
Ahja, der gute alte Bekannte, der ST7735 Controller für TFT-Displays. Das 128x160 Pixel Farb-TFT-Display war wegen seines günstigen Preises in China sehr beliebt.Wer die Hardware näher kennenlernen will: Mehr Infos zu ST7735-TFT-Displays findet ihr in meinen Artikeln:
- 128x160 Pixel 1.8" TFT ST7735 Farb-Display am Nano betreiben
- ESP32-2432S028 mit 2.8" Touchscreen (Cheap Yellow Display) Programmierung des Displays mit der TFT_eSPI-Library
Man fing dann damit an, in den Treibern angebbar zu machen, welche Display-Variante das wohl war und nahm dazu die Farbe des kleinen überstehenden Tabs an der Displayschutzfolie als Indikator. Bei meinem Board ist der Tab dunkelgrün.
Der ST7735 ist nicht sonderlich schwierig über SPI anzusprechen. Eigentlich könnte man einen eigenen Treiber dafür schreiben. Das ist nicht das Problem. Das Problem sind die "höheren" Funktionen wie die Darstellung von gut aussehender Schrift auf dem Display oder das Anzeigen von Grafiken. Das zu implementieren bedeutet jede Menge Arbeit. Und diese haben adafruit, OliKraus mit u8g2 oder Bodmer mit TFT_eSPI bereits schon erledigt. Warum also das Rad neu erfinden.
Ich habe in meinen CYD-Projekten mit TouchScreen schon positive Erfahrungen mit der TFT_eSPI-Library gesammelt. Vor allen die schönen Schriften und die LVGL-Kompatibilität haben mich überzeugt.
Also habe ich mich für diese Library entschieden und mache sie in der platformio.ini bekannt, um sie verwenden zu können:
[env:pico]
...
lib_deps =
bodmer/TFT_eSPI@^2.5.43Meine TFT_eSPI-Config für das Display sehen so aus:
// SPI: TFT
#define ST7735_DRIVER
#define TFT_WIDTH 128
#define TFT_HEIGHT 160
#define TFT_MOSI 19 // Automatically assigned with ESP8266 if not defined
#define TFT_SCLK 18 // Automatically assigned with ESP8266 if not defined
#define TFT_CS 17 // Chip select control pin D8
#define TFT_DC 16 // Data Command control pin
#define TFT_RST 22 // Reset pin (could connect to NodeMCU RST, see next line)
#define TFT_BL 23 // LED back-light (only for ST7789 with backlight control pin)
// das hier bringt alles nicht wirklich was in Sachen Versatz
// #define ST7735_GREENTAB3 // weitere Anpassungen in ST7735_Init.h#L162
// auch die Farbfehler bekomme ich selbst besser mit Werten ins ST7735 Register schreiben hin
// #define TFT_RGB_ORDER TFT_RGB // Colour order Red-Green-Blue
// #define TFT_RGB_ORDER TFT_BGR // Colour order Blue-Green-Red
// #define TFT_INVERSION_ON
// #define TFT_INVERSION_OFF
// auf den Versatz muss ich dann halt selbst acht geben, dafür ein paar defines
// der beschreibbare Bildschirm ist damit minimal kleiner
#define TFT_XOFF 2
#define TFT_XMAX 127
#define TFT_YOFF 1
#define TFT_YMAX 159
//ST7735-TFT init. und auf Variable mappen
TFT_eSPI tft = TFT_eSPI();Wie ihr meinen auskommentierten Zeilen entnehmen könnt, bietet die Library schon einiges an Versionen für unterschiedliche ST7735-Varianten, nämlich GREENTAB GREENTAB2, GREENTAB3, GREENTAB128 (128x128), GREENTAB160x80, (128x160), REDTAB, BLACKTAB und REDTAB160x80.
Die unterschiedlichen Varianten haben eine andere Farb-Konfiguration, also Byte-Reihenfolge für die Farben, die nicht unbedingt RGB sein muss und häufig auch einen Pixel-Versatz, was wohl daran liegt, dass die Start-Adresse für die Pixel nicht immer bei 0,0 beginnt.
Ich habe alle Tabs durchprobiert und entweder passten die Farben oder der Pixel-Offset nicht. Darum habe ich beschlossen, die Farben selbst Hardcore per ST7735-Register in setup() zu setzen:
// MADCTL: ST7735-TFT richtig einstellen: Farben und Orientation
tft.writecommand(0x36); tft.writedata(0xc0);
tft.fillScreen(TFT_BLACK);#define TFT_XOFF 2
#define TFT_XMAX 128
#define TFT_YOFF 1
#define TFT_YMAX 160Trotzdem habe ich noch ein Problem: wenn ich den Bildschirm löschen will mit tft.fillScreen(TFT_BLACK) dann bleibt immer ein bunter Pixel-Rand rechts und unten. Das liegt daran, dass der ST7735 einen Cache-Speicher hat, um schneller zu sein. Der übersteht sogar einen Reset des Boards. Am Anfang steht im Cache-Speicher natürlich nur zufälliger "Mist". Daher kommt das bunte Muster.
Und da ich ja nicht den vollen Bildschirm beschreiben kann, eben wegen des Versatzes und weil für TFT_eSPI bei 127 Schluss ist (0...127) löscht er nicht in voller Breite (2...129), sondern eben nur so, dass 2 Pixel rechts stehen bleiben (2...127).
Um den Bildschirm aber doch komplett zu löschen, habe ich mit den folgenden Trick ausgedacht: Ich drehe den Bildschirm einfach alle Richtungen durch und lösche von da aus. Damit wandert die 0,0 Koordinaten: oben links, oben rechts, unten rechts, unten links. Und füllen jeweils von da aus den Bildschirm. So wird jede Ecke erreicht und der Bildschirm ist komplett schwarz:
// Trick, um bunten Pixelrand loszuwerden, der Offset muss aber trotzdem noch bei Texten etc. berücksichtigt werden
for (int i=0; i < 4; i++) {
tft.setRotation(i);
tft.fillScreen(TFT_BLACK);
}
Die Buttons K1 bis K4

Oberhalb des Displays finden sich zwei LED-Ampeln, da kommen wir später zu.
Und unter dem Display befinden sich vier Knöpfe, die man auch ganz gut drücken kann und die nicht so winzig wie Boot und Reset-Knopf sind.
Das ist praktisch für eine Menüführung, dass man einblenden kann und dass dann so in etwa über den jeweiligen Knöpfen positioniert ist. Ich habe mich für folgende Funktionen für die Buttons im Menu entschieden:
K1: Cursor runter
K2: Cursor rauf
K3: OK / auswählen
K4: Exit / zurück
Das wird durch entsprechende Symbole am unteren Display-Rand angezeigt.
Was die Knöpfe / Funktionen tun, dürfte klar sein. Der Cursor, also die Auswahlmarkierung wird ebenfalls durch ein Dreieck angezeigt.
Hardwaretechnisch hängen alle vier Knöpfe an nur einem (analogen) Pin:
#define PIN_KEYS_ADC 29K1: 4
K2: 512
K3: 680
K4: 766
nichts gedrückt: 1024
Dies geschieht durch Widerstände mit unterschiedlichen Ohm-Werte, die in einer Spannungsteiler-Schaltung jeweils eine andere Spannung an den Pin legt, die dann wiederum in einem anderen Messwert zwischen 0 und 1024 resultiert.
Mit Checks auf die mittleren Werte zwischen den Grenzwerten können wir so abfragen, welcher Button gedrückt wurde:
while (1) {
int keysval = analogRead(PIN_KEYS_ADC);
if (keysval < 400) { key=1; break;}
else if (keysval < 600) { key=2; break;}
else if (keysval < 720) { key=3; break;}
else if (keysval < 900) { key=4; break;}
delay(10);
}
Die zwei LED-Ampeln
Oberhalb des Displays finden sich zwei LED-Ampeln mit je drei LEDs in den Farben grün, gelb und rot. Damit kann man wunderbar einen Status anzeigen, ohne das Display dafür zu bemühen.Nun ist es aber nicht so, dass man nicht einfach 6 GPIO-Pins für die 6 LEDs benutzen würde, das wäre Verschwendung an GPIO-Pins. Stattdessen hat man einen IO-Expander mit 8 Bit, nämlich den PCA9557 auf die Platine gelötet, der sozusagen 8 weitere IO-Pins zur Verfügung stellt, die dann allerdings über ein I2C-Protokoll angesprochen werden.
Ich persönlich hätte zwar eher einen 74HC595 verbaut, um Steuerleitungen einzusparen, ein Seriell-zu-Parallel-Schieberegister, aber das ist wohl Geschmackssache. Und der bräuchte auch 2 Leitungen bzw. GPIOs.
Für den PCA9557 gibt es eine extra Library, die wir in unsere platformio.ini inkludieren können, wenn wir sie benutzen möchten:
lib_deps =
maxpromer/PCA9557-arduino@^1.0.0Ich habe mich aber dafür entschieden, das selbst in die Hand zu nehmen, und die I2C-Befehle selbst zu managen. Auch weil später bestimmt noch mehr I2C-Bedarf bestehen wird, etwa für einen Sensor.
Zuerst wieder ein paar Defines:
// I2C-Adressen und Defines
//// LEDs: PCA9557-IO-Expander
#define ADDR_LEDS 0x18
#define LED_G1 0b01111111
#define LED_Y1 0b10111111
#define LED_R1 0b11011111
#define LED_G2 0b11101111
#define LED_Y2 0b11110111
#define LED_R2 0b11111011
#define LED_NONE 0xffUnd die LEDs sind als Bitmuster definiert. IO-Port 2 (gezählt ab 0) ist für LED_R2 (lese: Rot 2, also die rechte, rote LED), das geht weiter bis IO-Port 7 für LED_G1. Also von rechts nach links auf dem Board. Soll eine LED an sein, muss das entsprechende Bit auf 0 sein. Die beiden letzten Bits bleiben immer 1.
Will ich beispielsweise also die ersten drei LEDs anmachen, so wäre das Bitmuster 0b00011111. Ganz einfach, oder? Ich kann auch schreiben: "LED_G1 & LED_Y1 & LED_R1" und ein binäres AND benutzen. Das löscht die Bits an den entsprechenden Stellen und gibt unterm Strich auch 0b00011111. Und wenn ich nur eine LED anmachen will, wird es noch einfacher: dann schicke ich einfach nur z. B. LED_G2.
Die LEDs leuchten übrigens alle gleich hell, was sehr schön ist und nur mit der Wahl der richtigen Vorwiderstände zu bewerkstelligen ist. Ich habe übrigens mal einen Vorwiderstandsrechner programmiert, der dabei hilft.
Im setup() teile ich dem PCA9557 mit, dass ich alle IOs auf Ausgang haben will. Weil wir ja nichts messen wollen, sondern etwas ein- und ausschalten:
// Alle 8 Pins des PCA9557 für die farbigen LEDs als Ausgang konfigurieren (0 = Ausgang)
i2cWrite(ADDR_LEDS, 3, 0);
delay (10);
// LEDs ausschalten
i2cWrite (ADDR_LEDS, 1, LED_NONE);void ledOn(byte ledBits) {
i2cWrite(ADDR_LEDS, 1, ledBits);
}
void ledOffAll() {
i2cWrite(ADDR_LEDS, 1, LED_NONE);
}Und damit hätten wir auch schon alles an Grund-Hardware besprochen, was mich an dem Board interessiert. Alles, außer dem LoRa-Chip. Aber dem widme ich ein extra Artikel.
Der Rest an Hardware
Da wären die weiteren Schnittstellen: I2C, SPI, UART, Analog über Header und Grove-Konnektoren.Über die Grove-Konnektoren kann man sicher wunderbar I2C-Sensoren anschließen, um deren Messwerte dann über LoRa zu versenden. Oder ein analoges Thermometer (Thermistor). Oder ein Foto-Widerstand. GPIO 14 und 15 stehen als digitale Grove-Verbindungen zur Verfügung wie auch UART1
Alternativ kann man Header über DuPont-Kabel benutzen. Es sind auch noch einige digitalen GPIO-Ports (2, 3, 6...15) über Header zugänglich.
Über UART (RS232) lassen sich GPS-Module für Empfang der aktuellen Geo-Koordinaten, GSM-Module zum Versenden von SMS über Mobilfunk und ähnliches anschließen.
Und es ist sogar eine RS485-Schnittstelle über Schraubanschluss vorhanden. RS485 ist ein Industriestandard für die Datenkommunikation, der eine zuverlässige, differentielle Signalübertragung über lange Strecken und in rauen Umgebungen ermöglicht.
