MicroPythonin porttaaminen

MicroPython-projekti sisältää useita portteja eri mikrokontrolleriperheille ja arkkitehtuureille. Projektin repositoriossa on ports-hakemisto, joka sisältää alihakemiston jokaiselle tuetulle portille.

Portti sisältää tyypillisesti määritelmät useille ”korteille” (boards), joista jokainen on tietty laite, jolla kyseinen portti voi toimia, esim. kehityssarja tai laite.

Minimaalinen portti on saatavilla yksinkertaistettuna MicroPython-portin viitetoteutuksena. Se voi toimia sekä isäntäjärjestelmässä että STM32F4xx-mikrokontrollerilla.

Yleisesti ottaen portin aloittaminen vaatii:

  • Työkaluketjun asettaminen (Makefilejen määrittäminen jne.).

  • Käynnistyskokoonpanon ja suorittimen alustuksen toteuttaminen.

  • Kehitykseen ja virheenkorjaukseen tarvittavien perusajureiden alustaminen (esim. GPIO, UART).

  • Korttikohtaisten kokoonpanojen suorittaminen.

  • Porttikohtaisten moduulien toteuttaminen.

Minimaalinen MicroPython-laiteohjelmisto

Paras tapa aloittaa MicroPythonin porttaaminen uudelle kortille on integroida minimaalinen MicroPython-tulkki. Tätä läpikäyntiä varten luo uudelle portille alihakemisto ports-hakemistoon:

$ cd ports
$ mkdir example_port

Perus-MicroPython-laiteohjelmisto toteutetaan portin pääasiallisessa lähdetiedostossa, esim. 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);
}

Tässä vaiheessa tarvitsemme myös portille Makefilen:

# 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

Muista käyttää oikeita sarkaimia Makefilen sisennyksessä.

MicroPython-kokoonpanot

Yllä olevan minimaalisen koodin integroinnin jälkeen seuraava vaihe on luoda portille MicroPython-kokoonpanotiedostot. Käännösaikaiset kokoonpanot määritellään tiedostossa mpconfigport.h ja lisälaitteistoabstraktiofunktiot, kuten ajanpito, tiedostossa mphalport.h.

Seuraavassa on esimerkki mpconfigport.h-tiedostosta:

#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

Tämä kokoonpanotiedosto sisältää konekohtaisia kokoonpanoja, mukaan lukien sellaisia näkökohtia kuin se, pitäisikö eri MicroPython-ominaisuudet ottaa käyttöön, esim. #define MICROPY_ENABLE_GC (1). Tämän asettaminen arvoon (0) poistaa ominaisuuden käytöstä.

Muita kokoonpanoja ovat tyyppimääritykset, juuriosoittimet, kortin nimi, mikrokontrollerin nimi jne.

Vastaavasti minimaalinen esimerkki mphalport.h-tiedostosta näyttää tältä:

static inline void mp_hal_set_interrupt_char(char c) {}

Standardisyötteen/-tulosteen tuki

MicroPython vaatii vähintään tavan tulostaa merkkejä, ja jotta sillä olisi REPL, se vaatii myös tavan syöttää merkkejä. Tähän tarkoitetut funktiot voidaan toteuttaa tiedostossa mphalport.c, esimerkiksi:

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

Näitä syöte- ja tulostefunktioita on muokattava kortin tietyn API:n mukaan. Tässä esimerkissä käytetään standardia syöte-/tulostevirtaa.

Rakentaminen ja ajaminen

Tässä vaiheessa uuden portin hakemiston pitäisi sisältää:

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

Portin voi nyt rakentaa ajamalla make (tai muulla tavoin, järjestelmästäsi riippuen).

Jos käytät yllä annetun Makefilen oletuskääntäjäasetuksia, tämä luo suoritettavan tiedoston nimeltä build/firmware.elf, jonka voi suorittaa suoraan. Saadaksesi toimivan REPL:n sinun on ehkä ensin määritettävä pääte raakatilaan (raw mode):

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

Sen pitäisi antaa MicroPython-REPL. Voit sitten ajaa komentoja kuten:

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

Käytä Ctrl-D poistuaksesi ja aja sitten reset nollataksesi päätteen.

Moduulin lisääminen porttiin

Lisätäksesi mukautetun moduulin kuten myport lisää ensin moduulin määritelmä tiedostoon 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);

Sinun on myös muokattava Makefilea lisätäksesi modmyport.c SRC_C-luetteloon, ja lisättävä uusi rivi, joka lisää saman tiedoston SRC_QSTR-luetteloon (jotta qstr-merkkijonoja etsitään tästä uudesta tiedostosta), näin:

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

SRC_QSTR += modmyport.c

Jos kaikki meni oikein, sinun pitäisi uudelleenrakentamisen jälkeen pystyä tuomaan uusi moduuli:

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