移植micropython到jz2440开发板(samsung s3c2440 soc)记录
工程全部代码已经上传到了github:https://github.com/Asymptote1994/micropython
1. 概述
1.1 缘由
不知大家有没有了解过K210(一款64位双核带硬件FPU、卷积加速器、FFT、sha256的 RISC-V CPU) 这款AI芯片,如果对嵌入式AI感兴趣的话,非常建议入手。
MaixPy —— sipeed公司将 Micropython 移植到 K210的一个项目,本身python便非常适合AI领域,而将micropython与K210进行结合的MaixPy 项目可以让我们非常方便的在嵌入式设备上面运行AI应用,比如人脸识别、物体检测、目标分类等。
此外除了AI方面,还可以通过micropython控制嵌入式设备上的各种硬件,相比于c语言,micropython足够简洁,非常适合新手入门以及想熟悉python语言的嵌入式工程师。
基于以上,我萌生了将micropython移植到s3c2440的想法,虽然s3c2440没有AI相关的硬件支持,但是可以通过micropython控制各种外围硬件,以此来体验一把micropython语言的各种特性岂不很香吗?
1.2 介绍
本篇文章讲述的是移植micropython到裸机,此外还包括运行在类unix以及windows操作系统之上的版本,这里没有涉及。
下面是对micropython的简单介绍:
MicroPython 是Python 3编程语言的一种精简而高效的实现,它包含Python标准库的一个小子集,并且经过优化,可以在微控制器和受限环境中运行。MicroPython包含了许多高级特性,比如交互式提示符、任意精确整数、闭包、列表理解、生成器、异常处理等等。但是它足够紧凑,可以在256k的代码空间和16k的RAM中运行。MicroPython的目标是尽可能与普通Python兼容,允许轻松地将代码从桌面转移到微控制器或嵌入式系统。
2. 下载源码
micropython的源码可从其官方github下载到:https://github.com/micropython/micropython
执行如下命令即可下载到当前目录下:
git clone git@github.com:micropython/micropython.git
下载到的源码的目录名为micropython
3. 源码目录结构
首先我们看到micropython源码目录的结构为:
关于每个目录的功能,可从顶层目录的README.md得到答案:
Major components in this repository:
- py/ -- the core Python implementation, including compiler, runtime, and
core library.
- mpy-cross/ -- the MicroPython cross-compiler which is used to turn scripts
into precompiled bytecode.
- ports/unix/ -- a version of MicroPython that runs on Unix.
- ports/stm32/ -- a version of MicroPython that runs on the PyBoard and similar
STM32 boards (using ST's Cube HAL drivers).
- ports/minimal/ -- a minimal MicroPython port. Start with this if you want
to port MicroPython to another microcontroller.
- tests/ -- test framework and test scripts.
- docs/ -- user documentation in Sphinx reStructuredText format. Rendered
HTML documentation is available at http://docs.micropython.org.
Additional components:
- ports/bare-arm/ -- a bare minimum version of MicroPython for ARM MCUs. Used
mostly to control code size.
- ports/teensy/ -- a version of MicroPython that runs on the Teensy 3.1
(preliminary but functional).
- ports/pic16bit/ -- a version of MicroPython for 16-bit PIC microcontrollers.
- ports/cc3200/ -- a version of MicroPython that runs on the CC3200 from TI.
- ports/esp8266/ -- a version of MicroPython that runs on Espressif's ESP8266 SoC.
- ports/esp32/ -- a version of MicroPython that runs on Espressif's ESP32 SoC.
- ports/nrf/ -- a version of MicroPython that runs on Nordic's nRF51 and nRF52 MCUs.
- extmod/ -- additional (non-core) modules implemented in C.
- tools/ -- various tools, including the pyboard.py module.
- examples/ -- a few example Python scripts.
其中ports目录便是具体硬件代码的集合地了,从上文可以看到,stm32以及最近很火的物联网wifi芯片esp8266、esp32都已经被官方主线代码所支持。
而我们今天移植的基础便是ports/minimal目录,从名称也可以看出这是一个最小最基础的版本,所有的芯片移植工作都可以从这个目录做起。
4. 移植
首先,我们将minimal目录复制为s3c2440目录,然后看下该目录下的各个文件,功能如下:
- frozentest.py -- 测试用的micropython源代码文件
- frozentest.mpy -- 利用micropython自带的编译工具mpy-cross针对frozentest.py编译出的字节码文件
- main.c -- c代码入口
- uart_core.c -- 串口驱动文件
- mpconfigport.h -- 主要的配置文件
- mphalport.h -- hal层的配置文件
- qstrdefsport.h -- 用于外部符号的定义
- stm32f405.ld -- 默认的链接脚本
- Makefile -- 不用多说了吧
- README.md -- 用于说明的md文件
其中,需要修改的文件包括:Makefile、stm32f405.ld、uart_core.c、main.c、mpconfigport.h
另外,还需要增加如下文件:
- start.S -- 汇编初始化代码,也即最终编译出的bin文件的入口,用于初始化栈、sram、nand flash以及复制代码到sram等,并最终跳转到main.c文件中的main函数
- nand.c/nand.h -- nand flash驱动文件
- uart.h -- 串口驱动头文件
- libgcc.a -- 从编译工具链获得,用于提供除法相关的汇编符号定义
- mylibc.a -- 增加对printk函数以及string库的支持,这里没有使用工程自带的printf函数,原因在于自带的printf函数打印整形数据会出现错误
4.1 修改Makefile
主要改动如下:
-
CROSS_COMPILE ?= arm-linux-gnueabi-
关于交叉编译工具链,需要用比较新的,旧的工具链会有一些指令不支持
-
CFLAGS_ARM9 = -march=armv4t -marm -msoft-float -fsingle-precision-constant -Wdouble-promotion -Wfloat-conversion
指定具体芯片的编译选项,以上是关于s3c2440(arm920t)的选项,具体可以从u-boot编译时的最终链接命令得到
-
在SRC_C以及SRC_S下增加新文件
-
OBJ = $(addprefix $(BUILD)/, $(SRC_S:.S=.o) $(SRC_C:.c=.o)) $(PY_CORE_O)
这里相较于原文件修改了顺序,目的是让start.o、main.o等硬件相关的代码链接在bin文件的最前面
最终文件全部内容改为如下:
include ../../py/mkenv.mk
# qstr definitions (must come before including py.mk)
QSTR_DEFS = qstrdefsport.h
# MicroPython feature configurations
MICROPY_ROM_TEXT_COMPRESSION ?= 1
# include py core make definitions
include $(TOP)/py/py.mk
CROSS_COMPILE ?= arm-linux-gnueabi-
OBJDUMP = $(CROSS_COMPILE)objdump
INC += -I.
INC += -I$(TOP)
INC += -I$(BUILD)
# CFLAGS_CORTEX_M4 = -mthumb -mtune=cortex-m4 -mcpu=cortex-m4 -msoft-float -fsingle-precision-constant -Wdouble-promotion -Wfloat-conversion
CFLAGS_ARM9 = -march=armv4t -marm -msoft-float -fsingle-precision-constant -Wdouble-promotion -Wfloat-conversion
# CFLAGS_CUSTOM = -fno-builtin-printf
CFLAGS = $(INC) -Wall -nostdlib $(CFLAGS_ARM9) $(CFLAGS_CUSTOM)
LDFLAGS = -T s3c2440.ld -Map=$@.map --cref --gc-sections
CSUPEROPT = -Os # save some code space
# Tune for Debugging or Optimization
ifeq ($(DEBUG), 1)
CFLAGS += -O0 -ggdb
else
CFLAGS += -Os -DNDEBUG
# CFLAGS += -fdata-sections -ffunction-sections
endif
LIBS = mylibc.a libgcc.a
SRC_C = \
main.c \
nand.c \
uart_core.c \
lib/utils/pyexec.c \
lib/mp-readline/readline.c \
$(BUILD)/_frozen_mpy.c \
# SRC_C += lib/libc/string0.c
SRC_S = \
start.S \
# OBJ = $(PY_CORE_O) $(addprefix $(BUILD)/, $(SRC_C:.c=.o) $(SRC_S:.S=.o))
OBJ = $(addprefix $(BUILD)/, $(SRC_S:.S=.o) $(SRC_C:.c=.o)) $(PY_CORE_O)
all: $(BUILD)/firmware.bin
$(BUILD)/_frozen_mpy.c: frozentest.mpy $(BUILD)/genhdr/qstrdefs.generated.h
$(ECHO) "MISC freezing bytecode"
$(Q)$(TOP)/tools/mpy-tool.py -f -q $(BUILD)/genhdr/qstrdefs.preprocessed.h -mlongint-impl=none $< > $@
$(BUILD)/firmware.elf: $(OBJ)
$(ECHO) "LINK $@"
$(Q)$(LD) $(LDFLAGS) -o $@ $^ $(LIBS)
$(Q)$(SIZE) $@
$(BUILD)/firmware.bin: $(BUILD)/firmware.elf
$(Q)$(OBJCOPY) -S $< -O binary $@
$(Q)$(OBJDUMP) -D -m arm $< > $(BUILD)/firmware.dis
include $(TOP)/py/mkrules.mk
4.2 修改stm32f405.ld
修改名称为s3c2440.ld,这里没什么好说的,唯一需要注意的是0x30000000这个地址,原因是在start.s中会将最终编译出的firmware.bin文件又复制到sdram的0x30000000地址处,然后继续在sdram中执行。
文件全部内容改为如下:
OUTPUT_FORMAT("elf32-littlearm", "elf32-littlearm", "elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
SECTIONS {
. = 0x30000000;
. = ALIGN(4);
.text :
{
*(.text)
*(.text*)
}
. = ALIGN(4);
.rodata :
{
*(.rodata)
*(.rodata*)
}
. = ALIGN(4);
.data :
{
*(.data)
*(.data*)
}
. = ALIGN(4);
__bss_start = .;
.bss :
{
*(.bss)
*(.bss*)
*(COMMON)
}
__bss_end = .;
}
4.3 修改uart_core.c
主要是串口0的初始化、get、put函数。
文件全部内容改为如下:
#include <unistd.h>
#include "py/mpconfig.h"
#include "s3c2440_regs.h"
#include "uart.h"
#define PCLK 50000000 // clock_init函数设置PCLK为50MHz
#define UART_CLK PCLK // UART0的时钟源设为PCLK
#define UART_BAUD_RATE 115200 // 波特率
#define UART_BRD ((UART_CLK / (UART_BAUD_RATE * 16)) - 1)
void s3c2440_uart0_init(void)
{
GPHCON |= 0xa0; // GPH2,GPH3用作TXD0,RXD0
GPHUP = 0x0c; // GPH2,GPH3内部上拉
ULCON0 = 0x03; // 8N1(8个数据位,无较验,1个停止位)
UCON0 = 0x05; // 查询方式,UART时钟源为PCLK
UFCON0 = 0x00; // 不使用FIFO
UMCON0 = 0x00; // 不使用流控
UBRDIV0 = UART_BRD; // 波特率为115200
}
/* Used by printf in mylibc.a */
void putc(unsigned char c)
{
while (!(UTRSTAT0 & (1 << 2)));
UTXH0 = c;
}
/*
* Core UART functions to implement for a port
*/
// Receive single character
int mp_hal_stdin_rx_chr(void)
{
while (!(UTRSTAT0 & (1)));
return URXH0;
}
// Send string of given length
void mp_hal_stdout_tx_strn(const char *str, mp_uint_t len)
{
while (len--) {
while (!(UTRSTAT0 & (1 << 2)));
UTXH0 = *str++;
}
}
4.4 修改main.c
// #include <stdint.h>
// #include <stdio.h>
// #include <string.h>
#include "py/compile.h"
#include "py/runtime.h"
#include "py/repl.h"
#include "py/gc.h"
#include "py/mperrno.h"
#include "lib/utils/pyexec.h"
#include "uart.h"
/* fill in __assert_fail for libc */
void __assert_fail(const char *__assertion, const char *__file,
unsigned int __line, const char *__function)
{
printk("Assert at %s:%d:%s() \"%s\" failed\n", __file, __line, __function, __assertion);
for (;;) {;
}
}
/* Used by libgcc.a */
int raise (int signum)
{
printk("raise: Signal # %d caught\n", signum);
return 0;
}
int main(int argc, char **argv)
{
void *heap_start = (void *)0x31000000;
unsigned int heap_len = 0x100000;
s3c2440_uart0_init();
#if MICROPY_ENABLE_GC
gc_init(heap_start, heap_start + heap_len);
#endif
mp_init();
pyexec_friendly_repl();
mp_deinit();
while (1);
return 0;
}
#if 1
#if MICROPY_ENABLE_GC
void gc_collect(void) {
// WARNING: This gc_collect implementation doesn't try to get root
// pointers from CPU registers, and thus may function incorrectly.
volatile void *tmp = (void *)0x32000000;
volatile void *sp = &tmp;
gc_collect_start();
gc_collect_root((void**)sp, ((mp_uint_t)0x32000000 - (mp_uint_t)0x31000000) / sizeof(mp_uint_t));
gc_collect_end();
// gc_dump_info();
}
#endif
mp_lexer_t *mp_lexer_new_from_file(const char *filename) {
printk("mp_lexer_new_from_file\n");
mp_raise_OSError(MP_ENOENT);
}
mp_import_stat_t mp_import_stat(const char *path) {
printk("mp_import_stat\n");
return MP_IMPORT_STAT_NO_EXIST;
}
mp_obj_t mp_builtin_open(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) {
printk("mp_builtin_open\n");
return mp_const_none;
}
MP_DEFINE_CONST_FUN_OBJ_KW(mp_builtin_open_obj, 1, mp_builtin_open);
void nlr_jump_fail(void *val) {
while (1) {
;
}
}
void NORETURN __fatal_error(const char *msg) {
while (1) {
;
}
}
#ifndef NDEBUG
void MP_WEAK __assert_func(const char *file, int line, const char *func, const char *expr) {
printk("Assertion '%s' failed, at file %s:%d\n", expr, file, line);
__fatal_error("Assertion failed");
}
#endif
#include <stdarg.h>
int mp_vprintf(const mp_print_t *print, const char *fmt, va_list args);
int DEBUG_printf(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
int ret = mp_vprintf(MICROPY_DEBUG_PRINTER, fmt, ap);
va_end(ap);
return ret;
}
// Send "cooked" string of given length, where every occurrence of
// LF character is replaced with CR LF.
void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len) {
while (len--) {
printk("%c", *str++);
}
}
// Send zero-terminated string
void mp_hal_stdout_tx_str(const char *str) {
mp_hal_stdout_tx_strn(str, strlen(str));
}
#endif
4.5 修改mpconfigport.h
#include <stdint.h>
// options to control how MicroPython is built
// You can disable the built-in MicroPython compiler by setting the following
// config option to 0. If you do this then you won't get a REPL prompt, but you
// will still be able to execute pre-compiled scripts, compiled with mpy-cross.
#define MICROPY_ENABLE_COMPILER (1)
/* Debug */
#define DEBUG_PRINT (0)
#define MICROPY_DEBUG_VERBOSE (0)
#define MICROPY_REPL_INFO (0)
#define MICROPY_OBJ_REPR (MICROPY_OBJ_REPR_A)
#define MICROPY_MODULE_BUILTIN_INIT (1)
#define MICROPY_QSTR_BYTES_IN_HASH (1)
#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool
#define MICROPY_ALLOC_PATH_MAX (256)
#define MICROPY_ALLOC_PARSE_CHUNK_INIT (16)
#define MICROPY_COMP_CONST (1)
#define MICROPY_COMP_DOUBLE_TUPLE_ASSIGN (1)
#define MICROPY_ENABLE_GC (1)
#define MICROPY_GC_ALLOC_THRESHOLD (1)
#define MICROPY_HELPER_REPL (1)
#define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_TERSE)
#define MICROPY_BUILTIN_METHOD_CHECK_SELF_ARG (1)
#define MICROPY_PY_ASYNC_AWAIT (1)
#define MICROPY_PY_ASSIGN_EXPR (1)
#define MICROPY_PY_BUILTINS_BYTEARRAY (1)
#define MICROPY_PY_BUILTINS_DICT_FROMKEYS (1)
#define MICROPY_PY_BUILTINS_ENUMERATE (1)
#define MICROPY_PY_BUILTINS_FILTER (1)
#define MICROPY_PY_BUILTINS_REVERSED (1)
#define MICROPY_PY_BUILTINS_SET (1)
#define MICROPY_PY_BUILTINS_SLICE (1)
#define MICROPY_PY_BUILTINS_PROPERTY (1)
#define MICROPY_PY_BUILTINS_MIN_MAX (1)
#define MICROPY_PY_BUILTINS_STR_COUNT (1)
#define MICROPY_PY_BUILTINS_STR_OP_MODULO (1)
#define MICROPY_PY___FILE__ (1)
#define MICROPY_PY_GC (1)
#define MICROPY_PY_ARRAY (1)
#define MICROPY_PY_ATTRTUPLE (1)
#define MICROPY_PY_COLLECTIONS (1)
#define MICROPY_PY_IO (1)
#define MICROPY_PY_STRUCT (1)
#define MICROPY_PY_SYS (1)
#define MICROPY_MODULE_FROZEN_MPY (1)
#define MICROPY_CPYTHON_COMPAT (1)
#define MICROPY_MODULE_GETATTR (1)
#define BYTES_PER_WORD (sizeof(mp_uint_t))
#define MICROPY_ENABLE_SCHEDULER (1)
#define MICROPY_SCHEDULER_DEPTH (8)
// type definitions for the specific machine
#define UINT_FMT "%u"
#define INT_FMT "%d"
typedef int mp_int_t; // must be pointer size
typedef unsigned int mp_uint_t; // must be pointer size
typedef long mp_off_t;
// use vfs's functions for import stat and builtin open
#define MICROPY_VFS (1)
#define mp_import_stat mp_vfs_import_stat
#define mp_builtin_open mp_vfs_open
#define mp_builtin_open_obj mp_vfs_open_obj
#define MICROPY_PY_ATTRTUPLE (1)
#define MICROPY_PY_FUNCTION_ATTRS (1)
#define MICROPY_PY_DESCRIPTORS (1)
// extra built in names to add to the global namespace
#define MICROPY_PORT_BUILTINS \
{ MP_ROM_QSTR(MP_QSTR_open), MP_ROM_PTR(&mp_builtin_open_obj) }, \
// We need to provide a declaration/definition of alloca()
#include <alloca.h>
#define MICROPY_HW_BOARD_NAME "JZ2440"
#define MICROPY_HW_MCU_NAME "Samsung-s3c2440"
#ifdef __linux__
#define MICROPY_MIN_USE_STDOUT (1)
#endif
#ifdef __thumb__
#define MICROPY_MIN_USE_CORTEX_CPU (1)
#define MICROPY_MIN_USE_STM32_MCU (1)
#endif
#define MP_STATE_PORT MP_STATE_VM
#define MICROPY_PORT_ROOT_POINTERS \
const char *readline_hist[8];
5. 编译&烧录&运行
5.1 编译
-
进入micropython/ports/s3c2440目录,然后直接执行make即可,成功的话会在micropython/ports/s3c2440/build目录下生成firmware.bin
-
执行make clean可清除编译产物micropython/ports/s3c2440/build目录
5.2 烧录
将firmware.bin烧录到开发板nand flash的0地址处
5.3 运行
6. 遗留的问题
-
不能打印整型数据
比如执行print(66),应该打印66,但是现在没有任何输出,如下图:
又比如执行micropython自带函数min(6,20),应该返回较小的6,但是同样没有任何输出,如下图:
-
import问题
比如执行import sys导入sys模块后,执行sys类下的函数会出现name not defined的问题,如下图:
该问题会出现在导入的所有模块中。