请选择 进入手机版 | 继续访问电脑版

RT-Thread源码解读-------I/O设备驱动

[复制链接]
小小海 发表于 2020-12-31 18:13:11 | 显示全部楼层 |阅读模式 打印 上一主题 下一主题
本文解说RT-Thread操纵系统的I/O设备驱动模子。所谓的I/O设备驱动就是我们寻常所说的输入输出设备。它包罗的设备种类多种多样,从最简单的LED到负载的SPI设备、SDIO等等都属于I/O设备。由于我需要联合代码举行解说,所以本文将PIN设备驱动一块解说。首先把官方的图片贴上,通过观察这张图片,我只管分析出一些有用信息。
1)这张图总体上分为三部分,最上面是应用步伐;中间是一些设备相关的软件代码;最下面是硬件层。
2)这张图中可以或许看出,对于应用层步伐来说不需要关注具体硬件,实现了应用步伐和硬件的完全分离,这样做的利益是一套应用层步伐代码可以在多种硬件MCU平台上运行。
3)可以看到浅绿色的I/O设备管理层是毗连在一块的,但是下面设备驱动框架层和设备驱动层的各种模块是分离的。从这里可以看出I/O设备管理层的一个作用是给将底层各种设备驱动的多样性屏蔽起来,然后释放出一个统一的接口给应用层;I/O设备管理层向下代用各种设备驱动的时候怎么区分呢,答案是通过分配差别的设备范例,这个会在后续解说。
下面再把官方关于I/O设备管理层、设备驱动框架层、设备驱动层三者的作用说一下:
1)I/O 设备管理层 :实现了对设备驱动步伐的封装。应用步伐通过 I/O 设备层提供的标准接口访问底层设备,设备驱动步伐的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操纵相关的代码可以或许独立于应用步伐而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。
2)设备驱动框架层 :是对同类硬件设备驱动的抽象,将差别厂家的同类硬件设备驱动中相同的部分抽取出来,将差别部分留出接口,由驱动步伐实现。
3)设备驱动层 :是一组驱使硬件设备工作的步伐,实现访问硬件设备的功能。
下面两幅图片为从官网上截取的,通过对比这两幅图,然后说一下差别之处。
通过比力这两幅图发现几个结论,需要注意的是这些结论只是通过观察图片看出来的,后续会分析代码验证这些结论:
1)两幅图的共同点:a、应用步伐和I/O设备管理器之间通信的函数是固定的;b、在应用步伐操纵一个设备之前,底层的设备驱动步伐首先需要在I/O设备管理器上注册该设备;c、底层设备驱动步伐必须自己创建一个形貌设备的布局体。
2)两幅图的差别点:a、对于比力简单的设备,底层设备驱动步伐直接向I/O设备管理器注册设备,但是对于一些复杂的设备,在底层硬件驱动步伐和I/O设备管理器之间多了一层设备驱动框架(好比前面一片文章解说的SPI设备就会多了一层SPI设备驱动框架)。
再说明一下,对于设备驱动框架也是RT-Thread团队提供的一套代码,主要是提供的一套函数API接口,供底层设备驱动代码调用。别的该框架还毗连到I/O设备管理器为其提供函数API调用接口。
不啰嗦了,开始举行代码相关的分析。
一、布局体解说
我这里联合PIN设备解说关于I/O管理和PIN设备的代码,这样可以更好的帮助我们明白I/O设备驱动。我们解说布局体的顺序是从某一个具体的设备范例解说,然后深入该设备范例的内部数据域在举行解说。要联合PIN设备,那么我们就需要看一下PIN设备的布局体。
  1. static struct rt_device_pin _hw_pin;//申明一个静态的PIN设备变量/* pin device and operations for RT-Thread */struct rt_device_pin{    struct rt_device parent;//父设备,主要是为了I/O设备管理层使用的    const struct rt_pin_ops *ops;//PIN设备操纵函数,这是用来毗连操纵底层硬件函数的};
