移植micropython到jz2440开发板(samsung s3c2440 soc)记录

本篇文章讲述的是移植micropython到裸机,此外还包括运行在类unix以及windows操作系统之上的版本,这里没有涉及。

移植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源码目录的结构为:

attachments-2020-07-GvwPhHsa5efdbce4efc97.png

关于每个目录的功能,可从顶层目录的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

主要改动如下:

  • 1. CROSS_COMPILE ?= arm-linux-gnueabi-

        关于交叉编译工具链,需要用比较新的,旧的工具链会有一些指令不支持

  • 2. CFLAGS_ARM9 = -march=armv4t -marm -msoft-float -fsingle-precision-constant -Wdouble-promotion -Wfloat-conversion

        指定具体芯片的编译选项,以上是关于s3c2440(arm920t)的选项,具体可以从u-boot编译时的最终链接命令得到

  • 3. 在SRC_C以及SRC_S下增加新文件

  • 4. 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 运行

attachments-2020-07-smPysVyG5efdbf03ca35c.png

6. 遗留的问题

  • 不能打印整型数据

    比如执行print(66),应该打印66,但是现在没有任何输出,如下图:

    attachments-2020-07-XyXHl0Gi5efdbd6b3268b.png

又比如执行micropython自带函数min(6,20),应该返回较小的6,但是同样没有任何输出,如下图:

attachments-2020-07-RphT9V6t5efdbd81e5923.png

  • import问题

    比如执行import sys导入sys模块后,执行sys类下的函数会出现name not defined的问题,如下图:

    attachments-2020-07-gNQjzXca5efdbd95c54e5.png

    该问题会出现在导入的所有模块中。

  • 发表于 2020-07-02 19:00
  • 阅读 ( 211 )
  • 分类:经验分享

0 条评论&回复

请先 登录 后评论
渐进
渐进

12 篇文章

作家榜 »

  1. 百问网-周老师 18 文章
  2. st_ashang 14 文章
  3. 渐进 12 文章
  4. zxq 11 文章
  5. helloworld 8 文章
  6. 谢工 5 文章
  7. Litchi_Zheng 5 文章
  8. 星星之火 5 文章