Portarea MicroPython

Proiectul MicroPython conține mai multe porturi către diferite familii de microcontrolere și arhitecturi. Depozitul proiectului are un director ports care conține un subdirector pentru fiecare port acceptat.

Un port va conține de obicei definiții pentru mai multe „plăci”, fiecare dintre acestea fiind o anumită bucată de hardware pe care portul respectiv poate rula, de ex. un kit de dezvoltare sau un dispozitiv.

Portul minimal este disponibil ca o implementare de referință simplificată a unui port MicroPython. Acesta poate rula atât pe sistemul gazdă, cât și pe un MCU STM32F4xx.

În general, începerea unui port necesită:

  • Configurarea lanțului de instrumente (configurarea Makefile-urilor etc.).

  • Implementarea configurației de pornire și inițializarea CPU-ului.

  • Inițializarea driverelor de bază necesare pentru dezvoltare și depanare (de ex. GPIO, UART).

  • Efectuarea configurărilor specifice plăcii.

  • Implementarea modulelor specifice portului.

Firmware MicroPython minimal

Cea mai bună modalitate de a începe portarea MicroPython către o placă nouă este integrarea unui interpretor MicroPython minimal. Pentru acest tutorial, creați un subdirector pentru noul port în directorul ports:

$ cd ports
$ mkdir example_port

Firmware-ul MicroPython de bază este implementat în fișierul principal al portului, de 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);
}

În acest moment avem de asemenea nevoie de un Makefile pentru 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

Nu uitați să folosiți taburi corespunzătoare pentru a indenta Makefile-ul.

Configurații MicroPython

După integrarea codului minimal de mai sus, următorul pas este crearea fișierelor de configurare MicroPython pentru port. Configurațiile de la momentul compilării sunt specificate în mpconfigport.h, iar funcțiile suplimentare de abstractizare a hardware-ului, precum cele de cronometrare, în mphalport.h.

Următorul este un exemplu de fișier mpconfigport.h:

#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

Acest fișier de configurare conține configurări specifice mașinii, inclusiv aspecte precum dacă diferite caracteristici MicroPython ar trebui activate, de ex. #define MICROPY_ENABLE_GC (1). Setarea acestuia la (0) dezactivează caracteristica.

Alte configurări includ definiții de tipuri, pointeri rădăcină, numele plăcii, numele microcontrolerului etc.

În mod similar, un exemplu minimal de fișier mphalport.h arată astfel:

static inline void mp_hal_set_interrupt_char(char c) {}

Suport pentru intrarea/ieșirea standard

MicroPython necesită cel puțin o modalitate de a afișa caractere, iar pentru a avea un REPL necesită de asemenea o modalitate de a introduce caractere. Funcțiile pentru aceasta pot fi implementate în fișierul mphalport.c, de exemplu:

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

Aceste funcții de intrare și ieșire trebuie modificate în funcție de API-ul specific al plăcii. Acest exemplu folosește fluxul standard de intrare/ieșire.

Construirea și rularea

În această etapă, directorul noului port ar trebui să conțină:

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

Portul poate fi acum construit prin rularea make (sau altfel, în funcție de sistemul vostru).

Dacă folosiți setările implicite ale compilatorului din Makefile-ul dat mai sus, atunci aceasta va crea un executabil numit build/firmware.elf care poate fi executat direct. Pentru a obține un REPL funcțional, este posibil să fie nevoie mai întâi să configurați terminalul în modul brut (raw):

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

Aceasta ar trebui să vă ofere un REPL MicroPython. Puteți rula apoi comenzi precum:

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

Folosiți Ctrl-D pentru a ieși, iar apoi rulați reset pentru a reseta terminalul.

Adăugarea unui modul la port

Pentru a adăuga un modul personalizat precum myport, adăugați mai întâi definiția modulului într-un fișier 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);

Va trebui de asemenea să editați Makefile-ul pentru a adăuga modmyport.c la lista SRC_C și o linie nouă care adaugă același fișier la SRC_QSTR (astfel încât qstr-urile să fie căutate în acest fișier nou), astfel:

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

SRC_QSTR += modmyport.c

Dacă totul a decurs corect, atunci, după reconstruire, ar trebui să puteți importa noul modul:

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