RT-Thread记录(十三、I/O 设备模型之PIN设备)

2023-10-30

讲完UART设备之后,我们已经熟悉RT-Thread I/O 设备模型了,回头看看基本的 PIN 设备。

前言

我们学习一个 MCU 的最基本的 GPIO 口,也就是 PIN 设备模型,我们还没有讲过,至于原因之前也说了,因为 PIN 设备的操作函数与我们介绍的 I/O 设备模型的通用函数名称不太对应,对于新手来说先将 PIN 设备可能会让人难以理解。

所以前面的文章我们先讲了 UART 设备模型,从源码分析了一下 UART 设备的设计思路,从设备驱动层,和设备驱动框架层再到 I/O 设备管理层,最后到应用层,我们都理过一遍。

有了前面的经验,本文我们就来学习了解 RT-Thread PIN设备 。

❤️
本 RT-Thread 专栏记录的开发环境:
RT-Thread记录(一、RT-Thread 版本、RT-Thread Studio开发环境 及 配合CubeMX开发快速上手)
RT-Thread记录(二、RT-Thread内核启动流程 — 启动文件和源码分析)
❤️
RT-Thread 设备篇系列博文链接:
RT-Thread记录(十、全面认识 RT-Thread I/O 设备模型)
RT-Thread记录(十一、I/O 设备模型之UART设备 — 源码解析)
RT-Thread记录(十二、I/O 设备模型之UART设备 — 使用测试)

一、PIN 设备模型解析

一直说到 PIN 设备有点特殊,和我们讲 I/O 设备模型时候的设备感觉有一点区别的,那么到底怎么个特殊法?我们还是需要具体来分析一下:

1.1 初识 GPIO 操作函数

我们还是从上层的 I/O 设备管理层来开始,看看 PIN 设备管理层提供的访问 GPIO 的接口有哪些:

函数 描述 函数原型
rt_pin_get() 获取引脚编号 rt_base_t rt_pin_get(const char *name) 还有一种方式获取引脚编号
rt_pin_mode() 设置引脚模式 void rt_pin_mode(rt_base_t pin, rt_base_t mode);
rt_pin_write() 设置引脚电平 void rt_pin_write(rt_base_t pin, rt_base_t value)
rt_pin_read() 读取引脚电平 int rt_pin_read(rt_base_t pin)
rt_pin_attach_irq() 绑定引脚中断回调函数 rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,void (*hdr)(void *args), void *args)
rt_pin_irq_enable() 使能引脚中断 rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled)
rt_pin_detach_irq() 脱离引脚中断回调函数 rt_err_t rt_pin_detach_irq(rt_int32_t pin)

我们可以发现,上面的 PIN 设备管理接口的操作函数,与我们将的通用的函数完全不一样,如下图:

在这里插入图片描述

这也是为什么我们将设备示例的时候没有先讲 PIN 设备的原因,怕很多小伙伴刚开始不理解,那么为什么会这样呢?

1.2 PIN 设备框架

我们通过前面的 UART 设备的分析,已经知道了设备的基本的框架了,首先我们来看一下 上一篇文章讲到的 UART 设备框架:

在这里插入图片描述

对于 PIN 设备来说,框架总结如下图表:

硬件 PIN 设备驱动层 drv_gpio.c PIN 设备驱动框架层操作函数 pin.c I/O 设备管理层 device.c 应用程序
硬件 stm32_pin_write rt_pin_write() rt_device_write在官方介绍中,并没有使用这个接口函数介绍,但是也可以使用 应用程序
硬件 stm32_pin_attach_irq() rt_pin_attach_irq() 应用程序
注册说明 rt_hw_pin_init() rt_device_pin_register rt_device_register

❤️ 前面一直说 PIN 设备有点特别,那只不过是因为官方说明中 应用程序调用的不是 I/O 设备管理层的接口函数,而是直接调用的 PIN 设备驱动框架层的接口函数:

(下图中的说明有点欠缺,实际上其实也可以直接使用 device.c 中的接口对 PIN 设备进行控制的,这个我们后面会说明)
在这里插入图片描述

