RT-Thread的I/O设备模块及其驱动实现步骤



一、I/O设备控制块

1、I/O设备控制块

struct rt_device

{

struct rt_object
parent;

/* 设备类型 */

enum rt_device_class_type
type;

/* 设备参数及打开参数 */

rt_uint16_t flag, open_flag;

/* 提供给上层应用的回调函数 */

rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size);

rt_err_t (*tx_complete)(rt_device_t dev, void*
buffer);

/* 公共的设备接口(由驱动程序提供) */

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, rt_uint8_t cmd, void *args);

/* 用于支持电源管理的函数接口 */

#ifdef RT_USING_DEVICE_SUSPEND

rt_err_t (*suspend) (rt_device_t dev);

rt_err_t (*resumed) (rt_device_t dev);

#endif

/* 设备的私有数据 */

void*
user_data;

};

typedef struct rt_device* rt_device_t;

当前RT-Thread支持的设备类型包括:

enum rt_device_class_type

{

RT_Device_Class_Char = 0, /*
字符设备 */

RT_Device_Class_Block, /*
块设备 */

RT_Device_Class_NetIf, /*
网络接口 */

RT_Device_Class_MTD, /*
内存设备 */

RT_Device_Class_CAN, /*
CAN设备 */

RT_Device_Class_RTC, /*
RTC设备 */

RT_Device_Class_Sound, /*
声音设备 */

RT_Device_Class_Display, /*
显示设备 */

RT_Device_Class_Unknown /*
未知设备 */

};

注:uspend、resume回调函数只会在RT_USING_DEVICE_SUSPEND宏使能的情况下才

会有效。

从设备控制块,我们可以看到,每个设备对象都会在内核中维护一个设备控制块结构,

这种结构是使设备对象继承rt_object基类,然后形成rt_device设备类型。

2、注册设备

一个设备能够被上层应用访问前,需要先把这个设备注册到系统中,并添加一些相应的

一些属性。这些注册的设备均可以通过设备名,采用“查找设备接口”的方式从系统中查找

到,从而获得该设备控制块(或设备句柄)。注册设备的函数接口如下:

rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t
flags);

函数参数:

dev     设备句柄;

name    设备名称;

flag    设备模式标志:

flags参数支持下列参数(可以采用或的方式支持多种参数):

#define RT_DEVICE_FLAG_DEACTIVATE 0x000 /*
未初始化设备 */

#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_ACTIVATED 0x010 /*
已激活设备 */

#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”做分行。

函数返回

返回RT_EOK

警告:应当避免重复注册已经注册的设备,以及注册相同名字的设备。

3、移除设备

将设备从设备系统中移除,被卸载的设备将不能再通过“查找设备接口”被查找到。卸

载设备的函数接口如下所示:

rt_err_t rt_device_unregister(rt_device_t dev)

函数参数

参数 描述

dev 设备句柄;

函数返回

返回RT_EOK

注:卸载设备并不会释放设备控制块所占用的内存

4、初始化所有设备

初始化所有注册到设备对象管理器中的未初始化的设备,可以通过如下函数接口完成:

rt_err_t rt_device_init_all(void)

函数参数

函数返回

返回RT_EO

? 注:此函数将逐渐废弃,不推荐在应用程序中调用。当一个设备初始化完成后它

的flags域中的RT_DEVICE_FLAG_ACTIVATED应该被置位。如果设备的flags域已经是

RT_DEVICE_FLAG_ACTIVATED,调用这个接口将不再重复做初始化。

5、查找设备

根据指定的设备名称查找设备,可以通过如下接口完成:

rt_device_t rt_device_find(const char* name)

使用这个函数接口时,系统会在设备对象类型所对应的对象容器中遍历寻找设备对象,

然后返回该设备的句柄,如果没有找到相应的设备对象,则返回RT_NULL。

函数参数

参数 描述

name 设备名称。

函数返回

查找到对应设备将返回相应的设备句柄;否则返回RT_NULL。

6、打开设备

根据设备控制块来打开设备,可以通过如下函数接口完成:

rt_err_t rt_device_open
(rt_device_t dev, rt_uint16_t oflags)

函数参数

参数    描述

dev    设备句柄;

oflags  访问模式。

其中oflags支持以下列表中的参数:

#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 /*
设备已经打开(内部使用) */

函数返回

返回驱动的open函数返回值

