RT-Thread记录(十、全面认识 RT-Thread I/O 设备模型)

171 阅读15分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第12篇文章,点击查看活动详情

学完 RT-Thread 内核,从本文开始熟悉了解 RT-Thread I/O 设备管理相关知识。

前言

我们已经把 RT-Thread 内核学习完成,也已经使用 RT-Thread 做了一个实例应用。

但是作为一个操作系统, RT-Thread 除了内核部分还有自己的设备框架,类似于 Linux 操作系统设备的管理方式的 I/O 设备模型。

从本文开始,我们要开始学习了解 RT-Thread 的 I/O 设备模型。

说明,概念性质的说明主要还是使用引用方式,毕竟有权威的官方在,对于一些细节的理解,我会阐述自己的看法,同时会设计一些实例加深我们对基本概念的理解。

我想做到的是,仅此一片文章,让所有人都能明白RT-Thread 的I/O 设备模型

一、RT-Thread 的 I/O设备管理

我们先从基本概念说起,了解RT-Thread 对 I/O 设备的管理方式,以及 I/O 设备模型框架的用途:

1.1 什么是 I/O 设备模型

有些小伙伴在刚接触到这个概念的时候还不太明白, I/O 设备模型,IO口? IO口的模型?

注意这里的 I/O 指的是 Input/Output。I/O 设备,就是指的输入 / 输出设备。

所谓 I/O 设备模型,指的是 RT-Thread 把所有的 输入 / 输出设备当做一类对象,然后通过自己的一套体系对这类对象进行管理,这类 I/O 设备对象就可以认为是 I/O 设备模型。

RT-Thread 提供了一套模型框架用来对所有的输入/输出设备进行管理的,名叫 I/O 设备模型框架 ,其 位于硬件层和应用程序之间,包括IO设备管理层、设备驱动框架层和设备驱动层,向上层层抽象,目标是针对各种不同的I/O设备提供给应用程序相同的接口,如下图:

图片.png

1.2 I/O 设备模型框架解析

根据上图,结合工程代码,我们对每个层分别说:

1.2.1 应用程序

我们写的应用程序,调用 I/O 设备管理层给的接口进行底层硬件操作;

对应 main.c :

图片.png

1.2.2 I/O 设备管理层

I/O 设备管理层实现了对设备驱动程序的封装。 应用程序通过 I/O 设备管理接口获得正确的设备驱动,然后通过这个设备驱动与底层 I/O 硬件设备进行数据交互。 设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。

对应 device.c

图片.png

1.2.3 设备驱动框架层

设备驱动框架层对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。

对应的比如 serial.c 等

图片.png

上面的 I/O 设备管理层 和 设备驱动框架层 是属于RT-Thread 系统的范畴,官方已写好,所以在项目中的位置存放于 rt-thread 文件夹下面。

1.2.4 设备驱动层

设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,就类似于使用裸机编写程序时候的底层驱动。 裸机的驱动是直接被应用层调用,这里的驱动是提供给 设备驱动框架层调用 或者 直接给 I/O 设备管理层调用的。

对于一些常用的芯片或者与官方有合作的芯片,官方也会提供了写好的驱动,比如STM32,下图就是官方已经写好的基于STM32 的设备驱动层的代码。

对应比如 drv_gpio.c 、drv_usart.c 这些 :

图片.png

设备驱动层的编写是需要基于芯片或外设的手册、SDK,是需要额外实现的。 但是有些常用芯片和外设官方已经帮我们写好了,比如基于 STM32 的 HAL 库,RT-Thread官方已经实现了基于 STM32 的设备驱动层 。

1.2.5 硬件层

比如Flash芯片,SD卡,stm32芯片等外设和MCU设备。

1.3 I/O 设备操作逻辑说明

简单介绍一下 I/O 设备操作逻辑,这部分应用官方的说明。

对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中,过程如下:

在我们本文后面的新建设备模型实例中就举了个简单设备的例子。

图片.png

对于复杂点的设备,需要经过设备驱动框架层:

图片.png

图片.png