知道了这一点的话,其实我们都不需要进行过多的分析,具体的过程分析可以查看前面几篇博文,我们这里只需要对 PIN 设备驱动框架层 和 设备驱动层的接口简单的了解一下,毕竟 GPIO 的操作还是很简单的。

1.3 PIN 设备驱动框架层

通过上面的说明,我们知道 PIN 设备的使用是直接调用的 设备驱动框架层的接口,所以我们来看看 PIN 设备驱动框架层的文件(pin.c)有哪些函数接口:

实现的函数

//私有的
static struct rt_device_pin _hw_pin;
static rt_size_t _pin_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
static rt_size_t _pin_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size)
static rt_err_t  _pin_control(rt_device_t dev, int cmd, void *args)

//可调用的
int rt_device_pin_register(const char *name, const struct rt_pin_ops *ops, void *user_data);

/* Get pin number by name,such as PA.0,P0.12 */
rt_base_t rt_pin_get(const char *name);
void rt_pin_mode(rt_base_t pin, rt_base_t mode);
void rt_pin_write(rt_base_t pin, rt_base_t value);
int  rt_pin_read(rt_base_t pin);
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
                             void (*hdr)(void *args), void  *args);
rt_err_t rt_pin_detach_irq(rt_int32_t pin);
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);

挑一个函数简单看看:

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

函数先断言判断_hw_pin.ops这个结构体是否有效,有效的情况下就设置引脚的模式。

第一个参数是引脚的索引号(这个在我们下面讲解 PIN 设备驱动层的时候会有说明什么是索引号),
第二个参数是引脚模式(具体的模式我们也会再下面讲解GPIO 设置时候统一说明)。

PIN 设备控制块

在 RT-Thread 中 PIN 设备作为一个对象,那么肯定有他的对象控制块,和我们前面学习的所有的对象一样,在pin.h中有 PIN 设备的对象结构体:

/* pin device and operations for RT-Thread */
struct rt_device_pin
{
    struct rt_device parent;  // rt_device 我们前面讲过的,所有 device 的父类
    const struct rt_pin_ops *ops;
};

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

❤️ PIN 设备的访问函数都是在 PIN 设备控制块中的结构体成员 ops 中实现的,也是通过这个结构体成员与 底层驱动层关联起来 —— 在设备驱动层定义rt_pin_ops类型的变量,实现这些操作函数。

注册函数

在 PIN设备初始化的时候,rt_hw_pin_init()会调用 rt_device_pin_register 函数进行 PIN 设备的初始化。

PIN 设备注册函数,使用这个注册函数,可以绑定底层驱动层的函数,也同时将设备接口提供给上层 I/O 设备管理层:

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; //PIN 设备不需要
    _hw_pin.parent.open         = RT_NULL; //
    _hw_pin.parent.close        = RT_NULL; //
    _hw_pin.parent.read         = _pin_read; //* 把设备的read操作绑定在pin.c的_pin_read函数 */
    _hw_pin.parent.write        = _pin_write; //同上
    _hw_pin.parent.control      = _pin_control; //同上
#endif
	/* 
	把drv_gpio.c所实现的_stm32_pin_ops绑定在_hw_pin.ops上 
	因为 PIN 设备驱动层使用的注册函数为:
	rt_device_pin_register("pin", &_stm32_pin_ops, RT_NULL);
	*/
    _hw_pin.ops                 = ops;
    _hw_pin.parent.user_data    = user_data;

    /*
    register a character device
    /* 将其注册进device设备框架中 */ 
    */
    rt_device_register(&_hw_pin.parent, name, RT_DEVICE_FLAG_RDWR);

    return 0;
}

在注册函数中:_hw_pin.ops = ops; 这个操作就把设备驱动层实现的硬件操作函数给关联到了 设备驱动框架层。

官方说明的 PIN 设备访问的接口就是在 设备驱动框架层 提供的函数接口。

但是我们看到:

    _hw_pin.parent.read         = _pin_read; //把设备的read操作绑定在pin.c的_pin_read函数 
    _hw_pin.parent.write        = _pin_write;
    _hw_pin.parent.control      = _pin_control;

这说明我们不仅可以使用 rt_pin_read 获取 PIN 设备的值,还可以使用 rt_device_read 获取 PIN 设备的值!!!