注:如果设备注册时指定的参数中包括RT_DEVICE_FLAG_STANDALONE参数,此设备将

不允许重复打开,返回-RT_EBUSY。

7、关闭设备

根据设备控制块来关闭设备,可以通过如下函数接口完成:

rt_err_t rt_device_close(rt_device_t dev)

函数参数

参数  描述

dev  设备句柄;

函数返回

返回驱动的close函数返回值

8、读设备

从设备中读取,或获得数据,可以通过如下函数接口完成:

rt_size_t rt_device_read (rt_device_t dev, rt_off_t pos,
void* buffer, rt_size_t size)

调用这个函数,会从设备dev中获得数据,并存放在buffer缓冲区中。这个缓冲区的最

大长度是size。pos根据不同的设备类别存在不同的意义。

函数参数

参数    描述

dev    
设备句柄;

pos  
  读取数据偏移量;

buffer  
内存缓冲区指针,读取的数据将会被保存在缓冲区中;

size  
 读取数据的大小。

函数返回

返回读到数据的实际大小(如果是字符设备,返回大小以字节为单位;如果是块设备,

返回的大小以块为单位);如果返回0,则需要读取当前线程的errno来判断错误状态。

9、写设备

向设备中写入数据,可以通过如下函数接口完成:

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根据不同的设备类别存在不同的意义。

函数参数

参数    描述

dev  
  设备句柄;

pos  
  读取数据偏移量;

buffer  
内存缓冲区指针,放置要写入的数据;

size    写入数据的大小。

函数返回

返回写入数据的实际大小(如果是字符设备,返回大小以字节为单位;如果是块设备,

返回的大小以块为单位);如果返回0,则需要读取当前线程的errno来判断错误状态

? 注:在RT-Thread的块设备中,从1.0.0版本开始, rt_device_read()/rt_device_write()接

口的pos、size参数按照以块为单位。0.3.x以前的版本则按字节为单位。

10、控制设备

根据设备控制块来控制设备,可以通过下面的函数接口完成:

rt_err_t rt_device_control(rt_device_t
dev, rt_uint8_t cmd, void* arg)

函数参数

参数  描述

dev   设备句柄;

cmd  
命令控制字,这个参数通常与设备驱动程序相关;

arg   控制的参数

函数返回

返回驱动控制接口的返回值。

11、设置数据接收指示

设置一个回调函数,当硬件设备收到数据时回调以通知用程序有数据到达。可以通过如

下函数接口完成设置接收指示:

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

在调用这个函数时,回调函数rx_ind由调用者提供。当硬件设备接收到数据时,会回调

这个函数并把收到的数据长度放在size参数中传递给上层应用。上层应用线程应在收到指示

后,立刻从设备中读取数据。

函数参数

参数      描述

dev  
    设备句柄;

rx_ind     接收回调函数。

函数返回

返回RT_EOK

12、设置发送完成指示

在上层应用调用rt_device_write写入数据时,如果底层硬件能够支持自动发送,那么上层应用可以设置一个回调函数。这个回调函数会在底层硬件给出的发送完成后(例如DMA传送完成或FIFO已经写入完毕产生完成中断时)被调用。可以通过如下函数接口设置设备发送完成指示:

rt_err_t rt_device_set_tx_complete(rt_device_t
dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer))

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

函数参数

参数    描述

dev  
  设备句柄;

tx_done  发送回调函数。

函数返回

返回RT_EOK

二、设备驱动

设备驱动必须实现的接口

在10.1节中提及了RT-Thread设备接口类,我们着重看看其中包含的一套公共设备接口

(类似上节说的设备访问接口,但面向的层次已经不一样,这里是面向底层驱动):

/* 公共的设备接口(由驱动程序提供) */

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, rt_uint8_t cmd, void *args);

/* 用于支持电源管理的函数接口 */

#ifdef RT_USING_DEVICE_SUSPEND

rt_err_t (*suspend) (rt_device_t dev);

rt_err_t (*resumed) (rt_device_t dev);

#endif

这些接口也是上层应用通过RT-Thread设备接口进行访问的实际底层接口(如 设备操作

接口与设备驱动程序接口的映射 ):

即这些驱动实现的底层接口是上层应用最终访问的落脚点,例如上层应用调用rt_device_read接口进行设备读取数据操作,上层应先调用rt_device_find获得相对应的设备句柄,而在调用rt_device_read时,就是使用这个设备句柄所对应驱动的driver_read。上述的接口是一一对应关系。

