ZYNQ -- 裸机移植 micropython

ZYNQ -- 裸机移植 micropython

H_Haozi Lv3

A 前言

这阵子做一个项目,需要用到ZYNQ,使用的环境是Xilinx + Vivado 2018.3 SDK开发,开发板是ZYNQ的7010/7020系列,这里主要记录移植micropython过程,移植过程还使用了 xftp(虚拟机传输文件) vscode(ssh插件远程连接) 任意串口调试助手 VMware(虚拟机 使用的是ubuntu16.04)
(ps:不是FPGA方向)


B 工程模版相关

这里的工程模版使用的是串口+GPIO输出即可(我这里选择的是一个串口,和三位的LED输出,大家可以参考之间的ZYNQ文章配置(ps 也有可能这个文章还能写完),注意要测试好串口能成功输出): (ps:建立在能够配置好引脚并导出到SDK开发的前提下)

奥对,这里简单介绍一下micropython:

MicroPython​ 是 Python 3 语言的精简高效实现,专为微控制器和资源受限的嵌入式设备设计。它由 Damien P. George 于 2013 年发起,并在 Kickstarter 上成功众筹,如今已成为物联网、教育、快速原型开发等领域的热门工具。其核心特点包括:

  • 语法兼容 Python 3:支持 Python 3.4+ 的主要语法特性(如异常处理、with/yield from/async/await等),内置常用数据类型(str、bytes、list、dict、set等)和标准库模块(sys、time、struct等)。

  • 交互式解释器(REPL):通过串口连接可直接进入交互环境,实时执行代码、调试外设,大幅降低开发门槛。

  • 硬件抽象层:对 GPIO、UART、I2C、SPI、PWM、ADC 等常见外设提供统一 API(主要通过 machine模块),兼顾易用性与底层控制能力。

  • 跨平台支持:已适配 STM32、ESP32/8266、Raspberry Pi Pico(RP2040)、K210/K230 等主流 MCU,官方提供多版本预编译固件

这里因为是ZYNQ的裸机移植(双核ARM Cortex-A9),所以我们选择最简单的ARM系列的模版即可


C 移植过程

C.1 获取micropython的源码

这里要先获取源码,必须要安装git(大家可以自行查找教程),当然也可以去github在浏览器下载压缩包再传给虚拟机也行

1
git clonehttps://github.com/micropython/micropython 

然后就可以得到一个文件夹,主要介绍我们待会要使用的几个文件:

  • ports 针对不同微控制器和系统的移植代码,是开发和应用的主要入口

  • lib 存放芯片原厂SDK、底层驱动库等第三方库代码,为各端口提供底层支持

  • py MicroPython的核心实现,包括编译器、运行时和核心库

下面我们选择ports文件夹里面的bare-arm 这是一个极其精简的版本,我们在这基础上慢慢添加代码,也可以更好的理解移植过程

下面来介绍这里我们需要修改的文件:

配置文件: mpconfigport.h, mphalport.h 第一个文件是配置宏定义,第二个文件是我移植GPIO需要使用的接口函数定义
核心代码 main.c, lib.c, system.c 分别是入口,依赖和配置文件,后面我们移植的端口都写在后面创建的mphalport.c文件里面,main.c我们使用创建ZYNQ工程时的即可,后期修改main.c为pymain.c,里面修改成交互的python代码入口
构建文件 Makefile,编译生成qstr文件需要使用 注意这里的ld链接文件是给stm32使用的,我们移植到ZYNQ的SDK编译器上,是不需用这个文件的,不用管它就行


C.2 根据模版修改配置文件

现在开始修改配置文件,这里我们只保留最简的功能即可,也就是简单的python的代码运行,通过串口交互,GPIO控制。不包含操作系统,文件系统等

我们先修改 mpconfigport.H 修改成下面的文件,具体内容都有注释

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include <stdint.h>

// 关闭 micropython 内置模块(避免 extmod 依赖)
#define MICROPY_PY_MICROPYTHON (0) // 禁用 micropython 模块
#define MICROPY_PY_BUILTINS (1) // 启用内置函数和异常
#define MICROPY_PY_GC (1) // 启用垃圾回收模块
#define MICROPY_ENABLE_GC (1) // 启用垃圾回收功能,否则变量功能无法使用

// 控制 MicroPython 构建方式的选项

// 使用最小化的起始配置(禁用所有可选功能)
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_MINIMUM)

// 编译器配置
#define MICROPY_ENABLE_COMPILER (1) // 启用编译器

// Python 内部特性
#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) // 详细错误报告
#define MICROPY_CPYTHON_COMPAT (0) // 不与 CPython 严格兼容以节省空间
#define MICROPY_DEBUG_PRINTERS (1) // 启用调试打印机

// 精细控制 Python 内置功能、类、模块等
#define MICROPY_PY_SYS (0) // 禁用 sys 模块

// 为 GPIO 控制添加的配置
#define MICROPY_PY_MACHINE (1) // 启用 machine 模块(包含GPIO控制)
#define MICROPY_PY_MACHINE_PIN_MAKE_NEW (1) // 启用创建新 Pin 对象的功能
#define MICROPY_PY_MACHINE_PIN_READ_METHOD (1) // 启用引脚读取方法
#define MICROPY_PY_MACHINE_PIN_WRITE_METHOD (1) // 启用引脚写入方法

// 特定机器的类型定义
typedef long mp_off_t;

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

C.3 编译源码,得到QSTR输出文件

如果直接移植,因为我们是移植到SDK开发,所以会缺少一点输出文件,所以这里我们在Linux环境下(win也行),首先在主目录下执行下面代码

1
2
3
4
# 在 micropython/ 根目录下执行,编译 mpy-cross交叉编译器,这是后续编译端口固件的前提
make -C mpy-cross
cd ports/bare-arm/
make

然后就会在ports/bare-arm/ 目前下生成一个build文件,其中build/genhdr/qstrdefs.generated.h 文件是我需要的输出文件,否则后面编译会报错,注意后面如果修改了配置文件,也需要重新编译生成这个输出文件

这里在源码方面的准备就结束了,我们下面将源码导入SDK中


C.4 在SDK中导入源码并修改

将工程创建完成之后,先测试简单的串口输出和点灯是否正常(创建一个最简的工程),如果正常再进行下面的步骤

首先右键工程,-New-Floder 添加一个文件夹:MicroPython

然后右键添加的文件夹,选择Import-General-File-System 然后店家上面的BroWse,选择分别添加下面列出的文件夹,最后点击Finish添加

这里需要添加的文件夹: 主文件夹下的: lib py 以及 ports/bare-arm

添加完毕之后目录结构如图

下面会报错,显示找不到头文件等错误,所以这里我们添加一下环境路径(如图所示,右键工程,然后按照图中选择刚刚添加的路径以及根目录),这样路径报错就解决了

添加完毕之后如下图所示,如果还有路径有问题,比如说有的地方包含了头文件还是没有效果,可以尝试把左侧的Debug文件删除重新编译试试,或者修改头文件为完整路径名

