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

2023-05-16

目录

  • 1 获取引脚编号
  • 2 设置引脚模式
  • 3 设置引脚电平
  • 4 绑定 PIN 中断回调函数
  • 5 使能引脚中断
  • 6 总结
  • 7 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);
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【RT-Thread】PIN 设备源码分析 的相关文章

  • ESP_C3在ubuntu下运行RT-Thread

    1 clone源代码RT Thread git clone git 64 github com RT Thread rt thread git 2 开始搭建ESP IDF环境 进入源码到bsp文件夹下找到ESP32 C3 xff0c 开始配
  • C++:std::thread:线程用法

    1 std thread的基本用法 最简单的 std thread用法如下 调用 thread将立即同时开始执行这个新建立的线程 新线程的任务执行完毕之后 main 的主线程也会继续执行 include
  • Win32环境下两种用于C++的线程同步类

    线程同步是多线程程序设计的核心内容 它的目的是正确处理多线程并发时的各种问题 例如线程的等待 多个线程访问同一数据时的互斥 防死锁等 Win32提供多种内核对象和手段用于线程同步 如互斥量 信号量 事件 临界区等 所不同的是 互斥量 信号量
  • 线程池创建类ThreadPoolExecutor介绍

    ThreadPoolExecutor 使用给定的初始参数和默认线程工厂和拒绝的执行处理程序创建一个新的线程池执行器 一 构造方法参数说明 有四个构造方法 最终都是调用构造方法四 构造方法参数说明 param corePoolSize 保留在
  • std::jthread与std::thread区别

    std jthread是C 20新引入的线程类 与 std thread 类似 或者说 jthread是对thread进一步的封装 功能更强大 std jthread的 j实际上是 joining的缩写 众所周知 std thread在其生
  • 03C++11多线程编程之测试join,detach传各种实参时形参的拷贝次数

    03C 11多线程编程之测试join detach传各种实参时形参的拷贝次数 首先我们看下面的总结测试图 然后一步步的测试 1 这里我们先测试join传实参的类型 当实参为普通对象时 1 当形参为普通对象时 拷贝了两次 2 当形参为引用时
  • java中的锁池和等待池

    在java中 每个对象都有两个池 锁 monitor 池和等待池 wait notifyAll notify 三个方法都是Object类中的方法 锁池 假设线程A已经拥有了某个对象 注意 不是类 的锁 而其它的线程想要调用这个对象的某个sy
  • java之Thread类详细分析(全)

    目录 前言 1 属性值 1 1 线程属性方法 2 常用方法 3 使用方法 前言 Thread是程序中的执行线程 jvm并发地运行多个执行线程 1 属性值 通过查看Thread源码 是继承Runnable接口的实现类 无论使用Runnable
  • Java 线程创建方法

    除了继承Thread 实现Runnable Callable三种创建线程方式外的第四种创建方式 实现java util concurrent ThreadFactory接口 实现newThread Runnable r 方法 这种方式应用于
  • 【最清晰】ThreadLocal和局部变量和成员变量的区别

    ThreadLocal是进程级别的全局变量 最近有一个疑惑 为什么线程类的局部变量不能完全替代ThreadLocal 每一次new 线程都是创建了一个副本啊照理来说也是独立的 为什么还需要ThreadLocal 实际上确实是独立的 但是答案
  • muduo1——编程风格:面向对象的编程和基于对象的编程(上)

    muduo库其实不是面向对象的编程 而是基于对象的编程 那么在进入正式的muduo源码分析之前 先来看看这两种编程风格 一 面向对象编程风格 通过对一个线程类的封装来进行讲解 Thread是一个抽象类不能实例化对象 TestThread是派
  • 第十三章:QT多线程(QThread)

    回顾 第一章 Qt的概述 第二章 在Ubuntu编写第一个Qt程序 第三章 Qt的字符串和字符编码 第四章 Qt的信号和槽 第五章 Qt容器窗口 父窗口 第六章 面向对象的Qt编程 第七章 Qt设计师使用 designer 第八章 Qt创造
  • 笔试题10:Runnable接口与Thread类的区别?

    1 线程类继承自Thread则不能继承自其它类 而Runnable接口可以 2 线程类继承自Thread相对于Runnable来说 使用线程的方法更方便一些 3 实现Runnable接口的线程类的多个线程 可以更方便的访问同一变量 而Thr
  • 关于RT-Thread中优先级翻转问题的简记

    最近在学习RT Thread的相关知识 记录一下心得 优先级翻转 是指当一个高优先级线程试图通过信号量机制访问共享资源时 如果该信号量已被低优先级线程持有 而这个低优先级线程在运行过程中可能又被其他一些中等优先级的线程抢占 从而造成高优先级
  • C++11多线程std::thread的简单使用

    文章转载自http blog csdn net star530 article details 24186783 在cocos2dx 2 0时代 我们使用的是pthread库 是一套用户级线程库 被广泛地使用在跨平台应用上 但在cocos2
  • Qt之QThread详解

    一 线程管理 1 线程启动 void start Priority priority InheritPriority 调用后会执行run 函数 但在run 函数执行前会发射信号started 操作系统将根据优先级参数调度线程 如果线程已经在
  • CreateRemoteThread的使用(转载)

    先解释一下远程进程 其实就是要植入你的代码的进程 相对于你的工作进程 如果叫本地进程的话 它就叫远程进程 可理解为宿主 首先介绍一下我们的主要工具CreateRemoteThread 这里先将函数原型简单介绍以下 CreateRemoteT
  • POSIX线程:API

    一 线程创建与取消 1 线程创建 1 1 线程与进程 相对进程而言 线程是一个更加接近于执行体的概念 它可以与同进程中的其他线程共享数据 但拥有自己的栈空间 拥有独立的执行序列 在串行程序基础上引入线程和进程是为了提高程序的并发度 从而提高
  • 无线传感网必知必会

    一 填空题 传感器网络三大基本要素 传感器 感知对象 用户 观测者 传感器节点的基本功能模块包括 数据采集模块 数据处理和控制模块 通信模块 供电模块 四个 其中 通信模块 能量消耗最大 传感器节点通信模块的工作模式有 发送 接收 空闲 睡
  • thread_Timer(线程中定时器)

    package com gzhs zsd thread import java util Date import java util Timer import java util TimerTask Timer定时器运用 author 谢泽

