ESP32: Assembler, aBelle und WLAN über das USB-Kabel

Xtensa-Assembler oder aBelle schreiben, aufs Board bringen und Schritt für Schritt debuggen – ohne JTAG-Probe, ohne esptool, ohne ESP-IDF.

Der ursprüngliche ESP32 von Espressif hat zwei Xtensa-LX6-Kerne mit 240 MHz und keinen eigenen USB-Anschluss. Asmonite assembliert seinen Code (.target esp32) und übersetzt Programme in der Sprache aBelle für ihn, lädt ein Programm ins RAM oder schreibt es in den Flash und debuggt es über das USB-Kabel des Boards: Breakpoints, Einzelschritte, Register, Speicher. Dazu kommen eine Konsole mit Dateien im Flash und das WLAN: Ein aBelle-Programm verbindet sich mit einem Netz, holt eine Adresse per DHCP und spricht UDP und TCP, auch während der Debugger es an einem Breakpoint anhält.

Getestet mit einem ESP32 DevKit V1 (ESP32-D0WD, CP2102, 4 MB Flash, LED an GPIO 2) und einem Board mit ESP32-WROVER (CH9102, 8 MB Flash). Die ausführliche Anleitung steht im Quelltext-Repository unter docs/de/esp32.md.

Was Asmonite mit dem ESP32 kann

AssemblerXtensa mit Registerfenstern, Literal-Pool und den 16-Bit-Befehlen; Code im IRAM, Daten hinter .data im DRAM.
aBelleEine kleine Sprache mit der Schreibweise von Python; target "esp32" übersetzt für dasselbe RAM, der Debugger zeigt den Quelltext.
DebuggerÜber den seriellen Port: Breakpoints, F7/F8, Pause eines laufenden Programms, Register, Speicher, die Ausgabe im Reiter Serial.
FlashEin Programm als Image des Boot-ROMs bei 0x1000; es startet nach jedem Reset von selbst.
KonsoleEin Dateisystem im Flash, ein Fenster für die Dateien, Programme starten und als Autostart.
WLANStation mit WPA2 oder offen, DHCP, DNS, Ping, UDP, TCP (Client und Server), mDNS (esp32.local).

Anschließen und loslegen

  1. Das Board anschließen. Ein Board mit ESP32 und USB-Seriell-Chip (CP2102 oder CP2104, CH340, CH9102, CH343, FTDI). Im Geräte-Manager erscheint es als COM-Port. Den Treiber der meisten dieser Chips bringt Windows mit, sonst kommt er vom Hersteller des Chips.
  2. Die Verbindung prüfen. Debug > Debug targets > ESP32 > Test the connection zeigt den COM-Port, die MAC-Adresse und den Flash, zum Beispiel „flash 4 MB (Boya, JEDEC id 68 40 16)“. Die Größe sagt der Flash-Chip selbst, wie bei esptool flash_id.
  3. Starten. examples\esp32\blink_ram.asm öffnen und F7 drücken: Asmonite setzt den Chip in den Download-Modus, lädt das Programm und hält bei _start. F7 und F8 gehen einen Schritt, F9 lässt laufen, F5 setzt einen Breakpoint, Debug > Pause hält an.

Der Download-Modus. Das Boot-ROM wartet auf Befehle über UART0, wenn GPIO 0 bei einem Reset low ist. Die Boards machen das über die Leitungen DTR und RTS des USB-Seriell-Chips; Asmonite macht es wie esptool und prüft nach jedem Reset die erste Zeile des Boot-ROMs. Auf manchen Boards braucht das viele Versuche, weil der Kondensator an EN zu klein ist. Kommt das Board nicht innerhalb von 10 Sekunden dorthin, bittet Asmonite, BOOT gedrückt zu halten. Ein Kondensator von etwa 10 µF zwischen EN und GND behebt es dauerhaft.

921600 Baud. Große Übertragungen laufen achtmal so schnell wie mit 115200: der Flash über das Boot-ROM, ein Programm ab 16 KB unter dem Debugger, Dateien ab 16 KB. Asmonite prüft die Rate mit einem kurzen Austausch und fällt auf 115200 zurück, wenn das Board sie nicht schafft. Ein Programm von 98 KB lädt in gut 6 Sekunden. Bevor das Programm läuft, gilt wieder 115200. Die Umgebungsvariable ASMONITE_ESP_BAUD setzt eine andere Rate.

