2025-09-24
Wovon ist die Rede?
Eigentlich sollte dies ein kurzer Artikel werden.
Er ist aber leider etwas länger geraten.
Ich benutze der Einfachheit halber das Arduino IDE 2.x.y. mit verschiedenen Linuxvarianten, aber generell Linux.
Ist frei verfügbar und es gibt eine Unzahl Bibliotheken und Beispiele.
Die neueste Version der integrierten Enwicklungsumgebung, Integrated Development Environment (IDE), bietet auch die Möglichkeit ein C-Programm Schrittweise durchzuspielen, genannt „debugging“.
Voraussetzung ist ein kleines Gerät, „Pico Debug Probe“.
Ein zweiter Pico kann ebenfalls als Debugger verwendete werden, ist aber hier kein Thema.
Ich habe meinen „Pico Debug Probe“ etwas modifiziert.
Ich habe eine weitere Verbindung auf dem Teil angebracht um vom USB-Stecker die Vusb über eine Schottkydiode an den Ziel-Pico weiterzuleiten auf Vsys.
Vorteil: ich brauche nur eine USB-Verbindung.
Hier ein Beispiel um den großen Bruder für Demos zu nutzen:

Hier noch ein anderes Beispiel:

Standardmäßig werden von dem „Pico Debug Probe“ zwei Kommunikationsverbindungen zum Ziel-Pico hergestellt.
- Verbindung zu UART0 mit GP0 und GP1, Pin1 und Pin2
- Die Debugverbindung mit SWC und SWD
Diese Verbindungen können zum Laden des Programmes und zum Anzeigen von Meldungen im Arduino Monitor.
Statt „Serial.println()“ muß aber „Serial1.println()“ benutzt werden, der durchgeleitete UART0.
Diese Möglichkeit hat sich als stabiler herausgestellt statt der Standardlösung per direkter USB-Verbindung.
Auf Linux müssen mehrere Dinge vorher eingestellt werden.
Der Benutzer muß Mitglied von „plugdev“ sein und eine „udev rule“ erstellt werden.
Kommandos:
|
1 2 3 4 5 6 7 8 |
sudo usermod –aG plugdev $USER # check the internet for actual values echo ‘ATTRS{idVendor}==”2e8a”, ATTRS{idProduct}==”000c”, MODE=”664″, GROUP=”plugdev”‘ \ | sudo tee /etc/udev/rules.d/98–CMSIS–DAP.rules sudo udevadm control —reload sudo udevadm trigger |
Ob das alles wirklich nötig ist, habe ich nicht verifiziert.
Die größere Herausforderung war zu verstehen wie Arduino nun überredet werden kann den Code in Einzelschritten zu durchforsten.
Nach vielen frustrierenden Versuchen und auch Suchen im Internet habe ich dann Erfolg gehabt.
Hier die kurze Erklärung.

Die Verwirrung kommt von den identischen Symbolen.
Das obere Symbol startet den Debuggerprozeß.
Das linke Symbol öffnet die Debugleiste.
Das sieht dann wie folgt aus:

Die Symbole bedeuten:

Die Funktionen können auch per Tastatur ausgeführt werden:
$$
\begin{array}
{ c | c }
Funktion & Tasten\\
\hline
Pause/Continue & F5 \\
Stop Debugging & Shift+F5 \\
Step Over & F10 \\
Step In & F11 \\
Step Out & Shift+F11 \\
Restart & Ctrl+Shift+F5 \\
\end{array}
$$
Breakpoints können dann per Mausklick erzeugt werden, aber auch hier an der richtigen Stelle.