复制代码
继承看一下布局体rt_device:
  1. /** * Device structure */struct rt_device{    struct rt_object          parent;      //rt_object对象,雷同于C++中的基类    enum rt_device_class_type type;    //I/O设备范例,内核中通过罗列范例罗列出设备的范例,比方:RT_Device_Class_CAN(表现CAN设备)、RT_Device_Class_Miscellaneous(PIN设备)    rt_uint16_t               flag;         //纪录设备的属性    rt_uint16_t               open_flag;    //设备打开的标志    rt_uint8_t                ref_count;    //纪录设备被打开的次数    rt_uint8_t                device_id;    //纪录设备的ID    //设备回调函数,在RT-Thread内里设置为RT_NULL    rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);    rt_err_t (*tx_complete)(rt_device_t dev, void *buffer);//设备相关的操纵函数,应用步伐调用设备读写等操纵函数,最终会调用这些函数指针所指向的设备底层函数#ifdef RT_USING_DEVICE_OPS    const struct rt_device_ops *ops;#else    /* common device interface */    rt_err_t  (*init)   (rt_device_t dev);    rt_err_t  (*open)   (rt_device_t dev, rt_uint16_t oflag);    rt_err_t  (*close)  (rt_device_t dev);    rt_size_t (*read)   (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size);    rt_size_t (*write)  (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size);    rt_err_t  (*control)(rt_device_t dev, int cmd, void *args);#endif#if defined(RT_USING_POSIX)//关于支持POSIX规范的定义    const struct dfs_file_ops *fops;    struct rt_wqueue wait_queue;#endif    void                     *user_data;    //存放设备的私有数据};
复制代码
布局体struct rt_pin_ops:
  1. struct rt_pin_ops{    void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);//设置PIN模式    void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);//对PIN设备写    int (*pin_read)(struct rt_device *device, rt_base_t pin);//对PIN设备举行读    /* 关于设置GPIO为中断相关的内容,这个会在本文的某一部分单独解说 */    rt_err_t (*pin_attach_irq)(struct rt_device *device, rt_int32_t pin,                      rt_uint32_t mode, void (*hdr)(void *args), void *args);    rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_int32_t pin);    rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled);};
复制代码
再看一下在PIN设备中关于上面这些布局体的具体赋值。
二、代码解读
上面一部分解说了关于PIN设备使用的布局体,那么一气呵成,就先看一下代码中是怎样对布局体的成员变量举行赋值的吧。
检察代码可以看出,代码中定义了一个静态全局变量**_hw_pin**来表现PIN设备。
  1. static struct rt_device_pin _hw_pin;
复制代码
关于变量的初始换函数如下:
  1. int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data){    _hw_pin.parent.type         = RT_Device_Class_Miscellaneous;//设置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;//PIN设备操纵函数    _hw_pin.parent.user_data    = user_data;//存放PIN设备的私有数据    /* register a character device */    rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR);//将设备注册到内核中    return 0;}
复制代码
从函数rt_device_pin_register可以看出我们的PIN设备在RT-Thread操纵系统中被视为RT_Device_Class_Miscellaneous设备范例。此中代码 _hw_pin.parent.ops = &pin_ops; pin_ops是一个全局变量,内里存放的是操纵PIN设备的函数指针(下面代码中会列出来),将该变量的指针赋值给struct rt_device_pin->struct rt_device->const struct rt_device_ops *成员变量,这样如果应用层对PIN设备举行读写等操纵的时候,就会找到底层硬件操纵函数,背面会详细解说调用过程。 表现_hw_pin.ops = ops; ops是函数传递过来的参数,是否和pin_ops重复呢?这个疑问本文下面会说到。_hw_pin.parent.user_data = user_data; 是存放私PIN设备私有数据的,也是使用的函数参数,同样本文背面会详解。最后,调用函数rt_device_register将这个PIN设备注册到RT-Thread中。总结一下,rt_device_pin_register函数实在就是做两件事:第一件是初始化PIN设备布局体的一些成员变量;第二件是将PIN设备注册到RT-Thread系统内。
下面看一下函数rt_device_register,同样先上代码:
  1. rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR);rt_err_t rt_device_register(rt_device_t dev,                            const char *name,                            rt_uint16_t flags){    if (dev == RT_NULL)        return -RT_ERROR;    if (rt_device_find(name) != RT_NULL)        return -RT_ERROR;    rt_object_init(&(dev->parent), RT_Object_Class_Device, name);    dev->flag = flags;    dev->ref_count = 0;    dev->open_flag = 0;#if defined(RT_USING_POSIX)    dev->fops = RT_NULL;    rt_wqueue_init(&(dev->wait_queue));#endif    return RT_EOK;}
复制代码
该函数就是将PIN设备注册到内核中,这里可以看出PIN设备被看做是一个可读可写的设备。该函数涉及到RT-Thread内核的一些东西,这里就不在深入研究了。挖坑背面的文章会专门解说这方面的内容。
上面从函数rt_device_pin_register解说了PIN设备个成员变量的设置,但是尚有几个成员变量是通过rt_device_pin_register函数参数来设置的,要想相识具体的数值,我们就必须找到调用还函数的地方。通过全局搜索检察代用该函数的文件,得到如下效果:
可以发现调用函数rt_device_pin_register都是在一些芯片相关的文件,那么函数rt_device_pin_register实在就是RT-Thread给半导体厂商开放的API函数接口。我们现在以STM32为例看一下调用过程:
  1. rtthread_startup    ->rt_hw_board_init        ->rt_hw_pin_init            ->rt_device_pin_register
复制代码
上面调用过程中的一些函数不在举行解说,这里主要看一下rt_hw_pin_init函数,因为只有它内里涉及到对PIN设备布局体_hw_pin的成员变量赋值。代码奉上:
  1. int rt_hw_pin_init(void){#if defined(__HAL_RCC_GPIOA_CLK_ENABLE)    __HAL_RCC_GPIOA_CLK_ENABLE();#endif    #if defined(__HAL_RCC_GPIOB_CLK_ENABLE)    __HAL_RCC_GPIOB_CLK_ENABLE();#endif    #if defined(__HAL_RCC_GPIOC_CLK_ENABLE)    __HAL_RCC_GPIOC_CLK_ENABLE();#endif    #if defined(__HAL_RCC_GPIOD_CLK_ENABLE)    __HAL_RCC_GPIOD_CLK_ENABLE();#endif#if defined(__HAL_RCC_GPIOE_CLK_ENABLE)    __HAL_RCC_GPIOE_CLK_ENABLE();#endif#if defined(__HAL_RCC_GPIOF_CLK_ENABLE)    __HAL_RCC_GPIOF_CLK_ENABLE();#endif#if defined(__HAL_RCC_GPIOG_CLK_ENABLE)    #ifdef SOC_SERIES_STM32L4        HAL_PWREx_EnableVddIO2();    #endif    __HAL_RCC_GPIOG_CLK_ENABLE();#endif#if defined(__HAL_RCC_GPIOH_CLK_ENABLE)    __HAL_RCC_GPIOH_CLK_ENABLE();#endif#if defined(__HAL_RCC_GPIOI_CLK_ENABLE)    __HAL_RCC_GPIOI_CLK_ENABLE();#endif#if defined(__HAL_RCC_GPIOJ_CLK_ENABLE)    __HAL_RCC_GPIOJ_CLK_ENABLE();#endif#if defined(__HAL_RCC_GPIOK_CLK_ENABLE)    __HAL_RCC_GPIOK_CLK_ENABLE();#endif//上面都是对GPIO的时钟使能,这里不再举行说明    return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);//传递给PIN设备布局体的参数}
复制代码
从上面的代码中可以看出来,对于PIN设备_hw_pin的成员变量ops赋值为&_stm32_pin_ops。下面找到_stm32_pin_ops的定义:
  1. const static struct rt_pin_ops _stm32_pin_ops ={    stm32_pin_mode,    stm32_pin_write,    stm32_pin_read,    stm32_pin_attach_irq,    stm32_pin_dettach_irq,    stm32_pin_irq_enable,};
复制代码
_stm32_pin_ops中存放的的是关于STM32的关于底层操纵GPIO的函数,现在对函数stm32_pin_mode举行解说,限于篇幅其他函数不再举行解说。函数stm32_pin_mode:
  1. static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode){    const struct pin_index *index;    GPIO_InitTypeDef GPIO_InitStruct;    index = get_pin(pin);    if (index == RT_NULL)    {        return;    }    /* Configure GPIO_InitStructure */    GPIO_InitStruct.Pin = index->pin;    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;    GPIO_InitStruct.Pull = GPIO_NOPULL;    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;    if (mode == PIN_MODE_OUTPUT)    {        /* output setting */        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;        GPIO_InitStruct.Pull = GPIO_NOPULL;    }    else if (mode == PIN_MODE_INPUT)    {        /* input setting: not pull. */        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;        GPIO_InitStruct.Pull = GPIO_NOPULL;    }    else if (mode == PIN_MODE_INPUT_PULLUP)    {        /* input setting: pull up. */        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;        GPIO_InitStruct.Pull = GPIO_PULLUP;    }    else if (mode == PIN_MODE_INPUT_PULLDOWN)    {        /* input setting: pull down. */        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;        GPIO_InitStruct.Pull = GPIO_PULLDOWN;    }    else if (mode == PIN_MODE_OUTPUT_OD)    {        /* output setting: od. */        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;        GPIO_InitStruct.Pull = GPIO_NOPULL;    }    HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);}
