通过网盘分享的文件:fr30xxc_sdk__202411.zip
链接: https://pan.baidu.com/s/1Z0uj7hP_S9XJj7uUjE1z0w?pwd=7ap6 提取码: 7ap6
--来自百度网盘超级会员v3的分享
打开fr30xxc_sdk__202411\examples\evb_demo\lvgl_demo\MDK-ARM\Project.uvprojx即可
详细过程参考公众号文章移植micropython到MCU(以MDK+CORTEX-M平台示例)
前言
MicroPython 是 Python 3 的精简和高效的实现,包含了一部分经过优化的Python 标准库,能在微控制器等资源受限的平台运行。MicroPython也支持一些高级功能,比如交互式提示、任意精度的整数、闭包、列表推导、生成器、异常处理等等。它的实现体积很小,可以在只有 256k 的代码空间和 16k 的内存空间的平台运行,特别适合移植到MCU上使用。MicroPython 的设计也是尽量和普通的 Python 兼容,这样能轻松地把代码从桌面平台等搬到微控制器或者嵌入式系统平台运行。个人觉得移植micropython到自己的平台,用于自动化验证测试也是一个非常不错的应用,搭建好micropython环境后,这样可以由验证和测试的同事直接编写python脚本验证测试,而无需软件工程师全程配合了,这样提高了协作效率。
官方提供了各种pyboard的适配,可以直接编译对应的固件使用。而对于我们自己的平台,官方可能没有适配,而且其构建环境可能也不是我们使用的,所以一个好的方式是只使用其源码将其移植到我们自己的平台。本文就分享Micropython基于MDK移植到CORTEX-M33平台。开发板使用的是的富芮坤的FR3068E-C。
二.获取源码
下载源码git clone https://github.com/micropython/micropython.git
为了了解代码框架等可以先在某一个官方支持的平台上构建一下,了解一下构建过程,这样可以知道需要编译哪些源码,有哪些过程。
可以直接在windows下使用vs打开micropython\mpy-cross\mpy-cross.vcxproj编译编译器。
然后再打开micropython\ports\windows\micropython.vcxproj编译解释器。
了解下其工程架构,注意构建前需要取消所有文件的只读属性。
添加源码
我们来移植一个最下实现,即暂时不添加外部模块,仅使用内建的一些模块,整个代码框架如下
Py源码
其中py目录下是micropyth解释器实现核心代码,我们复制到自己的工程目录中。
复制其下所有c文件到自己的工程中。
并将py路径添加到头文件包含路径。
其中以下文件按照平台选择,我这里是ARM CORTEX-M系列选择asmthumb.c,其他的不需要。
asmarm.c
asmrv32.c
asmthumb.c
asmx64.c
asmx86.c
asmxtensa.c
shared源码
暂时只添加以下内容,其他有需要时再添加
Shared/runtime/pyexec.c pyexec.h
Shared/readline/readline.c readline.h
extmod源码
暂时只添加以下内容,其他的有需要时再添加
modplatform.h
virtpin.h
py_port源码
然后新建一个移植文件夹py_port,我们参考micropython\ports\minimal移植
复制micropython\ports\minimal下的文件到我们的py_port路径下
将py_port添加到自己的工程目录
文件如下,其中uart_core.c实现串口输入输出接口,mpconfigport.h是配置文件,mphalport.h是一些hal层接口实现,py_main是解释器入口,qstrdefsport.h
unistd.h是类似unix平台的一个替代。
Py_main.c
py_port/main.c改为py_port/py_main.c
它默认是基于stm32或者pc主机实现的,我们需要按照自己的平台做一些修改。
把
#if MICROPY_MIN_USE_STM32_MCU
#endif
#if MICROPY_MIN_USE_CORTEX_CPU
#endif
的内容都删掉,因为我们使用自己的平台。
main函数名改为py_main
到时在自己的平台合适的地方调用py_main即可。
先保证其能编译过,后续再来看py_main如何实现。
Uart_core.c
需要实现以下接口,后面再来看实现
#include "py/mpconfig.h"
/*
* Core UART functions to implement for a port
*/
// Receive single character
int mp_hal_stdin_rx_chr(void) {
unsigned char c = 0;
return c;
}
// Send string of given length
mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
mp_uint_t ret = len;
return ret;
}
void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len){
}
void mp_hal_stdout_tx_str(const char *str){
}
Unistd.h
定义ssize_t,其其他一些宏
#ifndef MICROPY_INCLUDED_UNISTD_H
#define MICROPY_INCLUDED_UNISTD_H
typedef int ssize_t;
#define F_OK 0
#define W_OK 2
#define R_OK 4
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#define SEEK_CUR 1
#define SEEK_END 2
#define SEEK_SET 0
#endif
mphalport.h
Hal层接口
static inline mp_uint_t mp_hal_ticks_ms(void) {
return 0;
}
static inline void mp_hal_set_interrupt_char(char c) {
}
qstrdefsport.h
// qstrs specific to this port
// *FORMAT-OFF*
mpconfigport.h
配置文件,这个我呢金很重要,其中需要关注的是MICROPY_PY_XXX宏配置是否使能某些模块。
#include <stdint.h>
// options to control how MicroPython is built
// Use the minimal starting configuration (disables all optional features).
#define MICROPY_CONFIG_ROM_LEVEL (MICROPY_CONFIG_ROM_LEVEL_MINIMUM)
// 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)
//#define MICROPY_QSTR_EXTRA_POOL mp_qstr_frozen_const_pool
#define MICROPY_HELPER_REPL (1)
#define MICROPY_MODULE_FROZEN_MPY (0)
#define MICROPY_ENABLE_EXTERNAL_IMPORT (0)
#define MICROPY_PY_IO (0)
#define MICROPY_PY_IO_IOBASE (1)
#define MICROPY_PY_ERRNO (1)
#define MICROPY_PY_MATH (1)
#define MICROPY_PY_CMATH (1)
#define MICROPY_PY_BUILTINS_FLOAT (1)
#define MICROPY_PY_BUILTINS_COMPLEX (1)
#define MICROPY_PY_GC (1)
#define MICROPY_ENABLE_GC (1)
#define MICROPY_PY_SYS (1)
#define MICROPY_PY_MICROPYTHON (1)
#define MICROPY_USE_INTERNAL_PRINTF (0)
#define MICROPY_PY_ARRAY (1)
#define MICROPY_PY_BUILTINS_BYTEARRAY (1)
#define MICROPY_PY_BUILTINS_MEMORYVIEW (1)
#define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_DOUBLE)
#define MICROPY_ALLOC_PATH_MAX (256)
// Use the minimum headroom in the chunk allocator for parse nodes.
#define MICROPY_ALLOC_PARSE_CHUNK_INIT (16)
// type definitions for the specific machine
typedef intptr_t mp_int_t; // must be pointer size
typedef uintptr_t mp_uint_t; // must be pointer size
typedef long mp_off_t;
// We need to provide a declaration/definition of alloca()
#include <alloca.h>
#define MICROPY_HW_BOARD_NAME "minimal"
#define MICROPY_HW_MCU_NAME "unknown-cpu"
#define MICROPY_HEAP_SIZE (2048) // heap size 2 kilobytes
#define MP_STATE_PORT MP_STATE_VM
#define MICROPY_USE_INTERNAL_ERRNO 1
移植
标准库依赖
依赖的标准库如下:
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdarg.h>
#include <math.h>
#include <limits.h>
#include <unistd.h> 这个我们在py_port自己实现
errno
如果没有#include <errno.h>
则配置MICROPY_USE_INTERNAL_ERRNO为1
#include <alloca.h> 这个编译器支持
产生genhdr
我们可以从其他已经构建的,或者使用windows的样例先构建一下,然后复制对应的头文件
moduledefs.h 定义一些模块信息,由于没有使用官方的构建过程,可能没有自动更新对一个,可以手动修改,删减某些模块。 后面我们在MDK的工程配置中合适时机手动调用对应的脚本来实现。
mpversion.h
qstrdefs.generated.h
root_pointers.h
生成过程参考ports\windows\msvc\genhdr.targets
中的命令
<Exec Command="type $(QstrDefsCollected) >> $(DestDir)qstrdefs.preprocessed.h"/>
<Exec Command="$(PyPython) $(PySrcDir)makeqstrdata.py $(DestDir)qstrdefs.preprocessed.h > $(TmpFile)"/>
其中指定文件qstrdefscollected.h
<QstrDefsCollected>$(DestDir)qstrdefscollected.h</QstrDefsCollected>
该文件由以下命令产生
<Exec Command="$(PyPython) $(PySrcDir)makeqstrdefs.py split qstr $(DestDir)qstr.i.last $(DestDir)qstr _"/>
<Exec Command="$(PyPython) $(PySrcDir)makeqstrdefs.py cat qstr _ $(DestDir)qstr $(QstrDefsCollected)"/>
对应如下命令
makeqstrdata.py位于py目录下,在此打开cmd行,
先执行
python makeqstrdefs.py split qstr qstr.i.last qstr _
python makeqstrdefs.py cat qstr _ qstr qstrdefscollected.h
再执行
type qstrdefscollected.h >> qstrdefs.preprocessed.h
再执行
python makeqstrdata.py qstrdefs.preprocessed.h > qstrdefs.generated.h.tmp
适配串口
py_port\uart_core.c中删掉
#include <unistd.h>
添加我们自己的串口接口
#include “uart.h”
删掉
#if MICROPY_MIN_USE_STM32_MCU
#end
#if MICROPY_MIN_USE_STDOUT
#elif MICROPY_MIN_USE_STM32_MCU
#endif
中的内容
即实现阻塞读一个字符mp_hal_stdin_rx_chr
阻塞发送字符串的接口
mp_hal_stdout_tx_strn
mp_hal_stdout_tx_strn_cooked
mp_hal_stdout_tx_str
串口实现可以参考文章,采用FIFO方式实现
https://mp.weixin.qq.com/s/vzjWu2LxpVGZw-msCooh8Q?token=1312261758&lang=zh_CN 超级精简系列之十六:基于IO模拟+FIFO的串口驱动
Uart.c如下
#include <stdio.h>
#include <stdbool.h>
#include "uart.h"
#include "fifo.h"
#include <stdio.h>
#include "fr30xx.h"
#define CriticalAlloc()
#define EnterCritical() __disable_irq()
#define ExitCritical() __enable_irq()
static uint8_t s_uart_rx_buffer[64];
static fifo_st s_uart_fifo_dev=
{
.in = 0,
.len = 0,
.out = 0,
.buffer = s_uart_rx_buffer,
.buffer_len = sizeof(s_uart_rx_buffer),
};
volatile bool g_data_transmit_flag = false;
uint8_t rx_buffer[1];
void uart_rx_cb(uint8_t* buff, uint32_t len)
{
fifo_in(&s_uart_fifo_dev, buff, len);
}
uint32_t uart_send(uint8_t* buffer, uint32_t len)
{
g_data_transmit_flag = false;
for(uint32_t i=0;i<len;i++)
{
putchar(buffer);
}
return len;
}
uint32_t uart_read(uint8_t* buffer, uint32_t len)
{
uint32_t rlen;
CriticalAlloc();
EnterCritical();
rlen = fifo_out(&s_uart_fifo_dev, buffer, len);
ExitCritical();
return rlen;
}
Uart.h如下
#ifndef UART_H
#define UART_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
uint32_t uart_send(uint8_t* buffer, uint32_t len);
uint32_t uart_read(uint8_t* buffer, uint32_t len);
void uart_rx_cb(uint8_t* buff, uint32_t len);
#ifdef __cplusplus
}
#endif
#endif
Fifo.c如下
#include <string.h>
#include "fifo.h"
#define FIFO_PARAM_CHECK 0
/**
* in????? 0~(buffer_len-1)?
* out????? 0~(buffer_len-1)?
* in == out?????,?????,????len??????????
* ???in??,?????out???
* ????out??,?????in???
* in??out??[out,in)????????
* in??out??[out,buffer_len)?[0,in)????????
***********************************************************
* 0 buffer_len-1 buffer_len
* (1)?? in?out??0
* | |
* in(0)
* out(0)
* len = 0
* (2)??n???? in??n?out??0 ??in??out???
* | |
* out(0)————————————>in(n) |
* len = n
* (3)??m????(m<n) in??n?out??m ??in??out???
* | |
* out(m)————>in(n)
* len = n-m
* (4)??????,?????,??in??out???
* | |
* out(m)————————————————————————————————>
* ——>in(k)
* len = k + buffer_len-m
*/
uint32_t fifo_in(fifo_st* dev, uint8_t* buffer, uint32_t len)
{
uint32_t space = 0; /* ?????????? */
/* ???? */
#if FIFO_PARAM_CHECK
if((dev == 0) || (buffer == 0) || (len == 0))
{
return 0;
}
if(dev->buffer == 0)
{
return 0;
}
#endif
/* ??len??????buffer?? */
if(len > dev->buffer_len)
{
len = dev->buffer_len;
}
/* ????????
* ??dev->len?????dev->buffer_len
*/
if(dev->buffer_len >= dev->len)
{
space = dev->buffer_len - dev->len;
}
else
{
/* ???????, ?????? */
dev->len = 0;
space = dev->buffer_len;
}
/* ???????, ??len???????????????? */
len = (len >= space) ? space : len;
if(len == 0)
{
return 0; /* ??????????,???? */
}
/* ??len??????????,?????? */
space = dev->buffer_len - dev->in; /* ??????in???????????? */
if(space >= len)
{
/* ??????in??????????? */
memcpy(dev->buffer+dev->in,buffer,len);
}
else
{
/* ??????in???????,????????? */
memcpy(dev->buffer+dev->in,buffer,space); /* ???tail?? */
memcpy(dev->buffer,buffer+space,len-space); /* ???????? */
}
/* ????????????? */
dev->in += len;
if(dev->in >= dev->buffer_len)
{
dev->in -= dev->buffer_len; /* ????? ?? dev->in %= dev->buffer->len */
}
dev->len += len; /* dev->len??dev->buffer->len,??%= dev->buffer->len */
return len;
}
uint32_t fifo_out(fifo_st* dev, uint8_t* buffer, uint32_t len)
{
uint32_t space = 0;
/* ???? */
#if FIFO_PARAM_CHECK
if((dev == 0) || (buffer == 0) || (len == 0))
{
return 0;
}
if(dev->buffer == 0)
{
return 0;
}
#endif
/* ??????? */
if(dev->len == 0)
{
return 0;
}
/* ?????????????????? */
len = (dev->len) > len ? len : dev->len;
/* ??len??????????,?????? */
space = dev->buffer_len - dev->out; /* ??????out???????????? */
if(space >= len)
{
/* ??????out??????????? */
memcpy(buffer,dev->buffer+dev->out,len);
}
else
{
/* ??????out???????,????????? */
memcpy(buffer,dev->buffer+dev->out,space); /* ???tail?? */
memcpy(buffer+space,dev->buffer,len-space); /* ???????? */
}
/* ????????????? */
dev->out += len;
if(dev->out >= dev->buffer_len)
{
dev->out -= dev->buffer_len; /* ????? ?? dev->out %= dev->buffer->len */
}
dev->len -= len; /* ??dev->len ?????len,???? */
return len;
}
uint32_t fifo_getlen(fifo_st* dev)
{
#if FIFO_PARAM_CHECK
if(dev == 0)
{
return 0;
}
#endif
return dev->len;
}
void fifo_clean(fifo_st* dev)
{
#if FIFO_PARAM_CHECK
if(dev == 0)
{
return 0;
}
#endif
dev->len = 0;
dev->in = 0;
dev->out = 0;
}
Fifo,h如下
#ifndef FIFO_H
#define FIFO_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/**
* \struct fifo_st
* FIFO?????.
*/
typedef struct
{
uint32_t in; /**< ???? */
uint32_t out; /**< ???? */
uint32_t len; /**< ?????? */
uint32_t buffer_len; /**< ???? */
uint8_t* buffer; /**< ??,???? */
} fifo_st;
/**
* \fn fifo_in
* ?fifo????
* \param[in] dev \ref fifo_st
* \param[in] buffer ??????
* \param[in] len ??????
* \retval ??????????
*/
uint32_t fifo_in(fifo_st* dev, uint8_t* buffer, uint32_t len);
/**
* \fn fifo_out
* ?fifo????
* \param[in] dev \ref fifo_st
* \param[in] buffer ??????
* \param[in] len ?????????
* \retval ??????????
*/
uint32_t fifo_out(fifo_st* dev, uint8_t* buffer, uint32_t len);
uint32_t fifo_getlen(fifo_st* dev);
void fifo_clean(fifo_st* dev);
#ifdef __cplusplus
}
#endif
#endif
最终uart_core.c实现如下
#include <string.h>
#include "py/mpconfig.h"
#include "uart.h"
/*
* Core UART functions to implement for a port
*/
// Receive single character
int mp_hal_stdin_rx_chr(void) {
unsigned char c = 0;
while(0 == uart_read(&c,1))
{
};
return c;
}
// Send string of given length
mp_uint_t mp_hal_stdout_tx_strn(const char *str, mp_uint_t len) {
mp_uint_t ret = len;
uart_send((uint8_t*)str,len);
return ret;
}
void mp_hal_stdout_tx_strn_cooked(const char *str, size_t len){
uart_send((uint8_t*)str,len);
}
void mp_hal_stdout_tx_str(const char *str){
uart_send((uint8_t*)str,strlen(str));
}
Py_main入口
py_main.c中
MICROPY_ENABLE_GC配置为0
MICROPY_ENABLE_COMPILER配置为1
MICROPY_REPL_EVENT_DRIVEN 配置为0
即执行过程如下
mp_init();->pyexec_friendly_repl();
int py_main(int argc, char **argv) {
int stack_dummy;
stack_top = (char *)&stack_dummy;
#if MICROPY_ENABLE_GC
gc_init(heap, heap + sizeof(heap));
#endif
mp_init();
#if MICROPY_ENABLE_COMPILER
#if MICROPY_REPL_EVENT_DRIVEN
pyexec_event_repl_init();
for (;;) {
int c = mp_hal_stdin_rx_chr();
if (pyexec_event_repl_process_char(c)) {
break;
}
}
#else
pyexec_friendly_repl();
#endif
// do_str("print('hello world!', list(x+1 for x in range(10)), end='eol\\n')", MP_PARSE_SINGLE_INPUT);
// do_str("for i in range(10):\r\n print(i)", MP_PARSE_FILE_INPUT);
#else
pyexec_frozen_module("frozentest.py", false);
#endif
mp_deinit();
return 0;
}
创建一个线程运行py_main函数即可。
测试
运行后看到打印如下
进行一个简单的计算
使用了alloca.h从栈分配资源,所以注意栈大小先配置大一点。
总结
网上一些资料最多算是micropython的构建,算不上移植,这里分享完全从0开始移植micropython源码到自己的工程,一通百通。通过此可以了解一个最简的micropython移植需要哪些文件,以及其基本的架构。后续我们再来介绍外部模块的添加,这才是移植的最大头的工作量,要做到实用,实际就是要移植好各种平台相关的资源模块。