MicroPython核心:移植MicroPython

MicroPython 项目包含多个针对不同微控制器系列和架构的移植。项目资源库中有一个 ports 目录,其中包含每个支持移植的子目录。

一个移植通常包含多个"板子"的定义,每个 "板子"都是该port可以运行的特定硬件,例如开发套件或设备。

最小移植是 MicroPython 移植的简化参考实现。它可以在主机系统和 STM32F4xx MCU 上运行。

一般来说,进行一个移植需要

  • 设置工具链(配置 Makefile 等)。
  • 执行启动配置和 CPU 初始化。
  • 初始化开发和调试所需的基本驱动程序(如 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 "py/stackctrl.h"
#include "shared/runtime/gchelper.h"
#include "shared/runtime/pyexec.h"

// 为MicroPython垃圾回收分配内存
static char heap[4096];

int main(int argc, char **argv) {
    // 初始化MicroPython运行时.
    mp_stack_ctrl_init();
    gc_init(heap, heap + sizeof(heap));
    mp_init();

    // 开启一个常规的REPL; 在空白行时按Ctrl+d退出
    pyexec_friendly_repl();

    // 反初始化运行时
    gc_sweep_all();
    mp_deinit();
    return 0;
}

// 处理未捕获的异常(在正确的 C 语言实现中应该永远不会出现)。
void nlr_jump_fail(void *val) {
    for (;;) {
    }
}

// 进行一次垃圾回收循环
void gc_collect(void) {
    gc_collect_start();
    gc_helper_collect_regs_and_stack();
    gc_collect_end();
}

// T没有文件系统,因此什么也不会返回
mp_import_stat_t mp_import_stat(const char *path) {
    return MP_IMPORT_STAT_NO_EXIST;
}

// 没有文件系统,因此打开文件会引发异常。
mp_lexer_t *mp_lexer_new_from_file(qstr filename) {
    mp_raise_OSError(MP_ENOENT);
}

切记使用适当的制表符缩进 Makefile。

MicroPython 配置

集成上述最小代码后,下一步就是为端口创建 MicroPython 配置文件mpconfigport.h中指定了编译时配置,mphalport.h中指定了额外的硬件抽象函数,如时间保持。

下面是mpconfigport.h文件的示例:

#include <stdint.h>

// Python 内部功能
#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)

// 对 Python 内置程序、类、模块等进行精细控制。
#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)

// 特定机器的类型定义。

typedef intptr_t mp_int_t; // 必须是指针大小
typedef uintptr_t mp_uint_t; // 必须是指针大小
typedef long mp_off_t;

// 需要提供 alloca() 的声明/定义
#include <alloca.h>

// 定义移植名称和硬件
#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) 则禁用该功能。

其他配置包括类型定义、根指针、电路板名称、微控制器名称等。

同样,一个最小的mphalport.h文件示例如下:

static inline void mp_hal_set_interrupt_char(char c) {}

支持标准输入/输出

MicroPython 至少需要一种输出字符的方法,而要有 REPL,还需要一种输入字符的方法。例如,可以在mphalport.c文件中实现相关函数:

#include <unistd.h>
#include "py/mpconfig.h"

// 接收单字符,阻塞直到有字符可用。
int mp_hal_stdin_rx_chr(void) {
    unsigned char c = 0;
    int r = read(STDIN_FILENO, &c, 1);
    (void)r;
    return c;
}

// 发送指定长度的字符串。
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,可能需要先将终端配置为原始模式:

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

这样就会出现一个 MicroPython REPL。然后就可以运行以下命令

MicroPython v1.13 on 2021-01-01; example-board with unknown-cpu
>>> import sys
>>> sys.implementation
('micropython', (1, 13, 0))
>>>

使用 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,在 SRC_C 列表中添加 modmyport.c,并在 SRC_QSTR 中添加新的一行(以便在这个新文件中搜索 qstrs),就像这样:

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

SRC_QSTR += modmyport.c

如果一切顺利,那么在重新构建之后,就可以导入新模块了:

>>> import myport
>>> myport.info()
info about my port
>>>
  • 17
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

原子星

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值