This post was last edited by xiaolinen on 2023-8-20 17:34
Read "RT-Thread Device Driver Development Guide" --- I/O Device Framework
Preface:
First of all, as a developer who is learning and using RT-Thread at work, I am very grateful to the forum and RT-Thread for providing me with such an opportunity to get this book so that I can learn more. I also wish the platform and RT-Thread will develop to a higher level!!! In addition, this post is just my own study notes. Please give me more advice if there are any shortcomings.
Part 1: I/O device model recognition
What is the I/O device framework: The device framework is to abstract a unified set of operation methods and access standards for a certain type of peripherals. Note: The I/O devices mentioned here refer to input/output devices, not GPIO (don’t ask why I write this here, because a friend of mine once had such doubts).
What is the I/O device framework: The I/O device framework is located between the hardware and application layers, and is divided into three layers, from top to bottom, namely the I/O device management layer, the device driver framework layer, and the device driver layer. The details are as follows:
The application layer mentioned in the figure is our own business logic program, such as the well-known main.c file. Here, we call the function interface of the I/O device management layer to implement the functions we want, such as turning on a light.
The I/O device management layer mentioned in the figure: In this layer, RT-Thread implements the encapsulation of the device driver, which corresponds to the device.c file in the project. It provides a unified function interface for the application layer, so that when the device driver is upgraded, it will not affect the application layer; it operates the hardware through the device driver framework layer. In addition, this layer includes management interfaces such as rt_device_find, open, read, write, close, and register!
The device driver framework mentioned in the figure: In this layer, the drivers of similar hardware devices are abstracted, the same parts of the drivers of similar hardware devices from different manufacturers are extracted, and the different parts are left as interfaces to be implemented by the driver. In other words, seek common ground while reserving differences, and use the most unified format!!! For example: serial.c, adc.c and other files.
The device driver layer mentioned in the figure : This layer implements the access function to the hardware device through the program. It is responsible for the creation and registration of I/O devices; for example: drv_gpio.c, drv_adc.c, drv_usart.c and other files.
There are two ways to implement this part of the registration function:
1) Use the I/O device management interface to register directly, implemented through the rt_device_register() interface; the process is shown in the following figure:
2) Register through the registration function provided by the device driver framework layer. The registration function name is generally rt_hw_xxx_register(). Afterwards, the registration function of the device driver framework layer calls the rt_device_register() interface to implement the registration function. The process is shown in the following figure:
The hardware mentioned in the figure : for example: the peripherals of the microcontroller, external sensors, FLASH chips, etc.
What are the advantages of I/O Device Framework:
At first, I thought that it was so straightforward to call the driver directly in the bare metal program. Why should I use the real-time system? Why should I understand the I/O device framework? However, after experiencing the chip replacement trend in the past few years, I have changed my mind. I have to say that it is really good to have a unified framework! The RT-Thread real-time system reduces the coupling and complexity of the code, improves the reliability and maintainability of the system. Among them, because of the existence of the I/O device framework, no matter which MCU is used, as long as the function of the device operation called by the application layer remains unchanged, the developer only needs to modify the driver code, thus saving some time to do other meaningful things .
Part 2: Preparation for using the I/O device model
I/O device management interface:
1) Create a device:
rt_device_t rt_device_create(int type, int attach_size);
2) Destruction of equipment:
void rt_device_destroy(rt_device_t device);
3) Register the device:
rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);
4) Unregister the device:
rt_err_t rt_device_unregister(rt_device_t dev);
5) Find the device:
rt_device_t rt_device_find(const char* name);
6) Initialize the device:
rt_err_t rt_device_init(rt_device_t dev);
7) Turn on the device:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
8) Turn off the device:
rt_err_t rt_device_close(rt_device_t dev);
9) Control equipment:
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
10) Read device:
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
11) Write device:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);
12) Data sending callback function:
rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
13) Data receiving callback function:
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));
Note: The above only records the function interface, parameters and detailed description. Please go to the RT-Thread official website: https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/device
Part 3: I/O Device Model Practice (UART)
Usage process: Register -> Search -> Open -> Read or Send
1) Register serial port device:
Note: The parameter configuration of the serial port is saved in the uart_obj array; INIT_BOARD_EXPORT(rt_hw_usart_init) is automatic initialization (for more information about RT-Thread automatic initialization mechanism, please go to: https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/basic/basic?id=rt-thread-%E8%87%AA%E5%8A%A8%E5%88%9D%E5%A7%8B%E5%8C%96%E6%9C%BA%E5%88%B6 );
/*
功能:设置所需串口的参数,注册串口
*/
int rt_hw_usart_init(void)
{
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
stm32_uart_get_dma_config();
for (rt_size_t i = 0; i < sizeof(uart_obj) / sizeof(struct stm32_uart); i++)
{
/* init UART object */
uart_obj[i].config = &uart_config[i];
uart_obj[i].serial.ops = &stm32_uart_ops;
uart_obj[i].serial.config = config;
/* register UART device */
result = rt_hw_serial_register(&uart_obj[i].serial, uart_obj[i].config->name,
RT_DEVICE_FLAG_RDWR
| RT_DEVICE_FLAG_INT_RX
| RT_DEVICE_FLAG_INT_TX
| uart_obj[i].uart_dma_flag
, NULL);
RT_ASSERT(result == RT_EOK);
}
return result;
}
INIT_BOARD_EXPORT(rt_hw_usart_init);
2) Find the device:
#define PRO_SENSOR_UART "uart1"
/*
功能:查找传感器串口
*/
rt_device_t device = rt_device_find(PRO_SENSOR_UART);
if (!device){
LOG_E("find %s failed!\n", PRO_SENSOR_UART);
return RT_ERROR;
}
3) Turn on the device
/*
功能:以RT_DEVICE_FLAG_DMA_RX方式打开串口
*/
if (rt_device_open(device, RT_DEVICE_FLAG_DMA_RX) != RT_EOK){
LOG_E(" %s device open error,please check ...", PRO_SENSOR_UART);
}
4) Set the data receiving callback function:
/*
功能:传感器串口的接受回调函数
*/
static rt_err_t sensor_uart_recv_callfunc(rt_device_t dev, rt_size_t size)
{
if(size > 0){
rt_sem_release(&rx_sem);
}
return RT_EOK;
}
/*
功能:设置串口的回调函数
*/
if(rt_device_set_rx_indicate(device, sensor_uart_recv_callfunc) != RT_EOK){
LOG_E("uart_recv callfunc set error ,please check ...");
return false;
}
5) Receive data parsing function:
Note: Because the sensor part uses the software package Agile Modbus, the following function is called for parsing after the serial port receives the data:
bool pro_sensormcu_mosbus_prase(rt_uint8_t *inbuf,rt_uint16_t length)
{
rt_uint16_t hold_register[HOLD_REGISTER_NUM];
ctx->read_bufsz = length;
rt_memcpy(ctx->read_buf,inbuf,ctx->read_bufsz);
int rc = agile_modbus_deserialize_read_registers(ctx, ctx->read_bufsz, hold_register);
if (rc < 0) {
LOG_W("Receive failed.");
if (rc != -1)
LOG_W("Error code:%d", -128 - rc);
LOG_I("Hold Registers:");
for (int i = 0; i < HOLD_REGISTER_NUM; i++){
LOG_I("Register [%d]: 0x%04X", i, hold_register[i]);
}
return false;
}
LOG_I("Receive success Hold Registers:");
for (int i = 0; i < HOLD_REGISTER_NUM; i++){
LOG_I("Register [%d]: 0x%04X", i, hold_register[i]);
}
return true;
}
6) Collected data records:
ph_sensortwomcu1: Receive success Hold Registers:
ph_sensortwomcu1: Register [0]: 0x026C
ph_sensortwomcu1: Register [1]: 0x011A
ph_sensortwomcu1: Receive success Hold Registers:
ph_sensortwomcu1: Register [0]: 0x026B
ph_sensortwomcu1: Register [1]: 0x0119
ph_sensortwomcu1: Receive success Hold Registers:
ph_sensortwomcu1: Register [0]: 0x0268
ph_sensortwomcu1: Register [1]: 0x0118
ph_sensortwomcu1: Receive success Hold Registers:
ph_sensortwomcu1: Register [0]: 0x0249
ph_sensortwomcu1: Register [1]: 0x0118
ph_sensortwomcu1: Receive success Hold Registers:
ph_sensortwomcu1: Register [0]: 0x024A
ph_sensortwomcu1: Register [1]: 0x0117
Conclusion:
This is the first note after reading the "RT_Thread Device Driver Development Guide". I conducted a small experiment with an external temperature and humidity sensor. If there are any shortcomings, please correct me!