这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » 嵌入式开发 » 软件与操作系统 » rtthread pin驱动框架分析

共7条 1/1 1 跳转至

rtthread pin驱动框架分析

助工
2024-11-10 21:33:05     打赏

GPIO驱动,应该算是RTT设备驱动里除ADC和DAC驱动以外的最简单的驱动了,因gpio常用的操作这么几种:设置输入输出,设置上下拉,设置驱动能力,读写IO口状态,中断模式设置。而在看框架前,我个人理解的GPIO设置这些参数的入口,使用control + read + write + 中断回调注册便能完成对应功能。而对应用来说,仅仅是再一次把这些接口封装一层,把与应用不相关的参数封装起来,只暴露必要的信息即可。但实际是否是这么操作的,还是需要详细看代码才能清楚。

pin框架解析

源码路径

     \components\drivers\pin\pin.c

对接驱动入口

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops pin_ops =
{
    RT_NULL,
    RT_NULL,
    RT_NULL,
    _pin_read,
    _pin_write,
    _pin_control
};
#endif

int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data)
{
    _hw_pin.parent.type         = RT_Device_Class_Pin;
    _hw_pin.parent.rx_indicate  = RT_NULL;
    _hw_pin.parent.tx_complete  = RT_NULL;

#ifdef RT_USING_DEVICE_OPS
    _hw_pin.parent.ops          = &pin_ops;
#else
    _hw_pin.parent.init         = RT_NULL;
    _hw_pin.parent.open         = RT_NULL;
    _hw_pin.parent.close        = RT_NULL;
    _hw_pin.parent.read         = _pin_read;
    _hw_pin.parent.write        = _pin_write;
    _hw_pin.parent.control      = _pin_control;
#endif

    _hw_pin.ops                 = ops;
    _hw_pin.parent.user_data    = user_data;

    /* register a character device */
    rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR);

    return 0;
}

     从注册函数上来看,驱动方面的注册方式和前面看的几个驱动框架并没有太多的差异,构建struct rt_pin_ops *ops,而对接应用的入口,还是使用init,open,close,read,write,control几个接口。

struct rt_pin_ops需实现接口

struct rt_pin_ops
{
    // 基本上可以认为是设置工作模式的接口了,比如输入输出,上下拉,是否开漏之类的功能
    void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_uint8_t mode);
    // 输出模式的输出电平状态设置
    void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_uint8_t value);
    // 输入模式的电平状态读取
    rt_ssize_t  (*pin_read)(struct rt_device *device, rt_base_t pin);
    // 中断回调注册函数
    rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_base_t pin,
            rt_uint8_t mode, void (*hdr)(void *args), void *args);
    // 中断回调注销函数(其实个人认为完全可以像rx_indicate那样,直接注册函数传RT_NULL注销)
    rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_base_t pin);
    // 使能中断函数
    rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint8_t enabled);
    // 通过传入的管脚名字获取管脚实际编号信息的功能
    rt_base_t (*pin_get)(const char *name);
#ifdef RT_USING_DM // 设备树相关接口,应该是RTSmart所需要的接口
    rt_err_t (*pin_irq_mode)(struct rt_device *device, rt_base_t pin, rt_uint8_t mode);
    rt_ssize_t (*pin_parse)(struct rt_device *device, struct rt_ofw_cell_args *args, rt_uint32_t *flags);
#endif
#ifdef RT_USING_PINCTRL //也是设备树相关的功能
    rt_err_t (*pin_ctrl_confs_apply)(struct rt_device *device, void *fw_conf_np);
#endif /* RT_USING_PINCTRL */
};

     可以看到,设置gpio驱动能力的入口并未实现/。个人理解为支持驱动能力设置的芯片并不算多,大部分芯片的管脚驱动能力都是固定的,因此没有实现也是可以理解的

按框架暴露给应用的接口

gpio读接口