Wie man sieht links von der Zeilennummer.
Wichtig!
Der RP2040/RP2350 hat nur vier Hardware-Breakpoints.
Markiert man mehr als diese vier bekommt man sehr merkwürdige Fehlermeldungen, die alles beschreiben nur nicht den Fehler.
Aber das ist ja eine der typischen Eigenschaften von Fehlermeldungen.
Ist wahrscheinlich Absicht, um uns geistig flexibel zu halten. 😎
Was mir jetzt noch fehlt ist, wie kann ich den Wert von Variablen sehen?
Variablen per Debug untersuchen.
Wer glaubt, daß Variable in der Kategorie „VARIABLES“ zu finden sind, sieht sich getäuscht.
Nein, die müssen unter „WATCH“ hinzugefügt werden.
Um dies zu demonstrieren erweitere ich das „Blink“ Programm.
- Blinken wird nicht mit
delay()realisiert, sondern mit einer Funktion, die Millisekunden zählt. - Statt der „LED_BUILTIN“ benutze ich eine, die auf dem Display vorhanden sind.
Da ich dies schon mehrere Male benutzt habe, sind dies notwendigen Dateien schon in meinem „Common“ Ordner.
Das sieht bei mir wie folgt aus:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
$ tree arduino_pico_debug_test/blink_debug_test/ kps_common/ arduino_pico_debug_test/blink_debug_test/ ├── blink_debug_test.ino ├── flashled.ino -> ../../kps_common/flashled.ino ├── libraries -> ../../arduino_libraries/ └── pico_display_2_8_pins.h -> ../../kps_common/pico_display_2_8_pins.h kps_common/ ├── flashled.ino ├── pico_display_2_8_pins.h ├── print_value.ino ├── rp2040_debug_structs.h ├── rp2040_debug_structs_pico_w.h ├── rp2040_debug_structs_with_helpers.h └── standard_unions.h |
Die Datei „flashled.ino“:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void flashLED(int ledpin, int lowtime, int hightime) { static int ledValue; static long nextUpdate; if (millis() > nextUpdate) { ledValue = !ledValue; if(ledValue) { nextUpdate = millis() + lowtime; } else { nextUpdate = millis() + hightime; } digitalWrite(ledpin, ledValue); } } /* end flashLED */ |
Die Datei „pico_display_2_8_pins.h“:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
/* * gpio and pin assignments for PICO_DISPLAY_28 */ #ifndef PICO_DISP_28_FIRST #define PICO_DISP_28_FIRST // GP // pin #define UART0_TX 0 // 1 #define UART0_RX 1 // 2 #define GND1 // 3 #define NN1 2 // 4 // I2C1_SDA #define NN2 3 // 5 // I2C1_SCL #define I2C0_SDA 4 // 6 #define I2C0_SCl 5 // 7 #define GND2 // 8 #define NN3 6 // 9 // I2C1_SDA #define PWRKEY 7 // 10 #define UART1_TX 8 // 11 #define UART1_RX 9 // 12 #define GND3 // 13 #define NETLIGHT 10 // 14 #define RESET1 11 // 15 #define SW_A 12 // 16 #define SW_B 13 // 17 #define GND4 // 18 #define SW_X 14 // 19 #define SW_Y 15 // 20 #define LCD_DC 16 // 21 #define LCD_CSn 17 // 22 #define GND5 // 23 #define LCD_SCLK 18 // 24 #define LCD_MOSI 19 // 25 #define BACK_LGHT 20 // 26 #define LCD_TE 21 // 27 #define GND6 // 28 #define NN4 22 // 29 #define RUN // 30 #define LED_RD 26 // 31 #define LED_GN 27 // 32 #define GND7 // 33 #define LED_BL 28 // 34 #define VREF // 35 #define S3V3_OUT // 36 #define S3V3_EN // 37 #define GND8 // 38 #define VSYS // 39 #define VBUS // 40 #endif |
Das Programm „blink_debug_test.ino“:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
#include “pico_display_2_8_pins.h” #define NOP() asm(“nop”) /******************************************************************************/ // Pico W #define LED_HIGH_TIME 20 #define LED_LOW_TIME 500 /******************************************************************************/ // the setup function runs once when you press reset or power the board void setup() { // initialize digital pin LED_BUILTIN as an output. pinMode(LED_BUILTIN, OUTPUT); pinMode(LED_RD, OUTPUT); pinMode(LED_GN, OUTPUT); pinMode(LED_BL, OUTPUT); digitalWrite(LED_RD, HIGH); digitalWrite(LED_GN, HIGH); digitalWrite(LED_BL, HIGH); } int cnt = 0; bool led_state = false; // the loop function runs over and over again forever void loop() { //int cnt; NOP(); cnt++; flashLED( LED_BL // int ledpin ,LED_LOW_TIME // int lowtime ,LED_HIGH_TIME // int hightime ); led_state = digitalRead(LED_BL); } |
Die LEDs auf dem Display sind mit der Anode an +3V3 verbunden und nicht wie die LED_BUILTIN mit der Kathode gegen GND.
Daraus ergibt sich, das die Werte für LED_LOW_TIME und LED_HIGH_TIME getauscht werden müssen.
In der setup() Funktion müssen die LEDs mit digitalWrite(LED_xx, HIGH) abgeschaltet werden.
Das eigentliche Program, die Endloschleife, wird recht übersichtlich.
Aus alten Tagen habe ich mir noch angewöhnt mit NOPs gezielt Breakpoints einzusetzen.
Bei den Prozessoren in der Vergangenheit haben einige Debugger vor dem Breakpoint angehalten und andere danach.
Als ersten Versuch mit einer Variablen habe ich „int cnt“ lokal in loop() deklariert und im WATCH per „+“ hinzugefügt.
Jetzt schlägt der Optimizer zu.