❤️ 在 RT-Thread 的 PIN 设备模型中, rt_pin_read 函数和 rt_device_read 函数效果一样。

1.4 PIN 设备驱动层

PIN 设备驱动层,直接与硬件打交道的层面,对于我们使用的 STM32 来说,里面的很多操作我们应该都不会陌生,我们也简单了解下里面的函数,主要的目的在于实现 PIN 设备控制块中 rt_pin_ops 成员中的几个函数:

实现的函数

static const struct pin_index *get_pin(uint8_t pin)
static void stm32_pin_write(rt_device_t dev, rt_base_t pin, rt_base_t value)
static int stm32_pin_read(rt_device_t dev, rt_base_t pin)
static void stm32_pin_mode(rt_device_t dev, rt_base_t pin, rt_base_t mode)

rt_inline rt_int32_t bit2bitno(rt_uint32_t bit)
rt_inline const struct pin_irq_map *get_pin_irq_map(uint32_t pinbit)

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

rt_inline void pin_irq_hdr(int irqno)

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
void EXTI0_IRQHandler(void)
...//一系列的外部中断函数
...
int rt_hw_pin_init(void)

我们简单来看一个函数,根本不需要过多的解释:

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

初始化函数

初始化函数虽然重要,但是简单,看一下就能明白,首先就是熟悉的 GPIO 时钟初始化,

然后就是调用设备注册函数,设备名称 pin ,也是在这里定义的,如果改成其他的,在 shell 工具中使用 list_device 就会显示其他的名称了。

第二个参数,就是将设备驱动层中实现的对硬件的操作函数关联到 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

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

☆引脚定义☆

在驱动文件中,关于 GPIO 引脚的定义方式(STM32为例),我们有必要说明一下。

与 UART 不同的是,GPIO 配置简单能够更直接关联硬件,所以 HAL 库并没有为 GPIO 提供句柄结构体描述,在 HAL 库中描述 GPIO 使用了两个参数:GPIO_TypeDef* GPIOxGPIO_Pin,比如:

GPIO_PinState     HAL_GPIO_ReadPin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
void              HAL_GPIO_WritePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, GPIO_PinState PinState);

void              HAL_GPIO_TogglePin(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);

而在 RT-Thread 中,其定义了一个结构体 pin_index,通过一个变量即可描述一个 GPIO,如下:

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

针对这个结构体,驱动程序中给了一些补充的操作:

/*
相当于结构体pin_index以宏定义的形式被初始化
用C语言字符串连接定义引脚信息
index   	引脚的索引号,用户可自行定义的驱动程序定义的引脚编号
gpio   		相当于HAL库中的GPIO_TypeDef 
gpio_index 	相当于HAL库中的GPIO_Pin

例如宏__STM32_PIN(0, A, 0) 就表示结构体内容为 {0, GPIOA, GPIO_PIN_0}
*/

#define __STM32_PIN(index, gpio, gpio_index)                                \
    {                                                                       \
        index, GPIO##gpio, GPIO_PIN_##gpio_index                            \
    }

//保留未使用的宏定义,有些IO口未使用,使用这个宏定义
#define __STM32_PIN_RESERVE                                                 \
    {                                                                       \
        -1, 0, 0                                                            \
    }

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(17,  B, 1),

 //后面省略很多......

首先宏定义#define __STM32_PIN(index, gpio, gpio_index)

其中##为C语言连接符,其功能是在带参数的宏定义中将两个子串(token)联接起来,从而形成一个新的子串,例如宏__STM32_PIN(0, A, 0) 就表示结构体内容为 {0, GPIOA, GPIO_PIN_0},就等于定义了一个pin_index结构体。

然后宏定义__STM32_PIN_RESERVE

预留的IO楼,有些IO口未使用,使用这个宏定义

接下来的结构体数组pins

pinspin_index结构体类型的数组,RT-Thread 使用 pins 数组对 所有的 GPIO 引脚进行初始化定义。

这样就相当于芯片上所支持的 IO 口都进行了初始化定义,每一个 GPIO 都有了一个对应的索引号index
在 RT-Thread 提供的 PIN 设备操作函数中void rt_pin_mode(rt_base_t pin, rt_base_t mode);, 他的第一个参数也不是类似 PIN设备控制块之类的数据结构,而是一个引脚索引号,就是对应的上面这个index

