【RT-Thread】PIN 设备源码分析

关于 RT-Thread 的 PIN 设备驱动应用层面的介绍可以直接参考 RT-Thread 的官网:PIN 设备

下面结合相关源代码分析一下 PIN 设备驱动。

1 获取引脚编号

首先拿到标准的 BSP 后,main.c 里面会有一个 IO 口的操作,内容如下:

/* defined the LED0 pin: PB1 */
#define LED0_PIN    24	//GET_PIN(B, 8)

int main(void)
{
    int count = 1;
    /* set LED0 pin mode to output */
    rt_pin_mode(LED0_PIN, PIN_MODE_OUTPUT);

    while (count++)
    {
        rt_pin_write(LED0_PIN, PIN_HIGH);
        rt_thread_mdelay(500);
        rt_pin_write(LED0_PIN, PIN_LOW);
        rt_thread_mdelay(500);
    }

    return RT_EOK;
}

RT-Thread 提供的引脚编号需要和芯片的引脚号区分开来,它们并不是同一个概念,引脚编号由 PIN 设备驱动程序定义,和具体的芯片相关。有2种方式可以获取引脚编号:使用宏定义或者查看PIN 驱动文件。

第 1 种:使用宏定义
如果使用的是 STM32 芯片,可以直接使用宏定义来获取引脚编号:

GET_PIN(port, pin)

比如,我现在要获得 PB8 口,则使用 GET_PIN(B, 8), 即可

GET_PIN() 在 drv_gpio.h 文件中,内容如下:

#define GET_PIN(PORTx,PIN) (rt_base_t)((16 * ( ((rt_base_t)__STM32_PORT(PORTx) - (rt_base_t)GPIOA_BASE)/(0x0400UL) )) + PIN)

其中 __STM32_PORT(),也在 drv_gpio.h 文件中,内容如下:

#define __STM32_PORT(port)  GPIO##port##_BASE

所以 GET_PIN(B, 8),展开如下:

(rt_base_t)((16 * ((rt_base_t)GPIOB_BASE - (rt_base_t)GPIOA_BASE)/0x0400UL))) + 8)

rt_base_t 定义在 rtdef.h 中,内容如下:
在这里插入图片描述

而 GPIOB_BASE 就定义在 ST 官方 HAL 库中,在 stm32f103xe.h(和具体的芯片有关),如下图:
在这里插入图片描述
第二种:查看驱动文件
这里的驱动文件是指 drv_gpio.c 文件中的 pins[],内容如下:
在这里插入图片描述__STM32_PIN() 也是一个宏定义,定义在 drv_gpio.h 中,内容如下:
在这里插入图片描述
__STM32_PIN() 中的 index 参数是 芯片的引脚。例如,__STM32_PIN(24, B, 8),就可以得到 PB8 的引脚编号为 24。所以在 main.c 函数中有定义 LED0_PIN 24

而结构体 struct pin_index 也定义在 drv_gpio.h 中,内容如下:

/* STM32 GPIO driver */
struct pin_index
{
    int index;
    GPIO_TypeDef *gpio;
    uint32_t pin;
};

2 设置引脚模式

之前不用 RT-Thread 时,要使用 IO 口时,需要先初始化 IO 口,比如设置 IO 的复用功能、输入/输出等功能。而在 RT-Thread 操作系统中,设置引脚模式使用的是如下函数:

void rt_pin_mode(rt_base_t pin, rt_base_t mode);

此函数定义在 pin.c 文件中,内容如下:

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);
}

可见 rt_pin_mode() 函数是调用 _hw_pin.ops->pin_mode() 函数来完成初始化的。接下来需要了解结构体 _hw_pin 及其成员变量 ops 和 ops 所定义的函数指针。

(1)、_hw_pin 结构体定义在 pin.c 文件中,是全局静态变量,如下图:
在这里插入图片描述

接下来看一下 struct rt_device_pin, 此结构体定义在 pin.h 中,内容如下:

struct rt_device_pin
{
    struct rt_device parent;
    const struct rt_pin_ops *ops;
};

由此可以看出,rt_device_pin 继承于 rt_device,那来看下定义在 rtdef.h 文件中的 rt_device,内容如下:

/**
 * Device structure
 */
struct rt_device
{
    struct rt_object          parent;                   /**< inherit from rt_object */

    enum rt_device_class_type type;                     /**< device type */
    rt_uint16_t               flag;                     /**< device flag */
    rt_uint16_t               open_flag;                /**< device open flag */