Nun die Variable als „ volatile int cnt“ deklariert.

Jetzt wird zwar ein Wert angezeigt, wo immer der herkommt, aber nicht inkrementiert.
Also die Variable außerhalb von loop() global deklarieren und auf Null initialisieren.

Jetzt wird der Wert nach einem Neustart mit Null angezeigt und mit jedem „CONTINUE“ inkrementiert.
Das ist zwar so nicht gewünscht, aber während der Test-Phase akzeptabel.

Nun zum digitalen Ausgang mit welchem die LED angesteuert wird.
Wenn ein neuer Eintrag im WATCH hinzugefügt wird erscheint ein PopUp-Fenster:
Nun sollte man den Begriff „Expression“ nicht überbewerten.
Wer hier von komplexen Möglichkeiten träumt wie unter „Vscode“, muß dies auch weiterhin tun.
Hier einige Beispiele.
Als erstes die Anweisung digitalRead(LED_BL).
Warum?
Weil der Zustand des Pins per Eingang immer zur Verfügung steht.
Gesetzt wird ein Pin über ein spezielles Single Cycle register (sio_xyz).
Diese kann man sich natürlich auch ansehen, wird aber etwas später getan.

Der Eintrag digitalRead(LED_BL) führt nicht zum gewünschten Ergebnis.
Ändert man jedoch den Eintrag und benutzt die entsprechende GPIO Nummer, 28, dann wird der Zustand angezeigt.

Das führt zum nächsten Test.
Statt per #define den Wert vorzugeben, deklarieren einer Variablen und mit 28 initialisieren.

