本帖最后由 xiaolinen 于 2023-8-20 17:34 编辑
读《RT-Thread设备驱动开发指南》--- I/O设备框架
前言:
首先,作为一个正在工作中学习和使用RT-Thread的开发者,非常感谢论坛和RT-Thread提供这么一个机会,让自己拿到这本书,从而进行进一步的学习,也祝平台和RT-Thread的发展更上一层楼!!!此外,帖子只是自己的学习笔记,不足之处,请多多指教。
第一部分:I/O设备模型认知
I/O设备框架是什么:设备框架就是针对某一类外设,抽象出来一套统一的操作方法以及接入标准。注:所说的I/O设备指的是输入/输出设备,不是GPIO(不要问为什么在这里写这么一句,因为我有一个朋友曾经就有这样的疑惑)。
I/O设备框架有什么:I/O设备框架位于硬件和应用层之间,总共分为三层,从上到下分别是I/O设备管理层,设备驱动框架层,设备驱动层。具体如下如所示:
图中提到的应用层:也就是我们自己的业务逻辑程序,比如:众所周知的main.c文件。在这里,我们通过调用I/O设备管理层的函数接口,实现我们想要的功能,比如:点个灯。
图中提到的I/O设备管理层:在这一层,RT-Thread实现对设备驱动程序的封装,对应着工程中的device.c文件。实现对上为应用层提供统一的函数接口,使得设备驱动程序升级时,不会对应用层产生影响;对下通过设备驱动框架层操作硬件的动作。此外,在这一层包含rt_device_find,open,read,write,close,register等管理接口!
图中提到的设备驱动框架:在这一层进行对同类硬件设备驱动的抽象,将不同厂家的同类硬件设备驱动中相同的部分抽取出来,将不同部分留出接口,由驱动程序实现,也就是求同存异,最大力度的统一格式再使用!!!比如:serial.c,adc.c等文件。
图中提到出的设备驱动层:在这一层通过程序,实现对硬件设备的访问功能。负责I/O设备的创建和注册功能;比如:drv_gpio.c,drv_adc.c,drv_usart.c等文件。
这部分的注册功能有两种实现方式:
1)使用I/O设备管理接口直接注册,通过rt_device_register()接口实现;流程如下图所示:
2)通过设备驱动框架层提供的注册函数进行注册,注册函数名一般为rt_hw_xxx_register(),其后,设备驱动框架层的注册函数调用rt_device_register()接口,实现注册功能;流程如下图所示:
图中提到出的硬件:比如:单片机自带的外设,外接的传感器,FLASH芯片等等。
I/O设备框架的优势是什么:
一开始自己也认为在裸机程序中,直接调用驱动程序,多么直接,多么干脆,为什么要费劲巴拉的使用实时系统呢?为什么还要了解这个I/O设备框架呢?但是,经历了这几年的换芯潮之后,现在已经转变了自己的想法,不得不说有一个统一的框架确实香!RT-Thread实时系统降低了代码的耦合性,复杂性,提高了系统的可靠性,易维护性,在这其中呢,因为有I/O设备框架的存在,不管使用哪一款MCU,只要应用层调用的对设备操作的函数保持不变,那么开发人员就只需要修改驱动代码,从而节省一部分时间,去做其他有意义的事情。
第二部分:I/O设备模型使用准备
I/O设备管理接口:
1)创建设备:
rt_device_t rt_device_create(int type, int attach_size);
2)销毁设备:
void rt_device_destroy(rt_device_t device);
3)注册设备:
rt_err_t rt_device_register(rt_device_t dev, const char* name, rt_uint8_t flags);
4)注销设备:
rt_err_t rt_device_unregister(rt_device_t dev);
5)查找设备:
rt_device_t rt_device_find(const char* name);
6)初始化设备:
rt_err_t rt_device_init(rt_device_t dev);
7)打开设备:
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflags);
8)关闭设备:
rt_err_t rt_device_close(rt_device_t dev);
9)控制设备:
rt_err_t rt_device_control(rt_device_t dev, rt_uint8_t cmd, void* arg);
10)读设备:
rt_size_t rt_device_read(rt_device_t dev, rt_off_t pos,void* buffer, rt_size_t size);
11)写设备:
rt_size_t rt_device_write(rt_device_t dev, rt_off_t pos,const void* buffer, rt_size_t size);
12)数据发送回调函数:
rt_err_t rt_device_set_tx_complete(rt_device_t dev, rt_err_t (*tx_done)(rt_device_t dev,void *buffer));
13)数据接收回调函数:
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));
注:上面只是记录了函数接口,参数和详细说明,请移步RT-Thread官网:https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/device/device
第三部分:I/O设备模型实操(UART)
使用流程:注册-》查找-》打开-》读取或发送
1)注册串口设备:
注:串口的参数配置保存在uart_obj数组中;INIT_BOARD_EXPORT(rt_hw_usart_init)为自动初始化(关于RT-Thread自动初始化机制更多了解,请移步: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)查找设备:
#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)打开设备
/*
功能:以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)设置数据接收回调函数:
/*
功能:传感器串口的接受回调函数
*/
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)接收数据解析函数:
注:因为传感器部分使用到了软件包Agile Modbus,所以串口接收到数据后,调用了下面的函数进行解析:
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)采集到的数据记录:
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
结束语:
这是阅读《RT_Thread设备驱动开发指南》的第一篇笔记,结合外接的温湿度传感器,进行了一个小实验,不足之处,还请大家多多指正!