    rt_uint8_t                ref_count;                /**< reference count */
    rt_uint8_t                device_id;                /**< 0 - 255 */

    /* device call back */
    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	// 0
    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)	// 0
    const struct dfs_file_ops *fops;
    struct rt_wqueue wait_queue;
#endif

    void                     *user_data;                /**< device private data */
};

可以看出 rt_device 又继承于 rt_object , 而 rt_object 同样定义在 rtdef.h 中,内容如下:

/**
 * Base structure of Kernel object
 */
struct rt_object
{
    char       name[RT_NAME_MAX];                       /**< name of kernel object */
    rt_uint8_t type;                                    /**< type of kernel object */
    rt_uint8_t flag;                                    /**< flag of kernel object */

#ifdef RT_USING_MODULE	// 0
    void      *module_id;                               /**< id of application module */
#endif
    rt_list_t  list;                                    /**< list node of kernel object */
};
typedef struct rt_object *rt_object_t;                  /**< Type for kernel objects. */

rt_object 是 RT-Thread 内核中最基本的对象,其它对象都是继承于 rt_object,然后扩展出自己的成员变量。

接下来再看下 _hw_pin 的成员变量 ops,ops 是结构体 rt_pin_ops 类型,而 rt_pin_ops 结构体定义在 pin.h 文件中,内容如下:

struct rt_pin_ops
{
    void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);
    void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);
    int (*pin_read)(struct rt_device *device, rt_base_t pin);

    /* TODO: add GPIO interrupt */
    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);
};

rt_pin_ops 结构体成员变量中的函数指针相当于设备模型的设备管理层,是应用层通向设备驱动层的桥梁。

最终展开 _hw_pin 全部内容如下:
在这里插入图片描述
由此知道,对引脚的操作都是利用 _hw_pin 的成员变量 ops 中的函数指针来完成的。如设置引脚模式就是 rt_pin_mode() 函数中的 _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode); 来实现的。但 pin_mode() 是函数指针,实现这个函数的是谁?

这里就要用到 int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data) 设备引脚注册函数了,此函数定义在 pin.c 中,具体内容如下:

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;
    _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;
}

上面的代码先对 _hw_pin 进行初始化,再调用 rt_device_register() 函数将 _hw_pin 这个 PIN 设备注册到内核中,由 rt_list_t 双向链表来管理。而第 18 行可知,_hw_pin.ops 里的函数指针所指向的函数实体是由调用者传进去的 ops 决定的。那是谁在调用 rt_device_pin_register() 这个函数?全局搜索一下不就知道了。最终发现是在 drv_gpio.c 文件中的 int rt_hw_pin_init(void) 调用的。内容如下:

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

    return rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
}

rt_hw_pin_init() 此函数就是初始化引脚用的,可以看到,此函数先将各引脚时钟使能,然后调用 rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL); 可以看到 PIN 设备的名字为 “pin",PIN 设备用到的 ops 为 _stm32_pin_ops,不用说,直接找到 _stm32_pin_ops 一看究竟,此结构体也定义在 drv_gpio.c 文件中,内容如下:

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,
};

由此,我们可以把 _hw_pin 设备中的 ops 操作函数一一对应,如下:

_hw_pin对应结果
_hw_pin.ops->pin_modestm32_pin_mode
_hw_pin.ops->pin_writestm32_pin_write
_hw_pin.ops->pin_readstm32_pin_read
_hw_pin.ops->pin_attach_irqstm32_pin_attach_irq
_hw_pin.ops->pin_dettach_irqstm32_pin_dettach_irq
_hw_pin.ops->pin_irq_enablestm32_pin_irq_enable

所以 执行 _hw_pin.ops->pin_mode(&_hw_pin.parent, pin, mode); 这句代码,实际就是执行 stm32_pin_mode() 函数(其它函数原理一样)。

那接下来就看下 stm32_pin_mode() 实现了个啥?stm32_pin_mode() 还是定义在 drv_gpio.c(关于gpio的驱动程序都是在 drv_gpio.c 文件中实现),内容如下:

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);
}

这个函数我想不用再说了,STM32 裸机程序中都是这样写的。函数是利用形参 mode 的取值不同,对引脚实现不能的初始化,其中 mode 的取值如下:

#define PIN_MODE_OUTPUT 0x00             	/* 输出 */
#define PIN_MODE_INPUT 0x01                 /* 输入 */
#define PIN_MODE_INPUT_PULLUP 0x02         /* 上拉输入 */
#define PIN_MODE_INPUT_PULLDOWN 0x03   	 /* 下拉输入 */
#define PIN_MODE_OUTPUT_OD 0x04         /* 开漏输出 */

到这里简单总结一下:
在这里插入图片描述

到这里还没完,我们还要知道是谁调用了 rt_hw_pin_init() 函数,这样才能使 PIN 设备驱动完整。

这很简单,直接全局搜索,发现是 rt_hw_board_init() 函数调用的。就这样往前推,可以看到程序启动到注册完成 PIN 设备的流程如下图:
在这里插入图片描述

3 设置引脚电平

这里应用层调用 rt_pin_write() 来对 pin 电平设置,如下:

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);
}

由第 2 节知道 ,这里的 pin_wirte(),实际执行的是 stm32_pin_write,其函数内容如下:

static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
{
    const struct pin_index *index;

    index = get_pin(pin);
    if (index == RT_NULL)
    {
        return;
    }

    HAL_GPIO_WritePin(index->gpio, index->pin, (GPIO_PinState)value);
}

可以看到,最终是调用 HAL 库中的 HAL_GPIO_WritePin() 函数来对 gpio 操作。

而 rt_pin_read() 与 rt_pin_write() 实现方式一样,不再多说。接下来看下中断部分。

4 绑定 PIN 中断回调函数

若要使用到引脚的中断功能,可以使用如下函数将某个引脚配置为某种中断触发模式并绑定一个中断回调函数到对应引脚,当引脚中断发生时,就会执行回调函数:

rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
                            void (*hdr)(void *args), void *args);

我们知道 ,此函数会调用如下函数:

static rt_err_t stm32_pin_attach_irq(struct rt_device *device, rt_int32_t pin,
                                     rt_uint32_t mode, void (*hdr)(void *args), void *args)

其定义在 drv_gpio.c 文件中,实现内容如下:

static rt_err_t stm32_pin_attach_irq(struct rt_device *device, rt_int32_t pin,
                                     rt_uint32_t mode, void (*hdr)(void *args), void *args)
{
    const struct pin_index *index;
    rt_base_t level;
    rt_int32_t irqindex = -1;

    index = get_pin(pin);
    if (index == RT_NULL)
    {
        return RT_ENOSYS;
    }
    irqindex = bit2bitno(index->pin);
    if (irqindex < 0 || irqindex >= ITEM_NUM(pin_irq_map))
    {
        return RT_ENOSYS;
    }

    level = rt_hw_interrupt_disable();
    if (pin_irq_hdr_tab[irqindex].pin == pin &&
            pin_irq_hdr_tab[irqindex].hdr == hdr &&
            pin_irq_hdr_tab[irqindex].mode == mode &&
            pin_irq_hdr_tab[irqindex].args == args)
    {
        rt_hw_interrupt_enable(level);
        return RT_EOK;
    }
    if (pin_irq_hdr_tab[irqindex].pin != -1)
    {
        rt_hw_interrupt_enable(level);
        return RT_EBUSY;
    }
    pin_irq_hdr_tab[irqindex].pin = pin;
    pin_irq_hdr_tab[irqindex].hdr = hdr;
    pin_irq_hdr_tab[irqindex].mode = mode;
    pin_irq_hdr_tab[irqindex].args = args;
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

由上面函数可以看到,STM32 的 PIN 中断被组织到 pin_irq_hdr_tab[] 结构体数组中,共 16 个,其初始内容如下:

static struct rt_pin_irq_hdr pin_irq_hdr_tab[] =
{
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
    {-1, 0, RT_NULL, RT_NULL},
};

其结构体 rt_pin_irq_hdr 的4个成员如下:

struct rt_pin_irq_hdr
{
    rt_int16_t        pin;	//PIN 引脚
    rt_uint16_t       mode;	//中断触发方式
    void (*hdr)(void *args);//中断回调函数,用户自行定义这个函数
    void             *args;	//中断回调函数的参数,不需要时设置为RT_NULL
};

这4个成员变量就是函数 static rt_err_t stm32_pin_attach_irq(struct rt_device *device, rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args) 的其中后4个参数。此函数的主要目的就是设置某个 PIN 引脚中断相关信息(中断触发方式、中断回调函数、中断回调函数的参数)。

其中中断触发方式形参 mode 的取值如下(在 pin.h 中定义):

#define PIN_IRQ_MODE_RISING 0x00          /* 上升沿触发 */
#define PIN_IRQ_MODE_FALLING 0x01          /* 下降沿触发 */
#define PIN_IRQ_MODE_RISING_FALLING 0x02 /* 边沿触发(上升沿和下降沿都触发)*/
#define PIN_IRQ_MODE_HIGH_LEVEL 0x03      /* 高电平触发 */
#define PIN_IRQ_MODE_LOW_LEVEL 0x04      /* 低电平触发 */

5 使能引脚中断

应用层调用 rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled); 设备管理层调用 static rt_err_t stm32_pin_dettach_irq(struct rt_device *device, rt_int32_t pin),其函数内容如下:

static rt_err_t stm32_pin_irq_enable(struct rt_device *device, rt_base_t pin,
                                     rt_uint32_t enabled)
{
    const struct pin_index *index;
    const struct pin_irq_map *irqmap;
    rt_base_t level;
    rt_int32_t irqindex = -1;
    GPIO_InitTypeDef GPIO_InitStruct;

    index = get_pin(pin);
    if (index == RT_NULL)
    {
        return RT_ENOSYS;
    }

    if (enabled == PIN_IRQ_ENABLE)
    {
        irqindex = bit2bitno(index->pin);
        if (irqindex < 0 || irqindex >= ITEM_NUM(pin_irq_map))
        {
            return RT_ENOSYS;
        }

        level = rt_hw_interrupt_disable();

        if (pin_irq_hdr_tab[irqindex].pin == -1)
        {
            rt_hw_interrupt_enable(level);
            return RT_ENOSYS;
        }

        irqmap = &pin_irq_map[irqindex];

        /* Configure GPIO_InitStructure */
        GPIO_InitStruct.Pin = index->pin;        
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        switch (pin_irq_hdr_tab[irqindex].mode)
        {
        case PIN_IRQ_MODE_RISING:
            GPIO_InitStruct.Pull = GPIO_PULLDOWN;
            GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
            break;
        case PIN_IRQ_MODE_FALLING:
            GPIO_InitStruct.Pull = GPIO_PULLUP;
            GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
            break;
        case PIN_IRQ_MODE_RISING_FALLING:
            GPIO_InitStruct.Pull = GPIO_NOPULL;
            GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING_FALLING;
            break;
        }
        HAL_GPIO_Init(index->gpio, &GPIO_InitStruct);

        HAL_NVIC_SetPriority(irqmap->irqno, 5, 0);
        HAL_NVIC_EnableIRQ(irqmap->irqno);
        pin_irq_enable_mask |= irqmap->pinbit;

        rt_hw_interrupt_enable(level);
    }
    else if (enabled == PIN_IRQ_DISABLE)
    {
        irqmap = get_pin_irq_map(index->pin);
        if (irqmap == RT_NULL)
        {
            return RT_ENOSYS;
        }

        level = rt_hw_interrupt_disable();

        HAL_GPIO_DeInit(index->gpio, index->pin);

        pin_irq_enable_mask &= ~irqmap->pinbit;
#if defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32G0)
	// 此处省略,本文未使用       
#else      
        if (( irqmap->pinbit>=GPIO_PIN_5 )&&( irqmap->pinbit<=GPIO_PIN_9 ))
        {
            if(!(pin_irq_enable_mask&(GPIO_PIN_5|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9)))
            {    
                HAL_NVIC_DisableIRQ(irqmap->irqno);
            }
        }
        else if (( irqmap->pinbit>=GPIO_PIN_10 )&&( irqmap->pinbit<=GPIO_PIN_15 ))
        {
            if(!(pin_irq_enable_mask&(GPIO_PIN_10|GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15)))
            {    
                HAL_NVIC_DisableIRQ(irqmap->irqno);
            }
        }
        else
        {
            HAL_NVIC_DisableIRQ(irqmap->irqno);
        }        
#endif          
        rt_hw_interrupt_enable(level);  
    }
    else
    {
        return -RT_ENOSYS;
    }

    return RT_EOK;
}

此函数前 26行都是在检查参数的合法性,然后根据参数 enabled 判断是 PIN_IRQ_ENABLE 还是 PIN_IRQ_DISABLE;若是 PIN_IRQ_ENABLE 再根据 pin_irq_hdr_tab[irqindex].mode 设置的中断触发方式来分情况配置引脚。然后设置中断优先级,使用中断等。若是 PIN_IRQ_DISABLE 则去初始化,失能中断等。

之后就是硬件判断,若引脚有符合的中断触发,则 void EXTIx_IRQHandler(void) 会触发,如下:

void EXTI0_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
    rt_interrupt_leave();
}

void EXTI1_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_1);
    rt_interrupt_leave();
}

void EXTI2_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_2);
    rt_interrupt_leave();
}

void EXTI3_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_3);
    rt_interrupt_leave();
}

void EXTI4_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_4);
    rt_interrupt_leave();
}

void EXTI9_5_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_5);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_6);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_7);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_8);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_9);
    rt_interrupt_leave();
}

void EXTI15_10_IRQHandler(void)
{
    rt_interrupt_enter();
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_10);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
    HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_15);
    rt_interrupt_leave();
}

假如,这时中断到达,执行 EXTI0_IRQHandler() 中断处理函数,则 HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0) 会调用,接着执行 HAL 库中的中断回调函数 HAL_GPIO_EXTI_Callback(GPIO_Pin),此函数由用户实现,实现如下:

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
    pin_irq_hdr(bit2bitno(GPIO_Pin));
}

所以,中断到达后,pin_irq_hdr(bit2bitno(GPIO_Pin)) 会执行。

pin_irq_hdr() 定义为内联函数,可以提高执行效率,内容如下:

rt_inline void pin_irq_hdr(int irqno)
{
    if (pin_irq_hdr_tab[irqno].hdr)
    {
        pin_irq_hdr_tab[irqno].hdr(pin_irq_hdr_tab[irqno].args);
    }
}

所以,转了一大圏,最终是执行用户在 rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args) 中传入的回调函数 void (*hdr)(void *args)

6 总结

在这里插入图片描述

7 PIN 设备使用示例

PIN 设备的具体使用方式可以参考如下示例代码,示例代码的主要步骤如下:

设置蜂鸣器对应引脚为输出模式,并给一个默认的低电平状态。

设置按键 0 和 按键1 对应引脚为输入模式,然后绑定中断回调函数并使能中断。

按下按键 0 蜂鸣器开始响,按下按键 1 蜂鸣器停止响。
/*
 * 程序清单:这是一个 PIN 设备使用例程
 * 例程导出了 pin_beep_sample 命令到控制终端
 * 命令调用格式:pin_beep_sample
 * 程序功能:通过按键控制蜂鸣器对应引脚的电平状态控制蜂鸣器
*/

#include <rtthread.h>
#include <rtdevice.h>

/* 引脚编号,通过查看设备驱动文件drv_gpio.c确定 */
#ifndef BEEP_PIN_NUM
    #define BEEP_PIN_NUM            35  /* PB0 */
#endif
#ifndef KEY0_PIN_NUM
    #define KEY0_PIN_NUM            55  /* PD8 */
#endif
#ifndef KEY1_PIN_NUM
    #define KEY1_PIN_NUM            56  /* PD9 */
#endif

void beep_on(void *args)
{
    rt_kprintf("turn on beep!\n");

    rt_pin_write(BEEP_PIN_NUM, PIN_HIGH);
}

void beep_off(void *args)
{
    rt_kprintf("turn off beep!\n");

    rt_pin_write(BEEP_PIN_NUM, PIN_LOW);
}

static void pin_beep_sample(void)
{
    /* 蜂鸣器引脚为输出模式 */
    rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT);
    /* 默认低电平 */
    rt_pin_write(BEEP_PIN_NUM, PIN_LOW);

    /* 按键0引脚为输入模式 */
    rt_pin_mode(KEY0_PIN_NUM, PIN_MODE_INPUT_PULLUP);
    /* 绑定中断,下降沿模式,回调函数名为beep_on */
    rt_pin_attach_irq(KEY0_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_on, RT_NULL);
    /* 使能中断 */
    rt_pin_irq_enable(KEY0_PIN_NUM, PIN_IRQ_ENABLE);

    /* 按键1引脚为输入模式 */
    rt_pin_mode(KEY1_PIN_NUM, PIN_MODE_INPUT_PULLUP);
    /* 绑定中断,下降沿模式,回调函数名为beep_off */
    rt_pin_attach_irq(KEY1_PIN_NUM, PIN_IRQ_MODE_FALLING, beep_off, RT_NULL);
    /* 使能中断 */
    rt_pin_irq_enable(KEY1_PIN_NUM, PIN_IRQ_ENABLE);
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(pin_beep_sample, pin beep sample);
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值