为了更好的理解上面的流程,可以结合工程源码理解,多看源码,比如下图所示:

图片.png

1.4 I/O 设备模型框架有什么用?

有很多初学者会问, I/O 设备模型意义在哪里? 在裸机使用中,比如操作一个IO口,直接调用驱动函数,不是更简单,更直接,使用了设个模型,反而变得复杂了?

在某些时候,如果你只使用一种芯片一种方案,或许某种意义上说, I/O 设备模型确实可能会比直接调用驱动函数复杂。 也可以说,你使用的方案单一,项目简单,没有必要使用 I/O 设备模型,甚至可能连 RTOS 都不一定需要。

总之就是,只用一种芯片方案简单项目或者内存空间实在有限,完全是可以不用这个框架,直接用 RT-Thread Nano 完成功能,和我们前面讲的项目实例一样完成,是没有问题的!

但是作为一个工程师,不能局限在一种方案上面,尤其当今芯片市场变幻莫测,指不定哪天需要换芯片,换方案呢? 而且 RT-Thread 作为一个面向对象思想设计的操作系统,必须得全面考虑,需要降低了代码的耦合性、复杂性,提高了系统的可靠性,能够使得系统运行与不同的芯片设备上。

如果没有 I/O 设备模型,那么每次换方案,从底层到应用层所有的代码基本上都得重写,对于简单的项目无所谓,对于复杂一点的项目,那可是需要花费大量功夫。

使用了 RT-Thread 的 I/O 设备模型,不管你使用哪种MCU,应用层对设备操作的函数一模一样,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现。

如果学过 Linux 的朋友肯定知道,RT-Thread 的 I/O 设备模型 思想 是和 Linux 类似的,作为嵌入式工程师,如果你懂 Linux,那么就应该知道 I/O 设备模型框架 的优点。如果你不懂 Linux,那么学会了 RT-Thread 操作系统的 I/O 设备模型,对于以后深入学习 Linux 操作系统,也是有帮助的 。 这话没毛病! 感觉怎么说怎么有道理,人往高处走嘛!= =!

所有最终结果就是,反正就是好,人往高处走,学会了没坏处 = =!

二、I/O 设备模型操作 API

上文我们说明了RT-Thread I/O 设备模型的基本概念先关内容,也了解了 I/O 设备模型框架以及操作逻辑,那么我们用户该如何来实现这一流程呢?

所以现在我们就来学习一下I/O设备模型操作的相关API函数,包括创建、注册、访问等 。。。

2.1 I/O 设备控制块

我们不止一次的说明了 RT-Thread 的面向对象的思想,在RT-Thread中,设备也是一种内核对象,那么他和以前说的线程,IPC机制,定时器等对象一样,有自己的对象控制块。

在以前博文:RT-Thread记录(六、IPC机制之信号量、互斥量和事件集)这里再次额外说明一下,因为只要理解了这种思想,对于学会他们的使用就更加简单了,在IPC机制的时候我们使用图片说明过:

图片.png

图片.png

其实我们可以看一下设备对象的控制块:

图片.png

上源码方便以后复制:

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

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

2.1.1 设备类型 type

设备对象的控制块中对于设备类型使用了一个rt_device_class_type枚举的方式,其可能的设备类型如下(简单的没有注释,还有部分吗不太清楚的,以后更新):

图片.png

设备类型 需要在创建的时候选择好,写驱动的时候应该知道自己写的是什么类型的设备。

2.1.2 设备注册 flag

设备对象的控制块中有一个 flag 参数,设备模式标志 ,表示设备是属于什么状态的设备,可读、可写、收发等,其可以有的参数如下:

图片.png

注意,该标志可以采用或的方式支持多种参数。

设备注册 flag 需要在创建的时候选择好,写驱动的时候应该知道自己写的设备是什么状态,比如只读。只写,中断接收之类的。

2.1.3 设备访问 open_flag

设备对象的控制块中有一个 open_flag 参数,设备打开模式标志 ,表示对设备进行什么操作,其可以有的参数如下:

图片.png

设备访问的时候,需要使用这个 open_flag 来判断需要对设备进行什么操作,是读?还是写? 还是发送等。。。