I/O设备模块提供的这六个接口(rt_device_init/open/read/write/control),对应到设备驱动程序的六个接口(driver_init/open/read/write/control等),可以认为是底层设备驱动必须提供的接口:

1、init

设备的初始化。设备初始化完成后,设备控制块的flag会被置 成已激活状态(RT_DEVICE_FLAG_ACTIVATED)。如果设备控制块
的flag

不是已激活状态,那么在设备框架调用 rt_device_init_all接口时将调用此设备驱动的init接口进行
设备初始化;如果设备控制块中的flag标志已经设置成激活状 态,那么再运行初始化接口时,会立刻返回,而不会重新进行
初始化。

2、open

打开设备。有些设备并不是系统一启动就已经打开开始运行;
或者设备需要进行数据接收,但如果上层应用还未准备好,设 备也

不应默认已经使能并开始接收数据。所以建议在写底层驱 动程序时,应在调用open接口时才使能设备。

3、close

关闭设备。建议在打开设备时,设备驱动自行维护一个打开计数,在打开设备时进行+1操作,在关闭设备时进行-1操作,
当计数

器变为0时,进行真正的关闭操作。

4、read

从设备中读取数据。参数pos指出读取数据的偏移量,但是有些 设备并不一定需要指定偏移量,例如串口设备,设备驱动应忽
略这

个参数。而对于块设备来说,pos以及size都是以块设备的 数据块大小做为单位的。例如块设备的数据块大小是512,而参
数中pos= 10, size = 2,那么驱动应该返回设备中第10个块 (从第0个块做为起始),共计2个块的数据。这个接口返回的
类型rt_size_t,即读到的字节数或块数目。正常情况下应 该会返回参数中size的数值,如果返回零请设置对应的errno值。

5、write

向设备中写入数据。参数pos指出写入数据的偏移量。与读操作 类似,对于块设备来说,pos以及size都是以块设备的数据块
大小做

为单位的。这个接口返回的类型是rt_size_t,即真实写 入数据的字节数或块数目。正常情况下应该会返回参数中size
的数值,如果返回零请设置对应的errno值。

6、control

根据不同的cmd命令控制设备。命令往往是由底层各类设备驱 动自定义实现。例如参数RT_DEVICE_CTRL_BLK_GETGEOME,意思
是获取块设备的大小信息。

三、设备驱动实现的步骤

在实现一个RT-Thread设备时,可以按照如下的步骤进行(对于一些复杂的设备驱动,例如以太网接口驱动、图形设备驱动,请参看网络组件、GUI部分章节):

? 按照RT-Thread的对象模型,扩展一个对象有两种方式:

? 定义自己的私有数据结构,然后赋值到RT-Thread设备控制块的user_data指针上;

? 从struct rt_device结构中进行派生。

? 实现RT-Thread I/O设备模块中定义的6个公共设备接口,开始可以是空函数(返回类型是rt_err_t的可默认返回RT_EOK);

? 根据自己的设备类型定义自己的私有数据域。特别是在可能有多个相类似设备的情况下(例如串口1、2),设备接口可以共用同一套接口,不同的只是各自
 的数据域(例如寄存器基地址);

? 根据设备的类型,注册到RT-Thread设备框架中。

时间: 2024-11-16 11:22:40

RT-Thread的I/O设备模块及其驱动实现步骤的相关文章

RT Thread学习历程(1):串口乱码问题

因为学习实时系统,最近接触到RT Thread. 把RT Thread官网上的示例代码烧录到STM32的板子上之后,在串口软件上接收到的全是乱码,一开始以为是串口软件的问题,换了2个软件之后情况都一样,最后发现是晶振的问题,我用的是STM32F407VGT6,晶振要设为8MHz,代码相应的设置晶振的部分也要修改.

思科设备模块器GNS3V0.7.2​软件安装及详解

软件介绍 思科设备模块器GNS3V0.7.2 all-in-one工具软件,非常好的Cisco模拟软件,而且还集中了juniper的模拟!GNS3是一个图形化的网络模拟器,允许模拟复杂的网络. 为了让完整的模拟,GNS3与一下软件是密切相关的:dynamips,核心的程序,允许思科IOS仿真.Dynagen,基于文本的前端为dynamips的结束.Qemu,通用和开放源码机模拟器和虚拟器.GNS3是一个很好的补充工具,真正为网络实验室的工程师,管理人员如想通过CCNA证书,CCNP,CCIP,C