Debuggen ohne JTAG

F9 oder F7 setzt den Chip in den Download-Modus, das Boot-ROM lädt den Debug-Monitor von Asmonite (gut 2 KB bei 0x4009E000), und der Monitor lädt das Programm und startet es bei _start. Er setzt VECBASE auf seine eigene Vektortabelle: break 1,15 (ein Breakpoint), der Schritt von ICOUNT und jede Ausnahme landen bei ihm. Er sichert die Register und beantwortet die Befehle von Asmonite über UART0. Er hat die Handler der Registerfenster, so geht auch eine Aufrufkette mit call4, call8 und call12, die tiefer ist, als die 64 Register fassen.

Pause und Stop. Debug > Pause hält ein laufendes Programm an, wo es gerade ist, auch in einer Endlosschleife. Asmonite schickt dafür ein Byte über UART0; der Monitor leitet den Interrupt des UART auf das NMI des Kerns, über der Interrupt-Stufe 5, mit der das Programm läuft. Stop (Strg+F2) beendet die Sitzung sofort.

Ausnahmen. Ein ungültiger Befehl, ein Laden von einer Adresse ohne Speicher oder ein Byte-Speichern ins IRAM hält das Programm am Befehl an, mit dem Grund im Ausgabefenster. Das IRAM liest und schreibt der Kern nur in Wörtern zu 32 Bit: l32i und s32i gehen, l8ui und s8i nicht.

Daten im DRAM: .data

Texte, Tabellen und Puffer kommen hinter .data: Diese Zeilen landen im DRAM ab 0x3FFB0000, wo der Kern Bytes, Halbwörter und Wörter liest und schreibt. .code schaltet zurück zum Code.

lit_text: dd text           ; the address in the DRAM, for l32r

_start:
    l32r  a3, lit_text
    l8ui  a2, a3, 0           ; a byte of the text
    ...

.data
text:   db 'Hello, world!', 13, 10, 0
buffer: resb 64             ; room, filled with zeros when it is loaded

Der Debugger schreibt die Bytes von .data nach dem Code ins DRAM; ein Programm für den Flash hat sie als zweites Segment im Image, das das Boot-ROM bei jedem Start kopiert. Der Stack beginnt bei 0x3FFC0000 und wächst nach unten; Daten, die in ihn hineinreichen, sind ein Fehler.

RAM, Flash und die serielle Ausgabe

.target esp32F9 lädt das Programm ins IRAM ab 0x40080000 (120 KB bis zum Monitor). Schnell zum Ausprobieren; nach einem Reset läuft wieder, was im Flash ist.
.target esp32 flashF9 schreibt das Programm als Image des Boot-ROMs bei 0x1000 in den Flash und debuggt es dann im RAM. Nach jedem Reset startet es von selbst. Ein Image, das nicht in den Flash des Boards passt, weist Asmonite ab.
Write to Flash and Run (Shift+F9)Dasselbe für ein Programm mit .target esp32: derselbe Quelltext ohne Änderung ins RAM (F9) oder in den Flash (Shift+F9).

Ein Programm, das das Boot-ROM aus dem Flash startet, muss die Watchdogs selbst abschalten, sonst setzt sich der Chip nach ein paar Sekunden zurück; examples\esp32\blink_flash.asm zeigt es mit ein paar Zeilen bei _start.

Der Reiter Serial. Was das Programm in UART0 schreibt, kommt am COM-Port heraus, mit 115200 Baud. Unter dem Debugger teilt sich der Monitor den Port mit dem Programm, und die Ausgabe erscheint im Reiter Serial neben dem Ausgabefenster. Ohne Debugger ist der Reiter ein kleines Terminal: Connect, eine Eingabezeile und Reset, das den Chip über RTS zurücksetzt, ohne den Port zu schließen. Die Leitungen DTR und RTS lässt das Terminal sonst aus, damit ein Programm im RAM beim Öffnen des Ports nicht verschwindet.

Die Konsole und ihre Dateien

