Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
2024-RSOC
RT-Thread设备驱动学习
发布于 2024-08-02 02:10:49 浏览:291
订阅该版
[TOC] # RT-Thread设备驱动学习 2024/8/1日晚 ## I/O设备模型 RT-Thread 提供了一套简单的 I/O 设备模型框架,如下图所示,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。 ![io-dev.png](https://oss-club.rt-thread.org/uploads/20240802/5722a196503e198c48012a39f6354c8d.png) 应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据(或控制)交互。 I/O 设备管理层实现了对设备驱动程序的封装。应用程序通过图中的"I/O设备管理层"提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。 设备驱动框架层是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。 设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中,使用序列图如下图所示,主要有以下 2 点: - 设备驱动根据设备模型定义,创建出具备硬件访问能力的设备实例,将该设备通过 `rt_device_register()` 接口注册到 I/O 设备管理器中。 - 应用程序通过 `rt_device_find()` 接口查找到设备,然后使用 I/O 设备管理接口来访问硬件。 ![io-call.png](https://oss-club.rt-thread.org/uploads/20240802/4325ead0a144794832cc2b65745a6d92.png) 对于另一些设备,如看门狗等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器进行注册,主要有以下几点: - 看门狗设备驱动程序根据看门狗设备模型定义,创建出具备硬件访问能力的看门狗设备实例,并将该看门狗设备通过 `rt_hw_watchdog_register()` 接口注册到看门狗设备驱动框架中。 - 看门狗设备驱动框架通过 `rt_device_register()` 接口将看门狗设备注册到 I/O 设备管理器中。 - 应用程序通过 I/O 设备管理接口来访问看门狗设备硬件。 看门狗设备使用序列图: ![wtd-uml.png](https://oss-club.rt-thread.org/uploads/20240802/6f2e2da19b42674c72ebaccb24f5a06b.png) RT-Thread 的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性,下图是设备对象的继承和派生关系示意图。 ![io-parent.png](https://oss-club.rt-thread.org/uploads/20240802/2e5a2c697d54e75fd21b6ca2be0f403f.png) ### 设备对象具体定义 如下所示: ```c struct rt_device { struct rt_object parent; /* 内核对象基类 */ enum rt_device_class_type type; /* 设备类型 */ rt_uint16_t flag; /* 设备参数 */ rt_uint16_t open_flag; /* 设备打开标志 */ rt_uint8_t ref_count; /* 设备被引用次数 */ rt_uint8_t device_id; /* 设备 ID,0 - 255 */ /* 数据收发回调函数 */ rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); const struct rt_device_ops *ops; /* 设备操作方法 */ /* 设备的私有数据 */ void *user_data; }; typedef struct rt_device *rt_device_t; ``` ### I/O 设备类型 RT-Thread 支持多种 I/O 设备类型,主要设备类型如下所示: ```c RT_Device_Class_Char /* 字符设备 */ RT_Device_Class_Block /* 块设备 */ RT_Device_Class_NetIf /* 网络接口设备 */ RT_Device_Class_MTD /* 内存设备 */ RT_Device_Class_RTC /* RTC 设备 */ RT_Device_Class_Sound /* 声音设备 */ RT_Device_Class_Graphic /* 图形设备 */ RT_Device_Class_I2CBUS /* I2C 总线设备 */ RT_Device_Class_USBDevice /* USB device 设备 */ RT_Device_Class_USBHost /* USB host 设备 */ RT_Device_Class_SPIBUS /* SPI 总线设备 */ RT_Device_Class_SPIDevice /* SPI 设备 */ RT_Device_Class_SDIO /* SDIO 设备 */ RT_Device_Class_Miscellaneous /* 杂类设备 */ ``` ### 创建和注册 I/O 设备 驱动层负责创建设备实例,并注册到 I/O 设备管理器中,可以通过静态申明的方式创建设备实例,也可以用下面的接口进行动态创建: ```c rt_device_t rt_device_create(int type, int attach_size); ``` | **参数** | **描述** | | ----------- | -------------------------------------- | | type | 设备类型,可取前面小节列出的设备类型值 | | attach_size | 用户数据大小 | | **返回** | —— | | 设备句柄 | 创建成功 | | RT_NULL | 创建失败,动态内存分配失败 | 调用该接口时,系统会从动态堆内存中分配一个设备控制块,大小为 struct rt_device 和 attach_size 的和,设备的类型由参数 type 设定。设备被创建后,需要实现它访问硬件的操作方法。 ```c struct rt_device_ops { /* 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); }; ``` 各个操作方法的描述如下表所示: | **方法名称** | **方法描述** | | ------------ | ------------------------------------------------------------ | | init | 初始化设备。设备初始化完成后,设备控制块的 flag 会被置成已激活状态 (RT_DEVICE_FLAG_ACTIVATED)。如果设备控制块中的 flag 标志已经设置成激活状态,那么再运行初始化接口时会立刻返回,而不会重新进行初始化。 | | open | 打开设备。有些设备并不是系统一启动就已经打开开始运行,或者设备需要进行数据收发,但如果上层应用还未准备好,设备也不应默认已经使能并开始接收数据。所以建议在写底层驱动程序时,在调用 open 接口时才使能设备。 | | close | 关闭设备。在打开设备时,设备控制块会维护一个打开计数,在打开设备时进行 + 1 操作,在关闭设备时进行 - 1 操作,当计数器变为 0 时,才会进行真正的关闭操作。 | | read | 从设备读取数据。参数 pos 是读取数据的偏移量,但是有些设备并不一定需要指定偏移量,例如串口设备,设备驱动应忽略这个参数。而对于块设备来说,pos 以及 size 都是以块设备的数据块大小为单位的。例如块设备的数据块大小是 512,而参数中 pos = 10, size = 2,那么驱动应该返回设备中第 10 个块 (从第 0 个块做为起始),共计 2 个块的数据。这个接口返回的类型是 rt_size_t,即读到的字节数或块数目。正常情况下应该会返回参数中 size 的数值,如果返回零请设置对应的 errno 值。 | | write | 向设备写入数据。参数 pos 是写入数据的偏移量。与读操作类似,对于块设备来说,pos 以及 size 都是以块设备的数据块大小为单位的。这个接口返回的类型是 rt_size_t,即真实写入数据的字节数或块数目。正常情况下应该会返回参数中 size 的数值,如果返回零请设置对应的 errno 值。 | | control | 根据 cmd 命令控制设备。命令往往是由底层各类设备驱动自定义实现。例如参数 RT_DEVICE_CTRL_BLK_GETGEOME,意思是获取块设备的大小信息。 | 当一个动态创建的设备不再需要使用时可以通过如下函数来销毁: ```c void rt_device_destroy(rt_device_t device); ``` | **参数** | **描述** | | -------- | -------- | | device | 设备句柄 | | **返回** | 无 | 设备被创建后,需要注册到 I/O 设备管理器中,应用程序才能够访问,注册设备的函数如下所示: ```c rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags); ``` | **参数** | **描述** | | --------- | ------------------------------------------------------------ | | dev | 设备句柄 | | name | 设备名称,设备名称的最大长度由 rtconfig.h 中定义的宏 RT_NAME_MAX 指定,多余部分会被自动截掉 | | flags | 设备模式标志 | | **返回** | —— | | RT_EOK | 注册成功 | | -RT_ERROR | 注册失败,dev 为空或者 name 已经存在 | **注:应当避免重复注册已经注册的设备,以及注册相同名字的设备。** flags 参数支持下列参数 (可以采用或的方式支持多种参数): ```c #define RT_DEVICE_FLAG_RDONLY 0x001 /* 只读 */ #define RT_DEVICE_FLAG_WRONLY 0x002 /* 只写 */ #define RT_DEVICE_FLAG_RDWR 0x003 /* 读写 */ #define RT_DEVICE_FLAG_REMOVABLE 0x004 /* 可移除 */ #define RT_DEVICE_FLAG_STANDALONE 0x008 /* 独立 */ #define RT_DEVICE_FLAG_SUSPENDED 0x020 /* 挂起 */ #define RT_DEVICE_FLAG_STREAM 0x040 /* 流模式 */ #define RT_DEVICE_FLAG_INT_RX 0x100 /* 中断接收 */ #define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA 接收 */ #define RT_DEVICE_FLAG_INT_TX 0x400 /* 中断发送 */ #define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA 发送 */ ``` 设备流模式 RT_DEVICE_FLAG_STREAM 参数用于向串口终端输出字符串:当输出的字符是 `“\n”` 时,自动在前面补一个 `“\r”` 做分行。 注册成功的设备可以在 FinSH 命令行使用 `list_device` 命令查看系统中所有的设备信息,包括设备名称、设备类型和设备被打开次数: ```c msh />list_device device type ref count -------- -------------------- ---------- e0 Network Interface 0 sd0 Block Device 1 rtc RTC 0 uart1 Character Device 0 uart0 Character Device 2 msh /> ``` 当设备注销后的,设备将从设备管理器中移除,也就不能再通过设备查找搜索到该设备。注销设备不会释放设备控制块占用的内存。注销设备的函数如下所示: ```c rt_err_t rt_device_unregister(rt_device_t dev); ``` | **参数** | **描述** | | -------- | -------- | | dev | 设备句柄 | | **返回** | —— | | RT_EOK | 成功 | 下面代码为看门狗设备的注册示例,调用 `rt_hw_watchdog_register()` 接口后,设备通过 `rt_device_register()` 接口被注册到 I/O 设备管理器中。 ```c const static struct rt_device_ops wdt_ops = { rt_watchdog_init, rt_watchdog_open, rt_watchdog_close, RT_NULL, RT_NULL, rt_watchdog_control, }; rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd, const char *name, rt_uint32_t flag, void *data) { struct rt_device *device; RT_ASSERT(wtd != RT_NULL); device = &(wtd->parent); device->type = RT_Device_Class_Miscellaneous; device->rx_indicate = RT_NULL; device->tx_complete = RT_NULL; device->ops = &wdt_ops; device->user_data = data; /* register a character device */ return rt_device_register(device, name, flag); } ``` ### 访问 I/O 设备 应用程序通过 I/O 设备管理接口来访问硬件设备,当设备驱动实现后,应用程序就可以访问该硬件。I/O 设备管理接口与 I/O 设备的操作方法的映射关系下图所示: ![io-fun-call.png](https://oss-club.rt-thread.org/uploads/20240802/28d0a45249940f7c3bd39f06b21bfb0e.png) ### 查找设备 应用程序根据设备名称获取设备句柄,进而可以操作设备。查找设备函数如下所示: ```c rt_device_t rt_device_find(const char* name); ``` | **参数** | **描述** | | -------- | ---------------------------------- | | name | 设备名称 | | **返回** | —— | | 设备句柄 | 查找到对应设备将返回相应的设备句柄 | | RT_NULL | 没有找到相应的设备对象 | ### 初始化设备 获得设备句柄后,应用程序可使用如下函数对设备进行初始化操作: ```c rt_err_t rt_device_init(rt_device_t dev); ``` | **参数** | **描述** | | -------- | -------------- | | dev | 设备句柄 | | **返回** | —— | | RT_EOK | 设备初始化成功 | | 错误码 | 设备初始化失败 | **注:当一个设备已经初始化成功后,调用这个接口将不再重复做初始化 0。** ### 打开和关闭设备 通过设备句柄,应用程序可以打开和关闭设备,打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。通过如下函数打开设备: ```c rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); ``` | **参数** | **描述** | | ---------- | ------------------------------------------------------------ | | dev | 设备句柄 | | oflags | 设备打开模式标志 | | **返回** | —— | | RT_EOK | 设备打开成功 | | -RT_EBUSY | 如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开 | | 其他错误码 | 设备打开失败 | oflags 支持以下的参数: ```c #define RT_DEVICE_OFLAG_CLOSE 0x000 /* 设备已经关闭(内部使用)*/ #define RT_DEVICE_OFLAG_RDONLY 0x001 /* 以只读方式打开设备 */ #define RT_DEVICE_OFLAG_WRONLY 0x002 /* 以只写方式打开设备 */ #define RT_DEVICE_OFLAG_RDWR 0x003 /* 以读写方式打开设备 */ #define RT_DEVICE_OFLAG_OPEN 0x008 /* 设备已经打开(内部使用)*/ #define RT_DEVICE_FLAG_STREAM 0x040 /* 设备以流模式打开 */ #define RT_DEVICE_FLAG_INT_RX 0x100 /* 设备以中断接收模式打开 */ #define RT_DEVICE_FLAG_DMA_RX 0x200 /* 设备以 DMA 接收模式打开 */ #define RT_DEVICE_FLAG_INT_TX 0x400 /* 设备以中断发送模式打开 */ #define RT_DEVICE_FLAG_DMA_TX 0x800 /* 设备以 DMA 发送模式打开 */ ``` 注:如果上层应用程序需要设置设备的接收回调函数,则必须以 RT_DEVICE_FLAG_INT_RX 或者 RT_DEVICE_FLAG_DMA_RX 的方式打开设备,否则不会回调函数。 应用程序打开设备完成读写等操作后,如果不需要再对设备进行操作则可以关闭设备,通过如下函数完成: ```c rt_err_t rt_device_close(rt_device_t dev); ``` | **参数** | **描述** | | ---------- | ---------------------------------- | | dev | 设备句柄 | | **返回** | —— | | RT_EOK | 关闭设备成功 | | -RT_ERROR | 设备已经完全关闭,不能重复关闭设备 | | 其他错误码 | 关闭设备失败 | 注:关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。 ### 控制设备 通过命令控制字,应用程序也可以对设备进行控制,通过如下函数完成: ```c rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); ``` | **参数** | **描述** | | ---------- | ------------------------------------------ | | dev | 设备句柄 | | cmd | 命令控制字,这个参数通常与设备驱动程序相关 | | arg | 控制的参数 | | **返回** | —— | | RT_EOK | 函数执行成功 | | -RT_ENOSYS | 执行失败,dev 为空 | | 其他错误码 | 执行失败 | 参数 cmd 的通用设备命令可取如下宏定义: ```c #define RT_DEVICE_CTRL_RESUME 0x01 /* 恢复设备 */ #define RT_DEVICE_CTRL_SUSPEND 0x02 /* 挂起设备 */ #define RT_DEVICE_CTRL_CONFIG 0x03 /* 配置设备 */ #define RT_DEVICE_CTRL_SET_INT 0x10 /* 设置中断 */ #define RT_DEVICE_CTRL_CLR_INT 0x11 /* 清中断 */ #define RT_DEVICE_CTRL_GET_INT 0x12 /* 获取中断状态 */ ``` ### 读写设备 应用程序从设备中读取数据可以通过如下函数完成: ```c rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size); ``` | **参数** | **描述** | | ------------------ | ------------------------------------------------------------ | | dev | 设备句柄 | | pos | 读取数据偏移量 | | buffer | 内存缓冲区指针,读取的数据将会被保存在缓冲区中 | | size | 读取数据的大小 | | **返回** | —— | | 读到数据的实际大小 | 如果是字符设备,返回大小以字节为单位,如果是块设备,返回的大小以块为单位 | | 0 | 需要读取当前线程的 errno 来判断错误状态 | 调用这个函数,会从 dev 设备中读取数据,并存放在 buffer 缓冲区中,这个缓冲区的最大长度是 size,pos 根据不同的设备类别有不同的意义。 向设备中写入数据,可以通过如下函数完成: ```c rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size); ``` | **参数** | **描述** | | ------------------ | ------------------------------------------------------------ | | dev | 设备句柄 | | pos | 写入数据偏移量 | | buffer | 内存缓冲区指针,放置要写入的数据 | | size | 写入数据的大小 | | **返回** | —— | | 写入数据的实际大小 | 如果是字符设备,返回大小以字节为单位;如果是块设备,返回的大小以块为单位 | | 0 | 需要读取当前线程的 errno 来判断错误状态 | 调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的最大长度是 size,pos 根据不同的设备类别存在不同的意义。 ### 数据收发回调 当硬件设备收到数据时,可以通过如下函数回调另一个函数来设置数据接收指示,通知上层应用线程有数据到达: ```c rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)); ``` | **参数** | **描述** | | -------- | ------------ | | dev | 设备句柄 | | rx_ind | 回调函数指针 | | **返回** | —— | | RT_EOK | 设置成功 | 该函数的回调函数由调用者提供。当硬件设备接收到数据时,会回调这个函数并把收到的数据长度放在 size 参数中传递给上层应用。上层应用线程应在收到指示后,立刻从设备中读取数据。 在应用程序调用 `rt_device_write()` 写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。可以通过如下函数设置设备发送完成指示,函数参数及返回值见: ```c rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer)); ``` | **参数** | **描述** | | -------- | ------------ | | dev | 设备句柄 | | tx_done | 回调函数指针 | | **返回** | —— | | RT_EOK | 设置成功 | 调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。 ### I/O设备注册实例 ```C #include
#include
static int rt_device_test(void) { rt_device_t test_device; rt_err_t result = RT_EOK; test_device = rt_device_create(RT_Device_Class_Char, 0); if (test_device == RT_NULL) { rt_kprintf("创建设备失败!!\n"); return -RT_ERROR; } result = rt_device_register(test_device, "test_device", RT_DEVICE_FLAG_RDWR); if (result == RT_EOK) { rt_kprintf("设备登记成功\n"); } else { rt_kprintf("设备登记失败\n"); return -RT_ERROR; } return RT_EOK; } MSH_CMD_EXPORT(rt_device_test, "rt_device_test"); ``` ![image-20240801224751741.png](https://oss-club.rt-thread.org/uploads/20240802/7d519d00076ca98a1586d3da4b325ca6.png.webp) ## PIN设备 ### 引脚简介 芯片上的引脚一般分为 4 类:电源、时钟、控制与 I/O,I/O 口在使用模式上又分为 General Purpose Input Output(通用输入 / 输出),简称 GPIO,与功能复用 I/O(如 SPI/I2C/UART 等)。 大多数 MCU 的引脚都不止一个功能。不同引脚内部结构不一样,拥有的功能也不一样。可以通过不同的配置,切换引脚的实际功能。通用 I/O 口主要特性如下: - 可编程控制中断:中断触发模式可配置,一般有下图所示 5 种中断触发模式: ![pin2.png](https://oss-club.rt-thread.org/uploads/20240802/86543118e7d95fefe27a58b66844478e.png) - 输入输出模式可控制。 - 输出模式一般包括:推挽、开漏、上拉、下拉。引脚为输出模式时,可以通过配置引脚输出的电平状态为高电平或低电平来控制连接的外围设备。 - 输入模式一般包括:浮空、上拉、下拉、模拟。引脚为输入模式时,可以读取引脚的电平状态,即高电平或低电平。 ### 访问 PIN 设备 应用程序通过 RT-Thread 提供的 PIN 设备管理接口来访问 GPIO,相关接口如下所示: | **函数** | **描述** | | ------------------- | -------------------- | | rt_pin_get() | 获取引脚编号 | | rt_pin_mode() | 设置引脚模式 | | rt_pin_write() | 设置引脚电平 | | rt_pin_read() | 读取引脚电平 | | rt_pin_attach_irq() | 绑定引脚中断回调函数 | | rt_pin_irq_enable() | 使能引脚中断 | | rt_pin_detach_irq() | 脱离引脚中断回调函数 | ### 获取引脚编号 RT-Thread 提供的引脚编号需要和芯片的引脚号区分开来,它们并不是同一个概念,引脚编号由 PIN 设备驱动程序定义,和具体的芯片相关。有3种方式可以获取引脚编号: API 接口获取、使用宏定义或者查看PIN 驱动文件。 #### 使用 API 使用 rt_pin_get() 获取引脚编号,如下获取 PF9 的引脚编号: ```c pin_number = rt_pin_get("PF.9"); ``` #### 使用宏定义 如果使用 `rt-thread/bsp/stm32` 目录下的 BSP 则可以使用下面的宏获取引脚编号: ```c GET_PIN(port, pin) ``` 获取引脚号为 PF9 的 LED0 对应的引脚编号的示例代码如下所示: ```c #define LED0_PIN GET_PIN(F, 9) ``` #### 使用引脚公式 在RT_Thread中存在引脚公式 ``` PNs=(N-A)*16+s* ``` 该公式含义为:某引脚PNs对应的引脚编号为(N-A)*16+s 例如:PA9=(A-A)+9=9 #### 查看驱动文件 如果使用其他 BSP 则需要查看 PIN 驱动代码 drv_gpio.c 文件确认引脚编号。此文件里有一个数组存放了每个 PIN 脚对应的编号信息,如下所示: ```c static const rt_uint16_t pins[] = { __STM32_PIN_DEFAULT, __STM32_PIN_DEFAULT, __STM32_PIN(2, A, 15), __STM32_PIN(3, B, 5), __STM32_PIN(4, B, 8), __STM32_PIN_DEFAULT, __STM32_PIN_DEFAULT, __STM32_PIN_DEFAULT, __STM32_PIN(8, A, 14), __STM32_PIN(9, B, 6), ... ... }复制错误复制成功 ``` 以`__STM32_PIN(2, A, 15)`为例,2 为 RT-Thread 使用的引脚编号,A 为端口号,15 为引脚号,所以 PA15 对应的引脚编号为 2。 ### 设置引脚模式 引脚在使用前需要先设置好输入或者输出模式,通过如下函数完成: ```c void rt_pin_mode(rt_base_t pin, rt_base_t mode); ``` | **参数** | **描述** | | -------- | ------------ | | pin | 引脚编号 | | mode | 引脚工作模式 | 目前 RT-Thread 支持的引脚工作模式可取如所示的 5 种宏定义值之一,每种模式对应的芯片实际支持的模式需参考 PIN 设备驱动程序的具体实现: ```c #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 /* 开漏输出 */ ``` 使用示例如下所示: ```c #define BEEP_PIN_NUM 35 /* PB0 */ /* 蜂鸣器引脚为输出模式 */ rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); ``` ### 设置引脚电平 ```c void rt_pin_write(rt_base_t pin, rt_base_t value); ``` | **参数** | **描述** | | -------- | ------------------------------------------------------------ | | pin | 引脚编号 | | value | 电平逻辑值,可取 2 种宏定义值之一:PIN_LOW 低电平,PIN_HIGH 高电平 | 使用示例如下所示: ```c #define BEEP_PIN_NUM 35 /* PB0 */ /* 蜂鸣器引脚为输出模式 */ rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); /* 设置低电平 */ rt_pin_write(BEEP_PIN_NUM, PIN_LOW); ``` ### 读取引脚电平 读取引脚电平的函数如下所示: ```c int rt_pin_read(rt_base_t pin); ``` | **参数** | **描述** | | -------- | -------- | | pin | 引脚编号 | | **返回** | —— | | PIN_LOW | 低电平 | | PIN_HIGH | 高电平 | 使用示例如下所示: ```c #define BEEP_PIN_NUM 35 /* PB0 */ int status; /* 蜂鸣器引脚为输出模式 */ rt_pin_mode(BEEP_PIN_NUM, PIN_MODE_OUTPUT); /* 设置低电平 */ rt_pin_write(BEEP_PIN_NUM, PIN_LOW); status = rt_pin_read(BEEP_PIN_NUM); ``` ### 绑定引脚中断回调函数 若要使用到引脚的中断功能,可以使用如下函数将某个引脚配置为某种中断触发模式并绑定一个中断回调函数到对应引脚,当引脚中断发生时,就会执行回调函数: ```c rt_err_t rt_pin_attach_irq(rt_int32_t pin, rt_uint32_t mode, void (*hdr)(void *args), void *args); ``` | **参数** | **描述** | | -------- | ------------------------------------------ | | pin | 引脚编号 | | mode | 中断触发模式 | | hdr | 中断回调函数,用户需要自行定义这个函数 | | args | 中断回调函数的参数,不需要时设置为 RT_NULL | | **返回** | —— | | RT_EOK | 绑定成功 | | 错误码 | 绑定失败 | 中断触发模式 mode 可取如下 5 种宏定义值之一: ```c #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 /* 低电平触发 */ ``` 使用示例如下所示: ```c #define KEY0_PIN_NUM 55 /* PD8 */ /* 中断回调函数 */ void beep_on(void *args) { rt_kprintf("turn on beep!\n"); rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); } static void pin_beep_sample(void) { /* 按键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); } ``` ### 使能引脚中断 绑定好引脚中断回调函数后使用下面的函数使能引脚中断: ```c rt_err_t rt_pin_irq_enable(rt_base_t pin, rt_uint32_t enabled); ``` | **参数** | **描述** | | -------- | ------------------------------------------------------------ | | pin | 引脚编号 | | enabled | 状态,可取 2 种值之一:PIN_IRQ_ENABLE(开启),PIN_IRQ_DISABLE(关闭) | | **返回** | —— | | RT_EOK | 使能成功 | | 错误码 | 使能失败 | 使用示例如下所示: ```c #define KEY0_PIN_NUM 55 /* PD8 */ /* 中断回调函数 */ void beep_on(void *args) { rt_kprintf("turn on beep!\n"); rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); } static void pin_beep_sample(void) { /* 按键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); } ``` ### 脱离引脚中断回调函数 可以使用如下函数脱离引脚中断回调函数: ```c rt_err_t rt_pin_detach_irq(rt_int32_t pin); ``` | **参数** | **描述** | | -------- | -------- | | pin | 引脚编号 | | **返回** | —— | | RT_EOK | 脱离成功 | | 错误码 | 脱离失败 | 引脚脱离了中断回调函数以后,中断并没有关闭,还可以调用绑定中断回调函数再次绑定其他回调函数。 ```c #define KEY0_PIN_NUM 55 /* PD8 */ /* 中断回调函数 */ void beep_on(void *args) { rt_kprintf("turn on beep!\n"); rt_pin_write(BEEP_PIN_NUM, PIN_HIGH); } static void pin_beep_sample(void) { /* 按键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); /* 脱离中断回调函数 */ rt_pin_detach_irq(KEY0_PIN_NUM); } ``` ### 具体实例利用引脚驱动操作按键打印信息 ```c #include
#include
#include
#define LOG_TAG "pin.irq" #define LOG_LVL LOG_LVL_DBG #include
#define KEY_UP GET_PIN(C,5) #define KEY_DOWN GET_PIN(C,1) #define KEY_LEFT GET_PIN(C,0) #define KEY_RIGHT GET_PIN(C,4) void key_up_callback(void* args) { int value = rt_pin_read(KEY_UP); LOG_I("KEY UP! %d", value); } void key_down_callback(void* args) { int value = rt_pin_read(KEY_DOWN); LOG_I("KEY down! %d", value); } void key_left_callback(void* args) { int value = rt_pin_read(KEY_LEFT); LOG_I("KEY left! %d", value); } void key_right_callback(void* args) { int value = rt_pin_read(KEY_RIGHT); LOG_I("KEY right! %d", value); } static int rt_pin_irq_example(void) { rt_pin_mode(KEY_UP, PIN_MODE_INPUT_PULLUP); rt_pin_mode(KEY_DOWN, PIN_MODE_INPUT_PULLUP); rt_pin_mode(KEY_LEFT, PIN_MODE_INPUT_PULLUP); rt_pin_mode(KEY_RIGHT, PIN_MODE_INPUT_PULLUP); rt_pin_attach_irq(KEY_UP, PIN_IRQ_MODE_FALLING, key_up_callback, RT_NULL); rt_pin_attach_irq(KEY_DOWN, PIN_IRQ_MODE_FALLING, key_down_callback, RT_NULL); rt_pin_attach_irq(KEY_LEFT, PIN_IRQ_MODE_FALLING, key_left_callback, RT_NULL); rt_pin_attach_irq(KEY_RIGHT, PIN_IRQ_MODE_FALLING, key_right_callback, RT_NULL); rt_pin_irq_enable(KEY_UP, PIN_IRQ_ENABLE); rt_pin_irq_enable(KEY_DOWN, PIN_IRQ_ENABLE); rt_pin_irq_enable(KEY_LEFT, PIN_IRQ_ENABLE); rt_pin_irq_enable(KEY_RIGHT, PIN_IRQ_ENABLE); return RT_EOK; } MSH_CMD_EXPORT(rt_pin_irq_example, test example); ``` ![image-20240802005451106.png](https://oss-club.rt-thread.org/uploads/20240802/96f44bb05a8929408f8edf73a067be66.png)
0
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
dianfu233
这家伙很懒,什么也没写!
文章
3
回答
0
被采纳
0
关注TA
发私信
相关文章
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
UART
ota在线升级
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
13
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
7
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
3
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部