<3 介绍了 I/O 设备控制块,让我们清楚我们要操作的对象是什么,我们在 设备驱动层 要写的设备驱动以及上层应用对 I/O 设备的访问, 就是基于这个控制块来进行的。

2.2 创建 I/O 设备相关

我们按照 先创建设备,再访问设备 的顺序来介绍对于的 API 函数。

2.2.1 创建设备

老规矩用源码,解释看注释(使用起来也方便复制 ~ ~!):

/*
参数的含义:
1、type 		设备类型,上面 2.1.1 小结说明的设备类型
2、attach_size  用户数据大小
返回值:
创建成功,返回设备的控制块指针
创建失败,返回RT_BULL 
*/

rt_device_t rt_device_create(int type, int attach_size)

/**
 * device (I/O) class type  设备类型
 */
enum rt_device_class_type
{
    RT_Device_Class_Char = 0,                           /**< character device */
    RT_Device_Class_Block,                              /**< block device */
    RT_Device_Class_NetIf,                              /**< net interface */
    RT_Device_Class_MTD,                                /**< memory device */
    RT_Device_Class_CAN,                                /**< CAN device */
    RT_Device_Class_RTC,                                /**< RTC device */
    RT_Device_Class_Sound,                              /**< Sound device */
    RT_Device_Class_Graphic,                            /**< Graphic device */
    RT_Device_Class_I2CBUS,                             /**< I2C bus device */
    RT_Device_Class_USBDevice,                          /**< USB slave device */
    RT_Device_Class_USBHost,                            /**< USB host bus */
    RT_Device_Class_SPIBUS,                             /**< SPI bus device */
    RT_Device_Class_SPIDevice,                          /**< SPI device */
    RT_Device_Class_SDIO,                               /**< SDIO bus device */
    RT_Device_Class_PM,                                 /**< PM pseudo device */
    RT_Device_Class_Pipe,                               /**< Pipe device */
    RT_Device_Class_Portal,                             /**< Portal device */
    RT_Device_Class_Timer,                              /**< Timer device */
    RT_Device_Class_Miscellaneous,                      /**< Miscellaneous device */
    RT_Device_Class_Sensor,                             /**< Sensor device */
    RT_Device_Class_Touch,                              /**< Touch device */
    RT_Device_Class_PHY,                                /**< PHY device */
    RT_Device_Class_Unknown                             /**< unknown device */
};

设备被创建后,需要实现它访问硬件的操作方法,要按照下面的函数指针实现这些对于设备的操作函数:

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

这里引用官方的说明:

图片.png

2.2.2 销毁设备

此函数不一定需要使用,但是有创建就应该有销毁:

/*
参数的含义:
dev 	设备句柄
*/
void rt_device_destroy(rt_device_t dev)

2.2.3 设备注册

设备被创建后,需要注册到 I/O 设备管理器中,应用程序才能够访问:

/*
参数的含义:
dev 	设备句柄
name 	设备名称,
设备名称的最大长度由 rtconfig.h 中定义的宏 RT_NAME_MAX 指定,多余部分会被自动截掉
flags 	设备模式标志,就是上面介绍的 2.1.2 设备注册 flag
返回值:
RT_EOK 	注册成功
-RT_ERROR 	注册失败,dev 为空或者 name 已经存在
*/
rt_err_t rt_device_register(rt_device_t dev,
                            const char *name,
                            rt_uint16_t flags)


/*设备注册 flag*/
#define RT_DEVICE_FLAG_DEACTIVATE       0x000           /**< device is not not initialized */

#define RT_DEVICE_FLAG_RDONLY           0x001           /**< read only */
#define RT_DEVICE_FLAG_WRONLY           0x002           /**< write only */
#define RT_DEVICE_FLAG_RDWR             0x003           /**< read and write */

#define RT_DEVICE_FLAG_REMOVABLE        0x004           /**< removable device */
#define RT_DEVICE_FLAG_STANDALONE       0x008           /**< standalone device */
#define RT_DEVICE_FLAG_ACTIVATED        0x010           /**< device is activated */
#define RT_DEVICE_FLAG_SUSPENDED        0x020           /**< device is suspended */
#define RT_DEVICE_FLAG_STREAM           0x040           /**< stream mode */