然后编译会出现下面的报错,因为我们并没有包含extmod文件,这里我们通过SDK来自己实现有关GPIO的完整的HAL,也就是mp_hal_pin_write等函数,我们在创建一个文件bare-arm/mphalport.c中添加对GPIO的操作以及在mphalport中添加定义,实现简单的定时实现等,先给出空实现,让编译通过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#ifndef __MPHALPORT_H
#define __MPHALPORT_H

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

// 时间控制函数
mp_uint_t mp_hal_ticks_ms(void);

mp_uint_t mp_hal_ticks_us(void);
mp_uint_t mp_hal_ticks_cpu(void);
void mp_hal_delay_ms(mp_uint_t ms);
void mp_hal_delay_us(mp_uint_t us);
// 标准输入输出函数
int mp_hal_stdin_rx_chr(void);

void mp_hal_stdout_tx_str(const char *str);

mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len);

void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len);
uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags);

// GPIO引脚操作函数(自定义GPIO API)
// 引脚对象类型定义
typedef struct _machine_pin_obj_t mp_hal_pin_obj_t;

// 引脚读取
int mp_hal_pin_read(mp_hal_pin_obj_t *pin);

// 引脚写入
void mp_hal_pin_write(mp_hal_pin_obj_t *pin, int value);

// 引脚模式设置
void mp_hal_pin_open_set(void *machine_pin, int mode);

// 系统控制函数
void mp_hal_set_interrupt_char(char c);

uint64_t mp_hal_time_ns(void);

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
// 在 mphalport.h 或相应移植文件中实现

#include <stdio.h>
#include "py/runtime.h"
#include "py/stream.h"
#include "py/mphal.h"
#include "xparameters.h"
#include "xuartps.h"

// 时间控制函数
mp_uint_t mp_hal_ticks_ms(void) {
// 返回系统启动后的毫秒数
return 0;
}

mp_uint_t mp_hal_ticks_us(void) {
// 返回系统启动后的微秒数
return 0;
}

mp_uint_t mp_hal_ticks_cpu(void) {
// 返回CPU时钟计数(用于高精度计时)
return 0;
}

void mp_hal_delay_ms(mp_uint_t ms) {
// 阻塞延时毫秒
}

void mp_hal_delay_us(mp_uint_t us) {
// 阻塞延时微秒
}

// 标准输入输出函数
int mp_hal_stdin_rx_chr(void) {
// u8 ReceivedData;

// // 尝试接收一个字节
// if (Uart1_ReceiveByte(&ReceivedData)) {
// // 接收到数据,直接回显
// //XUartPs_SendByte(Uart1_Inst.Config.BaseAddress, ReceivedData);
// return ReceivedData;
// }
return 0 //空实现
}

void mp_hal_stdout_tx_str(const char *str) {
// 向标准输出发送字符串
// printf("%.*s", strlen(str), str);
}

mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) {
// 向标准输出发送指定长度字符串
// printf("%.*s", len, str);
return len;
}

void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) {
// printf("%.*s", len, str);
}

uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) {
// 检查标准输入/输出状态(用于非阻塞操作)
return 0;
}

// 引脚读取
int mp_hal_pin_read(mp_hal_pin_obj_t *pin) {
// 读取引脚电平
return 0;
}

// 引脚写入
void mp_hal_pin_write(mp_hal_pin_obj_t *pin, int value) {
// 设置引脚电平
}

// 引脚模式设置
void mp_hal_pin_open_set(void *machine_pin, int mode) {
// 设置引脚模式(输入/输出等)
}

// 系统控制函数
void mp_hal_set_interrupt_char(char c) {
// 设置中断字符(如Ctrl+C)
}

uint64_t mp_hal_time_ns(void) {
// 返回自纪元以来的纳秒数
return 0;
}

然后是要裁剪一些库,因为很多功能我们都用不着(还有一些数学库,SDK里面已经存在了),所以我们可以通过SDK直接选择让某些文件/文件夹不参加编译,如图,比如我们移除uzlib,因为这个和我们的功能无关,选择这个选项之后,全选,然后重新编译即可

这里我们选择将 uzlib re1.5 oofatfs mbedtls_errors crypto-algorithms libm libm_dbl都移除,然后重新编译

然后找到py/gc.h在头文件里面添加: #include "bare-arm/mpconfigport.h"

同时我们将将ststem.c文件清空,这里我们暂时不写代码,具体接口都先写在bare-arm/mphalport.c中,然后实现gc_collect()函数,这是垃圾回收的函数,如果不使用也可以空实现一下即可,或者和我一样用下面的代码替换system.c文件,后面再次make的时候,如果缺少gc_collect()也是空实现一下即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2021 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

//暂时为空

#include "py/gc.h"
#include <stdint.h> // 定义 uintptr_t 类型
#include <stddef.h> // 定义 size_t 类型

// 确保链接脚本中的符号正确定义
extern int _stack;
extern int _stack_end;

// 3. 辅助函数:获取当前所有寄存器的值并保存到数组中
// 这些寄存器可能包含当前函数调用上下文中的对象指针
static void gc_helper_get_regs(volatile uintptr_t arr[]) {
// 使用扩展汇编明确告知编译器我们需要读取这些通用寄存器的值
__asm__ volatile (
"mov %0, r0\n" // 将寄存器 r0 的值存入数组第0个元素
"mov %1, r1\n" // 将寄存器 r1 的值存入数组第1个元素
"mov %2, r2\n" // ... 以此类推
"mov %3, r3\n"
"mov %4, r4\n"
"mov %5, r5\n"
"mov %6, r6\n"
"mov %7, r7\n"
"mov %8, r8\n"
"mov %9, r9\n"
"mov %10, r10\n"
"mov %11, r11\n"
"mov %12, r12\n" // 寄存器 r12
: "=r" (arr[0]), "=r" (arr[1]), "=r" (arr[2]), "=r" (arr[3]),
"=r" (arr[4]), "=r" (arr[5]), "=r" (arr[6]), "=r" (arr[7]),
"=r" (arr[8]), "=r" (arr[9]), "=r" (arr[10]), "=r" (arr[11]),
"=r" (arr[12])
: // 没有输入操作数
: // 没有显式声明的被破坏寄存器(编译器会自动处理)
);
}

// 4. 实现 gc_collect 函数
void gc_collect(void) {
// 启动垃圾回收过程
gc_collect_start();

// 第二阶段:扫描根对象(寄存器和栈)

// 扫描CPU寄存器
// 在当前栈帧上创建一个数组来保存寄存器值
volatile uintptr_t regs[13]; // 用于保存 R0-R12
gc_helper_get_regs(regs);

// 将保存寄存器值的数组本身也作为一个需要扫描的根区域
// 因为数组里的每个元素都可能是某个Python对象的地址
gc_collect_root((void**)regs, sizeof(regs) / sizeof(uintptr_t));

// 扫描整个栈空间
// 计算栈的大小(以 void* 为单位)。因为栈是向下生长的,所以:
// 栈的起始地址(低地址)是 _stack_end
// 栈的结束地址(高地址)是 _stack
uintptr_t stack_top = (uintptr_t)&_stack_end;
uintptr_t stack_bottom = (uintptr_t)&_stack;
// 确保计算不会出现负数,进行安全转换
size_t stack_size_in_words = (stack_bottom > stack_top) ?
(stack_bottom - stack_top) / sizeof(void*) : 0;

if (stack_size_in_words > 0) {
gc_collect_root((void**)stack_top, stack_size_in_words);
}

// 第三阶段:结束垃圾回收,进行实际的清扫工作
gc_collect_end();
}

