为micropython添加模块(2)-类模块

本文详述了如何在MicroPython中移植和创建Pin类,涵盖从实现底层功能函数,定义类结构,初始化外设,定义类属性结构体,到编写类方法,构造函数以及注册到模块的过程。通过具体的代码示例,展示了如何将C语言与MicroPython的类系统结合,以实现硬件驱动的Python化操作。

这篇是我早年学习micropython的学习笔记.
在这里插入图片描述
当时关于micropython的开发文档资料相当匮乏, 我自己很多开发的思路都是通过研读代码, 连蒙带猜一点一点摸索出来的. 这篇<移植mpy:向模块中添加类>的文档成文之后, 一直存放在我的一个私有代码仓库里, 作为我近几年学习micropython的知识基础重要组成部分, 为我研究和应用micropython提供重要的依据.

最近看到大家对micropython的关注度又有所提高, 因此我把陈年的私藏开放出来, 与同行们共勉, 以此也希望更多优秀的工程师对micropython的开发过程进行丰富的和充分的实践, 少走弯路, 并贡献出更多有价值的开发文档, 推动对micropython的规范化开发.


早年的开发经验和和心态同现在也是不同了, 这三年中发生了太多的事情…(这会是另一个系列的故事中的一部分). micropython在这几年的代码也在不断演进, 我最近把micropython又捡起来了, 根据最新的代码和开发环境, 重新把这些基础的过程走一遍, 并开始撰写新的笔记. 但从知识上, 还是以之前的经验总结作为基础. 看着之前的笔记, 自己时不时会得意一下, 自己那时候还是有点小聪明的嘛 ^v^

移植mpy:向模块中添加类


本文以向pyb模块中添加Pin类为例,说明在mpy中添加类的操作步骤

实现底层功能函数并定义类结构

在pyb目录下创建pin.c/.h文件,包含Pin类定义的主体内容

其中pin.h文件中的内容为:

    #ifndef __PYB_PIN_H__
    #define __PYB_PIN_H__
    
    #include <stdio.h>
    #include <stdint.h>
    #include <string.h>
    
    #include "py/nlr.h"
    #include "py/obj.h"
    #include "py/runtime.h"
    #include "py/binary.h"
    
    extern const mp_obj_type_t pyb_pin_type;
    
    #endif /*__PYB_PIN_H__*/

暂时不用管这里包含的头文件的含义,只要照抄就行。唯一有用的一句话是最后一句,定义了“pyb_pin_type”的一个类型实例,这个类型实例将在pyb.c文件中被整合到pyb模块下面。

pin.c文件中实现mpy通过C语言操作硬件的功能函数。这个文件里可以直接包含C源代码的驱动程序文件,可以像平时写C语言的单片机程序一样自由发挥。

例如:

    #include "pin.h"
    #include "hal_gpio.h" /* dac hardware hal driver. */
    
    #define HAL_GPIO_PORT_COUNT    5
    #define HAL_GPIO_PIN_COUNT     32
    
    #define HAL_GPIO_DIR_OUTPUT 1
    #define HAL_GPIO_DIR_INPUT  0
    
    #define HAL_PORT_PULL_NONE  0x0
    #define HAL_PORT_PULL_DOWN  0x2
    #define HAL_PORT_PULL_UP    0x3
    
    PORT_Type * const cPortBasePtrArr[] = PORT_BASE_PTRS;
    GPIO_Type * const cGpioBasePtrArr[] = GPIO_BASE_PTRS;

这些代码同用C语言操作底层寄存器的代码没有任何区别。

但是,需要通过必要的结构入口同mpy关联起来。

定义类属性结构体类型

    /* Define a type of pin handler. */
    typedef struct _pyb_pin_obj_t
    {
   
   
        mp_obj_base_t base;
        GPIO_Type *dev_base;
        uint8_t port_id; /* 端口号 */
        uint8_t pin_id;  /* 引脚号 */
        uint8_t pin_val; /* 用户最后操作引脚的值 */
    } pyb_pin_obj_t;

这个结构体类型的变量将被mpy类方法的初始化函数使用,用于保存在mpy脚本中创建类实例的内部数据。这个结构体类型定义了类实例中的所有底层驱动开发者可用的全局内部变量。“内部”指的是仅在类内可见,而用户在应用层的脚本上不可见。“全局”指的是在类内部的多个方法的实现过程中均可访问,相当于是类的属性。因此,也可以称这个结构体为“类属性”结构体。

定义初始化外设的函数


    STATIC mp_obj_t pyb_pin_init_helper( pyb_pin_obj_t *self,
                                         size_t n_args,
                                         const mp_obj_t *pos_args,
                                         mp_map_t *kw_args);
    
    STATIC mp_obj_t pyb_pin_init(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args)
    {
   
   
        /* 调用此函数时,参数列表中的第一个参数就是self */
        return pyb_pin_init_helper(args[0], n_args - 1, args + 1, kw_args);
    }
    
    STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_pin_init_obj, 1, pyb_pin_init);

