MicroPython porten

Het MicroPython-project bevat verschillende ports naar uiteenlopende microcontrollerfamilies en architecturen. De projectrepository heeft een ports-map met een submap voor elke ondersteunde port.

Een port bevat doorgaans definities voor meerdere “boards”, die elk een specifiek stuk hardware zijn waarop die port kan draaien, bv. een ontwikkelkit of apparaat.

De minimal port is beschikbaar als een vereenvoudigde referentie-implementatie van een MicroPython-port. Hij kan zowel op het hostsysteem als op een STM32F4xx-MCU draaien.

Over het algemeen vereist het starten van een port:

  • Het opzetten van de toolchain (het configureren van Makefiles, enz.).

  • Het implementeren van bootconfiguratie en CPU-initialisatie.

  • Het initialiseren van basisdrivers die nodig zijn voor ontwikkeling en debugging (bv. GPIO, UART).

  • Het uitvoeren van de board-specifieke configuraties.

  • Het implementeren van de port-specifieke modules.

Minimale MicroPython-firmware

De beste manier om te beginnen met het porten van MicroPython naar een nieuw board is door een minimale MicroPython-interpreter te integreren. Maak voor deze walkthrough een submap voor de nieuwe port aan in de map ports:

$ cd ports
$ mkdir example_port

De basis-MicroPython-firmware wordt geïmplementeerd in het hoofdportbestand, bv. 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);
}

We hebben op dit punt ook een Makefile nodig voor de 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

Vergeet niet om correcte tabs te gebruiken om de Makefile in te springen.

MicroPython-configuraties

Na het integreren van de minimale code hierboven is de volgende stap het aanmaken van de MicroPython-configuratiebestanden voor de port. De compile-time-configuraties worden gespecificeerd in mpconfigport.h en aanvullende hardware-abstractiefuncties, zoals tijdregistratie, in mphalport.h.

Het volgende is een voorbeeld van een mpconfigport.h-bestand:

#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

Dit configuratiebestand bevat machine-specifieke configuraties, waaronder aspecten zoals of verschillende MicroPython-functies moeten worden ingeschakeld, bv. #define MICROPY_ENABLE_GC (1). Door dit op (0) te zetten wordt de functie uitgeschakeld.

Andere configuraties omvatten typedefinities, root pointers, boardnaam, microcontrollernaam, enz.

Op vergelijkbare wijze ziet een minimaal voorbeeld van een mphalport.h-bestand er als volgt uit:

static inline void mp_hal_set_interrupt_char(char c) {}

Ondersteuning voor standaardinvoer/-uitvoer

MicroPython vereist ten minste een manier om karakters uit te voeren, en om een REPL te hebben vereist het ook een manier om karakters in te voeren. Functies hiervoor kunnen worden geïmplementeerd in het bestand mphalport.c, bijvoorbeeld:

#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;
}

Deze invoer- en uitvoerfuncties moeten worden aangepast afhankelijk van de specifieke board-API. Dit voorbeeld gebruikt de standaardinvoer-/uitvoerstroom.

Bouwen en uitvoeren

In dit stadium zou de map van de nieuwe port het volgende moeten bevatten:

ports/example_port/
├── main.c
├── Makefile
├── mpconfigport.h
├── mphalport.c
└── mphalport.h

De port kan nu worden gebouwd door make uit te voeren (of anderszins, afhankelijk van je systeem).

Als je de standaard compiler-instellingen in de hierboven gegeven Makefile gebruikt, dan maakt dit een uitvoerbaar bestand genaamd build/firmware.elf aan dat rechtstreeks kan worden uitgevoerd. Om een functionele REPL te krijgen moet je mogelijk eerst de terminal in raw mode configureren:

$ stty raw opost -echo
$ ./build/firmware.elf

Dat zou een MicroPython-REPL moeten geven. Je kunt dan commando’s uitvoeren zoals:

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)
>>>

Gebruik Ctrl-D om af te sluiten, en voer vervolgens reset uit om de terminal te resetten.

Een module aan de port toevoegen

Om een aangepaste module zoals myport toe te voegen, voeg je eerst de moduledefinitie toe in een bestand modmyport.c:

#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);

Je moet ook de Makefile bewerken om modmyport.c toe te voegen aan de SRC_C-lijst, en een nieuwe regel die hetzelfde bestand toevoegt aan SRC_QSTR (zodat er in dit nieuwe bestand naar qstrs wordt gezocht), zoals dit:

SRC_C = \
    main.c \
    modmyport.c \
    mphalport.c \
    ...

SRC_QSTR += modmyport.c

Als alles correct verliep, zou je na het opnieuw bouwen de nieuwe module moeten kunnen importeren:

>>> import myport
>>> myport.info()
info about my port
>>>