一介绍
Micro Python运行在微控制器上的Python。遵守MIT协议。由剑桥大学的理论物理学家乔治·达明设计。
Micro Python的软件特点:
-
Python 3 语法.
-
完整的Python词法分析器, 解析器,编译器,虚拟机和运行时。
-
包含命令行接口,可离线运行。
-
Python 字节码由内置虚拟机编译运行.
-
有效的内部存储算法,能带来高效的内存利用率。整数变量存储在内存堆中,而不是栈中。
-
使用Python decorators特性,函数可以被编译成原生机器码,虽然这一特性会带来大约2倍的内存消耗,但也使python有更快的执行速度。
-
函数编译可设置使用底层整数代替python内建对象作为数字使用。有些代码的运行效率可以媲美c的效率,并且可以被python直接调用,适合做时间紧迫性,运算复杂度高的应用。
-
通过内联汇编功能,应用可以完全接入底层运行时,内联汇编器也可以像普通的python函数一样调用。
-
基于简单和快速标记的内存垃圾回收算法,运行周期少于4ms,许多函数都可以避免使用栈内存段,因此也不需要垃圾回收功能。
二 源码分析
MicroPython实现了基本的python的词法分析器, 解析器,编译器,虚拟机和运行时。在github上获得代码:
解压之后目录大概如下:
主要目录的解释也可在根目录的README.md中找到。
本文以STM32 MicroPython为例。目前MicroPython可运行于类unix系统,PIC16,ARM等平台。
首先,需要了解Python的运行机制,可以参考:。
然后,micropython-master\py目录下个人认为是最核心的发动机,也就是MicroPython的实现,C语言实现的python的解析器,运行时,虚拟机组建。
最后我们来看STM32是如何实现Python编程与控制的:
源码图中绿色注释的部分是STM32 MicroPython的主要代码,STM32(以下简称MCU)main函数位于stmhal/main.c,MCU启动后进入main函数,那么我们看main函数里干了什么事情:
int main(void) {
// TODO disable JTAG
// Stack limit should be less than real stack size, so we have a chance
// to recover from limit hit. (Limit is measured in bytes.)
mp_stack_ctrl_init();
mp_stack_set_limit((char*)&_ram_end - (char*)&_heap_end - 1024);
/* STM32F4xx HAL library initialization:
- Configure the Flash prefetch, instruction and Data caches
- Configure the Systick to generate an interrupt each 1 msec
- Set NVIC Group Priority to 4
- Global MSP (MCU Support Package) initialization
*/
HAL_Init();
// set the system clock to be HSE
SystemClock_Config();
.........
port init, LED init, switch init , sdcard initµÈ°åÉÏÍâÉè³õʼ»¯
......
// GC init
gc_init(&_heap_start, &_heap_end);
// Micro Python init
mp_init();
mp_obj_list_init(mp_sys_path, 0);
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR_)); // current dir (or base dir of the script)
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_flash));
mp_obj_list_append(mp_sys_path, MP_OBJ_NEW_QSTR(MP_QSTR__slash_flash_slash_lib));
mp_obj_list_init(mp_sys_argv, 0);
// zero out the pointers to the mounted devices
memset(MP_STATE_PORT(fs_user_mount), 0, sizeof(MP_STATE_PORT(fs_user_mount)));
// Initialise low-level sub-systems. Here we need to very basic things like
// zeroing out memory and resetting any of the sub-systems. Following this
// we can run Python scripts (eg boot.py), but anything that is configurable
// by boot.py must be set after boot.py is run.
readline_init0();
pin_init0();
extint_init0();
timer_init0();
uart_init0();
// Define MICROPY_HW_UART_REPL to be PYB_UART_6 and define
// MICROPY_HW_UART_REPL_BAUD in your mpconfigboard.h file if you want a
// REPL on a hardware UART as well as on USB VCP
#if defined(MICROPY_HW_UART_REPL)
{
mp_obj_t args[2] = {
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_UART_REPL),
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_UART_REPL_BAUD),
};
MP_STATE_PORT(pyb_stdio_uart) = pyb_uart_type.make_new((mp_obj_t)&pyb_uart_type, MP_ARRAY_SIZE(args), 0, args);
}
#else
MP_STATE_PORT(pyb_stdio_uart) = NULL;
#endif
i2c_init0();
soft_reset_exit:
}
在main函数中大概是初始化MCU的时钟,初始化各个外设,然后调用python的垃圾回收器gc_init(), 初始化python对象列表,相当于启动了python的虚拟机(本人 暂时对python的实现机制理解有限)。后面紧接着启动了python的REPL,bind到uart6,这也就允许你通过串口来执行python代码了。
启动之后单片机底层的操作通过micropython-master\stmhal\hal\f4目录下的HAL驱动来完成,那么在串口敲下的命令又是如何调用底层的驱动的呢?
三 内建对象集成
拿简单的DAC来说,与之相关的文件为micropython-master\stmhal\下的dac.h, dac.c, dac.x文件声明并定义了MicroPython board DAC类的方法与属性,通过MP_DEFINE_CONST_FUN_OBJ_KW或MP_DEFINE_CONST_FUN_OBJ_x等注册给MicroPython的内建对象。比如下面的函数:pyb_dac_write()函数调用STM32 的HAL lib实现硬件的操作,通过MP将此函数注册为pyb_dac_write_obj
/// \method write(value)
/// Direct access to the DAC output (8 bit only at the moment).
STATIC mp_obj_t pyb_dac_write(mp_obj_t self_in, mp_obj_t val) {
pyb_dac_obj_t *self = self_in;
if (self->state != DAC_STATE_WRITE_SINGLE) {
DAC_ChannelConfTypeDef config;
config.DAC_Trigger = DAC_TRIGGER_NONE;
config.DAC_OutputBuffer = DAC_OUTPUTBUFFER_DISABLE;
HAL_DAC_ConfigChannel(&DAC_Handle, &config, self->dac_channel);
self->state = DAC_STATE_WRITE_SINGLE;
}
// DAC output is always 12-bit at the hardware level, and we provide support
// for multiple bit "resolutions" simply by shifting the input value.
HAL_DAC_SetValue(&DAC_Handle, self->dac_channel, DAC_ALIGN_12B_R,
mp_obj_get_int(val) << (12 - self->bits));
HAL_DAC_Start(&DAC_Handle, self->dac_channel);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(pyb_dac_write_obj, pyb_dac_write);
最后将所有的方法注册给MicroPython
STATIC const mp_map_elem_t pyb_dac_locals_dict_table[] = {
// instance methods
{ MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&pyb_dac_init_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_write), (mp_obj_t)&pyb_dac_write_obj },
#if defined(TIM6)
{ MP_OBJ_NEW_QSTR(MP_QSTR_noise), (mp_obj_t)&pyb_dac_noise_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_triangle), (mp_obj_t)&pyb_dac_triangle_obj },
{ MP_OBJ_NEW_QSTR(MP_QSTR_write_timed), (mp_obj_t)&pyb_dac_write_timed_obj },
#endif
// class constants
{ MP_OBJ_NEW_QSTR(MP_QSTR_NORMAL), MP_OBJ_NEW_SMALL_INT(DMA_NORMAL) },
{ MP_OBJ_NEW_QSTR(MP_QSTR_CIRCULAR), MP_OBJ_NEW_SMALL_INT(DMA_CIRCULAR) },
};
STATIC MP_DEFINE_CONST_DICT(pyb_dac_locals_dict, pyb_dac_locals_dict_table);
const mp_obj_type_t pyb_dac_type = {
{ &mp_type_type },
.name = MP_QSTR_DAC,
.make_new = pyb_dac_make_new,
.locals_dict = (mp_obj_t)&pyb_dac_locals_dict,
};
该结构体在dac.h中声明为外部变量:
extern const mp_obj_type_t pyb_dac_type;
然后在stmhal/modpyb.c中包含了dac.h,并将pyb_dac_type声明为python的对象:
#if MICROPY_HW_ENABLE_DAC
{ MP_OBJ_NEW_QSTR(MP_QSTR_DAC), (mp_obj_t)&pyb_dac_type },
#endif
因此如果想添加内建的对象或方法应该遵循如下的步骤与原则:
1. 建立mp对象:
const mp_obj_type_t pyb_led_type = {
{ &mp_type_type },
.name = MP_QSTR_LED, ///name
.print = led_obj_print, ///重载的print方法
.make_new = led_obj_make_new, ///构造函数
.locals_dict = (mp_obj_t)&led_locals_dict, ///该对象所拥有的方法字典
};
2. 建立方法字典led_locals_dict:
3. 实现方法
实现上图中画横线的方法以及第一步中的重载的print方法,构造方法等。
4. 将mp对象pyb_led_type 添加到modpyb.c中的pyb_module_globals_table[]全局python对象表里:
{ MP_OBJ_NEW_QSTR(MP_QSTR_DAC), (mp_obj_t)&pyb_dac_type }