目录
2.2.4、rt_device_unregister()函数
2.2.12、rt_device_set_rx_indicate()函数
2.2.13、rt_device_set_tx_complete()函数
1、简介
RT-Thread 提供了一套简单的 I/O 设备模型框架,它位于硬件和应用程序之间,共分成三层,从上到下分别是 I/O 设备管理层、设备驱动框架层、设备驱动层。
使用 I/O 设备管理框架开发应用程序,有如下优点:
1)移植性:使用同一套标准的 API 开发应用程序,使应用程序具有更好的移植性
2)解耦:底层驱动的升级和修改不会影响到上层代码
3)协同开发:驱动和应用程序相互独立,方便多个开发者协同开发
I/O 设备管理层 | 实现对设备驱动程序的封装。应用程序通过 I/O 设备层提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。这种方式使得设备的硬件操作相关的代码能够独立于应用程序而存在,双方只需关注各自的功能实现,从而降低了代码的耦合性、复杂性,提高了系统的可靠性。 |
设备驱动框架层 | 是对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现。 |
设备驱动层 | 是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备。 |
1.1、时序1
对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中。如设备驱动HWTIMER。
1.2、时序2
对于另一些设备,如看门狗等,则会将创建的设备实例先注册到对应的设备驱动框架中,再由设备驱动框架向 I/O 设备管理器进行注册
2、I/O 设备模型
RT-Thread 的设备模型是建立在内核对象模型基础之上的,设备被认为是一类对象,被纳入对象管理器的范畴。每个设备对象都是由基对象派生而来,每个具体设备都可以继承其父类对象的属性,并派生出其私有属性。
2.1、设备对象
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);
#ifdef RT_USING_DEVICE_OPS
const struct rt_device_ops *ops; /* 设备操作方法 */
#else
/* 常见设备接口 */
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 /* RT_USING_DEVICE_OPS */
/* 兼容POSIX */
#ifdef RT_USING_POSIX_DEVIO
const struct dfs_file_ops *fops;
struct rt_wqueue wait_queue;
#endif /* RT_USING_POSIX_DEVIO */
/* 设备的私有数据 */
void *user_data;
};
typedef struct rt_device *rt_device_t;
2.1.1、设备类型
字符设备、块设备是常用的设备类型,它们的分类依据是设备数据与系统之间的传输处理方式。
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_USBOTG, /**< USB OTG 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_Security, /**< Security device */
RT_Device_Class_WLAN, /**< WLAN device */
RT_Device_Class_Pin, /**< Pin device */
RT_Device_Class_ADC, /**< ADC device */
RT_Device_Class_DAC, /**< DAC device */
RT_Device_Class_WDT, /**< WDT device */
RT_Device_Class_PWM, /**< PWM device */
RT_Device_Class_Unknown /**< unknown device */
};
1)字符模式设备允许非结构的数据传输,即通常数据传输采用串行的形式,每次一个字节。字符设备通常是一些简单设备,如串口、按键。
2)块设备每次传输一个数据块,例如每次传输 512 个字节数据。这个数据块是硬件强制性的,数据块可能使用某类数据接口或某些强制性的传输协议,否则就可能发生错误。因此,有时块设备驱动程序对读或写操作必须执行附加的工作,如下图所示:
在实际过程中,最后一部分数据尺寸有可能小于正常的设备块尺寸。如上图中每个块使用单独的写请求写入到设备中,头 3 个直接进行写操作。但最后一个数据块尺寸小于设备块尺寸,设备驱动程序必须使用不同于前 3 个块的方式处理最后的数据块。通常情况下,设备驱动程序需要首先执行相对应的设备块的读操作,然后把写入数据覆盖到读出数据上,然后再把这个 “合成” 的数据块作为一整个块写回到设备中。例如上图中的块 4,驱动程序需要先把块 4 所对应的设备块读出来,然后将需要写入的数据覆盖至从设备块读出的数据上,使其合并成一个新的块,最后再写回到块设备中。
2.1.2、操作方法
设备被创建后,需要实现它访问硬件的操作方法。
#ifdef RT_USING_DEVICE_OPS
struct rt_device_ops
{
/* 常见设备接口 */
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 /* RT_USING_DEVICE_OPS */
方法名称 | 方法描述 |
---|---|
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,意思是获取块设备的大小信息。 |
2.1.3、设备flag
设备流模式 RT_DEVICE_FLAG_STREAM 参数用于向串口终端输出字符串:当输出的字符是 “\n”
时,自动在前面补一个 “\r”
做分行。
#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 */
#define RT_DEVICE_OFLAG_CLOSE 0x000 /**< device is closed */
#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 */
2.1.4、常用设备命令
#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.2、设备接口
2.2.1、rt_device_create()函数
该接口创建一个具有用户数据大小的设备对象。系统会从动态堆内存中分配一个设备控制块,大小为 struct rt_device 和 attach_size 的和,设备的类型由参数 type 设定。
rt_device_t rt_device_create(int type, int attach_size)
2.2.2、rt_device_destroy()函数
该接口销毁特定的设备对象。
void rt_device_destroy(rt_device_t dev)
2.2.3、rt_device_register()函数
该接口用指定的名称注册一个设备驱动程序。
rt_err_t rt_device_register(rt_device_t dev,
const char *name,
rt_uint16_t flags)
2.2.4、rt_device_unregister()函数
该接口删除先前注册的设备驱动程序。
rt_err_t rt_device_unregister(rt_device_t dev)
2.2.5、rt_device_find()函数
该接口根据指定的名称查找设备驱动程序。
rt_device_t rt_device_find(const char *name)
2.2.6、rt_device_init()函数
该接口将初始化指定的设备。最终会调用设备驱动层注册的init()函数进行设备初始化。
rt_err_t rt_device_init(rt_device_t dev)
注:当一个设备已经初始化成功后,调用这个接口将不再重复做初始化。
2.2.7、rt_device_open()函数
该接口将打开一个设备。最终会调用设备驱动层注册的open()函数打开设备。
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag)
注:打开设备时,会检测设备是否已经初始化,没有初始化则会默认调用初始化接口初始化设备。
注:如果上层应用程序需要设置设备的接收回调函数,则必须以 RT_DEVICE_FLAG_INT_RX 或者 RT_DEVICE_FLAG_DMA_RX 的方式打开设备,否则不会回调函数。
2.2.8、rt_device_close()函数
该接口将关闭一个设备。最终会调用设备驱动层注册的close()函数关闭设备。
rt_err_t rt_device_close(rt_device_t dev)
注:关闭设备接口和打开设备接口需配对使用,打开一次设备对应要关闭一次设备,这样设备才会被完全关闭,否则设备仍处于未关闭状态(ref_count为0时,才真正执行close函数关闭设备)。
2.2.9、rt_device_read()函数
该接口将从设备中读取一些数据。最终会调用设备驱动层注册的read()的函数从 dev 设备中读取数据,并存放在 buffer 缓冲区中,这个缓冲区的最大长度是 size,pos 根据不同的设备类别有不同的意义。
rt_size_t rt_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size)
2.2.10、rt_device_write()函数
该接口将向设备写入一些数据。最终会调用设备驱动层注册的write()的函数把缓冲区 buffer 中的数据写入到设备 dev 中,写入数据的最大长度是 size,pos 根据不同的设备类别存在不同的意义。
rt_size_t rt_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size)
2.2.11、rt_device_control()函数
该接口将对设备执行各种控制功能。最终会调用设备驱动层注册的control()函数控制设备。
rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg)
2.2.12、rt_device_set_rx_indicate()函数
该接口将设置接收指示回调函数。此回调函数在设备接收数据时调用设备驱动层注册的rx_indicate()函数。
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))
2.2.13、rt_device_set_tx_complete()函数
该接口将设置一个回调函数。当设备向物理硬件写入数据时,将调用设备驱动层注册的tx_complete()函数。
rt_err_t rt_device_set_tx_complete(rt_device_t dev,
rt_err_t (*tx_done)(rt_device_t dev,
void *buffer))