static rt_ssize_t _pin_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
{
    struct rt_device_pin_value *value;
    struct rt_device_pin *pin = (struct rt_device_pin *)dev;

    /* check parameters */
    RT_ASSERT(pin != RT_NULL);

    value = (struct rt_device_pin_value *)buffer;
    if (value == RT_NULL || size != sizeof(*value))
        return 0;

    value->value = pin->ops->pin_read(dev, value->pin);
    return size;
}

    老实说,看到了adc和dac的实现后,个人并不觉得这个实现是很好的实现,原因是,这个实现明摆着只能实现单个GPIO状态的读取(虽然同时读多个连续管脚状态的场景很少),value->pin接口,仅仅只能传递一个pin编码,而调用上,仅仅是调用了一次。

GPIO写接口

static rt_ssize_t _pin_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
{
    struct rt_device_pin_value *value;
    struct rt_device_pin *pin = (struct rt_device_pin *)dev;

    /* check parameters */
    RT_ASSERT(pin != RT_NULL);

    value = (struct rt_device_pin_value *)buffer;
    if (value == RT_NULL || size != sizeof(*value))
        return 0;

    pin->ops->pin_write(dev, (rt_base_t)value->pin, (rt_base_t)value->value);

    return size;
}

     与read接口类似,write接口,仅仅是将单个管脚的电平值设置下去,不过有点奇怪的,适合value->value是 rt_base_t格式的,不是rt_bool_t格式的。

gpio控制接口

static rt_err_t _pin_control(rt_device_t dev, int cmd, void *args)
{
    struct rt_device_pin_mode *mode;
    struct rt_device_pin *pin = (struct rt_device_pin *)dev;

    /* check parameters */
    RT_ASSERT(pin != RT_NULL);

    mode = (struct rt_device_pin_mode *)args;
    if (mode == RT_NULL)
        return -RT_ERROR;

    pin->ops->pin_mode(dev, (rt_base_t)mode->pin, (rt_base_t)mode->mode);

    return 0;
}

暴露给应用的接口

    暴露给应用的接口都是将struct rt_pin_ops内的函数封装一层实现的,而从实现上看,中断相关函数和gpio编号获取入口是非必要的,个人理解可能是考虑到有些芯片并不支持中断功能,因此没必要实现这些入口。

gpio中断注册入口

rt_err_t rt_pin_attach_irq(rt_base_t pin, rt_uint8_t mode,
                           void (*hdr)(void *args), void *args)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);
    if (_hw_pin.ops->pin_attach_irq)
    {
        return _hw_pin.ops->pin_attach_irq(&_hw_pin.parent, pin, mode, hdr, args);
    }
    return -RT_ENOSYS;
}

gpio中断解注册入口

rt_err_t rt_pin_detach_irq(rt_base_t pin)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);
    if (_hw_pin.ops->pin_detach_irq)
    {
        return _hw_pin.ops->pin_detach_irq(&_hw_pin.parent, pin);
    }
    return -RT_ENOSYS;
}

中断使能入口

rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint8_t enabled)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);
    if (_hw_pin.ops->pin_irq_enable)
    {
        return _hw_pin.ops->pin_irq_enable(&_hw_pin.parent, pin, enabled);
    }
    return -RT_ENOSYS;
}

gpio工作模式设置入口

void rt_pin_mode(rt_base_t pin, rt_uint8_t mode)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);
    _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode);
}

gpio写入口

void rt_pin_write(rt_base_t pin, rt_ssize_t value)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);
    _hw_pin.ops->pin_write(&_hw_pin.parent, pin, value);
}

gpio读入口

rt_ssize_t rt_pin_read(rt_base_t pin)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);
    return _hw_pin.ops->pin_read(&_hw_pin.parent, pin);
}