examples\esp32\console.asm macht aus dem Board einen kleinen Rechner mit einem Dateisystem im Flash, von 2 MB bis zum Ende des Flash: 2 MB bei 4 MB Flash, 6 MB bei 8 MB. Jedes Terminalprogramm arbeitet mit ihr (ls, cat, put, rm, df, format, run, load, boot, help), und Debug > ESP32 Board Files zeigt ihre Dateien in einem Fenster.

Write Console to FlashSchreibt die Konsole über das Boot-ROM in den Flash; beim ersten Mal legt Format das Dateisystem an. Die Kopfzeile nennt den Flash des Boards.
Upload File, Download, DeleteDateien jeder Art zwischen PC und Board, jede an ihrer CRC-32 geprüft: 100 KB hoch in knapp 4 Sekunden, herunter in 2.
Upload This Program, Run, AutostartÜbersetzt das Programm im Editor (Assembler oder aBelle) und lädt es als Datei hoch; Run startet es, Autostart startet es nach jedem Reset.
Reset BoardSetzt das Board zurück und hält es in der Konsole (wie BOOT oder eine Taste im Terminal gleich nach dem Reset).
Erase FlashLöscht den ganzen Flash wie bei einem neuen Board (8 bis 17 Sekunden bei 4 MB). Danach meldet das Boot-ROM „flash read err, 1000“, bis wieder etwas im Flash ist.
Install WiFiBringt das WLAN aufs Board (siehe unten).
Asmonite: the window ESP32 Board Files with the files wlan.rom and wlansvc on a board with 4 MB of flash
Das Fenster ESP32 Board Files: oben der Flash des Boards, in der Liste die Dateien des WLAN nach Install WiFi.

Ein Programm für F9 passt unverändert in die Konsole: Sein Code darf 0x40080000..0x40097FFF belegen, seine Daten 0x3FFB0000..0x3FFCFFFF. Unter dem Debugger läuft die Konsole nicht, denn der Debug-Monitor nimmt jedes Byte auf UART0 als Pause.

Das WLAN

Das WLAN des ESP32 steckt in Bibliotheken von Espressif ohne Quelltext. Asmonite bindet sie einmal mit seinem eigenen Linker zu einer festen Datei zusammen, wlan.rom, und bringt einen kleinen Dienst mit, wlansvc, der sie startet und Programmen einfache Funktionen anbietet. ESP-IDF braucht es dafür nicht.

  1. Einmal pro Board einrichten. Debug > ESP32 Board Files (die Konsole im Flash, das Dateisystem formatiert), dann Install WiFi: Der Teil für den Flash (330 KB bei 0x100000) geht über das Boot-ROM in den Flash, wlan.rom und wlansvc in die Dateien. Das dauert etwa 25 Sekunden.
  2. Das Netz eintragen. In examples\abelle\esp32\inc\network.abel den Namen und das Passwort deines WLAN eintragen (leer für ein offenes Netz, sonst WPA2, 2,4 GHz).
  3. Starten. Zum Beispiel ntp.abel öffnen und F9 drücken. Das Programm lädt den Dienst beim ersten WLAN-Aufruf selbst aus den Dateien des Boards, mit F9 genauso wie mit run ntp in der Konsole oder als Autostart.

Debuggen, während das WLAN läuft. Der Dienst und das WLAN laufen auf dem zweiten Kern des ESP32, das Programm auf dem ersten. Der Debugger hält nur den ersten an: Steht das Programm an einem Breakpoint, bleibt die Station verbunden, und was ankommt, wartet in einem Puffer des Dienstes (6 Frames). So kannst du durch einen DHCP-Austausch oder eine TCP-Verbindung Zeile für Zeile gehen.