引脚中断的分析和 引脚定义类似,可自行查看代码,这里就不过多说明。

.

二、PIN 设备操作函数

文章开头我们虽然已经认识过 PIN 设备的操作函数,但是我们没有对函数参数可取值做说明,学习 API 的使用还是老样子,直接放函数原型然后看注释。

2.1 获取 PIN 索引号方法

在我们使用某个 GPIO 的时候,第一步要做的就是获取 GPIO 的索引号,即上文说到的index。因为对 PIN 设备的访问操作都是通过这个索引号进行的。

在 RT-Thread 中,提供了 3种方式获取 PIN 设备索引号:

方法一: 使用函数rt_pin_get()

pin.c 文件中提供了一个函数:

rt_base_t rt_pin_get(const char *name)

里面的参数为一个名字,那么这个名字是什么呢?在函数申明有注释:
在这里插入图片描述

对于STM32而言,使用示例如下:

//获取索引号
pin_number = rt_pin_get("PA.9"); // pin_number 就是索引号
//设置GPIO模式
rt_pin_mode(pin_number , PIN_MODE_INPUT_PULLUP);

方法二: 使用宏定义GET_PIN

drv_common.h文件中有宏定义,可以直接获取 GPIO 的索引号:

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

在这里插入图片描述

对于STM32而言,使用示例如下:

//获取索引号
#define LED0_PIN        GET_PIN(B,  9)

//LED0 点亮或者熄灭
#define LED0(n)	(n ? rt_pin_write(LED0_PIN, PIN_HIGH) : rt_pin_write(LED0_PIN, PIN_LOW))

方法三: 查看驱动文件drv_gpio.c

上面讲解 PIN 设备驱动层的时候说到过,所有的 GPIO 对应的索引号都会在驱动文件中定义,直接查看文件使用索引号就可以:

在这里插入图片描述

对于STM32而言,使用示例如下:

//对应驱动文件,下面的代码含义就是 设置 PA0 的模式为 PIN_MODE_INPUT_PULLUP
rt_pin_mode(0, PIN_MODE_INPUT_PULLUP);

说明,查看驱动文件的方式并不直观。

2.2 操作函数

操作函数说明老样子

2.2.1 设置 GPIO 模式

/*
参数 	描述
pin 	引脚编号:索引号
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         	 开漏输出
*/
void rt_pin_mode(rt_base_t pin, rt_base_t mode);

2.2.2 设置/ 读取 GPIO 电平

设置引脚电平:

/*
参数 	描述
pin 	引脚编号
value 	电平逻辑值,

value 	取值:
PIN_LOW 低电平,
PIN_HIGH 高电平
*/
void rt_pin_write(rt_base_t pin, rt_base_t value);

读取引脚电平:

/*
参数 	描述
pin 	引脚编号
返回 	
PIN_LOW 	低电平
PIN_HIGH 	高电平
*/
int  rt_pin_read(rt_base_t pin);

2.2.3 绑定/脱离中断回调函数

绑定中断回调函数:

/*
参数 	描述
pin 	引脚编号
mode 	中断触发模式
hdr 	中断回调函数,用户需要自行定义这个函数
args 	中断回调函数的参数,不需要时设置为 RT_NULL
返回 	——
RT_EOK 	绑定成功
错误码 	绑定失败

其中 mode 可选参数:
#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      	低电平触发 
*/
rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode,
                             void (*hdr)(void *args), void  *args);

脱离中断回调函数:

/*
参数 	描述
pin 	引脚编号
返回 	——
RT_EOK 	脱离成功
错误码 	脱离失败
*/
rt_err_t rt_pin_detach_irq(rt_int32_t pin);

说明:引脚脱离了中断回调函数以后,中断并没有关闭,还可以调用绑定中断回调函数再次绑定其他回调函数。

2.2.4 使能中断

绑定好引脚中断回调函数后需要使用下面的函数使能引脚中断:

/*
参数 	描述
pin 		引脚编号
enabled 	状态
返回 	——
RT_EOK 	使能成功
错误码 	使能失败

enabled 	可取 2 种值之一:
PIN_IRQ_ENABLE	(开启)
PIN_IRQ_DISABLE	(关闭)
*/
rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled);

