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

本文讲解RT-Thread操作系统的I/O设备驱动模型。所谓的I/O设备驱动就是我们平时所说的输入输出设备。它包含的设备种类多种多样,从最简单的LED到负载的SPI设备、SDIO等等都属于I/O设备。由于我需要结合代码进行讲解,所以本文将PIN设备驱动一块讲解。首先把官方的图片贴上,通过观察这张图片,我尽量分析出一些有用信息。
RT-Thread源码解读-------I/O设备驱动_第1张图片1)这张图总体上分为三部分,最上面是应用程序;中间是一些设备相关的软件代码;最下面是硬件层。
2)这张图中能够看出,对于应用层程序来说不需要关注具体硬件,实现了应用程序和硬件的完全分离,这样做的好处是一套应用层程序代码可以在多种硬件MCU平台上运行。
3)可以看到浅绿色的I/O设备管理层是连接在一块的,但是下面设备驱动框架层和设备驱动层的各种模块是分离的。从这里可以看出I/O设备管理层的一个作用是给将底层各种设备驱动的多样性屏蔽起来,然后释放出一个统一的接口给应用层;I/O设备管理层向下代用各种设备驱动的时候怎么区分呢,答案是通过分配不同的设备类型,这个会在后续讲解。
下面再把官方关于I/O设备管理层、设备驱动框架层、设备驱动层三者的作用说一下:
1)I/O 设备管理层 :实现了对设备驱动程序的封装。应用程序通过 I/O 设备层提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。
2)设备驱动框架层 :是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。
3)设备驱动层 :是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。
下面两幅图片为从官网上截取的,通过对比这两幅图,然后说一下不同之处。
RT-Thread源码解读-------I/O设备驱动_第2张图片RT-Thread源码解读-------I/O设备驱动_第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设备的结构体。

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:

/**
 * 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:

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设备。

static struct rt_device_pin _hw_pin;

关于变量的初始换函数如下:

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,同样先上代码:

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-Thread源码解读-------I/O设备驱动_第4张图片可以发现调用函数rt_device_pin_register都是在一些芯片相关的文件,那么函数rt_device_pin_register其实就是RT-Thread给半导体厂商开放的API函数接口。我们现在以STM32为例看一下调用过程:

rtthread_startup
    ->rt_hw_board_init
        ->rt_hw_pin_init
            ->rt_device_pin_register

上面调用过程中的一些函数不在进行讲解,这里主要看一下rt_hw_pin_init函数,因为只有它里面涉及到对PIN设备结构体_hw_pin的成员变量赋值。代码奉上:

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的定义:

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:

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函数执行之后,我们会的到如下关系:

_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

前面我们通过分析还得到如下关系:

_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效果是一样的:

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进行讲解):

/* 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设备写函数:

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

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设备开放给应用层的框架。
细心的朋友可以发现我们在调用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来说,我们看到如下代码:

/* 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,下面把代码贴出来

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);这句代码中。继续贴代码:

//先看一下几个结构体
/* 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)。在通过如下代码:

#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引脚的信息了。关于PIN设备框架中设置GPIO的方式有一点思考,下面从应用层编程人员和半导体厂商底层软件开发人员的角度进行阐述:对于应用层软件编程人员需要了解怎样将我们设置的具体GPIO引脚转换成应用层的rt_base_t类型(对于STM32来说就是如何使用GET_PIN(F, 9));对于半导体底层开发人员就是实现从rt_base_t到hal库中具体的GPIO数据类型之间的转换。

你可能感兴趣的:(RTOS,RT-Thread,物联网,嵌入式)