复制代码
该函数是关于GPIO引脚模式设置的函数,本函数最终是调用函数HAL_GPIO_Init真正设置STM32的GPIO引脚的工作模式。由此可见_stm32_pin_ops中的成员变量是STM32为RT-Thread的PIN设备框架编写的底层调用函数;换句话说,就是半导体昌厂商实现的PIN设备框架的底层接口函数。
对于STM32来说,经过rt_device_pin_register函数执行之后,我们会的到如下关系:
  1. _hw_pin.ops.pin_mode = stm32_pin_mode_hw_pin.ops.pin_write = stm32_pin_write_hw_pin.ops.pin_read = stm32_pin_read_hw_pin.ops.pin_attach_irq = stm32_pin_attach_irq_hw_pin.ops.pin_detach_irq = stm32_pin_dettach_irq_hw_pin.ops.pin_irq_enable = stm32_pin_irq_enable
复制代码
前面我们通过分析还得到如下关系:
  1. _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;
复制代码
这里针对上面函数_pin_read的源代码发现,对于STM32来说,_pin_read最终还是调用函数stm32_pin_read,与_hw_pin.ops.pin_read效果是一样的:
  1. static rt_size_t _pin_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size){    struct rt_device_pin_status *status;    struct rt_device_pin *pin = (struct rt_device_pin *)dev;    /* check parameters */    RT_ASSERT(pin != RT_NULL);    status = (struct rt_device_pin_status *) buffer;    if (status == RT_NULL || size != sizeof(*status)) return 0;    status->status = pin->ops->pin_read(dev, status->pin);//对于STM32来说实际就是调用函数stm32_pin_read    return size;}
复制代码
上面解说了PIN设备初始化和注册相关的函数,下面在我们通过一个例程,检察应用层代码是怎么操纵PIN设备的。我们就以控制一个LED灯闪烁为例,其对应的是对PIN设备举行写操纵。先看一下代码(同样代码也是基于STM32举行解说):
  1. /* defined the LED0 pin: PF9 */#define LED0_PIN    GET_PIN(F, 9)//LED毗连到GPIOF9引脚rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);//设置GPIOF9管脚为输出模式/* RT-Thread Hardware PIN APIs */void rt_pin_mode(rt_base_t pin, rt_base_t mode){    RT_ASSERT(_hw_pin.ops != RT_NULL);    _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode);}FINSH_FUNCTION_EXPORT_ALIAS(rt_pin_mode, pinMode, set hardware pin mode);
复制代码
通过上面发现该函数就是通过_hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode); 调用底层的函数。前面的分析,我们知道真正调用的函数是stm32_pin_mode ,它就是调用STM32的hal库函数设置GPIOF9为输出模式。挖坑对于**FINSH_FUNCTION_EXPORT_ALIAS(rt_pin_mode, pinMode, set hardware pin mode);**是将该函数作为一个shell下令导出,具体实现细节本文不会解说,后续我会专门写一篇博客解说。
接着先容PIN设备写函数:
  1. rt_pin_write(LED0_PIN, PIN_HIGH);//外接LED灯灭void rt_pin_write(rt_base_t pin, rt_base_t value){    RT_ASSERT(_hw_pin.ops != RT_NULL);    _hw_pin.ops->pin_write(&_hw_pin.parent, pin, value);}FINSH_FUNCTION_EXPORT_ALIAS(rt_pin_write, pinWrite, write value to hardware pin);
