RT-Thread PIN设备


PIN设备又叫GPIO设备,是MCU输入输出的一种设备,RT-Thread将GPIO抽象成PIN设备,以实现对GPIO的基本操作。
在这里插入图片描述
在这里插入图片描述

比如上面两个GPIO设备,其中一个作为输出控制LED灯的亮灭,一个作为输入判断按键的高低电平。
对于GPIO的操作都有以下几点:

  • 设置GPIO的方向,是作为输入还是输出
  • 设置GPIO的属性,是上拉,下拉,还是推挽、开漏等
  • 设置GPIO的高低电平或者读取GPIO的电平
  • 如果需要中断,还需要设置GPIO的中断
  • 有些GPIO能复用成其他外设功能,比如ADC、UART等

RT-Thread的PIN设备框架就需要实现上面的GPIO基本功能。

RT-Thread PIN设备驱动框架

RT-Thread PIN设备驱动层次图

在这里插入图片描述

RT-Thread PIN设备注册

RT-Thread 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;
    _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;

    /* register a character device */
    rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR); // 注册PIN设备

    return 0;
}

PIN设备的关键是用户的OPS操作函数

RT-Thread PIN设备操作函数

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);
    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_base_t (*pin_get)(const char *name);
};

  • pin_mode:设置GPIO的模式,比如上拉下拉。
  • pin_write:设置GPIO的电平状态。
  • pin_read:读取GPIO的电平。
  • pin_attach_irq:绑定GPIO中断。
  • pin_detach_irq:脱离GPIO中断。
  • pin_irq_enable:GPIO中断使能。
  • pin_get:获取GPIO编号。

注意事项:引脚编号
对于MCU厂商来说,会给自家MCU的GPIO进行一个分类,比如STM32的MCU就会以PAX、PBX等名称进行分类,而像NXP的就会以GPIO1_IOX、GPIO2_IOX等名称进行分类。RT-Thread的PIN设备框架为了能做到通用,统一使用了引脚编号这个属性,不管MCU的引脚怎么命名,在RT-Thread里面都是以0、1、2、3等数字进行操作。

pin_get

pin_get函数的作用是获取MCU的引脚编号。下面以STM32为例进行说明

#define PIN_NUM(port, no) (((((port)&0xFu) << 4) | ((no)&0xFu)))
/* e.g. PE.7 */
static rt_base_t stm32_pin_get(const char *name)
{
    rt_base_t pin = 0;
    int hw_port_num, hw_pin_num = 0;
    int i, name_len;

    name_len = rt_strlen(name);

    if ((name_len < 4) || (name_len >= 6)) // 判断传入的引脚名字长度是否非法
    {
        goto out;
    }
    if ((name[0] != 'P') || (name[2] != '.')) // 判断传入的引脚名字的第0和第2位是否非法
    {
        goto out;
    }

    if ((name[1] >= 'A') && (name[1] <= 'Z')) // 判断传入的引脚范围是否非法
    {
        hw_port_num = (int)(name[1] - 'A');
    }
    else
    {
        goto out;
    }

    for (i = 3; i < name_len; i++) // 计算引脚编号
    {
        hw_pin_num *= 10;
        hw_pin_num += name[i] - '0';
    }

    pin = PIN_NUM(hw_port_num, hw_pin_num); // 返回引脚编号

    return pin;

out:
    rt_kprintf("Px.y  x:A~Z  y:0-15, e.g. PA.0\n");
    return -RT_EINVAL;
}

比如传入的引脚名字为PB.0,那么通过计算可以得到PB.0的引脚编号为16。不同的MCU计算方法可能会不一样,但是道理都是一样的,就是将引脚名称全部转化为引脚编号

pin_mode

pin_mode作用是设置GPIO引脚的模式

/*
device :设备句柄
pin    :引脚编号
mode   :引脚模式
*/

void (*pin_mode)(struct rt_device *device, rt_base_t pin, rt_base_t mode);

RT-Thread提供以下几种GPIO的引脚模式

模式说明
PIN_MODE_OUTPUT输出模式
PIN_MODE_INPUT输入模式
PIN_MODE_INPUT_PULLUP输入上拉
PIN_MODE_INPUT_PULLDOWN输入下拉
PIN_MODE_OUTPUT_OD开漏输出
下面以STM32为例进行说明