gpio编号获取入口

    可能为了适配这个入口需要消耗不少存储空间(所有的gpio都需要用字符串维护),因此不少bsp都没有适配这个入口,而从这个函数的实现来看,这个接口也并不是强制性的,如果不支持,则返回。

     另外,还有一个点,即使适配了这个入口,应用层也做不到换IC不需要改应用管脚配置,这就让这个功能变得十分鸡肋,与其实现这功能,倒不如芯片适配时将芯片管脚与gpio编码之间的换算规则公布出来,在实际使用时用户根据规则换算出最终的管脚编号。

rt_base_t rt_pin_get(const char *name)
{
    RT_ASSERT(_hw_pin.ops != RT_NULL);

    if (_hw_pin.ops->pin_get == RT_NULL)
    {
        return -RT_ENOSYS;
    }
    return _hw_pin.ops->pin_get(name);
}

总结

   到这,pin驱动框架就已经分析完毕了,而从实现上看,我们完全可以直接使用RTT文档推荐的方式进行gpio操作,因为内部暴露的其他接口,要么需要应用层构建特定结构体参数,要么接口被RTT推荐入口实现,因此综合考量,使用RTT推荐入口实现属于最优的操作。

       另外,按照以上的分析,我们可以大致获得以下pin驱动实现模板,后续编码可直接套用此模板实现。

static void pin_mode(struct rt_device *dev, rt_base_t pin, rt_uint8_t mode)
{
    RT_ASSERT(PIN_BANK(pin) < GPIO_BANK_NUM);

    switch (mode)
    {
    case PIN_MODE_OUTPUT:
    // TODO: 输出配置
        break;
    case PIN_MODE_OUTPUT_OD:
    // TODO: 开漏输出
        break;

    case PIN_MODE_INPUT:
    // TODO: 输入配置
        break;
    case PIN_MODE_INPUT_PULLUP:
    // TODO:输入带上拉配置
        break;
    case PIN_MODE_INPUT_PULLDOWN:
    // TODO:输入带下拉配置
        break;

    default:
        break;
    }
}

static void pin_write(struct rt_device *dev, rt_base_t pin, rt_uint8_t value)
{
    // TODO: 设置输出电平
}

static rt_ssize_t pin_read(struct rt_device *dev, rt_base_t pin)
{
    rt_ssize_t level;
    // TODO:获取输入电平
    // level = xxxx;
    return level;
}

static rt_err_t pin_attach_irq(struct rt_device *device, rt_int32_t pin,
                               rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
    // TODO: 中断回调注册        

    return RT_EOK;
}

static rt_err_t pin_detach_irq(struct rt_device *device, rt_int32_t pin)
{

    // TODO:注销中断回调
    return RT_EOK;
}

static rt_err_t pin_irq_enable(struct rt_device *dev, rt_base_t pin, rt_uint32_t enabled)
{

    // TODO:使能中断
    return RT_EOK;
}

// TODO:pin硬件中断处理函数
// 调用pin_attach_irq传入的中断回调处理函数

static const struct rt_pin_ops pin_ops =
{
    pin_mode,
    pin_write,
    pin_read,
    pin_attach_irq,
    pin_detach_irq,
    pin_irq_enable,
};


int rt_hw_gpio_init(void)
{
    // pin内部资源初始化,比如中断向量表,pin偏置使能之类的

    rt_device_pin_register("pin", &pin_ops, RT_NULL);

    return 0;
}
INIT_BOARD_EXPORT(rt_hw_gpio_init);





关键词: rtthread     驱动     框架     分析     pin    

专家
2024-11-10 21:33:15     打赏
2楼

感谢分享


专家
2024-11-10 21:36:01     打赏
3楼

感谢分享


高工
2024-11-10 22:37:11     打赏
4楼

一个GPIO的控制需要写这么多的代码吗?


专家
2024-11-11 01:45:45     打赏
5楼

感谢楼主分享


专家
2024-11-11 08:04:11     打赏
6楼

感谢分享


工程师
2024-11-11 08:55:07     打赏
7楼

学习了。


共7条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]