复制代码
同样以STM32为例,该函数会调用底层的函数stm32_pin_write
  1. int  rt_pin_read(rt_base_t pin){    RT_ASSERT(_hw_pin.ops != RT_NULL);    return _hw_pin.ops->pin_read(&_hw_pin.parent, pin);}FINSH_FUNCTION_EXPORT_ALIAS(rt_pin_read, pinRead, read status from hardware pin);
复制代码
同样以STM32为例,该函数会调用底层的函数stm32_pin_read
总结,通过检察这几个函数存放在如下文件中Pin.c (components\drivers\misc) 。这几个函数是RT-Thread操纵系统的PIN设备开放给应用层的框架。
[size=4.5]细心的朋侪可以发现我们在调用RT-Thread的PIN设备框架的API函数对于传入的参数大概会产生疑惑。举例说明:对于设置一个PIN设备的工作模式函数rt_pin_mode,它的第一个参数为rt_base_t范例表现的是设置的引脚,第二个参数也是rt_base_t范例代表为设置引脚的工作模式(输入、输出等等),rt_base_t是RT-Thread操纵系统自己定义的变量范例,它实际就是long范例。当应用层传入rt_base_t范例的参数之后,对于STM32来说是怎么的到设置的具体是哪一个GPIO引脚的那种工作模式呢?? 在这里我们首先说明RT-Thread的实现原则:RT-Thread操纵系统在应用层开放的API接口规定好函数参数的寄义,然后半导体厂商根据自己芯片的hal库来实现相应的GPIO设置。对于STM32来说,我们看到如下代码:
  1. /* defined the LED0 pin: PF9 */#define LED0_PIN    GET_PIN(F, 9)//外部毗连的LED灯为GPIOF9#define GET_PIN(PORTx,PIN) (rt_base_t)((16 * ( ((rt_base_t)__STM32_PORT(PORTx) - (rt_base_t)GPIOA_BASE)/(0x0400UL) )) + PIN)#define __STM32_PORT(port)  GPIO##port##_BASE#define GPIOA_BASE            (AHB1PERIPH_BASE + 0x0000UL)//STM32内部GPIOA的寄存器组的基地点,也就是一个32位的地点//通过上面的得到GET_PIN(F,9)=105