三、PIN 设备示例

只要明白了PIN 设备模型原理,使用起来还是很简单的,我们先看一下原理图:

在这里插入图片描述

程序如下,测试OK,太简单所以没有什么好说的:

...
//添加这两个头文件
#include <rtdevice.h>
#include "board.h"
...

static struct rt_thread led1_thread;    //led1线程
static char led1_thread_stack[256];

static rt_thread_t key1_thread = RT_NULL; //

#define LED1_PIN        GET_PIN(D,  9)
#define LED2_PIN        GET_PIN(D,  8)
#define KEY1_PIN        GET_PIN(D,  11)
#define KEY2_PIN        GET_PIN(D,  10)

#define key1_read   rt_pin_read(KEY1_PIN)
#define LED1_ON     rt_pin_write(LED1_PIN, PIN_LOW);
#define LED1_OFF    rt_pin_write(LED1_PIN, PIN_HIGH);
#define LED2_ON     rt_pin_write(LED2_PIN, PIN_LOW);
#define LED2_OFF    rt_pin_write(LED2_PIN, PIN_HIGH);
//#define LED0(n) (n ? rt_pin_write(LED0_PIN, PIN_HIGH) : rt_pin_write(LED0_PIN, PIN_LOW))

static void led1_thread_entry(void *par){
    while(1){
        LED1_ON;
        rt_thread_mdelay(1000);
        LED1_OFF;
        rt_thread_mdelay(1000);
    }
}

static void key1_thread_entry(void *par){
    while(1){
        if(key1_read == 0){
            rt_thread_mdelay(10); //去抖动
            if(key1_read == 0){
                rt_kprintf("key1 kicked..\r\n");
             }
             while(key1_read == 0){rt_thread_mdelay(10);//去抖动
            }
        }
        rt_thread_mdelay(1);
   }
}

