Porting di MicroPython¶
Il progetto MicroPython contiene diversi port per varie famiglie di microcontrollori e architetture. Il repository del progetto ha una directory ports contenente una sottodirectory per ogni port supportato.
Un port conterrà tipicamente le definizioni per più «board», ciascuna delle quali è uno specifico pezzo di hardware su cui quel port può essere eseguito, ad es. un kit di sviluppo o un dispositivo.
Il port minimal è disponibile come implementazione di riferimento semplificata di un port di MicroPython. Può essere eseguito sia sul sistema host che su un MCU STM32F4xx.
In generale, avviare un port richiede:
Configurare la toolchain (configurare i Makefile, ecc.).
Implementare la configurazione di avvio e l’inizializzazione della CPU.
Inizializzare i driver di base richiesti per lo sviluppo e il debug (ad es. GPIO, UART).
Effettuare le configurazioni specifiche della board.
Implementare i moduli specifici del port.
Firmware minimo di MicroPython¶
Il modo migliore per iniziare a portare MicroPython su una nuova board è integrando un interprete MicroPython minimo. Per questa guida pratica, crea una sottodirectory per il nuovo port nella directory ports:
$ cd ports
$ mkdir example_port
Il firmware di base di MicroPython è implementato nel file principale del port, ad es. 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);
}
A questo punto ci serve anche un Makefile per il 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
Ricorda di usare tab appropriati per indentare il Makefile.
Configurazioni di MicroPython¶
Dopo aver integrato il codice minimo di cui sopra, il passo successivo è creare i file di configurazione di MicroPython per il port. Le configurazioni di compilazione sono specificate in mpconfigport.h e le funzioni aggiuntive di astrazione dell’hardware, come la gestione del tempo, in mphalport.h.
Quello che segue è un esempio di file 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
Questo file di configurazione contiene configurazioni specifiche della macchina, inclusi aspetti come l’abilitazione o meno di diverse funzionalità di MicroPython, ad es. #define MICROPY_ENABLE_GC (1). Impostandolo a (0) la funzionalità viene disabilitata.
Altre configurazioni includono definizioni di tipi, root pointer, nome della board, nome del microcontrollore, ecc.
Analogamente, un esempio minimo di file mphalport.h ha questo aspetto:
static inline void mp_hal_set_interrupt_char(char c) {}
Supporto per standard input/output¶
MicroPython richiede almeno un modo per emettere caratteri, e per avere un REPL richiede anche un modo per immettere caratteri. Le funzioni per questo possono essere implementate nel file mphalport.c, ad esempio:
#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;
}
Queste funzioni di input e output devono essere modificate a seconda dell’API specifica della board. Questo esempio usa lo stream standard di input/output.
Compilazione ed esecuzione¶
A questo punto la directory del nuovo port dovrebbe contenere:
ports/example_port/
├── main.c
├── Makefile
├── mpconfigport.h
├── mphalport.c
└── mphalport.h
Il port può ora essere compilato eseguendo make (o altrimenti, a seconda del tuo sistema).
Se stai usando le impostazioni predefinite del compilatore nel Makefile fornito sopra, allora questo creerà un eseguibile chiamato build/firmware.elf che può essere eseguito direttamente. Per ottenere un REPL funzionante potresti dover prima configurare il terminale in modalità raw:
$ stty raw opost -echo
$ ./build/firmware.elf
Questo dovrebbe fornire un REPL di MicroPython. Puoi quindi eseguire comandi come:
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)
>>>
Usa Ctrl-D per uscire, e quindi esegui reset per reimpostare il terminale.
Aggiungere un modulo al port¶
Per aggiungere un modulo personalizzato come myport, aggiungi prima la definizione del modulo in un file 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);
Dovrai inoltre modificare il Makefile per aggiungere modmyport.c all’elenco SRC_C, e una nuova riga che aggiunge lo stesso file a SRC_QSTR (così che le qstr vengano cercate in questo nuovo file), in questo modo:
SRC_C = \
main.c \
modmyport.c \
mphalport.c \
...
SRC_QSTR += modmyport.c
Se tutto è andato correttamente, dopo la ricompilazione dovresti essere in grado di importare il nuovo modulo:
>>> import myport
>>> myport.info()
info about my port
>>>