MicroPython分析

 

相关参考:Micropython--WIN7-64位下搭建开发环境

一介绍
Micro Python运行在微控制器上的Python。遵守MIT协议。由剑桥大学的理论物理学家乔治·达明设计。

Micro Python的软件特点:

  • Python 3 语法.
  • 完整的Python词法分析器, 解析器,编译器,虚拟机和运行时。
  • 包含命令行接口,可离线运行。
  • Python 字节码由内置虚拟机编译运行.
  • 有效的内部存储算法,能带来高效的内存利用率。整数变量存储在内存堆中,而不是栈中。
  • 使用Python decorators特性,函数可以被编译成原生机器码,虽然这一特性会带来大约2倍的内存消耗,但也使python有更快的执行速度。
  • 函数编译可设置使用底层整数代替python内建对象作为数字使用。有些代码的运行效率可以媲美c的效率,并且可以被python直接调用,适合做时间紧迫性,运算复杂度高的应用。
  • 通过内联汇编功能,应用可以完全接入底层运行时,内联汇编器也可以像普通的python函数一样调用。
  • 基于简单和快速标记的内存垃圾回收算法,运行周期少于4ms,许多函数都可以避免使用栈内存段,因此也不需要垃圾回收功能。

二 源码分析

      MicroPython实现了基本的python的词法分析器, 解析器,编译器,虚拟机和运行时。在github上获得代码:https://github.com/micropython/micropython

解压之后目录大概如下:

     主要目录的解释也可在根目录的README.md中找到。

本文以STM32 MicroPython为例。目前MicroPython可运行于类unix系统,PIC16,ARM等平台。

首先,需要了解Python的运行机制,可以参考:http://tech.uc.cn/?p=1932

     然后,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 }

你可能感兴趣的:(MicroPython)