然后我们将bar-arm/main.c修改成 bar-arm/pymain.c同时添加一个pymain.h文件,我们这里实现一些简单的初始化和接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
* pymain.h
*
* Created on: 2026年1月11日
* Author: Mouse
*/

#ifndef __PYMAIN_H
#define __PYMAIN_H

#include <string.h>
#include "py/compile.h"
#include "py/runtime.h"

void do_str(const char *src, mp_parse_input_kind_t input_kind);
void py_init(); //初始化
void py_uinit();
void bare_main(void);

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include <string.h>
#include "py/compile.h"
#include "py/runtime.h"
#include "py/gc.h"

#define HEAP_SIZE (32 * 1024) // 定义内存大小
static char heap[HEAP_SIZE];
extern int Uart1_ReceiveByte(u8 *Data);


//初始化
void py_init()
{
gc_init(heap, heap + sizeof(heap)); //内存初始化
mp_init(); //python初始化
}

void py_uinit()
{
mp_deinit();
}

static const char *demo_single_input =
"print('hello world!', list(x + 1 for x in range(10)), end='eol\\n')";

static const char *demo_file_input =
"import micropython\n"
"\n"
"print(dir(micropython))\n"
"\n"
"for i in range(10):\n"
" print('iter {:08}'.format(i))";

void do_str(const char *src, mp_parse_input_kind_t input_kind) {
nlr_buf_t nlr;
if (nlr_push(&nlr) == 0) {
// Compile, parse and execute the given string.
mp_lexer_t *lex = mp_lexer_new_from_str_len(MP_QSTR__lt_stdin_gt_, src, strlen(src), 0);
qstr source_name = lex->source_name;
mp_parse_tree_t parse_tree = mp_parse(lex, input_kind);
mp_obj_t module_fun = mp_compile(&parse_tree, source_name, true);
mp_call_function_0(module_fun);
nlr_pop();
} else {
// Uncaught exception: print it out.
mp_obj_print_exception(&mp_plat_print, (mp_obj_t)nlr.ret_val);
}
}

// Main entry point: initialise the runtime and execute demo strings.
void bare_main(void) {
mp_init();
do_str(demo_single_input, MP_PARSE_SINGLE_INPUT);
do_str(demo_file_input, MP_PARSE_FILE_INPUT);
mp_deinit();
}

// Called if an exception is raised outside all C exception-catching handlers.
void nlr_jump_fail(void *val) {
for (;;) {
}
}

#ifndef NDEBUG
// Used when debugging is enabled.
void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) {
for (;;) {
}
}
#endif

最后我们还要补充前面空实现的端口,这里我们先补充串口输入输出即可,先来测试python的基础功能,注意这里使用的**Uart1_ReceiveByte()**函数是自己实现的(我写在主函数中了),大家还是参考之前的工程模版构建,下面给出我的主函数和修改后的mphalport.c文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// 在 mphalport.h 或相应移植文件中实现

#include <stdio.h>
#include "py/runtime.h"
#include "py/stream.h"
#include "py/mphal.h"
#include "xparameters.h"
#include "xuartps.h"

// 时间控制函数
mp_uint_t mp_hal_ticks_ms(void) {
// 返回系统启动后的毫秒数
return 0;
}

mp_uint_t mp_hal_ticks_us(void) {
// 返回系统启动后的微秒数
return 0;
}

mp_uint_t mp_hal_ticks_cpu(void) {
// 返回CPU时钟计数(用于高精度计时)
return 0;
}

void mp_hal_delay_ms(mp_uint_t ms) {
// 阻塞延时毫秒
}

void mp_hal_delay_us(mp_uint_t us) {
// 阻塞延时微秒
}

// 标准输入输出函数
int mp_hal_stdin_rx_chr(void) {
u8 ReceivedData;

// 尝试接收一个字节
if (Uart1_ReceiveByte(&ReceivedData)) {
// 接收到数据,直接回显
//XUartPs_SendByte(Uart1_Inst.Config.BaseAddress, ReceivedData);
return ReceivedData;
}
}

void mp_hal_stdout_tx_str(const char *str) {
// 向标准输出发送字符串
printf("%.*s", strlen(str), str);
}

mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) {
// 向标准输出发送指定长度字符串
printf("%.*s", len, str);
return len;
}

void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) {
printf("%.*s", len, str);
}

uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) {
// 检查标准输入/输出状态(用于非阻塞操作)
return 0;
}

// 引脚读取
int mp_hal_pin_read(mp_hal_pin_obj_t *pin) {
// 读取引脚电平
return 0;
}

// 引脚写入
void mp_hal_pin_write(mp_hal_pin_obj_t *pin, int value) {
// 设置引脚电平
}

// 引脚模式设置
void mp_hal_pin_open_set(void *machine_pin, int mode) {
// 设置引脚模式(输入/输出等)
}

// 系统控制函数
void mp_hal_set_interrupt_char(char c) {
// 设置中断字符(如Ctrl+C)
}