static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_uint8_t mode)
{
    GPIO_InitTypeDef GPIO_InitStruct;

    if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断输入的引脚编号是否非法
    {
        return;
    }

    /* Configure GPIO_InitStructure */
    GPIO_InitStruct.Pin = PIN_STPIN(pin);
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    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(PIN_STPORT(pin), &GPIO_InitStruct); // 配置GPIO
}

如果MCU还有其他的模式配置可以自行添加

pin_write

pin_write作用是设置GPIO引脚的电平状态

/*
device :设备句柄
pin    :引脚编号
value  :输出电平
*/

void (*pin_write)(struct rt_device *device, rt_base_t pin, rt_base_t value);

RT-Thread提供以下的电平

电平状态说明
PIN_LOW低电平
PIN_HIGH高电平
下面以STM32为例进行说明
static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_uint8_t value)
{
    GPIO_TypeDef *gpio_port;
    uint16_t gpio_pin;

    if (PIN_PORT(pin) < PIN_STPORT_MAX) // 判断输入的引脚编号是否非法
    {
        gpio_port = PIN_STPORT(pin);
        gpio_pin = PIN_STPIN(pin);

        HAL_GPIO_WritePin(gpio_port, gpio_pin, (GPIO_PinState)value); // 设置GPIO电平
    }
}

pin_read

pin_read作用是读取GPIO引脚的电平状态

/*
device :设备句柄
pin    :引脚编号
返回值: GPIO电平
*/

int (*pin_read)(struct rt_device *device, rt_base_t pin);
电平状态说明
PIN_LOW低电平
PIN_HIGH高电平
下面以STM32为例进行说明
static rt_ssize_t stm32_pin_read(rt_device_t dev, rt_base_t pin)
{
    GPIO_TypeDef *gpio_port;
    uint16_t gpio_pin;
    GPIO_PinState state = GPIO_PIN_RESET;

    if (PIN_PORT(pin) < PIN_STPORT_MAX) // 判断输入的引脚编号是否非法
    {
        gpio_port = PIN_STPORT(pin);
        gpio_pin = PIN_STPIN(pin);
        state = HAL_GPIO_ReadPin(gpio_port, gpio_pin); // 读取GPIO电平
    }
    else
    {
        return -RT_EINVAL;
    }

    return (state == GPIO_PIN_RESET) ? PIN_LOW : PIN_HIGH; // 返回高或低
}

pin_attach_irq

pin_attach_irq作用是绑定GPIO引脚中断

/*
device :设备句柄
pin    :引脚编号
mode   :引脚中断类型
hdr    :引脚中断函数
args   :中断函数参数
返回值:错误代码
*/

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-Thread提供以下的引脚触发类型

触发类型说明
PIN_IRQ_MODE_RISING上升沿触发
PIN_IRQ_MODE_FALLING下升沿触发
PIN_IRQ_MODE_RISING_FALLING双边沿触发
PIN_IRQ_MODE_HIGH_LEVEL高电平触发
PIN_IRQ_MODE_LOW_LEVEL低电平触发

引脚的中断绑定函数需要在程序中进行保存,所以可以建一张中断表保存引脚的中断函数,当中断来临的时候,再通过查表的方式进行调用。下面以STM32为例进行说明

// 引脚的中断表
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},
};

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

    if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断引脚编号是否非法
    {
        return -RT_ENOSYS;
    }

    irqindex = bit2bitno(PIN_STPIN(pin)); // 通过引脚编号反推得到中断号
    if (irqindex < 0 || irqindex >= (rt_int32_t)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;
}
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);
    }
}

上面的函数通过引脚编号得到相应的引脚中断号,然后将引脚的中断函数注册进中断表,当中断来临的时候通过pin_irq_hdr函数在中断表找到相应的中断函数然执行。

pin_detach_irq

pin_detach_irq作用是解绑GPIO引脚中断

/*
device :设备句柄
pin    :引脚编号
返回值: 错误代码
*/

rt_err_t (*pin_detach_irq)(struct rt_device *device, rt_int32_t pin);

下面以STM32为例进行说明