#define RT_DEVICE_FLAG_INT_RX           0x100           /**< INT mode on Rx */
#define RT_DEVICE_FLAG_DMA_RX           0x200           /**< DMA mode on Rx */
#define RT_DEVICE_FLAG_INT_TX           0x400           /**< INT mode on Tx */
#define RT_DEVICE_FLAG_DMA_TX           0x800           /**< DMA mode on Tx */
上面需要额外说明的一点,设备流模式 `RT_DEVICE_FLAG_STREAM` 参数用于向串口终端输出字符串:
当输出的字符是 “\n” 时,自动在前面补一个 “\r” 做分行。

2.2.4 设备注销

创建对销毁,注册对注销:

/**
参数 	描述
dev 	设备句柄
返回 	——
RT_EOK 	成功
 */
rt_err_t rt_device_unregister(rt_device_t dev)

当设备注销后的,设备将从设备管理器中移除,也就不能再通过设备查找搜索到该设备。注销设备不会释放设备控制块占用的内存。 注销只是把这个 设备对象结构体 从管理链表中去掉,并不会释放这个对象结构体的空间,要释放空间需要调用销毁设备函数。

<3 创建 I/O 设备相关的函数,和 I/O 设备模型框架中 设备驱动层 有关,在我们底层写驱动的时候需要使用。

2.3 访问 I/O 设备相关

上面我们说的创建 I/O 设备相关是和 设备驱动层 有关的代码,本小结说的访问 I/O 设备就是和 应用程序有关的代码,就是告诉我们应用程序怎么去操作我们上面创建的设备。

I/O 设备管理接口与 I/O 设备的操作方法的映射关系下图:

图片.png

2.3.1 查找设备

注册过的设备才能被查找到,名字对应注册时候使用的名字:

/**
参数 	描述
name 	设备名称
返回 	——
设备句柄 	查找到对应设备将返回相应的设备句柄
RT_NULL 	没有找到相应的设备对象
 */
rt_device_t rt_device_find(const char *name)

2.3.2 初始化设备

对应底层rt_err_t (*init) (rt_device_t dev); 的实现函数:

/**
参数 	描述
dev 	设备句柄
返回 	——
RT_EOK 	设备初始化成功
错误码 	设备初始化失败
 */
rt_err_t rt_device_init(rt_device_t dev)

当一个设备已经初始化成功后,调用这个接口将不再重复做初始化 0。

2.3.3 打开和关闭设备

打开设备:

对应底层rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); 的实现函数:

/*
参数 	描述
dev 	设备句柄
oflags 	设备打开模式标志,上面 2.1.3 小结说明的设备访问 open_flag
返回 	——
RT_EOK 	设备打开成功
-RT_EBUSY 	如果设备注册时指定的参数中包括 RT_DEVICE_FLAG_STANDALONE 参数,此设备将不允许重复打开
其他错误码 	设备打开失败
*/
rt_err_t  rt_device_open (rt_device_t dev, rt_uint16_t oflag);

#define RT_DEVICE_FLAG_INT_RX           0x100           /**< INT mode on Rx */
#define RT_DEVICE_FLAG_DMA_RX           0x200           /**< DMA mode on Rx */
#define RT_DEVICE_FLAG_INT_TX           0x400           /**< INT mode on Tx */
#define RT_DEVICE_FLAG_DMA_TX           0x800           /**< DMA mode on Tx */

#define RT_DEVICE_OFLAG_CLOSE           0x000           /**< 设备已经关闭(内部使用) */
#define RT_DEVICE_OFLAG_RDONLY          0x001           /**< read only access */
#define RT_DEVICE_OFLAG_WRONLY          0x002           /**< write only access */
#define RT_DEVICE_OFLAG_RDWR            0x003           /**< read and write */
#define RT_DEVICE_OFLAG_OPEN            0x008           /**< device is opened */
#define RT_DEVICE_OFLAG_MASK            0xf0f           /**< mask of open flag */

