Portierung von MicroPython¶
Das MicroPython-Projekt enthält mehrere Ports für verschiedene Mikrocontroller-Familien und -Architekturen. Das Projekt-Repository hat ein ports-Verzeichnis, das ein Unterverzeichnis für jeden unterstützten Port enthält.
Ein Port enthält typischerweise Definitionen für mehrere „Boards“, von denen jedes ein bestimmtes Stück Hardware ist, auf dem dieser Port laufen kann, z. B. ein Entwicklungskit oder ein Gerät.
Der Minimal-Port ist als vereinfachte Referenzimplementierung eines MicroPython-Ports verfügbar. Er kann sowohl auf dem Host-System als auch auf einem STM32F4xx-MCU laufen.
Im Allgemeinen erfordert der Beginn eines Ports:
Einrichten der Toolchain (Konfigurieren von Makefiles usw.).
Implementieren der Boot-Konfiguration und der CPU-Initialisierung.
Initialisieren grundlegender Treiber, die für Entwicklung und Debugging erforderlich sind (z. B. GPIO, UART).
Durchführen der board-spezifischen Konfigurationen.
Implementieren der port-spezifischen Module.
Minimale MicroPython-Firmware¶
Der beste Weg, mit der Portierung von MicroPython auf ein neues Board zu beginnen, ist die Integration eines minimalen MicroPython-Interpreters. Erstellen Sie für diese Anleitung ein Unterverzeichnis für den neuen Port im Verzeichnis ports:
$ cd ports
$ mkdir example_port
Die grundlegende MicroPython-Firmware wird in der Haupt-Port-Datei implementiert, z. B. main.c:
#include "py/builtin.h"
#include "py/compile.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "shared/runtime/gchelper.h"
#include "shared/runtime/pyexec.h"
// Allocate memory for the MicroPython GC heap.
static char heap[4096];
int main(int argc, char **argv) {
// Initialise the MicroPython runtime.
mp_cstack_init_with_sp_here(2048);
gc_init(heap, heap + sizeof(heap));
mp_init();
// Start a normal REPL; will exit when ctrl-D is entered on a blank line.
pyexec_friendly_repl();
// Deinitialise the runtime.
gc_sweep_all();
mp_deinit();
return 0;
}
// Handle uncaught exceptions (should never be reached in a correct C implementation).
void nlr_jump_fail(void *val) {
for (;;) {
}
}
// Do a garbage collection cycle.
void gc_collect(void) {
gc_collect_start();
gc_helper_collect_regs_and_stack();
gc_collect_end();
}
// There is no filesystem so stat'ing returns nothing.
mp_import_stat_t mp_import_stat(const char *path) {
return MP_IMPORT_STAT_NO_EXIST;
}
// There is no filesystem so opening a file raises an exception.
mp_lexer_t *mp_lexer_new_from_file(qstr filename) {
mp_raise_OSError(MP_ENOENT);
}
Wir benötigen an dieser Stelle außerdem ein Makefile für den Port:
# Include the core environment definitions; this will set $(TOP).
include ../../py/mkenv.mk
# Include py core make definitions.
include $(TOP)/py/py.mk
include $(TOP)/extmod/extmod.mk
# Set CFLAGS and libraries.
CFLAGS += -I. -I$(BUILD) -I$(TOP)
LIBS += -lm
# Define the required source files.
SRC_C = \
main.c \
mphalport.c \
shared/readline/readline.c \
shared/runtime/gchelper_generic.c \
shared/runtime/pyexec.c \
shared/runtime/stdout_helpers.c \
# Define source files containing qstrs.
SRC_QSTR += shared/readline/readline.c shared/runtime/pyexec.c
# Define the required object files.
OBJ = $(PY_CORE_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o))
# Define the top-level target, the main firmware.
all: $(BUILD)/firmware.elf
# Define how to build the firmware.
$(BUILD)/firmware.elf: $(OBJ)
$(ECHO) "LINK $@"
$(Q)$(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
$(Q)$(SIZE) $@
# Include remaining core make rules.
include $(TOP)/py/mkrules.mk
Denken Sie daran, korrekte Tabs zum Einrücken des Makefiles zu verwenden.
MicroPython-Konfigurationen¶
Nach der Integration des obigen minimalen Codes besteht der nächste Schritt darin, die MicroPython-Konfigurationsdateien für den Port zu erstellen. Die Konfigurationen zur Kompilierzeit werden in mpconfigport.h angegeben und zusätzliche Hardware-Abstraktionsfunktionen, wie etwa die Zeitmessung, in mphalport.h.
Das Folgende ist ein Beispiel für eine mpconfigport.h-Datei:
#include <stdint.h>
// Python internal features.
#define MICROPY_ENABLE_GC (1)
#define MICROPY_HELPER_REPL (1)
#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE)
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT)
// Fine control over Python builtins, classes, modules, etc.
#define MICROPY_PY_ASYNC_AWAIT (0)
#define MICROPY_PY_BUILTINS_SET (0)
#define MICROPY_PY_ATTRTUPLE (0)
#define MICROPY_PY_COLLECTIONS (0)
#define MICROPY_PY_MATH (0)
#define MICROPY_PY_IO (0)
#define MICROPY_PY_STRUCT (0)
// Type definitions for the specific machine.
typedef long mp_off_t;
// We need to provide a declaration/definition of alloca().
#include <alloca.h>
// Define the port's name and hardware.
#define MICROPY_HW_BOARD_NAME "example-board"
#define MICROPY_HW_MCU_NAME "unknown-cpu"
#define MP_STATE_PORT MP_STATE_VM
Diese Konfigurationsdatei enthält maschinenspezifische Konfigurationen, einschließlich Aspekten wie der Frage, ob verschiedene MicroPython-Funktionen aktiviert werden sollen, z. B. #define MICROPY_ENABLE_GC (1). Wenn man dies auf (0) setzt, wird die Funktion deaktiviert.
Weitere Konfigurationen umfassen Typdefinitionen, Root-Pointer, Board-Name, Mikrocontroller-Name usw.
Ebenso sieht eine minimale Beispiel-mphalport.h-Datei wie folgt aus:
static inline void mp_hal_set_interrupt_char(char c) {}
Unterstützung für Standard-Ein-/Ausgabe¶
MicroPython benötigt mindestens eine Möglichkeit, Zeichen auszugeben, und um eine REPL zu haben, benötigt es auch eine Möglichkeit, Zeichen einzugeben. Funktionen hierfür können in der Datei mphalport.c implementiert werden, zum Beispiel:
#include <unistd.h>
#include "py/mpconfig.h"
// Receive single character, blocking until one is available.
int mp_hal_stdin_rx_chr(void) {
unsigned char c = 0;
int r = read(STDIN_FILENO, &c, 1);
(void)r;
return c;
}
// Send the string of given length.
void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
int r = write(STDOUT_FILENO, str, len);
(void)r;
}
Diese Ein- und Ausgabefunktionen müssen je nach der spezifischen Board-API angepasst werden. Dieses Beispiel verwendet den Standard-Ein-/Ausgabestream.
Erstellen und Ausführen¶
An diesem Punkt sollte das Verzeichnis des neuen Ports Folgendes enthalten:
ports/example_port/
├── main.c
├── Makefile
├── mpconfigport.h
├── mphalport.c
└── mphalport.h
Der Port kann nun durch Ausführen von make erstellt werden (oder anderweitig, abhängig von Ihrem System).
Wenn Sie die Standard-Compiler-Einstellungen im oben angegebenen Makefile verwenden, wird dadurch eine ausführbare Datei namens build/firmware.elf erstellt, die direkt ausgeführt werden kann. Um eine funktionsfähige REPL zu erhalten, müssen Sie möglicherweise zunächst das Terminal in den Raw-Modus konfigurieren:
$ stty raw opost -echo
$ ./build/firmware.elf
Das sollte eine MicroPython-REPL ergeben. Sie können dann Befehle wie diese ausführen:
MicroPython v1.26.0-preview on 2025-08-01; minimal with unknown-cpu
>>> def sum(n, m):
... return n + m
...
>>> 3, 4, sum(3, 4)
(3, 4, 7)
>>>
Verwenden Sie Strg-D zum Beenden und führen Sie dann reset aus, um das Terminal zurückzusetzen.
Hinzufügen eines Moduls zum Port¶
Um ein benutzerdefiniertes Modul wie myport hinzuzufügen, fügen Sie zunächst die Moduldefinition in einer Datei modmyport.c hinzu:
#include "py/runtime.h"
static mp_obj_t myport_info(void) {
mp_printf(&mp_plat_print, "info about my port\n");
return mp_const_none;
}
static MP_DEFINE_CONST_FUN_OBJ_0(myport_info_obj, myport_info);
static const mp_rom_map_elem_t myport_module_globals_table[] = {
{ MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(MP_QSTR_myport) },
{ MP_ROM_QSTR(MP_QSTR_info), MP_ROM_PTR(&myport_info_obj) },
};
static MP_DEFINE_CONST_DICT(myport_module_globals, myport_module_globals_table);
const mp_obj_module_t myport_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t *)&myport_module_globals,
};
MP_REGISTER_MODULE(MP_QSTR_myport, myport_module);
Sie müssen außerdem das Makefile bearbeiten, um modmyport.c zur SRC_C-Liste hinzuzufügen, sowie eine neue Zeile, die dieselbe Datei zu SRC_QSTR hinzufügt (damit in dieser neuen Datei nach qstrs gesucht wird), wie folgt:
SRC_C = \
main.c \
modmyport.c \
mphalport.c \
...
SRC_QSTR += modmyport.c
Wenn alles korrekt verlaufen ist, sollten Sie nach dem erneuten Erstellen in der Lage sein, das neue Modul zu importieren:
>>> import myport
>>> myport.info()
info about my port
>>>