Portera MicroPython

MicroPython-projektet innehåller flera porteringar till olika mikrokontrollerfamiljer och arkitekturer. Projektets arkiv har en ports-katalog som innehåller en underkatalog för varje port som stöds.

En port innehåller vanligtvis definitioner för flera ”kort”, där vart och ett är en specifik del av hårdvaran som den porten kan köras på, t.ex. ett utvecklingskit eller en enhet.

Den minimala porten finns tillgänglig som en förenklad referensimplementering av en MicroPython-port. Den kan köras både på värdsystemet och på en STM32F4xx-MCU.

I allmänhet kräver det följande att starta en port:

  • Sätta upp verktygskedjan (konfigurera Makefiler osv).

  • Implementera startkonfiguration och CPU-initialisering.

  • Initiera grundläggande drivrutiner som krävs för utveckling och felsökning (t.ex. GPIO, UART).

  • Utföra de kortspecifika konfigurationerna.

  • Implementera de portspecifika modulerna.

Minimal MicroPython-firmware

Det bästa sättet att börja portera MicroPython till ett nytt kort är genom att integrera en minimal MicroPython-tolk. För denna genomgång, skapa en underkatalog för den nya porten i katalogen ports:

$ cd ports
$ mkdir example_port

Den grundläggande MicroPython-firmwaren implementeras i portens huvudfil, t.ex. 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);
}

Vi behöver också en Makefile vid det här laget för porten:

# 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

Kom ihåg att använda riktiga tabbar för att indentera Makefilen.

MicroPython-konfigurationer

Efter att ha integrerat den minimala koden ovan är nästa steg att skapa MicroPython-konfigurationsfilerna för porten. Konfigurationerna vid kompileringstid anges i mpconfigport.h och ytterligare funktioner för hårdvaruabstraktion, såsom tidshållning, i mphalport.h.

Följande är ett exempel på en mpconfigport.h-fil:

#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

Denna konfigurationsfil innehåller maskinspecifika konfigurationer inklusive aspekter som om olika MicroPython-funktioner ska aktiveras, t.ex. #define MICROPY_ENABLE_GC (1). Att sätta detta till (0) inaktiverar funktionen.

Andra konfigurationer inkluderar typdefinitioner, root-pekare, kortnamn, mikrokontrollernamn osv.

På liknande sätt ser en minimal exempelfil mphalport.h ut så här:

static inline void mp_hal_set_interrupt_char(char c) {}

Stöd för standard in-/utdata

MicroPython kräver åtminstone ett sätt att mata ut tecken, och för att ha en REPL krävs också ett sätt att mata in tecken. Funktioner för detta kan implementeras i filen mphalport.c, till exempel:

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

Dessa in- och utdatafunktioner måste modifieras beroende på det specifika kortets API. Detta exempel använder standardströmmen för in-/utdata.

Bygga och köra

I detta skede bör katalogen för den nya porten innehålla:

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

Porten kan nu byggas genom att köra make (eller på annat sätt, beroende på ditt system).

Om du använder standardkompilatorinställningarna i Makefilen ovan kommer detta att skapa en exekverbar fil som heter build/firmware.elf som kan köras direkt. För att få en fungerande REPL kan du behöva först konfigurera terminalen till råläge:

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

Det bör ge en MicroPython-REPL. Du kan sedan köra kommandon som:

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

Använd Ctrl-D för att avsluta, och kör sedan reset för att återställa terminalen.

Lägga till en modul i porten

För att lägga till en egen modul som myport, lägg först till moduldefinitionen i en fil 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);

Du måste också redigera Makefilen för att lägga till modmyport.c i SRC_C-listan, samt en ny rad som lägger till samma fil i SRC_QSTR (så att qstrs söks efter i denna nya fil), så här:

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

SRC_QSTR += modmyport.c

Om allt gick korrekt bör du, efter ombyggnad, kunna importera den nya modulen:

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