注:如果上层应用程序需要设置设备的接收回调函数,则必须以 RT_DEVICE_FLAG_INT_RX 或者 RT_DEVICE_FLAG_DMA_RX 的方式打开设备,否则不会回调函数。

关闭设备:

对应底层rt_err_t (*close) (rt_device_t dev); 的实现函数:

/*
dev 	设备句柄
返回 	——
RT_EOK 	关闭设备成功
-RT_ERROR 	设备已经完全关闭,不能重复关闭设备
其他错误码 	关闭设备失败
*/
rt_err_t  rt_device_open (rt_device_t dev, rt_uint16_t oflag);

关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态。

2.3.4 读写设备

读设备:

对应底层rt_size_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); 的实现函数:

/**
参数 	描述
dev 	设备句柄
pos 	读取数据偏移量
buffer 	内存缓冲区指针,读取的数据将会被保存在缓冲区中
size 	读取数据的大小
返回 	——
读到数据的实际大小 	如果是字符设备,返回大小以字节为单位,如果是块设备,返回的大小以块为单位
0 	需要读取当前线程的 errno 来判断错误状态
 */
rt_size_t rt_device_read(rt_device_t dev,
                         rt_off_t    pos,
                         void       *buffer,
                         rt_size_t   size)

调用这个函数,会从 dev 设备中读取数据,并存放在 buffer 缓冲区中,这个缓冲区的最大长度是 size,pos 根据不同的设备类别有不同的意义。

写设备:

对应底层rt_size_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); 的实现函数:

/**
参数 	描述
dev 	设备句柄
pos 	写入数据偏移量
buffer 	内存缓冲区指针,放置要写入的数据
size 	写入数据的大小
返回 	——
写入数据的实际大小 	如果是字符设备,返回大小以字节为单位;如果是块设备,返回的大小以块为单位
0 	需要读取当前线程的 errno 来判断错误状态
 */
rt_size_t rt_device_write(rt_device_t dev,
                          rt_off_t    pos,
                          const void *buffer,
                          rt_size_t   size)

调用这个函数,会把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的最大长度是 size,pos 根据不同的设备类别存在不同的意义。

2.3.5 控制设备

对应底层rt_err_t (*control)(rt_device_t dev, int cmd, void *args); 的实现函数:

/**
参数 	描述
dev 	设备句柄
cmd 	命令控制字,这个参数通常与设备驱动程序相关,见下面列出的参数
arg 	控制的参数
返回 	——
RT_EOK 		函数执行成功
-RT_ENOSYS 	执行失败,dev 为空
其他错误码 	执行失败
 */
rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)


/**
 * general device commands 上面 cmd 的参数
 */
#define RT_DEVICE_CTRL_RESUME           0x01            /**< resume device */
#define RT_DEVICE_CTRL_SUSPEND          0x02            /**< suspend device */
#define RT_DEVICE_CTRL_CONFIG           0x03            /**< configure device */
#define RT_DEVICE_CTRL_CLOSE            0x04            /**< close device */

#define RT_DEVICE_CTRL_SET_INT          0x10            /**< set interrupt */
#define RT_DEVICE_CTRL_CLR_INT          0x11            /**< clear interrupt */
#define RT_DEVICE_CTRL_GET_INT          0x12            /**< get interrupt status */

2.3.6 数据收发回调

这个对于 设备控制块中参数中的两个设备回调函数:

图片.png

硬件设备收到数据:

/**
参数 	描述
dev 	设备句柄
rx_ind 	回调函数指针
返回 	——
RT_EOK 	设置成功
 */
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))

该函数的回调函数由调用者提供。当硬件设备接收到数据时,会回调这个函数并把收到的数据长度放在 size 参数中传递给上层应用。上层应用线程应在收到指示后,立刻从设备中读取数据。

硬件设备发送数据:

在应用程序调用 rt_device_write() 写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。 这个回调函数会在底层硬件数据发送完成后 (例如 DMA 传送完成或 FIFO 已经写入完毕产生完成中断时) 调用。