这里的重点在于pyb_pin_init函数,因此其内部调用的pyb_pin_init_helper函数仅仅放了一个声明在pyb_pin_init函数之前,pyb_pin_init_helper函数本身实现也是比较讲究的,放到后面再详细说明。类初始化函数在这里仅仅是一个格式化的封装,将作为一个回调函数“注册”到mpy的类管理器中,在实例化类对象时调用构造函数的过程中调用此处的初始化函数。因此,此函数的接口要严格符合mpy类管理器对类实例初始化函数的要求。简单来说,pyb_pin_init仅仅是个格式化的壳子,它的助理“helper”函数才是真正干活的家伙。

这里剧透一下,后面将要介绍的“pyb_pin_make_new”就是本类实例的构造函数,构造函数将会解析部分定长参数填充类属性结构体

对函数接口进行说明:

  • 返回值:pyb_pin_init通过pyb_pin_init_helper,最终返回一个状态对象,实际上就是一个状态值常量,但是在mpy中,一切皆是对象,哪怕仅仅是一个常量。
  • 参数1:“size_t n_args”是从构造函数的回调中传入的参数总个数。在最终的应用脚本中,实例化一个Pin对象时,构造函数是可以接收变长参数列表的。例如, pin1 = Pin(0, 1, dir=DIR_INPUT),但是对另一个引脚对象的实例化就变成了pin2 = Pin(0, 2, dir=DIR_OUTPUT, val=1)。处理变长参数列表,最终还是要当成有确定长度的数组来处理,因此记录变长参数列表,两个必要的描述属性之一就是参数数量(数组长度),另一个是参数队列(数组元素)。
  • 参数2:“const mp_obj_t *args”描述的就是构造函数传入变长参数列表的数组元素清单,等价于数组的首指针。特别注意,当类实例的构造过程回调初始化函数时,传入参数列表的第一个参数就是之前定义的pyb_pin_obj_t结构体指针,并且在回调初始化函数之前已经分配好内存,之后在pyb_pin_init_helper函数中从参数列表中解析出有效数据并填充到类属性结构体中,以便于后续方法使用。
  • 参数3:“mp_map_t *kw_args”对于移植代码的开发者没有意义,这应该是mpy内核临时借给初始化过程的一块内存,在pyb_pin_init_helper函数中带有赋值语句的参数解析成字典,保存在kw_args指向的一块内存(数组)中。具体可参见pyb_pin_init_helper函数中调用mp_arg_parse_all的操作。

pyb_pin_init函数对pyb_pin_init_helper函数的调用,就是把args参数数组中的首个元素,也就是属性结构体,作为第一个参数传入;第二、三个参数是回调变长参数列表中剩余参数的数量的元素数组;最后一个参数传入构造函数借给初始化函数用于解析变长参数并保存为字典的内存块。

定义好pyb_pin_init函数之后,需要通过“MP_DEFINE_CONST_FUN_OBJ_KW”宏指令“开光”,将它转化成对象“pyb_pin_init_obj”。其中,操作指令的“KW”表示此处转化为变参数函数,后面还会见到对应位置有“1”,“2”,“3”等等的宏指令,表示的是不同定参数函数;第二个参数的“1”表示此变参数函数至少保证有前1个固定参数。“开光”之后变身为pyb_pin_init_obj对象的实体将通过mpy的标准注入流程被注册到mpy的类管理器中。

下面专门介绍pyb_pin_init_helper函数的实现内容。pyb_pin_init_helper函数:内部调用mp_arg_parse_all函数,对构造函数传入的参数列表进行解析,同预定于的参数字段进行匹配,并找到用户为对应参数赋予的值;将解析后的值保存到类对象的属性结构体中,便于后续方法使用;调用底层硬件的驱动函数,“接地气”,完成对硬件的初始化配置;最后返回“mp_const_none”常量对象,表示正常完成初始化过程。

    /* 处理关键字参数列表 */
    STATIC mp_obj_t pyb_pin_init_helper( pyb_pin_obj_t *self,
                                         size_t n_args,
                                         const mp_obj_t *pos_args,
                                         mp_map_t *kw_args)
    {
   
   
        /* 定义可以接收的关键字参数并指定默认值 */
        static const mp_arg_t allowed_args[] =
        {
   
   
            {
   
    MP_QSTR_dir,  MP_ARG_KW_ONLY | MP_ARG_INT, {
   
   .u_int = HAL_GPIO_DIR_INPUT} }, /* GPIO数据方向,默认为输入 */
            {
   
    MP_QSTR_val,  MP_ARG_KW_ONLY | MP_ARG_INT, {
   
   .u_int = 0 } }, /* GPIO输出初值,默认为0 */
            {
   
    MP_QSTR_pull, MP_ARG_KW_ONLY | MP_ARG_INT, {
   
   .u_int = HAL_PORT_PULL_UP}}, /* 上拉/下拉电阻,默认上拉电阻 */
        };
    
        /* 解析参数:
         * 在传入参数序列中匹配出有效的关键字参数,并为其赋值(覆盖默认值)
         * 匹配参数的顺序同上述定义顺序一致
         */
        mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
        mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
    
        /* 根据最终的设定,配置硬件模块 */
        PORT_Type * port_base = (PORT_Type * )cPortBasePtrArr[self->port_id];
        GPIO_Type * gpio_base = (GPIO_Type * )cGpioBasePtrArr[self->port_id];
    
        
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值