uint64_t mp_hal_time_ns(void) {
// 返回自纪元以来的纳秒数
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
/******************************************************************************
*
* Copyright (C) 2009 - 2014 Xilinx, Inc. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* Use of the Software is limited solely to applications:
* (a) running on a Xilinx device, or
* (b) that interact with a Xilinx device through a bus or interconnect.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* XILINX BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*
* Except as contained in this notice, the name of the Xilinx shall not be used
* in advertising or otherwise to promote the sale, use or other dealings in
* this Software without prior written authorization from Xilinx.
*
******************************************************************************/

/*
* helloworld.c: simple test application
*
* This application configures UART 16550 to baud rate 9600.
* PS7 UART (Zynq) is not initialized by this application, since
* bootrom/bsp configures it to baud rate 115200
*
* ------------------------------------------------
* | UART TYPE BAUD RATE |
* ------------------------------------------------
* uartns550 9600
* uartlite Configurable only in HW design
* ps7_uart 115200 (configured by bootrom/bsp)
*/

#include "xparameters.h"
#include "xuartps.h"
#include "xuartps_hw.h"
#include <string.h>
#include <stdio.h>
#include "platform.h"
#include "xgpio.h"
#include <unistd.h> // for sleep()
#include "xil_printf.h"
// MicroPython 头文件
#include "bare-arm/pymain.h"


#define LED_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID
XGpio LEDInst;

// 串口1设备ID
#define UART1_DEVICE_ID XPAR_PS7_UART_1_DEVICE_ID

// 全局串口实例
XUartPs Uart1_Inst;

// 输入缓冲区大小
#define INPUT_BUFFER_SIZE 256
static char input_buffer[INPUT_BUFFER_SIZE];
static int input_index = 0;
int Uart1_ReceiveByte(u8 *Data);

// 简单的串口初始化函数
int Uart1_Simple_Init(void)
{
XUartPs_Config *UartConfig;
int Status;

// 查找串口配置
UartConfig = XUartPs_LookupConfig(UART1_DEVICE_ID);
if (UartConfig == NULL) {
xil_printf("UART1 LookupConfig Failed!\r\n");
return XST_FAILURE;
}

// 初始化串口
Status = XUartPs_CfgInitialize(&Uart1_Inst, UartConfig, UartConfig->BaseAddress);
if (Status != XST_SUCCESS) {
xil_printf("UART1 CfgInitialize Failed!\r\n");
return XST_FAILURE;
}

// 设置波特率为115200
XUartPs_SetBaudRate(&Uart1_Inst, 115200);

// 设置正常操作模式
XUartPs_SetOperMode(&Uart1_Inst, XUARTPS_OPER_MODE_NORMAL);

xil_printf("UART1 Simple Initialization Successful!\r\n");
return XST_SUCCESS;
}

// 轮询方式接收一个字符(非阻塞)
int Uart1_ReceiveByte(u8 *Data)
{
// 检查是否有数据可读
if (XUartPs_IsReceiveData(Uart1_Inst.Config.BaseAddress)) {
*Data = XUartPs_ReadReg(Uart1_Inst.Config.BaseAddress, XUARTPS_FIFO_OFFSET);
return 1; // 成功接收到数据
}
return 0; // 没有数据
}

// 发送字符串到串口
void Uart1_SendString(const char *str)
{
int len = strlen(str);
int sent = 0;

while (sent < len) {
sent += XUartPs_Send(&Uart1_Inst, (u8*)&str[sent], len - sent);
}
}


// 简单的回显处理函数
void Uart1_EchoHandler(void)
{
u8 ReceivedData;

// 尝试接收一个字节
if (Uart1_ReceiveByte(&ReceivedData)) {
// 回显字符
XUartPs_SendByte(Uart1_Inst.Config.BaseAddress, ReceivedData);

// 处理回车键
if (ReceivedData == '\r' || ReceivedData == '\n') {
if (input_index > 0) {
input_buffer[input_index] = '\0'; // 添加字符串结束符

// 换行
Uart1_SendString("\r\n");

// 处理特殊命令
if (strcmp(input_buffer, "exit") == 0) {
Uart1_SendString("Exiting MicroPython REPL...\r\n");
input_index = 0;
return;
}
else if (strcmp(input_buffer, "help") == 0) {
Uart1_SendString("MicroPython REPL Commands:\r\n");
Uart1_SendString(" help - Show this help message\r\n");
Uart1_SendString(" exit - Exit REPL mode\r\n");
Uart1_SendString(" Enter Python code to execute\r\n");
}
else if (strlen(input_buffer) > 0) {
// 执行Python代码
do_str(input_buffer, MP_PARSE_SINGLE_INPUT);
}

input_index = 0;
}

// 显示提示符
Uart1_SendString(">>> ");
}
// 处理退格键
else if (ReceivedData == 0x7F || ReceivedData == 0x08) { // Backspace or Delete
if (input_index > 0) {
input_index--;
// 在终端上显示退格效果
Uart1_SendString("\b \b");
}
}
// 处理普通字符
else if (ReceivedData >= 0x20 && ReceivedData <= 0x7E) { // 可打印字符
if (input_index < INPUT_BUFFER_SIZE - 1) {
input_buffer[input_index++] = ReceivedData;
}
}
}
}

int main()
{
init_platform();
py_init(); //初始化python
xil_printf("ZYNQ MicroPython REPL Starting...\r\n");

int status;
status = XGpio_Initialize(&LEDInst, LED_DEVICE_ID);
if(status != XST_SUCCESS)
return XST_FAILURE;
XGpio_SetDataDirection(&LEDInst, 1, 0); // 设置GPIO方向为输出

XGpio_DiscreteWrite(&LEDInst, 1, 0b000); //测试LED

print("Hello World\n\r");

// 测试1: 基本输出和字符串操作
do_str("print('=== MicroPython Basic Test ===')", MP_PARSE_SINGLE_INPUT);
do_str("print('Hello, ZYNQ!')", MP_PARSE_SINGLE_INPUT);

// 测试2: 创建和操作数组(列表)
do_str("arr = [1, 2, 3, 4, 5]", MP_PARSE_SINGLE_INPUT);
do_str("print('Array:', arr)", MP_PARSE_SINGLE_INPUT);
do_str("arr.append(6)", MP_PARSE_SINGLE_INPUT);
do_str("print('After append:', arr)", MP_PARSE_SINGLE_INPUT);
do_str("print('Array length:', len(arr))", MP_PARSE_SINGLE_INPUT);

// 测试3: 算术运算
do_str("a, b = 10, 3", MP_PARSE_SINGLE_INPUT);
do_str("print('10 + 3 =', a + b)", MP_PARSE_SINGLE_INPUT);
do_str("print('10 - 3 =', a - b)", MP_PARSE_SINGLE_INPUT);
do_str("print('10 * 3 =', a * b)", MP_PARSE_SINGLE_INPUT);
do_str("print('10 // 3 =', a // b)", MP_PARSE_SINGLE_INPUT);

// 测试4: 逻辑判断和条件语句
do_str("x = 15", MP_PARSE_SINGLE_INPUT);
do_str("if x > 10: print('x is greater than 10')", MP_PARSE_SINGLE_INPUT);
do_str("if x % 2 == 0: print('x is even')", MP_PARSE_SINGLE_INPUT);
do_str("else: print('x is odd')", MP_PARSE_SINGLE_INPUT);

// 测试5: 循环操作
do_str("print('Counting from 0 to 4:')", MP_PARSE_SINGLE_INPUT);
do_str("for i in range(5): print('Number:', i)", MP_PARSE_SINGLE_INPUT);

// 测试6: 列表推导式(Python特色功能)
do_str("squares = [i**2 for i in range(6)]", MP_PARSE_SINGLE_INPUT);
do_str("print('Squares of 0-5:', squares)", MP_PARSE_SINGLE_INPUT);

// 测试7: 字典操作
do_str("student = {'name': 'ZYNQ', 'score': 95}", MP_PARSE_SINGLE_INPUT);
do_str("print('Student info:', student)", MP_PARSE_SINGLE_INPUT);
do_str("print('Student name:', student['name'])", MP_PARSE_SINGLE_INPUT);

// 测试8: 函数定义和调用
do_str("def multiply(x, y): return x * y", MP_PARSE_SINGLE_INPUT);
do_str("result = multiply(7, 8)", MP_PARSE_SINGLE_INPUT);
do_str("print('7 * 8 =', result)", MP_PARSE_SINGLE_INPUT);

do_str("print('=== All tests completed! ===')", MP_PARSE_SINGLE_INPUT);

// while(1) //注释主循环,则会进入REPL交互模式
// {
// // 主循环
// }

// 初始化串口1
if (Uart1_Simple_Init() != XST_SUCCESS) {
xil_printf("UART1 Init Failed! System Halted.\r\n");
while(1); // 初始化失败,停止系统
}

Uart1_SendString("========================================\r\n");
Uart1_SendString(" ZYNQ MicroPython REPL Environment\r\n");
Uart1_SendString("========================================\r\n");
Uart1_SendString("Type 'help' for available commands.\r\n");
Uart1_SendString("Type Python code to execute immediately.\r\n");
Uart1_SendString(">>> ");

// 主循环 - REPL交互模式
while(1) {
Uart1_EchoHandler();
// 可以在这里添加其他任务或微小延迟
}

py_uinit();
cleanup_platform();

return 0;
}

运行成功后串口会返回python执行的结果,然后大家可以注释掉代码中间的while循环(上述代码已经注释了),然后就可以通过串口调试助手,主动发送python代码执行(交互式,以回车结尾),同时也可以来测试之前写的内存回收是否正确,如图

到此,基础功能移植结束,下面是GPIO输入输出,后期的GPIO测试依旧是使用这个主函数的交互功能


C.5 添加GPIO功能

之前我们只是简单定义了typedef struct _machine_pin_obj_t mp_hal_pin_obj_t;,这里我们要根据自己的硬件来实现_machine_pin_obj_t,以及相关接口

注意这里我使用的是AXI GPIO,而非PS端的EMIO/MIO

为了区别GPIO功能,我们在bare-arm下面创建四个文件,专门用来实现GPIO功能
分别是:

  • machine_pin.c
  • machine_pin.h
  • modmachine.c
  • qstrdefs_port.h

前两个是具体驱动接口实现,第三个设备注册,最后一个是QSTR生成需要使用,注意,这四个文件和最开写配置文件一样,需要再linux环境下添加,然后使用make指令,生成新的bulid文件,然后再导入到SDK中开发才能正常通过编译,当然,bare-arm下面的Mkaefile文件也需要修改
同时也要修改删除mphalport.h 和mphalport.c中重复的部分,最后添加引脚映射以及基本的输入输出,暂时不包含中断等
下面给出修改后的文件参考:

这两个文件(machine_pin.c/h)主要是定义GPIO输入输出的接口,后期如果有新的按键LED等可以在这里添加修改,当然最好还是写一个映射表,我这里就写的比较简单,大家make的时候需要先把里面有关AXI GPIO的接口先注释掉,空实现即可,同时.h文件里面的宏也要暂时注释,否则编译会报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#ifndef MICROPY_INCLUDED_BARE_ARM_MACHINE_PIN_H
#define MICROPY_INCLUDED_BARE_ARM_MACHINE_PIN_H

#include "py/obj.h"

#define MACHINE_PIN_MODE_IN (0)
#define MACHINE_PIN_MODE_OUT (1)


#define LED_DEVICE_ID XPAR_AXI_GPIO_0_DEVICE_ID

//这里简单给出映射表:其中mode 1表示输入,0表示输出
/* pin_number name local ID
* 0 led0 Y14 LED_DEVICE_ID
* 1 led1 W14 LED_DEVICE_ID
* 2 led2 Y16 LED_DEVICE_ID
* 自行添加
*/

typedef struct _machine_pin_obj_t {
mp_obj_base_t base;
uint32_t pin_id;
uint32_t mode;
} machine_pin_obj_t;

/* Pin 类型 */
extern const mp_obj_type_t machine_pin_type;

/* GPIO HAL */
void mp_hal_gpio_init(uint32_t pin, uint32_t mode);
void mp_hal_gpio_write(uint32_t pin, int value);
int mp_hal_gpio_read(uint32_t pin);

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include "py/runtime.h"
#include "py/obj.h"
#include "machine_pin.h"

#include "xparameters.h"
#include "xuartps.h"
#include "xuartps_hw.h"
#include <string.h>
#include <stdio.h>
#include "xgpio.h"
#include <unistd.h> // for sleep()
#include "xil_printf.h"

extern XGpio LEDInst;

/* ===================== Pin 构造函数 ===================== */
static mp_obj_t machine_pin_make_new(const mp_obj_type_t *type,
size_t n_args,
size_t n_kw,
const mp_obj_t *args) {
mp_arg_check_num(n_args, n_kw, 2, 2, false);

machine_pin_obj_t *self = m_new_obj(machine_pin_obj_t);
self->base.type = type;

self->pin_id = mp_obj_get_int(args[0]);
self->mode = mp_obj_get_int(args[1]);

/* GPIO 初始化(需要自己实现) */
mp_hal_gpio_init(self->pin_id, self->mode);

return MP_OBJ_FROM_PTR(self);
}

/* ===================== Pin.value() ===================== */
static mp_obj_t machine_pin_value(size_t n_args, const mp_obj_t *args) {
machine_pin_obj_t *self = MP_OBJ_TO_PTR(args[0]);

if (n_args == 1) {
int val = mp_hal_gpio_read(self->pin_id);
return mp_obj_new_int(val);
} else {
int val = mp_obj_get_int(args[1]);
mp_hal_gpio_write(self->pin_id, val);
return mp_const_none;
}
}
static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(
machine_pin_value_obj, 1, 2, machine_pin_value
);

/* ===================== locals_dict ===================== */

static const mp_rom_map_elem_t machine_pin_locals_dict_table[] = {
{ MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) },

/* 类常量 */
{ MP_ROM_QSTR(MP_QSTR_IN), MP_ROM_INT(MACHINE_PIN_MODE_IN) },
{ MP_ROM_QSTR(MP_QSTR_OUT), MP_ROM_INT(MACHINE_PIN_MODE_OUT) },
};