static rt_err_t stm32_pin_dettach_irq(struct rt_device *device, rt_base_t pin)
{
    rt_base_t level;
    rt_int32_t irqindex = -1;

    if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断引脚编号是否非法
    {
        return -RT_ENOSYS;
    }

    irqindex = bit2bitno(PIN_STPIN(pin));
    if (irqindex < 0 || irqindex >= (rt_int32_t)ITEM_NUM(pin_irq_map)) // 判断中断号是否非法
    {
        return -RT_ENOSYS;
    }

    level = rt_hw_interrupt_disable(); // 关闭中断
    if (pin_irq_hdr_tab[irqindex].pin == -1) // GPIO引脚中断没有中断
    {
        rt_hw_interrupt_enable(level);
        return RT_EOK;
    }
    // 将GPIO中断号对应的中断表索引属性清空
    pin_irq_hdr_tab[irqindex].pin = -1;
    pin_irq_hdr_tab[irqindex].hdr = RT_NULL;
    pin_irq_hdr_tab[irqindex].mode = 0;
    pin_irq_hdr_tab[irqindex].args = RT_NULL;
    rt_hw_interrupt_enable(level);

    return RT_EOK;
}

pin_irq_enable

pin_irq_enable作用是使能GPIO引脚中断

/*
device :设备句柄
pin    :引脚编号
enabled: 使能
返回值: 错误代码
*/
rt_err_t (*pin_irq_enable)(struct rt_device *device, rt_base_t pin, rt_uint32_t enabled);

以下以STM32为例


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

    if (PIN_PORT(pin) >= PIN_STPORT_MAX) // 判断引脚编号是否非法
    {
        return -RT_ENOSYS;
    }

    if (enabled == PIN_IRQ_ENABLE) // 如果引脚中断使能
    {
        irqindex = bit2bitno(PIN_STPIN(pin));
        if (irqindex < 0 || irqindex >= (rt_int32_t)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 = PIN_STPIN(pin);
        GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
        switch (pin_irq_hdr_tab[irqindex].mode)  // 设置GPIO中断模式
        {
        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(PIN_STPORT(pin), &GPIO_InitStruct);
 		// 使能GPIO中断
        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(PIN_STPIN(pin));
        if (irqmap == RT_NULL)
        {
            return -RT_ENOSYS;
        }

        level = rt_hw_interrupt_disable();

        HAL_GPIO_DeInit(PIN_STPORT(pin), PIN_STPIN(pin));

        pin_irq_enable_mask &= ~irqmap->pinbit;
#if defined(SOC_SERIES_STM32F0) || defined(SOC_SERIES_STM32G0)
        if ((irqmap->pinbit >= GPIO_PIN_0) && (irqmap->pinbit <= GPIO_PIN_1))
        {
            if (!(pin_irq_enable_mask & (GPIO_PIN_0 | GPIO_PIN_1)))
            {
                HAL_NVIC_DisableIRQ(irqmap->irqno);
            }
        }
        else if ((irqmap->pinbit >= GPIO_PIN_2) && (irqmap->pinbit <= GPIO_PIN_3))
        {
            if (!(pin_irq_enable_mask & (GPIO_PIN_2 | GPIO_PIN_3)))
            {
                HAL_NVIC_DisableIRQ(irqmap->irqno);
            }
        }
        else if ((irqmap->pinbit >= GPIO_PIN_4) && (irqmap->pinbit <= GPIO_PIN_15))
        {
            if (!(pin_irq_enable_mask & (GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7 | GPIO_PIN_8 | GPIO_PIN_9 |
                                         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);
        }
#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;
}
  • 23
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RT-Thread Studio是一个本土化的中文免费集成开发环境,由RT-Thread Studio团队在2019年末推出。它提供了工程创建和管理、代码编辑、SDK管理、RT-Thread配置、构建配置、调试配置、程序下载和调试等功能,结合图形化配置系统以及软件包和组件资源,可以减少重复工作,提高开发效率。\[1\]\[2\] 在使用RT-Thread Studio进行裸机开发时,可以按照以下步骤进行操作: 1. 创建一个工程并进行相关配置。 2. 编写代码,可以使用RT-Thread提供的API进行开发。 3. 进行编译和构建配置,生成可执行文件。 4. 将可执行文件下载到目标设备上进行调试和测试。 例如,在一个简单的点灯程序中,可以使用#define定义LED端口,然后在主函数中使用rt_pin_mode和rt_pin_write函数来控制LED的亮灭。通过循环控制LED的状态,可以实现LED的闪烁效果。\[3\] 总之,RT-Thread Studio是一个功能强大的集成开发环境,可以帮助开发者进行裸机开发,并提高开发效率。 #### 引用[.reference_title] - *1* *2* *3* [基于rt-thread studio的STM32裸机开发第一节:点亮一个LED](https://blog.csdn.net/qq_34187873/article/details/126129164)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值