Asmonite: ping.abel on the ESP32 at a breakpoint after the pings, the replies in the tab Serial, the variables of the network in Watch
ping.abel an einem Breakpoint nach den Pings: Die Verbindung steht weiter, im Reiter Serial die Antworten, rechts die Variablen des IP-Stacks (Adresse, Maske, Router, Lease).
FunktionWas sie tut
wifi_scan, wifi_connect, wifi_statusNetze suchen, sich verbinden (offen oder WPA2), den Zustand lesen
wifi_send, wifi_recvEthernet-Frames hinaus und herein
dhcp_runeine Adresse, die Maske, den Router und den DNS-Server vom Router holen
dns_lookupeinen Namen in eine Adresse auflösen
ping_sendeinen Ping schicken (Pings an das Board beantwortet es von selbst)
udp_send, net_recvUDP
tcp_connect, tcp_listen, tcp_accept, tcp_send, tcp_recv, tcp_closeTCP, eine Verbindung zur Zeit, als Client oder Server
net_set_name, mdns_startder Name des Boards: Der Router lernt ihn per DHCP, mDNS beantwortet name.local (ping esp32.local)
wifi_error_textein Fehler als Satz, zum Beispiel der Hinweis auf Install WiFi, wenn die Dateien fehlen

Die Funktionen stehen in inc\wifi.abel, inc\net.abel und inc\tcp.abel, in aBelle geschrieben: DHCP, DNS, ARP, ICMP, UDP, TCP und mDNS kannst du dort nachlesen und im Debugger verfolgen.

Die Beispiele

In examples\esp32 liegen die Beispiele in Assembler: blink, hello_uart (je für RAM und Flash), clock (der Kern mit 80, 160 und 240 MHz) und die Konsole. In examples\abelle\esp32 liegen die Beispiele in aBelle: blink, hello_uart, chipinfo, clock, timer, dice, reaction, sieve, temperature, und für das WLAN scan, dhcp, dns, ping, wol, arpscan, ntp, udpecho, httpget, webserver und telnetd. Die Register, die sie benutzen, stehen in ihren Kommentaren, mit der Quelle.

Alle Beispiele mit Beschreibung →

Wie der Speicher aufgeteilt ist

BereichWas dort liegt
Flash 0x001000die Konsole (oder das Programm von Write to Flash and Run)
Flash 0x100000 .. 0x152000der Teil des WLAN für den Flash (Code und Konstanten, über den Cache)
Flash 0x200000 .. Endedas Dateisystem der Konsole
IRAM 0x40080000 .. 0x40097FFFdas Programm (mit WLAN: sein Code bis 0x40093000)
IRAM 0x40094000der Dienst wlansvc, darüber wlan.rom
IRAM 0x4009C000 / 0x4009E000die Konsole / der Debug-Monitor
DRAM 0x3FFB0000 ..die Daten des Programms (mit WLAN: bis 0x3FFD8000), der Stack ab 0x3FFC0000 abwärts
DRAM 0x3FFD0000die Daten der Konsole
DRAM 0x3FFD8000 .. 0x3FFDFFFF, 0x3FFE8000 ..die Daten des WLAN und sein Heap

Wenn etwas nicht geht

Meldung oder AnzeichenWas zu tun ist
„No board with an ESP32 is plugged in“Das USB-Kabel (manche führen nur Strom), der Treiber des USB-Seriell-Chips
„… does not go into the download mode by itself: hold the BOOT button“BOOT gedrückt halten, bis das Programm läuft
„COM14 is open in another program“Das Terminal schließen, dann noch einmal F9
„The console does not answer“ (Board Files)Ein Programm läuft (Reset Board), oder die Konsole ist nicht im Flash (Write Console to Flash)
„flash read err, 1000“ am COM-PortDer Flash ist leer (ein neues oder gelöschtes Board): Write Console to Flash
„wifi_connect: error 2041 (wlan.rom or wlansvc is not on the board …)“Install WiFi in Board Files
„… the access point said no“ / „no answer of the access point“Das Passwort in inc\network.abel, der Name des Netzes, ein Netz mit 2,4 GHz in Reichweite
Das Board startet alle paar Sekunden neuEin Programm im Flash, das die Watchdogs nicht abschaltet
„Stopped: exception at …: load/store error“Ein Bytezugriff auf das IRAM, oder eine Adresse ohne Speicher

Was es (noch) nicht gibt

Weiter

Die Beispiele

Jedes Beispiel für den ESP32: was es zeigt, was es braucht, wie es startet.

Zu den Beispielen →

aBelle

Die Sprache mit der Schreibweise von Python, für den ESP32 und Windows (x86, x64).

Die aBelle-Seite →

Befehlsreferenz

Jeder Xtensa-Befehl mit Bitkodierung und Pseudocode, für den ESP32 und den ESP32-S3.

Xtensa →