复制代码
对于STM32F407来说,数字89与GPIOF9之间会有什么关系呢?上面已经说过,对于STM32来说rt_pin_mode最终是调用函数stm32_pin_mode,下面把代码贴出来
  1. static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode){    const struct pin_index *index;    GPIO_InitTypeDef GPIO_InitStruct;    index = get_pin(pin);//通过传过来的引脚号得到一个struct pin_index *布局体    if (index == RT_NULL)    {        return;    }    /* Configure GPIO_InitStructure */    GPIO_InitStruct.Pin = index->pin;    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;    GPIO_InitStruct.Pull = GPIO_NOPULL;    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;    if (mode == PIN_MODE_OUTPUT)    {        /* output setting */        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;        GPIO_InitStruct.Pull = GPIO_NOPULL;    }    else if (mode == PIN_MODE_INPUT)    {        /* input setting: not pull. */        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;        GPIO_InitStruct.Pull = GPIO_NOPULL;    }    else if (mode == PIN_MODE_INPUT_PULLUP)    {        /* input setting: pull up. */        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;        GPIO_InitStruct.Pull = GPIO_PULLUP;    }    else if (mode == PIN_MODE_INPUT_PULLDOWN)    {        /* input setting: pull down. */        GPIO_InitStruct.Mode = GPIO_MODE_INPUT;        GPIO_InitStruct.Pull = GPIO_PULLDOWN;    }    else if (mode == PIN_MODE_OUTPUT_OD)    {        /* output setting: od. */        GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;        GPIO_InitStruct.Pull = GPIO_NOPULL;    }    HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);}
复制代码
对于应用层传过来的pin引脚(上面分析指导GPIOF9的引脚为105)最终是传递给函数stm32_pin_mode的第二个参数。函数stm32_pin_mode对第二个参数做了什么样的操纵得到我们想要设置的GPIOF9引脚的呢?它的玄机就藏在index = get_pin(pin);这句代码中。继承贴代码:
  1. //先看一下几个布局体/* STM32 GPIO driver */struct pin_index{    int index;    GPIO_TypeDef *gpio;    uint32_t pin;};#define ITEM_NUM(items) sizeof(items) / sizeof(items[0])static const struct pin_index *get_pin(uint8_t pin){    const struct pin_index *index;    if (pin < ITEM_NUM(pins))    {        index = &pins[pin];        if (index->index == -1)            index = RT_NULL;    }    else    {        index = RT_NULL;    }    return index;};static const struct pin_index pins[] = {#if defined(GPIOA)    __STM32_PIN(0 ,  A, 0 ),    __STM32_PIN(1 ,  A, 1 ),    __STM32_PIN(2 ,  A, 2 ),    __STM32_PIN(3 ,  A, 3 ),    __STM32_PIN(4 ,  A, 4 ),    __STM32_PIN(5 ,  A, 5 ),    __STM32_PIN(6 ,  A, 6 ),    __STM32_PIN(7 ,  A, 7 ),    __STM32_PIN(8 ,  A, 8 ),    __STM32_PIN(9 ,  A, 9 ),    __STM32_PIN(10,  A, 10),    __STM32_PIN(11,  A, 11),    __STM32_PIN(12,  A, 12),    __STM32_PIN(13,  A, 13),    __STM32_PIN(14,  A, 14),    __STM32_PIN(15,  A, 15),#if defined(GPIOB)    __STM32_PIN(16,  B, 0),    ....//代码太多,只贴一部分    __STM32_PIN(89,  F, 9),    ....}
复制代码
通过检察函数get_pin源代码发现该函数的作用就是根据应用层传入的GPIO编号作为全局数组pins[]的下标,返回一个struct pin_index范例的指针。这里应用层传过来89(GPIOF9引脚)找到对应的数据为__STM32_PIN(89, F, 9)。在通过如下代码:
  1. #define __STM32_PIN(index, gpio, gpio_index)                                \    {                                                                       \        index, GPIO##gpio, GPIO_PIN_##gpio_index                            \    }  __STM32_PIN(89,  F, 9)={89, GPIOF,GPIO_PIN_9}  //也就是  /* STM32 GPIO driver */struct pin_index{    int index=89;//在数组pins[]的下标    GPIO_TypeDef *gpio = GPIOF;//对应的是GPIOF寄存器组的基地点    uint32_t pin = GPIO_PIN_9;//对应GPIOF的第10个引脚};
复制代码
经过上面分析,我们就知道对于STM32来说是怎么识别到应用层设置GPIO引脚的信息了。[size=4.5]关于PIN设备框架中设置GPIO的方式有一点思考,下面从应用层编程人员和半导体厂商底层软件开发人员的角度举行论述:对于应用层软件编程人员需要相识怎样将我们设置的具体GPIO引脚转换成应用层的rt_base_t范例(对于STM32来说就是如何使用GET_PIN(F, 9));对于半导体底层开发人员就是实现从rt_base_t到hal库中具体的GPIO数据范例之间的转换。

来源:https://blog.csdn.net/renqingxin2011/article/details/109442574
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则


专注素材教程免费分享
全国免费热线电话

18768367769

周一至周日9:00-23:00

反馈建议

27428564@qq.com 在线QQ咨询

扫描二维码关注我们

Powered by Discuz! X3.4© 2001-2013 Comsenz Inc.( 蜀ICP备2021001884号-1 )