通过如下函数设置设备发送完成指示:

/**
参数 	描述
dev 	设备句柄
tx_done 	回调函数指针
返回 	——
RT_EOK 	设置成功
 */
rt_err_t
rt_device_set_tx_complete(rt_device_t dev,
                          rt_err_t (*tx_done)(rt_device_t dev, void *buffer))

调用这个函数时,回调函数由调用者提供,当硬件设备发送完数据时,由驱动程序回调这个函数并把发送完成的数据块地址 buffer 作为参数传递给上层应用。上层应用(线程)在收到指示时会根据发送 buffer 的情况,释放 buffer 内存块或将其作为下一个写数据的缓存。

<3 访问 I/O 设备相关的函数,和 I/O 设备模型框架中 应用程序 有关,是我们上层写应用程序直接调用的函数。

三、新建 I/O 设备模型实例

RT-Thread 驱动都是在 drivers 目录下面:

图片.png

我们在目录下新建一个文件,作为驱动示例:

图片.png

我们写一个简单的基本框架:

1、创建一个设备;
   使用 rt_device_create 创建一个设备,需要定义一个 rt_device_t 接口体接收设备设备句柄。

2、实现设备操作的函数:
   实现设备对象中对于 设备操作的init,open,close等 函数。

3、注册设备到 I/O 设备管理器;
   使用 rt_device_register 将设备注册到设备管理器。

drv_demo.c中,我们实现如下代码:

图片.png

其次,我们需要实现一下设备操作的函数:

图片.png

最后,别忘了使用 INIT_BOARD_EXPORT 把设备初始化的代码加入板级硬件初始化:

图片.png

上一下设备模型实例代码:

#include <rtdevice.h>
#include <rtdbg.h>

rt_err_t  demo_init(rt_device_t dev)
{
    rt_kprintf("demo_init ok!\n");
    return 0;
}
rt_err_t  demo_open(rt_device_t dev, rt_uint16_t oflag)
{
    rt_kprintf("demo_open ok!\n");
    return 0;
}
rt_err_t  demo_cloes(rt_device_t dev)
{
    rt_kprintf("demo_cloes ok!\n");
    return 0;
}

int rt_drvdemo_init(void){

    rt_device_t demo_dev = RT_NULL;

    demo_dev = rt_device_create(RT_Device_Class_Char, 0);
    if(demo_dev == RT_NULL){
        LOG_E("demo device create failed...\n");
        return -1;
    }

    demo_dev->init=demo_init;
    demo_dev->open=demo_open;
    demo_dev->close=demo_cloes;

    rt_device_register(demo_dev,"drvdemo",RT_DEVICE_FLAG_RDWR);
    return 0;
}


INIT_BOARD_EXPORT(rt_drvdemo_init);

上面我们完成的是 设备驱动层的 代码,接下来我们还需要简单演示一下,如果在应用层 使用这个 demo 设备。

我们根据上文所介绍的 访问 I/O 设备 进行对应操作,这里直接上图说明一下使用流程:

图片.png

看一下测试结果,我们实现的 3 个驱动函数都只有打印输出,所以我们可以通过打印信息查看是否正确执行的驱动函数的内容:

图片.png

通过上面的测试,我们实现了一个简单的设备驱动的设计,虽然demo比较简单,但是经过这么一个过程可以让我们更加的理解 RT-Thread I/O 设备模型的工作方式和流程。

结语

本文全面了解了 RT-Thread I/O 设备模型,说明了设备模型存在的意义,描述了一下设备模型相关的操作函数,最后使用了一个新建 I/O设备模型的例子,说明了 I/O 设备模型 的工作方式。

在我们使用 RT-Thread 的时候,其实大部分常用的设备 RT-Thread 已经帮我们写好了驱动,我们直接在应用层调用操作接口即可,接下来的系列文章我们将要学习 RT-Thread 常用的 I/O 设备模型。

希望开头的愿景能够实现,通过本文让所有人了解 RT-Thread I/O 设备模型 (* ̄︶ ̄)

本文就到这里,谢谢大家!