static MP_DEFINE_CONST_DICT(
machine_pin_locals_dict,
machine_pin_locals_dict_table
);

/* ===================== Pin 类型定义 ===================== */

MP_DEFINE_CONST_OBJ_TYPE(
machine_pin_type,
MP_QSTR_Pin,
MP_TYPE_FLAG_NONE,
make_new, machine_pin_make_new,
locals_dict, &machine_pin_locals_dict
);


void mp_hal_gpio_init(uint32_t pin, uint32_t mode)
{
int status;
if(pin ==0 || pin==1 || pin ==2)
{
status = XGpio_Initialize(&LEDInst, LED_DEVICE_ID);
if(status != XST_SUCCESS)
printf("mp_hal_gpio_init error!!!!\r\n");
XGpio_SetDataDirection(&LEDInst, 1, mode); // 设置GPIO方向为输出/输入
}
}
void mp_hal_gpio_write(uint32_t pin, int value)
{
if(pin ==0 || pin==1 || pin ==2)
{
// 使用位运算生成对应引脚的掩码
uint32_t pin_mask = (1 << pin); // 将引脚号转换为位掩码

if (value) {
XGpio_DiscreteSet(&LEDInst, 1, pin_mask); // 设置对应位为高电平
}
else {
XGpio_DiscreteClear(&LEDInst, 1, pin_mask); // 清除对应位为低电平
}
}
}
int mp_hal_gpio_read(uint32_t pin)
{
if(pin == 0 || pin == 1 || pin == 2)
{
// 读取整个GPIO通道的状态
uint32_t gpio_state = XGpio_DiscreteRead(&LEDInst, 1);
// 使用位掩码提取特定引脚的状态
uint32_t pin_mask = (1 << pin);

// 返回该引脚的状态 (0 或 1)
return (gpio_state & pin_mask) ? 1 : 0;
}
return -1; // 引脚号错误返回-1
}