int v_LED_BL = 28; und digitalRead(v_LED_BL) im WATCH führen zum Ziel.
Ich habe bis jetzt bewußt so getan als ob diese Ausdrücke nur ins WATCH eingetragen werden müssen und dann sieht man die Werte.
Leider genügt dies nicht.
Auch im Programm müssen diese Anweisungen irgendwie ausgeführt werden.
Dazu habe ich mir zwei Variable deklariert, die dann per readDigital() gefüllt werden.
Also zusätzlich noch bool led_state = false; und bool led_state_2 = false; deklarieren und im Programm laden.
|
1 2 3 4 5 6 7 8 9 10 |
... bool led_state = false; bool led_state_2 = false; ... led_state = digitalRead(LED_BL); led_state_2 = digitalRead(v_LED_BL); ... |
Um die LED mal hell mal dunkel zu sehen ist natürlich die flash() Funktion unsinnig.
Wer will schon 20ms und 500ms per mausclick durchklappern.
Aber hier geht es ja om Moment „nur“ um Variable.
Jetzt zu einem mir persönlich wichtigem Punkt.
Ich möcht den Inhalt eines Registers in seiner Gesamtheit sehen.
Die Pico Register sind 32 Bit weit.
Ich hätte gerne die Darstellung als Dezimalzahl, 32 Einzelbits, byteweise Dezimal und als „Nibbles“ hexadezimal.
Ähnliches habe ich schon in der Vergangenheit immer gemacht mit der Hilfe einer „union“, die die verschiedenen Daten auf einen gemeinsamen Speicherplatz abbildet.
Ich habe mir auch die Version mit Pointern angesehen, fand sie aber verwirrend.
Hier also die Lösung für schlichte Gemüter.
standard_unions.h
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
typedef struct _BITS_32 { uint32_t bit0 : 1; uint32_t bit1 : 1; uint32_t bit2 : 1; uint32_t bit3 : 1; uint32_t bit4 : 1; uint32_t bit5 : 1; uint32_t bit6 : 1; uint32_t bit7 : 1; uint32_t bit8 : 1; uint32_t bit9 : 1; uint32_t bit10 : 1; uint32_t bit11 : 1; uint32_t bit12 : 1; uint32_t bit13 : 1; uint32_t bit14 : 1; uint32_t bit15 : 1; uint32_t bit16 : 1; uint32_t bit17 : 1; uint32_t bit18 : 1; uint32_t bit19 : 1; uint32_t bit20 : 1; uint32_t bit21 : 1; uint32_t bit22 : 1; uint32_t bit23 : 1; uint32_t bit24 : 1; uint32_t bit25 : 1; uint32_t bit26 : 1; uint32_t bit27 : 1; uint32_t bit28 : 1; uint32_t bit29 : 1; uint32_t bit30 : 1; uint32_t bit31 : 1; }BITS32; typedef struct _NIBBLES_8 { uint32_t nibble0 : 4; uint32_t nibble1 : 4; uint32_t nibble2 : 4; uint32_t nibble3 : 4; uint32_t nibble4 : 4; uint32_t nibble5 : 4; uint32_t nibble6 : 4; uint32_t nibble7 : 4; }NIBBLES8; typedef struct _BYTES_4 { uint32_t byte0 : 8; uint32_t byte1 : 8; uint32_t byte2 : 8; uint32_t byte3 : 8; }BYTES4; union WORD_BYTES_BITS { uint32_t out_word; NIBBLES8 hex; BYTES4 bytes; BITS32 bits; }; |
Diese Datei per Link in den Ordner einbinden und schon kann es losgehen.
|
1 2 3 4 5 6 7 8 9 |
#include “standard_unions.h” ... #include “hardware/regs/sio.h” #include “hardware/structs/sio.h” uint32_t out = sio_hw->gpio_out; // all output states uint32_t in = sio_hw->gpio_in; // all input states union WORD_BYTES_BITS out_bits; |
Um die Beziehung zwischen sio_hw und gpio_out/gpio_in zu verstehen muß man folgendes studieren:
- Raspberry Pi Pico-series C/C++ SDK, 745 Seiten!
(https://datasheets.raspberrypi.com/pico/raspberry-pi-pico-c-sdk.pdf) - hardware/regs/sio.h
- hardware/structs/sio.h
Man kann es aber auch sein lassen. 😇
Die kurze Fassung.
„sio“ steht für „Singlecycle Input Output“ und repräsentiert eine Adresse im Speicherraum des Pico, da dieser sogenanntes „Memory Mapped IO“ nutzt.
Das heißt Register verhalten sich wie Speicherzellen, man kann sie beschreiben und lesen.
Innerhalb des „sio“ Blocks sind nun irgendwo die Register „gpio_out/gpio_in“ angesiedelt und sind relativ zur „sio“ Startadresse definiert.
„sio_hw->gpio_in“ ist also nichts anderes als eine Adresse die sich aus Startadresse plus relativer Adresse (offset) errechnet.
So sind alle Register definiert.
Kennt man eines, kennt man alle. Na ja, nach Studium der 750 Seiten und der header Dateien.
Ich habe mich auf die zwei genannten beschränkt.
Nun zur Darstellung der Inhalte.
Im Vorspann sind werden zwei Variablen „in“ und „out“ deklariert und mit den Werten der Register zur Einschaltzeit initialisiert.
Diese Variablen benutze ich um den 32 bit Wert der „union“ zu laden und dann die einzelnen Mitglieder zu drucken.
„print_value.ino“ existiert in meinem Common Ordner und wird per Link nutzbar gemacht.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
union WORD_BYTES_BITS printout; void print32bits(uint32_t value) { //union WORD_BYTES_BITS printout; printout.inout_word = value; //out_bits.inout_word = value; Serial1.print(“Value: “); Serial1.println(printout.inout_word); Serial1.print(“Bytes hex: “); Serial1.print(printout.hex.nibble7, HEX); //Serial1.print(” “); Serial1.print(printout.hex.nibble6, HEX); Serial1.print(” “); Serial1.print(printout.hex.nibble5, HEX); //Serial1.print(” “); Serial1.print(printout.hex.nibble4, HEX); Serial1.print(” “); Serial1.print(printout.hex.nibble3, HEX); //Serial1.print(” “); Serial1.print(printout.hex.nibble2, HEX); Serial1.print(” “); Serial1.print(printout.hex.nibble1, HEX); //Serial1.print(” “); Serial1.print(printout.hex.nibble0, HEX); Serial1.print(“\n”); Serial1.print(“Bits: “); Serial1.print(printout.bits.bit31); Serial1.print(printout.bits.bit30); Serial1.print(printout.bits.bit29); Serial1.print(printout.bits.bit28); Serial1.print(printout.bits.bit27); Serial1.print(printout.bits.bit26); Serial1.print(printout.bits.bit25); Serial1.print(printout.bits.bit24); Serial1.print(” “); Serial1.print(printout.bits.bit23); Serial1.print(printout.bits.bit22); Serial1.print(printout.bits.bit21); Serial1.print(printout.bits.bit20); Serial1.print(printout.bits.bit19); Serial1.print(printout.bits.bit18); Serial1.print(printout.bits.bit17); Serial1.print(printout.bits.bit16); Serial1.print(” “); Serial1.print(printout.bits.bit15); Serial1.print(printout.bits.bit14); Serial1.print(printout.bits.bit13); Serial1.print(printout.bits.bit12); Serial1.print(printout.bits.bit11); Serial1.print(printout.bits.bit10); Serial1.print(printout.bits.bit9); Serial1.print(printout.bits.bit8); Serial1.print(” “); Serial1.print(printout.bits.bit7); Serial1.print(printout.bits.bit6); Serial1.print(printout.bits.bit5); Serial1.print(printout.bits.bit4); Serial1.print(printout.bits.bit3); Serial1.print(printout.bits.bit2); Serial1.print(printout.bits.bit1); Serial1.print(printout.bits.bit0); Serial1.print(“\n”); } |
Jetzt kann mit dieser Routine der Zustand von „out“ oder „in“ am Ende von setup() überprüft werden.
Innerhalb der Schleife sollten dann aber frische Werte gelesen werden.
|
1 2 |
out = sio_hw->gpio_out; in = sio_hw->gpio_in; |
Um nun auch etwas zu sehen sollte die ursprüngliche Variante von „Blink“ benutzt werden und dann jeweils die aufgefrischten Registerinhalte angezeigt werden.
Im Moment wird wieder per print entflöht.
Geduld, die WATCH Einträge kommen noch.
Eine generalisierte Lösung
Ich benutze gerne generalisierte Hardware um dann spezialisierte Software zu erstellen.
Der Raspberry Pi Pico ist wunderbar geeignet schnell mal eine Lösung zu bekommen.
Man muß jedoch einige Einschränkungen in Kauf nehmen.
Für ein Endprodukt mit eigenem Layout sind die Einschränkungen zu groß.
Ich habe mir daher „schnell mal“ eine Lösung zusammengestrickt und ein festes Gehäuse drumherum gedruckt.

Eigenschaften
- RP2350 80 Pin
- USB C auf USB Micro Kabel
- +3V3 LDO für SPI, I2C und UART
- LST XH 2,5mm für SPI1/2, I2C1,2 und UART1
- 1:1 Anschlüsse für Pico Debug Probe
- 2 x 20 Pin Sockel mit Belegung wie Pico jedoch größerer Abstand
- 2 x 12 Anschlüsse für die zusätzlichen IOs
Im Bild ist schon eine LED zu sehen, die mit GP25 verbunden ist, da die PGA Lösung keine „LED_BUILTIN“ besitzt.
Wie schon mehrfach erwähnt ist meine Entwicklungswelt Linux.
Statt Vscode die einfache Variante Arduino IDE v2, im moment 2.3.6.
SDK ist installiert mit dem „earlephilhower“ core, der auch den Debugger stellt, auf Linux – wichtig – „gdb“.
Daraus folgen alle nachstehenden Bemerkungen.
Sicherstellen, daß der Pico Debugger Probe erkannt wird folgende udev rule:
|
1 |
`ATTRS{idVendor}==“2e8a”, ATTRS{idProduct}==“000c”, MODE=“664”, GROUP=“plugdev”` |
Einige Webseiten zeigen für idProduct 0003.
Um den richtigen Wert zu finden muß „lsusb“
|
1 2 |
$ lsusb ... ID 2e8a:000c Raspberry Pi Debug Probe (CMSIS–DAP) |
konsultiert werden.
Und auch folgendes Überprüfen:
- Board Select: Pimoroni PGA2350 (in meinem Fall)
- Upload Method: “Picoprobe/Debugprobe (CMSIS-DAP)`”
- Port: “/dev/ttyACM0”
- Monitor Baud Rate: 115200
Zum Programm
Auch hier wird das „Blink“ Beispiel abgewandelt.
„tree“ zeigt folgende Struktur:
|
1 2 3 4 5 6 |
$ tree blink_debug blink_debug ├── blink_debug.ino ├── pico_display_2_8_pins.h -> ../../kps_common/pico_display_2_8_pins.h ├── print_value.ino -> ../../kps_common/print_value.ino └── standard_unions.h -> ../../kps_common/standard_unions.h |
Das Programm „blink_debug.ino“:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
/* using the original blink example enhanced for debugging */ /******************************************************************************/ #include “hardware/regs/sio.h” #include “hardware/structs/sio.h” uint32_t out = sio_hw->gpio_out; // all output states uint32_t in = sio_hw->gpio_in; // all input states uint32_t out_h = sio_hw->gpio_hi_out; uint32_t in_h = sio_hw->gpio_hi_in; /******************************************************************************/ int led_watch; #include “standard_unions.h” #include “pico_display_2_8_pins.h” union WORD_BYTES_BITS out_bits; union WORD_BYTES_BITS in_bits; bool bit_25_out = out_bits.bits.bit25; bool bit_25_in = in_bits.bits.bit25; #define NOP() asm (“nop”) char spinning[] = {‘|’,‘/’,‘-‘,‘\\’,‘|’}; int spincnt = 0; /******************************************************************************/ #define DEBUG long timeoutUSB = millis(); #define USB_TIMEOUT 5000 /******************************************************************************/ #define LED_25 25 void setup() { // put your setup code here, to run once: // initialize digital pin LED_BUILTIN as an output. pinMode(LED_RD, OUTPUT); pinMode(LED_GN, OUTPUT); pinMode(LED_BL, OUTPUT); digitalWrite(LED_RD, HIGH); // 26 digitalWrite(LED_GN, HIGH); // 27 digitalWrite(LED_BL, HIGH); // 28 pinMode(LED_25, OUTPUT); digitalWrite(LED_25, LOW); digitalWrite(LED_25, HIGH); digitalWrite(LED_25, LOW); // a direct USB connection to the PICO uses UART0 Serial1.begin(115200); #ifdef DEBUG while (!Serial1 && (millis() < timeoutUSB + USB_TIMEOUT)); #endif print32bits(out); NOP(); } void loop() { // put your main code here, to run repeatedly: // just for fun Serial1.print(“looping … “); Serial1.println(spinning[spincnt++]); if(spincnt > 3) { spincnt = 0; } digitalWrite(LED_BL, LOW); // turn the LED on digitalWrite(LED_25, HIGH); out_bits.inout_word = sio_hw->gpio_out; in_bits.inout_word = sio_hw->gpio_in; bit_25_out = out_bits.bits.bit25; bit_25_in = in_bits.bits.bit25; #ifdef DEBUG led_watch = digitalRead(LED_BL); out = sio_hw->gpio_out; in = sio_hw->gpio_in; out_h = sio_hw->gpio_hi_out; in_h = sio_hw->gpio_hi_in; Serial1.println(“\nLED is ON”); Serial1.println(“out”); print32bits(out); Serial1.println(“out_h”); print32bits(out_h); Serial1.println(“in”); print32bits(in); Serial1.println(“in_h”); print32bits(in_h); #else led_watch = digitalRead(LED_BL); out = sio_hw->gpio_out; in = sio_hw->gpio_in; out_h = sio_hw->gpio_hi_out; in_h = sio_hw->gpio_hi_in; #endif delay(50); // wait digitalWrite(LED_BL, HIGH); // turn the LED off digitalWrite(LED_25, LOW); out_bits.inout_word = sio_hw->gpio_out; in_bits.inout_word = sio_hw->gpio_in; bit_25_out = out_bits.bits.bit25; bit_25_in = in_bits.bits.bit25; #ifdef DEBUG led_watch = digitalRead(LED_BL); out = sio_hw->gpio_out; in = sio_hw->gpio_in; out_h = sio_hw->gpio_hi_out; in_h = sio_hw->gpio_hi_in; Serial1.println(“\nLED is OFF”); Serial1.println(“out”); print32bits(out); Serial1.println(“out_h”); print32bits(out_h); Serial1.println(“in”); print32bits(in); Serial1.println(“in_h”); print32bits(in_h); #else led_watch = digitalRead(LED_BL); out = sio_hw->gpio_out; in = sio_hw->gpio_in; out_h = sio_hw->gpio_hi_out; in_h = sio_hw->gpio_hi_in; #endif delay(500); // wait } |
Wenn Debug enabled (deunglisch) ist sollte im Monitor folgendes angezeigt werden:
(meine flatpak variante druckt einen Zeitstempel ?)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
11:39:13.053 -> Value: 0 11:39:13.053 -> Bytes hex: 00 00 00 00 11:39:13.085 -> Bits: 00000000 00000000 00000000 00000000 11:39:13.085 -> looping ... | 11:39:13.085 -> 11:39:13.085 -> LED is ON 11:39:13.085 -> out 11:39:13.085 -> Value: 234881024 11:39:13.085 -> Bytes hex: 0E 00 00 00 11:39:13.085 -> Bits: 00001110 00000000 00000000 00000000 11:39:13.085 -> out_h 11:39:13.085 -> Value: 0 11:39:13.085 -> Bytes hex: 00 00 00 00 11:39:13.085 -> Bits: 00000000 00000000 00000000 00000000 11:39:13.085 -> in 11:39:13.085 -> Value: 234881027 11:39:13.085 -> Bytes hex: 0E 00 00 03 11:39:13.085 -> Bits: 00001110 00000000 00000000 00000011 11:39:13.085 -> in_h 11:39:13.085 -> Value: 805339136 11:39:13.085 -> Bytes hex: 30 00 80 00 11:39:13.117 -> Bits: 00110000 00000000 10000000 00000000 11:39:13.149 -> 11:39:13.149 -> LED is OFF 11:39:13.149 -> out 11:39:13.149 -> Value: 469762048 11:39:13.149 -> Bytes hex: 1C 00 00 00 11:39:13.149 -> Bits: 00011100 00000000 00000000 00000000 11:39:13.149 -> out_h 11:39:13.149 -> Value: 0 11:39:13.149 -> Bytes hex: 00 00 00 00 11:39:13.149 -> Bits: 00000000 00000000 00000000 00000000 11:39:13.181 -> in 11:39:13.181 -> Value: 469762051 11:39:13.181 -> Bytes hex: 1C 00 00 03 11:39:13.181 -> Bits: 00011100 00000000 00000000 00000011 11:39:13.181 -> in_h 11:39:13.181 -> Value: 268468224 11:39:13.181 -> Bytes hex: 10 00 80 00 11:39:13.181 -> Bits: 00010000 00000000 10000000 00000000 11:39:13.697 -> looping ... / 11:39:13.697 -> 11:39:13.697 -> LED is ON |
Der RP2350 80 pin hat mehr als die normalen 29 GPIOs.
Ein kompletter Satz wird mit zwei Registern verwirklicht.
Ich drucke im Monitor exemplarisch vier Register: out, out_h, in, in_h.
Für jedes Register den Wert decimal, hexadecimal und binär.
Nun Will man ja nicht unbedingt immer den Monitor bemühen, weil die ganze Druckerei ja auch Programmspeicher und Zeit kostet.
Also jetzt den Debugger konfigurieren um die Werte im WATCH Teil darzustellen.

Die gewählten Variablen werden alle decimal dargestellt.
Das ist bei einzelnen Bits wie LEDs kein problem mit 0 und 1.
Jedoch bei den Registern möchte ich auch die einzelnen Bits sehen.
Nach längerem Suchen im Internet habe ich folgende Lösung gefunden für „gdb“:
„out,b“ zeigt die Bits, allerdings ohne Führungsnullen. Aber immerhin.

Die Werte in hex mit „out,h“.

Wenn jetzt noch Debug disabled wird, werden die Variablen immer noch über #else gefüllt, aber im Monitor sollte nur noch das Spinningsymbol angezeigt werden.
Hier im Monitor auf einzelnen Zeilen und auf einem TFT Display immer in derselben als Animation.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
12:15:26.720 -> Value: 469762048 12:15:26.752 -> Bytes hex: 1C 00 00 00 12:15:26.752 -> Bits: 00011100 00000000 00000000 00000000 12:15:37.274 -> looping ... | 12:15:37.820 -> looping ... / 12:15:38.364 -> looping ... – 12:15:38.910 -> looping ... \ 12:15:39.454 -> looping ... | 12:15:40.032 -> looping ... / 12:15:40.579 -> looping ... – 12:15:41.124 -> looping ... \ 12:15:41.673 -> looping ... | 12:15:42.220 -> looping ... / 12:15:42.767 -> looping ... – 12:15:42.991 -> |
Die WATCH Werte sollten jetzt bei Einzelschritten die Bits anzeigen.
Dazu habe ich och zwei zusätzliche Breakpoints gesetzt auf die beiden
digitalWrite(LED_BL, LOW);
Zeilen.
Werte am ersten Breakpoint:

Ein mal Step Over:

Zweites mal Step Over:

Continue zu Breakpoint drei:

Ein mal Step Over:

Zweites mal Step Over:

Continue zu Breakpoint zwei:

Das ist natürlich sehr informativ, aber auch nicht wirklich übersichtlich.
Die einzelnen Bits aus der union können hier helfen.
Nach dem setzen per digitalWrite() sehe ich mir mal die einzelnen Bits an.
Hierzu benötige ich wieder zwei Variable bit_25_out und bit_25_in.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
union WORD_BYTES_BITS out_bits; union WORD_BYTES_BITS in_bits; bool bit_25_out = out_bits.bits.bit25; bool bit_25_in = in_bits.bits.bit25; ... // insert twice digitalWrite(LED_BL, LOW); // turn the LED on digitalWrite(LED_25, HIGH); out_bits.inout_word = sio_hw->gpio_out; in_bits.inout_word = sio_hw->gpio_in; bit_25_out = out_bits.bits.bit25; bit_25_in = in_bits.bits.bit25; ... digitalWrite(LED_BL, HIGH); // turn the LED off digitalWrite(LED_25, LOW); out_bits.inout_word = sio_hw->gpio_out; in_bits.inout_word = sio_hw->gpio_in; bit_25_out = out_bits.bits.bit25; bit_25_in = in_bits.bits.bit25; |
Dann sollten Continue Steps zeigen:

und

Und damit kann man leben.
Résumé
Arduino IDE 2 kann mit Debugger genutzt werden.
Es muß nicht alles in eine Datei gepackt werden.
Software-Projekte können, wenn auch primitiv, in Ordnerstrukturen gespeichert werden.
Es kann auf Serial.println() verzichtet werden, jedoch werden Variable für WATCH benötigt.
Nach diesem etwas längeren Artikel werde ich mich nun mal wieder der Lösung eines Projektes annehmen.
Wir haben uns eine Pause verdient.