int main(void)
{

    MX_USART1_UART_Init();
    //    MX_GPIO_Init();  //使用设备模型不需要初始化这个
    /*配置LED管脚为输出*/
    rt_pin_mode(LED1_PIN, PIN_MODE_OUTPUT);
    rt_pin_mode(LED2_PIN, PIN_MODE_OUTPUT);
    /*配置按键为输入*/
    rt_pin_mode(KEY1_PIN, PIN_MODE_INPUT);
    rt_pin_mode(KEY2_PIN, PIN_MODE_INPUT);
    /*LED默认状态*/
    rt_pin_write(LED1_PIN, 1);
    rt_pin_write(LED2_PIN, 0);
    
	rt_err_t rst2;
    rst2 = rt_thread_init(&led1_thread,
                        "led1_blink ",
                        led1_thread_entry,
                        RT_NULL,
                        &led1_thread_stack[0],
                        sizeof(led1_thread_stack),
                        RT_THREAD_PRIORITY_MAX -1,
                        50);

    if(rst2 == RT_EOK){
        rt_thread_startup(&led1_thread);
    }
    
    key1_thread = rt_thread_create("key1_control",
                                key1_thread_entry,
                                RT_NULL,
                                512,
                                RT_THREAD_PRIORITY_MAX -2,
                                50);

        /* 如果获得线程控制块,启动这个线程 */
        if (key1_thread != RT_NULL)
            rt_thread_startup(key1_thread);
  
  ...//后面省略

结语

本文我们详细的分析了 RT-Thread I/O 设备模型之PIN设备,最终看来,使用 PIN 设备模型操作还是特别的简单的。

其实关键的部分还是在于理解 PIN 设备模型的原理,理解了以后使用起来也更加的得心应手。

GPIO设备虽然简单,但是文章写下来也1W多字了,即便以前对 PIN 设备有点模糊,只要看了本文,相信大家肯定有拨云见日的感觉!

希望大家多多支持!本文就到这里,谢谢!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

RT-Thread记录(十三、I/O 设备模型之PIN设备) 的相关文章

  • STM32超声波——HC_SR04

    文章目录 一 超声波图片 二 时序图 三 超声波流程 四 单位换算 五 取余计算 六 换算距离 七 超声波代码 一 超声波图片 测量距离 2cm 400cm 二 时序图 1 以下时序图要先提供一个至少10us的脉冲触发信号 告诉单片机我准备
  • 操作系统内部机制学习

    切换线程时需要保存什么 函数需要保存吗 函数在Flash上 不会被破坏 无需保存 函数执行到了哪里 需要保存吗 需要保存 全局变量需要保存吗 全局变量在内存上 无需保存 局部变量需要保存吗 局部变量在栈里 也是在内存里 只要避免栈不会被破坏
  • [屏驱相关]【SWM166-SPI-Y1.28C1测评】+ 有点惊艳的开箱

    耳闻华芯微特许久了 看到论坛得评测活动赶紧上了末班车 毕竟对有屏幕得板子也是很喜欢得 京东快递小哥客客气气 微笑着把快递给了我 好评 直接拆了包 在此之前没看过视频号 所以这个圆盘盘得模具还是有点惊喜的 正面照如下 开机有灯光秀 还有动画
  • HAL 锁定和解锁函数如何使用以及为什么?

    我试图理解另一位程序员编写的代码 它使用了I C http en wikipedia org wiki I C2 B2C通信以将数据写入 STM32 微控制器的 EEPROM 一般来说 我理解他的代码是如何工作的 但我不明白他为什么使用HA
  • 硬件基础-电容

    电容 本质 电容两端电压不能激变 所以可以起到稳定电压作用 充放电 电容量的大小 想使电容容量大 使用介电常数高的介质 增大极板间的面积 减小极板间的距离 品牌 国外 村田 muRata 松下 PANASONIC 三星 SAMSUNG 太诱
  • VS Code 有没有办法导入 Makefile 项目?

    正如标题所说 我可以从现有的 Makefile 自动填充 c cpp properties json 吗 Edit 对于其他尝试导入 makefile 的人 我找到了一组脚本 它们完全可以实现我想要实现的目标 即通过 VS Code 管理
  • gpiod - 在设备树中使用标签

    我想用libgpiod通过自定义板上的用户空间控制一些 GPIO 我有一个 i MX6UL 处理器 它有数百个引脚 我将只使用其中 8 个 作为 GPIO 我读到了关于libgpiod因为它正在取代旧的 sysfs API 我很高兴您可以为
  • STM32 暂停调试器时冻结外设

    当到达断点或用户暂停代码执行时 调试器可以停止 Cortex 中代码的执行 但是 当皮质停止在暂停状态下执行代码时 调试器是否会冻结其他外设 例如 DMA UART 和定时器 您只能保留时间 r 取决于外围设备 我在进入主函数时调用以下代码
  • 无法使用 OpenOCD 找到脚本文件

    我正在尝试按照本教程将 OpenOCD 与我的 ST 发现板一起使用 https japaric github io discovery README html https japaric github io discovery READM
  • RT-Thread 内核基础(六)

    RT Thread内核配置示例 RT Thread的一个重要特性是高度可裁剪性 支持对内核进行精细调整 对组件进行灵活拆卸 配置主要是通过修改工程目录下的rtconfig h文件来进行 用户可以通过打开 关闭该文件中的宏定义来对代码进行条件
  • STM32F4XX的12位ADC采集数值超过4096&右对齐模式设置失败

    文章目录 一 前言 二 问题1 数值超过4096 三 问题1的排错过程 四 问题2 右对齐模式设置失败 五 问题2的解决方法 5 1 将ADC ExternalTrigConv设置为0 5 2 使用ADC StructInit 函数 一 前
  • 特殊寄存器

    特殊寄存器 文章目录 前言 一 背景 二 2 1 2 2 总结 前言 前期疑问 STM32特殊寄存器到底是什么 特殊寄存器怎么查看和调试代码 本文目标 记录和理解特殊寄存器 一 背景 最近在看ucosIII文章是 里面提到特殊寄存器 这就进
  • 嵌入式 C++11 代码 — 我需要 volatile 吗?

    采用 Cortex M3 MCU STM32F1 的嵌入式设备 它具有嵌入式闪存 64K MCU固件可以在运行时重新编程闪存扇区 这是由闪存控制器 FMC 寄存器完成的 所以它不像a b那么简单 FMC 获取缓冲区指针并将数据刻录到某个闪存
  • 在 Contiki 程序中使用 malloc

    考虑以下 Contiki 程序 include
  • Python使用sudo启动时找不到模块

    我有一个使用 Google Assistant 库的脚本 并且必须从那里导入一些模块 我发现这只适用于 Python 虚拟环境 这真的很奇怪 在同一个文件夹中 我有一个使用 GPIO 引脚并且必须使用 root 的脚本 它们相互交互 因此当
  • 使用 STM32 USB 设备库将闪存作为大容量存储设备

    我的板上有这个闪存IC 它连接到我的STM32F04 ARM处理器 处理器的USB端口可供用户使用 我希望我的闪存在通过 USB 连接到 PC 时被检测为存储设备 作为第一步 我在程序中将 USB 类定义为 MSC 效果很好 因为当我将主板
  • 如何在嵌入式Linux中检测GPIO线上的中断?

    GPIO 39 上每 10ms 产生一个中断熊猫板 OMAP4 http www ti com product OMAP4460 我已在 Linux 驱动程序代码中为此注册了一个处理程序 但由于未检测到中断 因此未调用该处理程序 我在硬件级
  • Beaglebone Black 上的 GPIO

    我目前遇到了 Beaglebone black GPIO 引脚的问题 我正在寻找一种正确的方法来读取 C 中的 GPIO 引脚 p8 4 的值 如果我理解正确的话 我尝试使用一个库 该库使用了在引入设备树之前不支持的旧方法 我尝试寻找其他解
  • 对 FINTEK F81866A 芯片组上的 GPIO 引脚进行编程

    我有一个德承DE 1000 http www cincoze com goods info php id 10工业 PC 具有芬泰克 F81866A http www fintek com tw index php i o controll
  • 使用 BeagleBone Black 内核 >= 3.8 打开/关闭 USB 电源

    我需要 关闭 gt 睡眠几秒钟 gt 打开 beaglebone black 的 USB 电源 能够对连接到 USB 的设备 华为 E220 调制解调器 进行硬件重置 已经尝试过软重置 使用取消绑定 绑定和授权0 1 但软件重置不足以使设备

随机推荐

  • 关于存储器由RAM芯片组成问题

    前言 进行微机原理 预习 进行备考 看到关于用多个RAM芯片组成一个存储器的题目 虽然很简单 但还是记录一下 基础概念 因为怕大家的基础和我一样太薄弱 直接说RAM组成存储器的题目 可能会讲的云里雾里的 所以我先拿几个题目当成练手 题目一
  • FreeRTOS快速上手

    FreeRTOS使用 一 源码下载和移植文件提取 1 1 源码下载 在网站https sourceforge net projects freertos 可以找到freertos最新的源码 1 2 移植文件提取 根据第一步 我们会得到一个f
  • Ribbon在Eureka上拉去不到服务id的问题

    我要说的是刚接触SpringCloud的道友们会用到里面的东西 我这边就不详细说他们怎么用了 网上也有很多 我只说我遇到的一个问题分享给大家以便你们以后遇到 可以不浪费时间解决 一个标准的springboot工程是 一个pom xml文件
  • 为 Visual Studio Code 的 Drawio 插件新增创建 Drawio 格式文件的右键菜单

    为 Visual Studio Code 的 Drawio 插件新增创建 Drawio 格式文件的右键菜单 即上一篇 定制功能强大的 Windows Terminal 右键菜单 之后再水一篇定制 Windows 的小技巧文档 什么是 dra
  • Spring JPA不写sql实现limit查询功能

    Spring JPA不写sql实现limit查询功能 目前笔者收集到两种方式 1 JPA默认没有直接支持limit 但我们可以使用JPA的分页 排序功能来实现limit的查询 代码如下 import org springframework
  • IE的代理设置在注册表中的位置

    HKEY CURRENT USER根键中保存的信息是当前用户的子键信息 其内容与HKEY USERS Default分支中所保存的信息是相同的 并且任何对HKEY CURRENT USER根键中的信息的修改都会导致对HKEY USERS D
  • 从后端接口拿过来的多个值,根据值得不同,定义不同的字体颜色或者背景颜色。 即定义不同样式;

    先看效果 在看对你有用没 从后端接口 拿过来的值得不同 每个定义不同颜色 以下代码 以下是vue的
  • 英特尔AI医疗实战曝光:10倍加速辅助诊断、准确度高达90%

    转载自 机器之心 ID almosthuman2014 作者 力琴 本文6078字14图 建议阅读16分钟 本文介绍英特尔AI医疗落地解决方案 深耕医疗健康领域 20 年 医疗健康数字化 药物治疗精确化一直是英特尔的重要议题 每年都有 18
  • Windows设置自己的程序开机自动启动

    http t csdn cn nDLQ3 Windows系统想要快速设置开机自动启动某个程序 可以使用以下几种方法设置 第一种 设置启动项 1 找到启动文件夹 我的是C Users ThinkPad AppData Roaming Micr
  • c++虚函数、成员变量内存布局

    大家都知道c 的虚函数有个虚表 那这个需要具体在哪呢 在程序的代码段还是数据段的 一个类有成员变量 成员变量在代码段内是怎么分布的呢 如何根据一个对象指针调用某个虚函数 如何根据一个对象的指针直接修改成员变量 如果生成多个对象 那是不是会为
  • Laravel 怎么查看执行的Sql语句

    我的个人博客 逐步前行STEP 1 如果是使用Eloquent ORM操作数据库的话 在sql查询时可以调用toSql 方法来获取sql App User where name like hezehua gt toSql 2 如果是执行原生
  • 前端开发规范

    开发规范 本文档主要包括以下是三个部分 vue 框架构建及开发规范 webpack 打包优化 前端错误日志收集 文章目录 开发规范 前言 1 vue 框架构建及开发规范 1 1 规范目的 1 2 vue 项目框架搭建 1 2 1 脚手架构建
  • python 绝对值_Python绝对值– abs()

    python 绝对值 Python abs function returns the absolute value of the number It s one of the built in functions in python bui
  • 给定三维空间里的任意三个点来确定一个平面方程Ax+By+Cz+D=0的求解过程及伪代码的实现

    给定三维空间里的任意三个点来确定一个平面方程Ax By Cz D 0的求解过程及伪代码的实现 其思想就是 1 先求解该平面的法向量n 2 由点法式将其中的任意一点代入公式即可以求得平面方程Ax By Cz D 0的系数ABCD 3 伪代码实
  • thingking in java test3.8练习(8)(9)

    题目 展示用十六进制和八进制计数法来操作Long值 用long toBinaryString 来显示结果 Java中十六进制八进制的表示方法同c语言相同 十六进制以0x开头 八进制以0开头 注意这里都是 零 public class tes
  • 数据结构(一)--合并两个数组为有序数组

    写在开头 最近复习数据结构 想来把数据结构中的算法或者代码都用python实现一下 分割线 描述 有两个无序数组 A B 将两个数组合并为从小到大排序的有序数组 如A 3 8 11 5 B 2 6 11 8 9 20 15 应该得到数组为
  • QT 5.13保姆级安装教程

    辨清关系 要想学习一个新的东西 我们必须知其事 达其理 悟其道 然后才能无往而不利也 我们常听到QT Qt Creator 和 Qt SDK 这三者究竟是什么 他们之间的关系又是如何的 在安装QT之前我们先来了解一下他们之间的关系 Qt Q
  • neo4j入门到精通——3、neo4j目录结构和配置文档

    本文主要对neo4j图数据库的目录结构和配置文档做简单介绍 neo4j的目录结构截图如下 其中 bin目录 用于存储Neo4j的可执行程序 conf目录 用于控制Neo4j启动的配置文件 data目录 用于存储核心数据库文件 import目
  • 大数源码解析

    大数的功能实现可以不用做追究 知道用处就好的 感兴趣的可以看源码研究 构造器就不展示了 BigInteger public class BigInteger extends Number implements Comparable
  • RT-Thread记录(十三、I/O 设备模型之PIN设备)

    讲完UART设备之后 我们已经熟悉RT Thread I O 设备模型了 回头看看基本的 PIN 设备 目录 前言 一 PIN 设备模型解析 1 1 初识 GPIO 操作函数 1 2 PIN 设备框架 1 3 PIN 设备驱动框架层 实现的