下面这个文件就是对machine模块进行注册的主体,参考官方模版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// machine.c
#include "py/obj.h"
#include "py/runtime.h"
#include "machine_pin.h"
#include "mpconfigport.h"

/* ===================== Pin 类型声明 ===================== */
extern const mp_obj_type_t machine_pin_type;

/* ===================== machine 模块 globals ===================== */
static const mp_rom_map_elem_t machine_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) },
};

//这里我们我直接写到objmodule.c文件中,所以这里不列出数组
//static const mp_rom_map_elem_t mp_builtin_module_table[] = {
// MICROPY_PORT_BUILTIN_MODULES
// // ... 其他模块
//};


static MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table);

/* ===================== machine 模块对象 ===================== */
const mp_obj_module_t machine_module = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&machine_module_globals,
};

//我们手动注册
/* ===================== 注册模块 ===================== */
//MP_REGISTER_MODULE(MP_QSTR_machine, machine_module);

这个文件主要是添加我们这次注册需要使用的QSTR,make的时候会帮我们把字符串更新到之前的那个qstrdefs.generated.h文件中

1
2
3
4
5
6
// qstrdefs_port.h
Q(Pin)
Q(IN)
Q(OUT)
Q(value)
Q(machine) // 注意这里定义模块名

这里是修改后的mphalport.h/c文件,因为我们GPIO独立出来了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
// 在 mphalport.h 或相应移植文件中实现

#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include "mphalport.h"
#include "py/runtime.h"
#include "py/mphal.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "xparameters.h"
#include "xgpio.h"
#include "xstatus.h"

extern int Uart1_ReceiveByte(u8 *Data);

// 时间控制函数
mp_uint_t mp_hal_ticks_ms(void) {
// 返回系统启动后的毫秒数
return 0;
}

mp_uint_t mp_hal_ticks_us(void) {
// 返回系统启动后的微秒数
return 0;
}

mp_uint_t mp_hal_ticks_cpu(void) {
// 返回CPU时钟计数(用于高精度计时)
return 0;
}

void mp_hal_delay_ms(mp_uint_t ms) {
// 阻塞延时毫秒
}

void mp_hal_delay_us(mp_uint_t us) {
// 阻塞延时微秒
}

// 标准输入输出函数
int mp_hal_stdin_rx_chr(void) {
u8 ReceivedData;

// 尝试接收一个字节
if (Uart1_ReceiveByte(&ReceivedData)) {
// 接收到数据,直接回显
//XUartPs_SendByte(Uart1_Inst.Config.BaseAddress, ReceivedData);
return ReceivedData;
}
return 0;
}

void mp_hal_stdout_tx_str(const char *str) {
// 向标准输出发送字符串
printf("%.*s", strlen(str), str);
}

mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len) {
// 向标准输出发送指定长度字符串
printf("%.*s", len, str);
return len;
}

void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) {
printf("%.*s", len, str);
}


uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) {
// 检查标准输入/输出状态(用于非阻塞操作)
return 0;
}


// 系统控制函数
void mp_hal_set_interrupt_char(char c) {
// 设置中断字符(如Ctrl+C)
}

uint64_t mp_hal_time_ns(void) {
// 返回自纪元以来的纳秒数
return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#ifndef __MPHALPORT_H
#define __MPHALPORT_H

#include <stdint.h>
#include "py/mpconfig.h"
#include "py/obj.h"
#include "xgpio.h" // 添加Xilinx GPIO支持

// 扩展的引脚对象结构体,这里暂时没用上
typedef struct _mp_hal_pin_obj_t {
mp_obj_base_t base; // MicroPython 对象基类
const char *name; // 引脚名称
uint8_t bank; // GPIO Bank
uint8_t pin_num; // 引脚编号
uint8_t channel; // 通道
uint8_t bit_mask; // 位掩码,用于区分LED
} mouse_mp_hal_pin_obj_t;

// GPIO引脚操作函数(自定义GPIO API)
// 引脚对象类型定义
typedef struct _machine_pin_obj_t mp_hal_pin_obj_t;

// 时间控制函数
mp_uint_t mp_hal_ticks_ms(void);
mp_uint_t mp_hal_ticks_us(void);
mp_uint_t mp_hal_ticks_cpu(void);
void mp_hal_delay_ms(mp_uint_t ms);
void mp_hal_delay_us(mp_uint_t us);

// 标准输入输出函数
int mp_hal_stdin_rx_chr(void);
void mp_hal_stdout_tx_str(const char *str);
mp_uint_t mp_hal_stdout_tx_strn(const char *str, size_t len);
void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len);
uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags);

// 系统控制函数
void mp_hal_set_interrupt_char(char c);
uint64_t mp_hal_time_ns(void);

#endif

这里要修改makefile,增加我们新创建的文件,否则我们添加的文件都没办法正常编译,添加在SRC_C之后,下面还新加一个Qstr的头文件定义,注意这里的文件名不要写错了,要和我们编译的文件名一致

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# Include the core environment definitions; this will set $(TOP).
include ../../py/mkenv.mk

# Include py core make definitions.
include $(TOP)/py/py.mk

# Set makefile-level MicroPython feature configurations.
MICROPY_ROM_TEXT_COMPRESSION ?= 0

# Define toolchain and other tools.
CROSS_COMPILE ?= arm-none-eabi-
DFU ?= $(TOP)/tools/dfu.py
PYDFU ?= $(TOP)/tools/pydfu.py

# Set CFLAGS.
CFLAGS += -I. -I$(TOP) -I$(BUILD)
CFLAGS += -Wall -Werror -std=c99 -nostdlib
CFLAGS += -mthumb -mtune=cortex-m4 -mcpu=cortex-m4 -msoft-float
CSUPEROPT = -Os # save some code space for performance-critical code

# Select debugging or optimisation build.
ifeq ($(DEBUG), 1)
CFLAGS += -Og
else
CFLAGS += -Os -DNDEBUG
CFLAGS += -fdata-sections -ffunction-sections
endif

# Set linker flags.
LDFLAGS += -nostdlib -T stm32f405.ld --gc-sections

# Define the required source files.
SRC_C += lib.c main.c system.c modmachine.c machine_pin.c

QSTR_DEFS += qstrdefs_port.h

# Define the required object files.
OBJ += $(PY_CORE_O)
OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o))

# Define the top-level target, the main firmware.
all: $(BUILD)/firmware.dfu

$(BUILD)/firmware.elf: $(OBJ)
$(ECHO) "LINK $@"
$(Q)$(LD) $(LDFLAGS) -o $@ $^
$(Q)$(SIZE) $@

$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(ECHO) "Create $@"
$(Q)$(OBJCOPY) -O binary -j .isr_vector -j .text -j .data $^ $@

$(BUILD)/firmware.dfu: $(BUILD)/firmware.bin
$(ECHO) "Create $@"
$(Q)$(PYTHON) $(DFU) -b 0x08000000:$^ $@

deploy: $(BUILD)/firmware.dfu
$(Q)$(PYTHON) $(PYDFU) -u $^

# Include remaining core make rules.
include $(TOP)/py/mkrules.mk

最后就是注册模块,我们放在配置文件底部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2014-2021 Damien P. George
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include <stdint.h>