Linux驱动入门篇(三):基本的字符设备模块(2)

上一节中介绍了设备号的申请和释放,这一节开始了解字符设备的相关操作. 首先定位到<linux/cdev.h>文件,查看内核提供给字符设备的接口. cdev结构 struct cdev { struct kobject kobj; //内嵌的kobject对象 struct module *owner; //此结构所属模块 const struct file_operations *ops; //文件操作结构 struct list_head list; //通用双向链表 dev_t dev;

视频在线率统计——基于驱动总线设备的领域驱动设计方法落地

目录 视频在线率统计--基于驱动总线设备的领域驱动设计方法落地 1.应用背景 2.各视频在线率统计方法比较 3.本文侧重点 4.基于领域驱动来设计摄像头网络状态这一领域 4.1 值对象driverContext 4.2 子领域CameraPingDM 4.2.1 枚举类型摄像头网络状态CameraState 4.2.2 属性 4.2.3 子领域的划分 4.3 聚合CameraPingBus 4.3.1 属性 4.3.3 与CameraPingDM所有子领域相关的行为 4.3.4 领域模型字段在数

天嵌E9平台下进行USB Wifi模块RT3070驱动的移植

因为项目工作须要,要在天嵌E9平台下实现wifi的点对点,点对多点的传输. Wifi 模块芯片:雷凌3070芯片 嵌入式平台:E9(ARM) 交叉编译环境:arm-none-linux-gnueabi 1 首先下载了wifi模块的驱动 2011_0719_RT3070_RT3370_RT5370_RT5372_RT2070_Linux_STA_V2.5.0.3_DPO 2 对驱动进行交叉编译 a 打开驱动文件解压后的文件,假设须要WPA等功能,可打开./os/linux/config.mk进行编

MTP设备无法安装驱动的解决办法

1,进入设备管理器右击带黄色问号的MTP,选择“属性”,“详细信息”“设备范例 ID”(用Ctrl+C复制). 2,找到c:\windows\inf\wpdmtp.inf打开(或者通过运行打开),找到[Generic.NTx86]%GenericMTP.DeviceDesc%=MTP, USB\MS_COMP_MTP[Generic.NTamd64]%GenericMTP.DeviceDesc%=MTP, USB\MS_COMP_MTP 3,将2中的USB\MS_COMP_MTP更改为1中复制的

RT thread 设备驱动之串口设备

本文以stm32f4xx平台介绍串口驱动,主要目的是: 1.RTT中如何编写中断处理程序 2.如何编写RTT设备驱动接口代码 3.了解串行设备的常见处理机制 所涉及的主要源码文件有:usart.c,usart.h,serial.c,serial.h 一.RTT的设备驱动程序概述 编写uart的驱动程序,首先需要了解RTT的设备框架,这里以usart的驱动来具体分析RTT的IO设备管理.注:参考<RTT实时操作系统编程指南> I/O设备管理一章. 我们可以将USART的硬件驱动分成两个部分,如下

STM32 + RT Thread OS 串口通讯

1.   创建项目 a)   禁用Finsh和console b)   默认情况下,项目文件包含了finsh,它使用COM1来通讯,另外,console输出(rt_kprintf)也使用了COM1.因此,在运行scons命令生成项目文件之前,修改rtconfig.h,禁用这两项.(下图L65, L70) c)   生成项目文件 运行scons --target=mdk4 –s 打开生成的项目文件,可以看到,文件组finsh已经不再被包含进来了. d)   创建echo.c 新建一个C文件echo

在linux和Windows下配置HP FC存储设备多路径驱动

主机安装HBA卡驱动,一般光纤卡的厂家就是QLogic和Emulex的. Linux下查看光纤卡驱动模块是否加载通过 lsmod |grep qla2xxx #查看是否加载了QLogic的光纤卡模块 lsmod |grep lpfc #查看是否加载了Emulex的光纤卡模块 modprobe lpfc  #加载相应的光纤卡模块 Windows下查看设备管理器查看是否加载了驱动(如果没有的话安装相应的光纤卡驱动). 2. 查看光纤卡的WWN号(记录下来为存储设备映射时使用) Linux more