הסבת (porting) MicroPython¶
פרויקט MicroPython מכיל מספר פורטים (ports) למשפחות מיקרו-בקרים וארכיטקטורות שונות. למאגר הפרויקט יש תיקיית ports המכילה תת-תיקייה עבור כל פורט נתמך.
פורט יכיל בדרך כלל הגדרות עבור מספר ”לוחות“ (boards), שכל אחד מהם הוא חומרה ספציפית שעליה הפורט יכול לרוץ, למשל ערכת פיתוח או התקן.
ה-פורט המינימלי זמין כמימוש ייחוס מפושט של פורט MicroPython. הוא יכול לרוץ הן על מערכת המארח והן על מיקרו-בקר STM32F4xx.
באופן כללי, התחלת פורט דורשת:
הקמת שרשרת הכלים (toolchain) (הגדרת קובצי Makefile וכו«).
מימוש תצורת אתחול ואתחול המעבד.
אתחול מנהלי התקנים בסיסיים הנדרשים לפיתוח ולניפוי באגים (למשל GPIO, UART).
ביצוע התצורות הספציפיות ללוח.
מימוש המודולים הספציפיים לפורט.
קושחת MicroPython מינימלית¶
הדרך הטובה ביותר להתחיל בהסבת MicroPython ללוח חדש היא על ידי שילוב מפרש MicroPython מינימלי. עבור הדרכה זו, צרו תת-תיקייה עבור הפורט החדש בתיקייה ports:
$ cd ports
$ mkdir example_port
קושחת ה-MicroPython הבסיסית ממומשת בקובץ הפורט הראשי, למשל 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);
}
בשלב זה אנו זקוקים גם ל-Makefile עבור הפורט:
# 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
זכרו להשתמש בטאבים מתאימים להזחת ה-Makefile.
תצורות MicroPython¶
לאחר שילוב הקוד המינימלי לעיל, השלב הבא הוא יצירת קובצי תצורת ה-MicroPython עבור הפורט. תצורות זמן-ההידור מצוינות ב-mpconfigport.h ופונקציות הפשטת-חומרה נוספות, כגון שמירת זמן, ב-mphalport.h.
להלן דוגמה לקובץ 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
קובץ תצורה זה מכיל תצורות ספציפיות-מכונה כולל היבטים כמו האם יש לאפשר תכונות MicroPython שונות, למשל #define MICROPY_ENABLE_GC (1). הגדרת הערך ל-(0) מנטרלת את התכונה.
תצורות אחרות כוללות הגדרות טיפוסים, מצביעי שורש (root pointers), שם הלוח, שם המיקרו-בקר וכו«.
באופן דומה, דוגמה מינימלית לקובץ mphalport.h נראית כך:
static inline void mp_hal_set_interrupt_char(char c) {}
תמיכה בקלט/פלט סטנדרטי¶
MicroPython דורש לכל הפחות דרך לפלוט תווים, וכדי שיהיה REPL הוא דורש גם דרך לקלוט תווים. ניתן לממש פונקציות לכך בקובץ mphalport.c, למשל:
#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;
}
פונקציות קלט ופלט אלו צריכות להשתנות בהתאם ל-API הספציפי של הלוח. דוגמה זו משתמשת בזרם הקלט/פלט הסטנדרטי.
בנייה והרצה¶
בשלב זה תיקיית הפורט החדש אמורה להכיל:
ports/example_port/
├── main.c
├── Makefile
├── mpconfigport.h
├── mphalport.c
└── mphalport.h
כעת ניתן לבנות את הפורט על ידי הרצת make (או בדרך אחרת, בהתאם למערכת שלכם).
אם אתם משתמשים בהגדרות המהדר ברירת המחדל ב-Makefile שניתן לעיל, אזי הדבר ייצור קובץ הרצה בשם build/firmware.elf שניתן להריץ ישירות. כדי לקבל REPL פונקציונלי ייתכן שתצטרכו תחילה להגדיר את הטרמינל למצב גולמי (raw):
$ stty raw opost -echo
$ ./build/firmware.elf
זה אמור לתת REPL של MicroPython. לאחר מכן תוכלו להריץ פקודות כמו:
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)
>>>
השתמשו ב-Ctrl-D כדי לצאת, ולאחר מכן הריצו reset כדי לאפס את הטרמינל.
הוספת מודול לפורט¶
כדי להוסיף מודול מותאם אישית כמו myport, תחילה הוסיפו את הגדרת המודול בקובץ 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);
תצטרכו גם לערוך את ה-Makefile כדי להוסיף את modmyport.c לרשימת SRC_C, ושורה חדשה המוסיפה את אותו קובץ ל-SRC_QSTR (כך שיתבצע חיפוש qstrs בקובץ חדש זה), כך:
SRC_C = \
main.c \
modmyport.c \
mphalport.c \
...
SRC_QSTR += modmyport.c
אם הכול התבצע כראוי אזי, לאחר בנייה מחדש, אתם אמורים להיות מסוגלים לייבא את המודול החדש:
>>> import myport
>>> myport.info()
info about my port
>>>