// 关闭 micropython 内置模块(避免 extmod 依赖)
#define MICROPY_PY_MICROPYTHON (0) // 禁用 micropython 模块
#define MICROPY_PY_BUILTINS (1) // 启用内置函数和异常
#define MICROPY_PY_GC (1) // 启用垃圾回收模块
#define MICROPY_ENABLE_GC (1) // 启用垃圾回收功能,否则变量功能无法使用

// 控制 MicroPython 构建方式的选项

// 使用最小化的起始配置(禁用所有可选功能)
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_MINIMUM)

// 编译器配置
#define MICROPY_ENABLE_COMPILER (1) // 启用编译器

// Python 内部特性
#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) // 详细错误报告
#define MICROPY_CPYTHON_COMPAT (0) // 不与 CPython 严格兼容以节省空间
#define MICROPY_DEBUG_PRINTERS (1) // 启用调试打印机

// 精细控制 Python 内置功能、类、模块等
#define MICROPY_PY_SYS (0) // 禁用 sys 模块

// 为 GPIO 控制添加的配置
#define MICROPY_PY_MACHINE (1) // 启用 machine 模块(包含GPIO控制)
#define MICROPY_PY_MACHINE_PIN_MAKE_NEW (1) // 启用创建新 Pin 对象的功能
#define MICROPY_PY_MACHINE_PIN_READ_METHOD (1) // 启用引脚读取方法
#define MICROPY_PY_MACHINE_PIN_WRITE_METHOD (1) // 启用引脚写入方法

// 特定机器的类型定义
typedef long mp_off_t;

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

// 在 mpconfigport.h 文件中

// 使用不完整类型(前置声明)来声明 machine_module,避免包含 py/obj.h,否则会报错,我也不知道什么原因,先能跑再说
extern const struct _mp_obj_module_t machine_module;
#define MICROPY_PORT_BUILTIN_MODULES \
{ MP_ROM_QSTR(MP_QSTR_machine), MP_ROM_PTR(&machine_module) },

这里遇到的问题是没有办法自动注册模块,因为我发现编译没办法生成我需要的moduleefs.h文件,所以这里我选择直接打开objmodule.c文件,然后在注册使用的数组mp_builtin_module_table里面主动添加我使用的模块名称,然后将模块名称定义在上面的mpconfigport.h文件最底部

这里简单修改objmodule.c文件,其实只是在数组后面添加了一个宏

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
/*
* This file is part of the MicroPython project, http://micropython.org/
*
* The MIT License (MIT)
*
* Copyright (c) 2013-2019 Damien P. George
* Copyright (c) 2014-2015 Paul Sokolovsky
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include "py/bc.h"
#include "py/objmodule.h"
#include "py/runtime.h"
#include "py/builtin.h"

static void module_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
(void)kind;
mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in);

const char *module_name = "";
mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_MAP_LOOKUP);
if (elem != NULL) {
module_name = mp_obj_str_get_str(elem->value);
}

#if MICROPY_MODULE___FILE__
// If we store __file__ to imported modules then try to lookup this
// symbol to give more information about the module.
elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___file__), MP_MAP_LOOKUP);
if (elem != NULL) {
mp_printf(print, "<module '%s' from '%s'>", module_name, mp_obj_str_get_str(elem->value));
return;
}
#endif

mp_printf(print, "<module '%s'>", module_name);
}

static void module_attr_try_delegation(mp_obj_t self_in, qstr attr, mp_obj_t *dest);

static void module_attr(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
mp_obj_module_t *self = MP_OBJ_TO_PTR(self_in);
if (dest[0] == MP_OBJ_NULL) {
// load attribute
mp_map_elem_t *elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(attr), MP_MAP_LOOKUP);
if (elem != NULL) {
dest[0] = elem->value;
#if MICROPY_CPYTHON_COMPAT
} else if (attr == MP_QSTR___dict__) {
dest[0] = MP_OBJ_FROM_PTR(self->globals);
#endif
#if MICROPY_MODULE_GETATTR
} else if (attr != MP_QSTR___getattr__) {
elem = mp_map_lookup(&self->globals->map, MP_OBJ_NEW_QSTR(MP_QSTR___getattr__), MP_MAP_LOOKUP);
if (elem != NULL) {
dest[0] = mp_call_function_1(elem->value, MP_OBJ_NEW_QSTR(attr));
} else {
module_attr_try_delegation(self_in, attr, dest);
}
#endif
} else {
module_attr_try_delegation(self_in, attr, dest);
}
} else {
// delete/store attribute
mp_obj_dict_t *dict = self->globals;
if (dict->map.is_fixed) {
#if MICROPY_CAN_OVERRIDE_BUILTINS
if (dict == &mp_module_builtins_globals) {
if (MP_STATE_VM(mp_module_builtins_override_dict) == NULL) {
MP_STATE_VM(mp_module_builtins_override_dict) = MP_OBJ_TO_PTR(mp_obj_new_dict(1));
}
dict = MP_STATE_VM(mp_module_builtins_override_dict);
} else
#endif
{
// can't delete or store to fixed map
module_attr_try_delegation(self_in, attr, dest);
return;
}
}
if (dest[1] == MP_OBJ_NULL) {
// delete attribute
mp_obj_dict_delete(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(attr));
} else {
// store attribute
mp_obj_dict_store(MP_OBJ_FROM_PTR(dict), MP_OBJ_NEW_QSTR(attr), dest[1]);
}
dest[0] = MP_OBJ_NULL; // indicate success
}
}

MP_DEFINE_CONST_OBJ_TYPE(
mp_type_module,
MP_QSTR_module,
MP_TYPE_FLAG_NONE,
print, module_print,
attr, module_attr
);

mp_obj_t mp_obj_new_module(qstr module_name) {
mp_map_t *mp_loaded_modules_map = &MP_STATE_VM(mp_loaded_modules_dict).map;
mp_map_elem_t *el = mp_map_lookup(mp_loaded_modules_map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP_ADD_IF_NOT_FOUND);
// We could error out if module already exists, but let C extensions
// add new members to existing modules.
if (el->value != MP_OBJ_NULL) {
return el->value;
}

// create new module object
mp_module_context_t *o = m_new_obj(mp_module_context_t);
o->module.base.type = &mp_type_module;
o->module.globals = MP_OBJ_TO_PTR(mp_obj_new_dict(MICROPY_MODULE_DICT_SIZE));

// store __name__ entry in the module
mp_obj_dict_store(MP_OBJ_FROM_PTR(o->module.globals), MP_OBJ_NEW_QSTR(MP_QSTR___name__), MP_OBJ_NEW_QSTR(module_name));

// store the new module into the slot in the global dict holding all modules
el->value = MP_OBJ_FROM_PTR(o);

// return the new module
return MP_OBJ_FROM_PTR(o);
}

/******************************************************************************/
// Global module table and related functions

static const mp_rom_map_elem_t mp_builtin_module_table[] = {
// built-in modules declared with MP_REGISTER_MODULE()
MICROPY_REGISTERED_MODULES \
MICROPY_PORT_BUILTIN_MODULES\
};
MP_DEFINE_CONST_MAP(mp_builtin_module_map, mp_builtin_module_table);