随机推荐

  • HFSS - 倒F天线的设计与仿真

    一 倒F天线概述 倒F天线是单极子天线的一种变形结构 xff0c 其衍变发展的过程可以看成是从1 4波长单极子天线到倒L天线再到倒F天线的过程 xff0c 如下图所示 首先 xff0c 将单极子天线进行90 弯曲 xff0c 就能得到倒L天
  • 理论 - 平面倒F天线(PIFA)

    一 概述 PIFA Planar Inverted F shaped Antenna 天线即平面倒F形天线 xff0c 因为整个天线的形状像个倒写的英文字母F而得名 多年来 xff0c 多数手机天线都一直沿用这种传统的PIFA天线设计方案
  • HFSS - GSM 900 单频PIFA天线的设计与仿真

    一 设计指标 中心频率 xff1a 920MHz回波损耗带宽 xff1a 大于80MHz 各个参数变量如下表 变量意义变量名变量初始值 xff08 单位 xff1a mm xff09 天线高度H10辐射金属片长度L155辐射金属片宽度W13
  • HFSS - GSM 900 和 DCS 1800 双频PIFA天线的设计与仿真

    一 概述 PIFA天线可以采用在辐射金属片上开槽的技术来实现双频和多频段工作 采用如下图所示的U形开槽方案来实现PIFA天线在GSM 900和DCS 1800 两个频段上的工作 其中 xff0c 在GSM 900频段 xff0c 信号上行频
  • HFSS - 矩形口径喇叭天线的设计与仿真

    一 理论 喇叭天线是一种应用广 泛的微波天线 xff0c 其优点是结构简单 xff0c 频带宽 xff0c 功率容量大 xff0c 调整与使用方便 合理地选择喇叭尺寸 xff0c 可以获得良好的辐射特性 相当尖锐的主瓣 较小副瓣和较高的增益
  • 【record】1、FS-I6设置与对码

    官方说明文档 先贴一张官方的说明文档 xff1b 注意的点 xff1a 遥控器设置中 xff0c 按住CANCEL保存 xff1b 遥控器设置 xff08 1 xff09 恢复出厂设置 这里我对FS i6直接恢复出厂设置 xff0c 当然可
  • keil无法观察局部变量

    keil观察局部变量的时候 xff0c 发现总是显示not in scope xff0c 是优化级别过高 xff0c 降低优化级别即可
  • rplidar连接计算机显示process has died.....解决方法

    检测是否打开roscore可能是串口未赋予权限 xff0c 解决方法 xff1a sudo chmod 777 dev ttyUSB0
  • ST-LINK/V2:cannot reset target shutting down debug session

    使用 ST LINK V2 为 STM32 下载程序时 xff0c 总是不成功 xff0c 并弹出如下对话框 xff1a 解决方法 xff1a 打开 MDK 的安装目录下的 STLink xff0c 如下是我的路径 xff1a D mdk5
  • C++声明与定义以及初始化,头文件的书写规范

    初始化 初始化 xff1a 当一个对象被创建的同时获得了特定的值 1 初始化不是赋值 xff0c 初始化的含义是创建变量时赋予其一个初始值 xff0c 而赋值的含义是把对象的当前值擦除 xff0c 而以一个新值代替 xff1b 2 默认初始
  • 【C++】Cmake使用教程(看这一篇就够了)

    文章目录 引言一 环境搭建二 简单入门2 1 项目结构2 2 示例源码2 3 运行查看 三 编译多个源文件3 1 在同一个目录下有多个源文件3 1 1 简单版本3 1 1 1 项目结构3 1 1 2 示例代码3 1 1 3 运行查看 3 1
  • 编码

    UTF 8 UTF 8以字节为单位对Unicode进行编码 从Unicode到UTF 8的编码方式如下 xff1a Unicode编码 十六进制 UTF 8 字节流 二进制 000000 00007F0xxxxxxx000080 0007F
  • tensorflow-gpu1.14 + Win10 + CUDA10.0 + CUDNN7.5.0 + Python3.6 + VS2015安装

    最近学习深度学习 xff0c 在配置环境中的过程中遇到很多问题 xff0c 在这进行总结 xff0c 希望对大家有帮助 一 整个软件安装配置过程 xff0c 很多博客写的很详细 xff0c 附上链接 xff1b https blog csd
  • VS2015下配置海康威视SDK

    1网络摄像头可以在官网下载到SDK开发包 xff0c 进入海康威视官网 xff0c 选择何时的版本 xff0c 点击下载 https www hikvision com cn download 61 html 下载完成进行解压 解压完成 x
  • 如何提升串口响应速度

    最近负责编写公司的工厂模式指令集 xff0c 碰到了一些代码之外的问题 xff0c 困扰了我很久 因为综测那边对串口响应速度的要求很高 xff0c 要求从上位机下发指令开始到上位机接收到完整回复 xff0c 整个过程的响应速度要达到几十个m
  • rv1126 SDK简单编译

    rv1126 SDK简单编译 在工程的根目录下执行命令 source envsetup sh 会出现很多选项 xff0c 选择 rockchip rv1126 rv1109 spi nand 这个选项 xff0c 输入93 我的FLASH是
  • socket套接字编程之UDP协议封装

    1 UDP 协议特点 xff1a 传输层协议 无连接 不可靠传输 面向数据报 2 封装之前先将清楚几个要点 xff1a 2 1网络字节序 xff1a 注意设备的大小端 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出 接收主机把
  • C语言 使用调用函数的方法,将两个字符串连接起来

    因本人才疏学浅 xff0c 见识浅薄 xff0c 有不当之处望指正 xff0c 谢谢 xff01 这次用调用函数的方法 xff0c 连接两个字符串 在被调函数中可以说明形参数组的大小 xff0c 也可以不说明形参数组的大小 例如cat st
  • STL容器特点对比

    1 分类 序列式容器 xff08 sequential container xff09 vector list array deque forward list关联式容器 xff08 associative container xff09
  • 【RT-Thread】PIN 设备源码分析

    目录 1 获取引脚编号2 设置引脚模式3 设置引脚电平4 绑定 PIN 中断回调函数5 使能引脚中断6 总结7 PIN 设备使用示例 关于 RT Thread 的 PIN 设备驱动应用层面的介绍可以直接参考 RT Thread 的官网 xf