Chuyển MicroPython sang nền tảng mới

Dự án MicroPython chứa nhiều cổng (port) cho các gia đình vi điều khiển và kiến trúc khác nhau. Kho lưu trữ dự án có một thư mục ports chứa một thư mục con cho mỗi cổng được hỗ trợ.

Một cổng thường sẽ chứa các định nghĩa cho nhiều "bo mạch", mỗi bo mạch là một phần cứng cụ thể mà cổng đó có thể chạy trên, ví dụ một bộ phát triển hoặc thiết bị.

Cổng minimal có sẵn như một triển khai tham chiếu đơn giản hóa của cổng MicroPython. Nó có thể chạy trên cả hệ thống máy chủ và MCU STM32F4xx.

Nhìn chung, việc bắt đầu một cổng yêu cầu:

  • Thiết lập chuỗi công cụ (cấu hình Makefile, v.v.).

  • Triển khai cấu hình khởi động và khởi tạo CPU.

  • Khởi tạo các driver cơ bản cần thiết cho việc phát triển và gỡ lỗi (ví dụ GPIO, UART).

  • Thực hiện các cấu hình dành riêng cho bo mạch.

  • Triển khai các module dành riêng cho cổng.

Firmware MicroPython tối giản

Cách tốt nhất để bắt đầu chuyển MicroPython sang một bo mạch mới là tích hợp một trình thông dịch MicroPython tối giản. Trong hướng dẫn này, hãy tạo một thư mục con cho cổng mới trong thư mục ports:

$ cd ports
$ mkdir example_port

Firmware MicroPython cơ bản được triển khai trong tệp cổng chính, ví dụ 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);
}

Chúng ta cũng cần một Makefile tại thời điểm này cho cổng:

# 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

Hãy nhớ sử dụng tab đúng cách để thụt lề Makefile.

Cấu hình MicroPython

Sau khi tích hợp mã tối giản ở trên, bước tiếp theo là tạo các tệp cấu hình MicroPython cho cổng. Các cấu hình tại thời điểm biên dịch được chỉ định trong mpconfigport.h và các hàm trừu tượng hóa phần cứng bổ sung, chẳng hạn như giữ thời gian, trong mphalport.h.

Sau đây là một ví dụ về tệp 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

Tệp cấu hình này chứa các cấu hình dành riêng cho máy bao gồm các khía cạnh như việc có nên bật các tính năng MicroPython khác nhau hay không, ví dụ #define MICROPY_ENABLE_GC (1). Đặt giá trị này thành (0) sẽ vô hiệu hóa tính năng.

Các cấu hình khác bao gồm định nghĩa kiểu, con trỏ gốc, tên bo mạch, tên vi điều khiển v.v.

Tương tự, một ví dụ tối giản về tệp mphalport.h trông như thế này:

static inline void mp_hal_set_interrupt_char(char c) {}

Hỗ trợ nhập/xuất chuẩn

MicroPython yêu cầu ít nhất một cách để xuất các ký tự và để có REPL, nó cũng yêu cầu một cách để nhập các ký tự. Các hàm cho điều này có thể được triển khai trong tệp mphalport.c, ví dụ:

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

Các hàm nhập và xuất này phải được sửa đổi tùy thuộc vào API bo mạch cụ thể. Ví dụ này sử dụng luồng nhập/xuất chuẩn.

Xây dựng và chạy

Ở giai đoạn này, thư mục của cổng mới phải chứa:

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

Cổng này giờ có thể được xây dựng bằng cách chạy make (hoặc theo cách khác, tùy thuộc vào hệ thống của bạn).

Nếu bạn đang sử dụng cài đặt trình biên dịch mặc định trong Makefile đã nêu ở trên thì thao tác này sẽ tạo ra một tệp thực thi gọi là build/firmware.elf có thể được thực thi trực tiếp. Để có REPL hoạt động, bạn có thể cần cấu hình terminal ở chế độ raw trước:

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

Điều đó sẽ cung cấp REPL cho MicroPython. Sau đó bạn có thể chạy các lệnh như:

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

Sử dụng Ctrl-D để thoát, sau đó chạy reset để đặt lại terminal.

Thêm module vào cổng

Để thêm một module tùy chỉnh như myport, trước tiên hãy thêm định nghĩa module trong tệp 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);

Bạn cũng sẽ cần chỉnh sửa Makefile để thêm modmyport.c vào danh sách SRC_C, và một dòng mới thêm cùng tệp đó vào SRC_QSTR (để các qstr được tìm kiếm trong tệp mới này), như thế này:

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

SRC_QSTR += modmyport.c

Nếu mọi thứ đều ổn, sau khi xây dựng lại, bạn có thể nhập module mới:

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