#if MICROPY_HAVE_REGISTERED_EXTENSIBLE_MODULES
static const mp_rom_map_elem_t mp_builtin_extensible_module_table[] = {
// built-in modules declared with MP_REGISTER_EXTENSIBLE_MODULE()
MICROPY_REGISTERED_EXTENSIBLE_MODULES
};
MP_DEFINE_CONST_MAP(mp_builtin_extensible_module_map, mp_builtin_extensible_module_table);
#endif

#if MICROPY_MODULE_ATTR_DELEGATION && defined(MICROPY_MODULE_DELEGATIONS)
typedef struct _mp_module_delegation_entry_t {
mp_rom_obj_t mod;
mp_attr_fun_t fun;
} mp_module_delegation_entry_t;

static const mp_module_delegation_entry_t mp_builtin_module_delegation_table[] = {
// delegation entries declared with MP_REGISTER_MODULE_DELEGATION()
MICROPY_MODULE_DELEGATIONS
};
#endif

// Attempts to find (and initialise) a built-in, otherwise returns
// MP_OBJ_NULL.
mp_obj_t mp_module_get_builtin(qstr module_name, bool extensible) {
#if MICROPY_HAVE_REGISTERED_EXTENSIBLE_MODULES
const mp_map_t *map = extensible ? &mp_builtin_extensible_module_map : &mp_builtin_module_map;
#else
const mp_map_t *map = &mp_builtin_module_map;
#endif
mp_map_elem_t *elem = mp_map_lookup((mp_map_t *)map, MP_OBJ_NEW_QSTR(module_name), MP_MAP_LOOKUP);

if (!elem) {
#if MICROPY_PY_SYS
// Special case for sys, which isn't extensible but can always be
// imported with the alias `usys`.
if (module_name == MP_QSTR_usys) {
return MP_OBJ_FROM_PTR(&mp_module_sys);
}
#endif

#if MICROPY_HAVE_REGISTERED_EXTENSIBLE_MODULES
if (extensible) {
// At this point we've already tried non-extensible built-ins, the
// filesystem, and now extensible built-ins. No match, so fail
// the import.
return MP_OBJ_NULL;
}

// We're trying to match a non-extensible built-in (i.e. before trying
// the filesystem), but if the user is importing `ufoo`, _and_ `foo`
// is an extensible module, then allow it as a way of forcing the
// built-in. Essentially, this makes it as if all the extensible
// built-ins also had non-extensible aliases named `ufoo`. Newer code
// should be using sys.path to force the built-in, but this retains
// the old behaviour of the u-prefix being used to force a built-in
// import.
size_t module_name_len;
const char *module_name_str = (const char *)qstr_data(module_name, &module_name_len);
if (module_name_str[0] != 'u') {
return MP_OBJ_NULL;
}
elem = mp_map_lookup((mp_map_t *)&mp_builtin_extensible_module_map, MP_OBJ_NEW_QSTR(qstr_find_strn(module_name_str + 1, module_name_len - 1)), MP_MAP_LOOKUP);
#endif

if (!elem) {
return MP_OBJ_NULL;
}
}

#if MICROPY_MODULE_BUILTIN_INIT
// If found, it's a newly loaded built-in, so init it. This can run
// multiple times, so the module must ensure that it handles being
// initialised multiple times.
mp_obj_t dest[2];
mp_load_method_maybe(elem->value, MP_QSTR___init__, dest);
if (dest[0] != MP_OBJ_NULL) {
mp_call_method_n_kw(0, 0, dest);
}
#endif

return elem->value;
}

static void module_attr_try_delegation(mp_obj_t self_in, qstr attr, mp_obj_t *dest) {
#if MICROPY_MODULE_ATTR_DELEGATION && defined(MICROPY_MODULE_DELEGATIONS)
// Delegate lookup to a module's custom attr method.
size_t n = MP_ARRAY_SIZE(mp_builtin_module_delegation_table);
for (size_t i = 0; i < n; ++i) {
if (*(mp_obj_t *)(&mp_builtin_module_delegation_table[i].mod) == self_in) {
mp_builtin_module_delegation_table[i].fun(self_in, attr, dest);
break;
}
}
#else
(void)self_in;
(void)attr;
(void)dest;
#endif
}

void mp_module_generic_attr(qstr attr, mp_obj_t *dest, const uint16_t *keys, mp_obj_t *values) {
for (size_t i = 0; keys[i] != MP_QSTRnull; ++i) {
if (attr == keys[i]) {
if (dest[0] == MP_OBJ_NULL) {
// load attribute (MP_OBJ_NULL returned for deleted items)
dest[0] = values[i];
} else {
// delete or store (delete stores MP_OBJ_NULL)
values[i] = dest[1];
dest[0] = MP_OBJ_NULL; // indicate success
}
return;
}
}
}

添加修改完上述文件之后,然后和之前一样make,生成新的build文件,我们将它添加到SDK中,然后再取消之前对接口的注释,编译成功之后直接烧录到板子中,注意注意,上述代码中有关ZYNQ的头文件,比如#include “xgpio.h”等,在make的时候都要注释掉,否则会报错找不到头文件,我因为是贴的SDK的最终代码,所以并没有注释或删除


简单说说这里添加的内容,我们并没有实现之前_mp_hal_pin_obj_t结构体,而是创建一个machine模块(后期如果实现IIC等的可能会更方便一点,但其实我也还没试),在里面添加我们需要的信息,然后建立一个映射表(这里我们使用的是if判断),将python代码中的名称映射到具体硬件引脚上,方便python代码的书写,其次,因为ZYNQ当使用 XGpioPs_WritePin这类函数时,它操作的是整个GPIO端口的32位数据寄存器,如果想独立控制多个LED,而不影响同一端口上的其他引脚,还需要引入掩码机制,上述代码主要就是实现这些功能,实现完成之后通过编译,可以在串口交互界面输入下面的python代码,看是否可以控制LED

1
2
3
4
# 假设板子上 LED 在 GPIO 0
led = machine.Pin(0, machine.Pin.OUT) #先注册一个LED0,设置为输出
led.value(1) # 点亮
led.value(0) # 熄灭

如果之前的步骤没有出问题,那么LED可以正常控制,那么这样简单的一个GPIO输入输出就OK啦


D 遇到的问题

这里可能会遇到的问题大多是工程模版创建和烧录的基本问题,大家可以去看模版创建的文章的问题小结

这里列举出已经解决的问题,解决方法都在之前的流程中

  1. Qstr无法生成
  2. 无法创建变量
  3. 配置文件包含py/obj.h会报错
  4. 新模块无法注册

F 小结

如果后面添加更新了其它功能也会更新这个文章,如果有什么问题可以在下面/邮箱留言,我会及时回复

参考文献

  1. 网络各种资源、文档及AI

留言

有问题请指出,你可以选择以下方式:

  1. 在下方评论区留言
  2. 邮箱留言
  • Title: ZYNQ -- 裸机移植 micropython
  • Author: H_Haozi
  • Created at : 2026-01-09 19:28:00
  • Updated at : 2026-01-12 02:32:40
  • Link: https://redefine.ohevan.com/2026/01/09/embedded_ZYNQ_micropython/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments