1@page page_device_framework I/O Device Framework 2 3Most embedded systems include some I/O (Input/Output) devices, data displays on instruments, serial communication on industrial devices, Flash or SD cards for saving data on data acquisition devices,as well as Ethernet interfaces for network devices, are examples of I/O devices that are commonly seen in embedded systems. 4 5This chapter describes how RT-Thread manages different I/O devices. 6 7# I/O Device Introduction 8 9## I/O Device Framework 10 11RT-Thread provides a set of I/O device framework, as shown in the following figure. It is located between the hardware and the application. It is divided into three layers. From top to bottom, they are I/O device management layer, device driver framework layer, and device driver layer. 12 13 14 15The application obtains the correct device driver through the I/O device management interface, and then uses this device driver to perform data (or control) interaction with the bottom I/O hardware device. 16 17The I/O device management layer implements the encapsulation of device drivers. The application accesses the bottom devices through the standard interface provided by the I/O device layer. The upgrade and replacement of the device driver will not affect the upper layer application. In this way, the hardware-related code of the device can exist independently of the application, and both parties only need to pay attention to the respective function implementation, thereby reducing the coupling and complexity of the code and improving the reliability of the system. 18 19The device driver framework layer is an abstraction of the same kind of hardware device driver. The same part of the same hardware device driver of different manufacturers is extracted, and the different parts are left out of interface, implemented by the driver. 20 21The device driver layer is a set of programs that drive the hardware devices to work, enabling access to hardware devices. It is responsible for creating and registering I/O devices. For devices with simple operation logic, you can register devices directly into the I/O Device Manager without going through the device driver framework layer. The sequence diagram is as shown below. There are mainly two points: 22 23* The device driver creates a device instance with hardware access capabilities based on the device model definition and registers the device with the `rt_device_register()` interface in the I/O Device Manager. 24* The application finds the device through the`rt_device_find()` interface and then uses the I/O device management interface to access the hardware. 25 26 27 28For other devices, such as watchdog, the created device instance will be registered to the corresponding device driver framework, and then the device driver framework will register with the I/O device manager. The main points are as follows: 29 30* The watchdog device driver creates a watchdog device instance with hardware access capability based on the watchdog device model definition and registers the watchdog device through the `rt_hw_watchdog_register()` interface into the watchdog device driver framework. 31* The watchdog device driver framework registers the watchdog device to the I/O Device Manager via the `rt_device_register()` interface. 32* The application accesses the watchdog device hardware through the I/O device management interface. 33 34Usage of Watchdog device: 35 36 37 38## I/O Device Model 39 40The device model of RT-Thread is based on the kernel object model, which is considered a kind of objects and is included in the scope of the object manager. Each device object is derived from the base object. Each concrete device can inherit the properties of its parent class object and derive its own properties. The following figure is a schematic diagram of the inheritance and derivation relationship of device object. 41 42 43 44The specific definitions of device objects are as follows: 45 46```c 47struct rt_device 48{ 49 struct rt_object parent; /* kernel object base class */ 50 enum rt_device_class_type type; /* device type */ 51 rt_uint16_t flag; /* device parameter */ 52 rt_uint16_t open_flag; /* device open flag */ 53 rt_uint8_t ref_count; /* number of times the device was cited */ 54 rt_uint8_t device_id; /* device ID,0 - 255 */ 55 56 /* data transceiving callback function */ 57 rt_err_t (*rx_indicate)(rt_device_t dev, rt_size_t size); 58 rt_err_t (*tx_complete)(rt_device_t dev, void *buffer); 59 60 const struct rt_device_ops *ops; /* device operate methods */ 61 62 /* device's private data */ 63 void *user_data; 64}; 65typedef struct rt_device *rt_device_t; 66 67``` 68 69## I/O Device Type 70 71RT-Thread supports multiple I/O device types, the main device types are as follows: 72 73```c 74RT_Device_Class_Char /* character device */ 75RT_Device_Class_Block /* block device */ 76RT_Device_Class_NetIf /* network interface device */ 77RT_Device_Class_MTD /* memory device */ 78RT_Device_Class_RTC /* RTC device */ 79RT_Device_Class_Sound /* sound device */ 80RT_Device_Class_Graphic /* graphic device */ 81RT_Device_Class_I2CBUS /* I2C bus device */ 82RT_Device_Class_USBDevice /* USB device */ 83RT_Device_Class_USBHost /* USB host device */ 84RT_Device_Class_SPIBUS /* SPI bus device */ 85RT_Device_Class_SPIDevice /* SPI device */ 86RT_Device_Class_SDIO /* SDIO device */ 87RT_Device_Class_Miscellaneous /* miscellaneous devices */ 88``` 89 90Character devices and block devices are commonly used device types, and their classification is based on the transmission processing between device data and the system. Character mode devices allow for unstructured data transfers, that is, data usually transfers in the form of serial, one byte at a time. Character devices are usually simple devices such as serial ports and buttons. 91 92A block device transfers one data block at a time, for example 512 bytes data at a time. This data block is enforced by the hardware. Data blocks may use some type of data interface or some mandatory transport protocol, otherwise an error may occur. Therefore, sometimes the block device driver must perform additional work on read or write operations, as shown in the following figure: 93 94 95 96When the system serves a write operation with a large amount of data, the device driver must first divide the data into multiple packets, each with the data size specified by the device. In the actual process, the last part of the data size may be smaller than the normal device block size. Each block in the above figure is written to the device using a separate write request, and the first three are directly written. However, the last data block size is smaller than the device block size, and the device driver must process the last data block differently than the first 3 blocks. Normally, the device driver needs to first perform a read operation of the corresponding device block, then overwrite the write data onto the read data, and then write the "composited" data block back to the device as a whole block. . For example, for block 4 in the above figure, the driver needs to read out the device block corresponding to block 4, and then overwrite the data to be written to the data read from the device block, and merge them into a new block. Finally write back to the block device. 97 98# Create and Register I/O Device 99 100The driver layer is responsible for creating device instances and registering them in the I/O Device Manager. You can create device instances in a statically declared manner or dynamically create them with the following interfaces: 101 102```c 103rt_device_t rt_device_create(int type, int attach_size); 104``` 105 106|**Parameters** |**Description** | 107|-------------|-------------------------------------| 108| type | device type, the device type values listed in "I/O Device Type" section can be used here | 109| attach_size | user data size | 110|**Return** | -- | 111| Device Handle | Create successfully | 112| RT_NULL | Creation failed, dynamic memory allocation failed | 113 114When this interface is called, the system allocates a device control block from the dynamic heap memory, the size of which is the sum of `struct rt_device` and `attach_size`, and the type of the device is set by the parameter type. After the device is created, implementing its access to the hardware is needed. 115 116```c 117struct rt_device_ops 118{ 119 /* common device interface */ 120 rt_err_t (*init) (rt_device_t dev); 121 rt_err_t (*open) (rt_device_t dev, rt_uint16_t oflag); 122 rt_err_t (*close) (rt_device_t dev); 123 rt_ssize_t (*read) (rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size); 124 rt_ssize_t (*write) (rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size); 125 rt_err_t (*control)(rt_device_t dev, int cmd, void *args); 126}; 127 128``` 129 130A description of each method of operation is shown in the following table: 131 132|**Method Name**|**Method Description** | 133|----|-----------------------| 134| init | Initialize the device. After the device is initialized, the flag of the device control block is set to the active state(RT_DEVICE_FLAG_ACTIVATED). If the flag in the device control block has been set to the active state, then the initialization interface will be returned immediately when running again, and will not be re-initialized. | 135| open | Open the device. Some devices are not started when the system is started, or the device needs to send and receive data. However, if the upper application is not ready, the device should not be enabled by default and start receiving data. Therefore, it is recommended to enable the device when calling the open interface when writing the bottom driver. | 136| close | Close the device. When the device is open, the device control block maintains an open count, the count will add 1 when the device is opended, and the count will subtract 1 when the device is closed, and a real shutdown operation is operated when the counter turns to 0. | 137| read | Read data from the device. The parameter pos is the offset of the read data, but some devices do not necessarily need to specify the offset, such as serial devices, the device driver should ignore this parameter. But for block devices, pos and size are measured in the block size of the block device. For example, the block size of the block device is 512 byte, and in the parameter pos = 10, size = 2, then the driver should return the 10th block in the device (starting from the 0th block) for a total of 2 blocks of data. The type returned by this interface is rt_size_t, which is the number of bytes read or the number of blocks. Normally, the value of size in the parameter should be returned. If it returns zero, set the corresponding errno value. | 138| write | Write data to the device. The parameter pos is the offset of the write data. Similar to read operations, for block devices, pos and size are measured in the block size of the block device. The type returned by this interface is rt_size_t, which is the number of bytes or blocks of data actually written. Normally, the value of size in the parameter should be returned. If it returns zero, set the corresponding errno value. | 139| control | Control the device according to the cmd command. Commands are often implemented by the bottom device drivers. For example, the parameter RT_DEVICE_CTRL_BLK_GETGEOME means to get the size information of the block device. | 140 141When a dynamically created device is no longer needed, it can be destroyed using the following function: 142 143```c 144void rt_device_destroy(rt_device_t device); 145``` 146 147|**Parameters**|**Description**| 148|----------|----------| 149| device | device handle | 150 151After the device is created, it needs to be registered to the I/O Device Manager for the application to access. The functions for registering the device are as follows: 152 153```c 154rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags); 155``` 156 157|**Parameters** |**Description** | 158|------------|-----------------------| 159| dev | device handle | 160| name | device name, the maximum length of the device name is specified by the macro RT_NAME_MAX defined in rtconfig.h, and the extra part is automatically truncated | 161| flags | device mode flag | 162|**Return** | -- | 163| RT_EOK | registration success | 164| -RT_ERROR | registration failed, dev is empty or name already exists | 165 166>It should be avoided to repeatedly register registered devices and to register devices with the same name. 167 168flags parameters support the following parameters (multiple parameters can be supported in OR logic): 169 170```c 171#define RT_DEVICE_FLAG_RDONLY 0x001 /* read only */ 172#define RT_DEVICE_FLAG_WRONLY 0x002 /* write only */ 173#define RT_DEVICE_FLAG_RDWR 0x003 /* read and write */ 174#define RT_DEVICE_FLAG_REMOVABLE 0x004 /* can be removed */ 175#define RT_DEVICE_FLAG_STANDALONE 0x008 /* stand alone */ 176#define RT_DEVICE_FLAG_SUSPENDED 0x020 /* suspended */ 177#define RT_DEVICE_FLAG_STREAM 0x040 /* stream mode */ 178#define RT_DEVICE_FLAG_INT_RX 0x100 /* interrupt reception */ 179#define RT_DEVICE_FLAG_DMA_RX 0x200 /* DMA reception */ 180#define RT_DEVICE_FLAG_INT_TX 0x400 /* interrupt sending */ 181#define RT_DEVICE_FLAG_DMA_TX 0x800 /* DMA sending */ 182``` 183 184Device Stream Mode The RT_DEVICE_FLAG_STREAM parameter is used to output a character string to the serial terminal: when the output character is `\n` , it automatically fills in a `\r` to make a branch. 185 186Successfully registered devices can use the `list_device` command on the FinSH command line to view all device information in the system, including the device name, device type, and number of times the device is opened: 187 188```c 189msh />list_device 190device type ref count 191-------- -------------------- ---------- 192e0 Network Interface 0 193sd0 Block Device 1 194rtc RTC 0 195uart1 Character Device 0 196uart0 Character Device 2 197msh /> 198``` 199 200When the device is logged off, the device will be removed from the device manager and the device will no longer be found through the device. Logging out of the device does not release the memory occupied by the device control block. The function to log off of the device is as follows: 201 202```c 203rt_err_t rt_device_unregister(rt_device_t dev); 204``` 205 206|**Parameters**|**Description**| 207|----------|----------| 208| dev | device handle | 209|**Return**| -- | 210| RT_EOK | successful | 211 212The following code is an example of registering a watchdog device. After calling the `rt_hw_watchdog_register()` interface, the device is registered to the I/O Device Manager via the `rt_device_register()` interface. 213 214```c 215const static struct rt_device_ops wdt_ops = 216{ 217 rt_watchdog_init, 218 rt_watchdog_open, 219 rt_watchdog_close, 220 RT_NULL, 221 RT_NULL, 222 rt_watchdog_control, 223}; 224 225rt_err_t rt_hw_watchdog_register(struct rt_watchdog_device *wtd, 226 const char *name, 227 rt_uint32_t flag, 228 void *data) 229{ 230 struct rt_device *device; 231 RT_ASSERT(wtd != RT_NULL); 232 233 device = &(wtd->parent); 234 235 device->type = RT_Device_Class_Miscellaneous; 236 device->rx_indicate = RT_NULL; 237 device->tx_complete = RT_NULL; 238 239 device->ops = &wdt_ops; 240 device->user_data = data; 241 242 /* register a character device */ 243 return rt_device_register(device, name, flag); 244} 245 246``` 247 248# Access I/O Devices 249 250The application accesses the hardware device through the I/O device management interface, which is accessible to the application when the device driver is implemented. The mapping relationship between the I/O device management interface and the operations on the I/O device is as follows: 251 252 253 254## Find Device 255 256The application obtains the device handle based on the device name, which in turn allows the device to operate. To find device, use function below: 257 258```c 259rt_device_t rt_device_find(const char* name); 260``` 261 262|**Parameters**|**Description** | 263|----------|------------------------------------| 264| name | device name | 265|**Return**| -- | 266| device handle | finding the corresponding device will return the corresponding device handle | 267| RT_NULL | no corresponding device object found | 268 269## Initialize Device 270 271Once the device handle is obtained, the application can initialize the device using the following functions: 272 273```c 274rt_err_t rt_device_init(rt_device_t dev); 275``` 276 277|**Parameters**|**Description** | 278|----------|----------------| 279| dev | device handle | 280|**Return**| -- | 281| RT_EOK | device initialization succeeded | 282| Error Code | device initialization failed | 283 284>When a device has been successfully initialized, calling this interface will not repeat initialization. 285 286## Open and Close Device 287 288Through the device handle, the application can open and close the device. When the device is opened, it will detect whether the device has been initialized. If it is not initialized, it will call the initialization interface to initialize the device by default. Open the device with the following function: 289 290```c 291rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags); 292``` 293 294|**Parameters** |**Description** | 295|------------|-----------------------------| 296| dev | device handle | 297| oflags | open device in oflag mode | 298|**Return** | -- | 299| RT_EOK | device successfully turned on | 300|-RT_EBUSY | device will not allow being repeated opened if the RT_DEVICE_FLAG_STANDALONE parameter is included in the parameters specified when the device is registered. | 301| Other Error Code | device failed to be turned on | 302 303oflags supports the following parameters: 304 305```c 306#define RT_DEVICE_OFLAG_CLOSE 0x000 /* device was already closed(internal use)*/ 307#define RT_DEVICE_OFLAG_RDONLY 0x001 /* open the device in read-only mode */ 308#define RT_DEVICE_OFLAG_WRONLY 0x002 /* open the device in write-only mode */ 309#define RT_DEVICE_OFLAG_RDWR 0x003 /* open the device in read-and_write mode */ 310#define RT_DEVICE_OFLAG_OPEN 0x008 /* device was already closed(internal use) */ 311#define RT_DEVICE_FLAG_STREAM 0x040 /* open the device in stream mode */ 312#define RT_DEVICE_FLAG_INT_RX 0x100 /* open the device in interrupt reception mode */ 313#define RT_DEVICE_FLAG_DMA_RX 0x200 /* open the device in DMA mode */ 314#define RT_DEVICE_FLAG_INT_TX 0x400 /* open the device in interrupt sending mode */ 315#define RT_DEVICE_FLAG_DMA_TX 0x800 /* open the device in DMA mode */ 316``` 317 318>If the upper application needs to set the device's receive callback function, it must open the device as RT_DEVICE_FLAG_INT_RX or RT_DEVICE_FLAG_DMA_RX, otherwise the callback function will not be called. 319 320After the application opens the device to complete reading and writing, if no further operations are needed, you can close the device using the following functions: 321 322```c 323rt_err_t rt_device_close(rt_device_t dev); 324``` 325 326|**Parameters** |**Description** | 327|------------|------------------------------------| 328| dev | device handle | 329|**Return** | -- | 330| RT_EOK | device successfully closed | 331| \-RT_ERROR | device has been completely closed and cannot be closed repeatedly | 332| Other Error Code | failed to close device | 333 334>Device interfaces `rt_device_open()` and `rt_device_close()` need to used in pairs. Open a device requires close the device, so that the device will be completely closed, otherwise the device will remain on. 335 336## Control Device 337 338By commanding the control word, the application can also control the device with the following function: 339 340```c 341rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg); 342``` 343 344|**Parameters** |**Description** | 345|-------------|--------------------------------------------| 346| dev | device handle | 347| cmd | command control word, this parameter is usually related to the device driver | 348| arg | controlled parameter | 349|**Return** | -- | 350| RT_EOK | function executed successfully | 351| -RT_ENOSYS | execution failed, dev is empty | 352| Other Error Code | execution failed | 353 354The generic device command for the parameter `cmd` can be defined as follows: 355 356```c 357#define RT_DEVICE_CTRL_RESUME 0x01 /* resume device */ 358#define RT_DEVICE_CTRL_SUSPEND 0x02 /* suspend device */ 359#define RT_DEVICE_CTRL_CONFIG 0x03 /* configure device */ 360#define RT_DEVICE_CTRL_SET_INT 0x10 /* set interrupt */ 361#define RT_DEVICE_CTRL_CLR_INT 0x11 /* clear interrupt */ 362#define RT_DEVICE_CTRL_GET_INT 0x12 /* obtain interrupt status */ 363``` 364 365## Read and Write Device 366 367Application can read data from the device by the following function: 368 369```c 370rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size); 371``` 372 373|**Parameters** |**Description** | 374|--------------------|--------------------------------| 375| dev | device handle | 376| pos | read data offset | 377| buffer | memory buffer pointer, the data read will be saved in the buffer | 378| size | size of the data read | 379|**Return** | -- | 380| Actual Size of the Data Read | If it is a character device, the return size is in bytes. If it is a block device, the returned size is in block units. | 381| 0 | need to read the current thread's errno to determine the error status | 382 383Calling this function will read the data from the dev device and store it in the buffer. The maximum length of this buffer is *size*, and *pos* has different meanings depending on the device class. 384 385Writing data to the device can be done by the following function: 386 387```c 388rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size); 389``` 390 391|**Parameters** |**Description** | 392|--------------------|--------------------------------| 393| dev | device handle | 394| pos | write data offset | 395| buffer | memory buffer pointer, placing the data to be written in | 396| size | size of the written data | 397|**Return** | -- | 398| Actual Size of the Data Written | If it is a character device, the return size is in bytes. If it is a block device, the returned size is in block units. | 399| 0 | need to read the current thread's errno to determine the error status | 400 401Calling this function will write the data in the buffer to the *dev* device . The maximum length of the written data is *size*, and *pos* has different meanings depending on the device class. 402 403## Data Transceiving and Call-back 404 405When the hardware device receives the data, the following function can be used to call back another function to set the data receiving indication to notify the upper application thread that the data arrives: 406 407```c 408rt_err_t rt_device_set_rx_indicate(rt_device_t dev, rt_err_t (*rx_ind)(rt_device_t dev,rt_size_t size)); 409``` 410 411|**Parameters**|**Description** | 412|----------|--------------| 413| dev | device handle | 414| rx_ind | callback function pointer | 415|**Return**| -- | 416| RT_EOK | set successfully | 417 418The callback of this function will be provided by the user. When the hardware device receives the data, it will perform the callback function and pass the received data length to the upper layer application in the *size* parameter. The upper application thread should read the data from the device as soon as it receives the indication. 419 420When the application calls `rt_device_write()` to write data, if the bottom hardware can support automatic sending, the upper application can set a callback function. This callback function is called after the bottom hardware data has been sent (for example, when the DMA transfer is complete or the FIFO has been written to complete, triggered interrupt). Use the following function to set the device with a send completion indication. The function parameters and return values are as follows: 421 422```c 423rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer)); 424``` 425 426|**Parameters**|**Description** | 427|----------|--------------| 428| dev | device handle | 429| tx_done | callback function pointer | 430|**Return**| -- | 431| RT_EOK | set successfully | 432 433When this function is called, the callback function is provided by the user. When the hardware device sends the data, the driver calls back the function and passes the sent data block address buffer as a parameter to the upper application. When the upper layer application (thread) receives the indication, it will release the buffer memory block or use it as the buffer for the next write data according to the condition of sending the buffer. 434 435## Access Device Sample 436 437The following code is an example of accessing a device. First, find the watchdog device through the `rt_device_find()` port, obtain the device handle, then initialize the device through the `rt_device_init()` port, and set the watchdog device timeout through the `rt_device_control()`port. 438 439```c 440#include <rtthread.h> 441#include <rtdevice.h> 442 443#define IWDG_DEVICE_NAME "iwg" 444 445static rt_device_t wdg_dev; 446 447static void idle_hook(void) 448{ 449 /* feed the dog in the callback function of the idle thread */ 450 rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_KEEPALIVE, NULL); 451 rt_kprintf("feed the dog!\n "); 452} 453 454int main(void) 455{ 456 rt_err_t res = RT_EOK; 457 rt_uint32_t timeout = 1000; /* timeout */ 458 459 /* find the watchdog device based on the device name, and obtain the device handle */ 460 wdg_dev = rt_device_find(IWDG_DEVICE_NAME); 461 if (!wdg_dev) 462 { 463 rt_kprintf("find %s failed!\n", IWDG_DEVICE_NAME); 464 return -RT_ERROR; 465 } 466 /* initialize device */ 467 res = rt_device_init(wdg_dev); 468 if (res != RT_EOK) 469 { 470 rt_kprintf("initialize %s failed!\n", IWDG_DEVICE_NAME); 471 return res; 472 } 473 /* set watchdog timeout */ 474 res = rt_device_control(wdg_dev, RT_DEVICE_CTRL_WDT_SET_TIMEOUT, &timeout); 475 if (res != RT_EOK) 476 { 477 rt_kprintf("set %s timeout failed!\n", IWDG_DEVICE_NAME); 478 return res; 479 } 480 /* set idle thread callback function */ 481 rt_thread_idle_sethook(idle_hook); 482 483 return res; 484} 485``` 486 487