文章目录
一、驱动分层思想
通过前面对RT-Thread设备模型框架,以及UART、IIC、SPI 等设备驱动实现过程的介绍,我们应该对驱动分层思想并不陌生了。操作系统为什么对设备驱动采用分层管理呢?驱动分层有什么好处呢?
在介绍驱动分层的好处前,我们先看看著名的TCP/IP协议栈分层模型:
TCP/IP协议栈每层都有自己独特的作用:
- 网络接口层负责对网卡硬件设备的访问,在进行网卡驱动开发时,需要将网卡硬件的属性、接口等信息注册到网络接口层;
- 网络层则负责网际寻址与路由,负责两个主机之间的寻址与连接;
- 传输层则负责两个应用程序端口之间的连接与数据传输;
- 应用层则负责网络数据格式的定义,并向上层APP提供网络访问的接口等。
类比TCP/IP协议的分层思想,不难得知操作系统驱动分层的好处,再参照RT-Thread I/O 设备模型框架:
RT-Thread I/O设备模型框架位于硬件和应用程序之间,共分成三层,每一层也都有自己的作用:
- 设备驱动层负责对各种硬件设备的访问,在进行设备驱动开发时,需要将硬件设备的属性配置、访问接口等信息注册到上面的设备驱动框架层;
- 设备驱动框架层负责定义主机控制器(比如CPU、MCU)与外设之间传输数据的格式和规则(比如总线通信协议中与硬件无关的上层协议),并按照上面 I/O 设备管理层要求的接口形式,向上层注册设备对象与访问接口;
- I/O 设备管理层则将不同总线设备的访问接口封装为统一的标准接口,让上面的应用程序可以通过该层提供的标准接口访问底层设备,设备驱动程序的升级、更替不会对上层应用产生影响。
协议和驱动虽然分层管理,每一层专注完成自己的事情,但层与层之间想要协同工作,还需要留出各自的接口用来交互资源信息等,最常见的接口就是设备对象的注册或初始化,当然也包括设备访问接口的注册(包含在设备对象的注册过程中)。RT-Thread创建设备对象、向上面的 I/O 设备管理器注册设备对象和访问接口、应用程序通过设备对象和接口访问设备的一般过程如下所示:
创建设备实际上就是在设备驱动层创建一个设备对象,并将该硬件设备的属性信息(比如基地址、中断号、时钟、DMA等)与访问接口保存到该设备对象结构体或类中,将初始化后的设备对象向上注册到 I/O 设备管理器中。
既然可以将设备对象向上注册,又可以从上层访问该设备对象,这就要求不同层级描述设备的结构体或类相互兼容,存在自上而下的继承关系。上层保存同类设备的通用属性和访问接口,下层增加硬件设备的专有属性并完成硬件访问接口的实现,就像下面这样:
下面分别用前面介绍过的串口设备、I2C设备、SPI设备为例,回顾下具体的设备驱动是如何将驱动分层思想实现在代码中的。
1.1 UART设备驱动分层
每一层都有自己的设备描述结构和接口函数集合,每一层的接口函数集合由低一层实现并注册。在当前层直接调用,用来实现更上层的接口函数集合。
1.1.1 串口设备驱动框架层
串口设备的驱动框架层提供的设备描述结构和接口函数集合如下:
// .\rt-thread-4.0.1\components\drivers\include\drivers\serial.h
struct rt_serial_device
{
struct rt_device parent;
const struct rt_uart_ops *ops;
struct serial_configure config;
void *serial_rx;
void *serial_tx;
};
typedef struct rt_serial_device rt_serial_t;
struct rt_uart_ops
{
rt_err_t (*configure)(struct rt_serial_device *serial, struct serial_configure *cfg);
rt_err_t (*control)(struct rt_serial_device *serial, int cmd, void *arg);
int (*putc)(struct rt_serial_device *serial, char c);
int (*getc)(struct rt_serial_device *serial);
rt_size_t (*dma_transmit)(struct rt_serial_device *serial, rt_uint8_t *buf, rt_size_t size, int direction);
};
串口设备框架层使用rt_uart_ops接口函数实现 I / O 设备管理层的接口函数集合rt_device_ops,并将实现的接口函数集合serial_ops注册到上层的过程如下:
// .\rt-thread-4.0.1\components\drivers\serial\serial.c
#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops serial_ops =
{
rt_serial_init,
rt_serial_open,
rt_serial_close,
rt_serial_read,
rt_serial_write,
rt_serial_control
};
#endif
/*
* serial register
*/
rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
const char *name,
rt_uint32_t flag,
void *data)
{
rt_err_t ret;
struct rt_device *device;
RT_ASSERT(serial != RT_NULL);
device = &(serial->parent);
device->type = RT_Device_Class_Char;
device->rx_indicate = RT_NULL;
device->tx_complete = RT_NULL;
#ifdef RT_USING_DEVICE_OPS
device->ops = &serial_ops;
#else
......
#endif
device->user_data = data;
/* register a character device */
ret = rt_device_register(device, name, flag);
#if defined(RT_USING_POSIX)
/* set fops */
device->fops = &_serial_fops;
#endif
return ret;
}
接口函数集合serial_ops中的函数实现最终都是通过调用rt_uart_ops接口函数实现的,rt_uart_ops接口函数则由更底层的UART设备驱动层来实现并注册。
1.1.2 串口设备驱动层
串口设备驱动层提供的设备描述结构如下:
// .\libraries\HAL_Drivers\drv_usart.h
/* stm32 uart dirver class */
struct stm32_uart
{
UART_HandleTypeDef handle;
struct stm32_uart_config *config;
#ifdef RT_SERIAL_USING_DMA
struct
{
DMA_HandleTypeDef handle;
rt_size_t last_index;
} dma;
#endif
rt_uint8_t uart_dma_flag;
struct rt_serial_device serial;
};
// .\libraries\HAL_Drivers\drv_usart.c
static struct stm32_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {
0};
static struct stm32_uart_config uart_config[] =
{
#ifdef BSP_USING_UART1
{
.name = "uart1",
.Instance = USART1,
.irq_type = USART1_IRQn,
}
#endif
......
};
stm32_uart结构中包含的UART_HandleTypeDef由芯片厂商ST提供的标准库HAL提供,UART驱动层用以实现上层rt_uart_ops接口函数的基础函数也由HAL库提供,可能用到的HAL库函数如下:
// .\libraries\STM32L4xx_HAL\STM32L4xx_HAL_Driver\Inc\stm32l4xx_hal_uart.h
HAL_StatusTypeDef HAL_UART_Init(UART_HandleTypeDef *huart);
HAL_StatusTypeDef HAL_UART_DeInit(UART_HandleTypeDef *huart);
HAL_StatusTypeDef HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_UART_Transmit_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_IT(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Transmit_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_UART_Receive_DMA(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size);
void HAL_UART_IRQHandler(UART_HandleTypeDef *huart);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void HAL_UART_ErrorCallback(UART_HandleTypeDef *huart);
HAL_UART_StateTypeDef HAL_UART_GetState(UART_HandleTypeDef *huart);
uint32_t HAL_UART_GetError(UART_HandleTypeDef *huart);
串口设备驱动层使用HAL库函数实现串口设备框架层的接口函数集合rt_uart_ops,并将实现的接口函数集合stm32_uart_ops注册到上层的过程如下:
// .\libraries\HAL_Drivers\drv_usart.c
static const struct rt_uart_ops stm32_uart_ops =
{
.configure = stm32_configure,
.control = stm32_control,
.putc = stm32_putc,
.getc = stm32_getc,
};
int rt_hw_usart_init(void)
{
rt_size_t obj_num = sizeof(uart_obj) / sizeof(struct stm32_uart);
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
rt_err_t result = 0;
stm32_uart_get_dma_config();
for (int i = 0; i < obj_num; i++)
{
uart_obj[i].config = &uart_config[i];
uart_obj[i].serial.ops = &stm32_uart_ops;
uart_obj[i].serial.config = config;
#if defined(RT_SERIAL_USING_DMA)
if(uart_obj[i].uart_dma_flag)
{
/* 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_DMA_RX
,&uart_obj[i]);
}
else
#endif
{
/* 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
,&uart_obj[i]);
}
RT_ASSERT(result == RT_EOK);
}
return result;
}
函数rt_hw_usart_init内部有一个for循环,可以将所有启用的串口设备全部初始化并注册到 I/O 设备管理层。启用串口设备也比较简单,先使用CubeMX配置好要启用的串口设备引脚,再在Kconfig和menuconfig中配置并使能相关的宏定义就可以了。rt_hw_usart_init函数已经在board.c文件中的rt_hw_board_init()函数内部被调用(需要定义宏RT_USING_SERIAL),在博客系统启动与初始化过程中有介绍。
1.1.3 串口设备中断回调支持
到这里就可以通过 I/O 设备管理接口rt_device_ops来访问uart串口设备了,还记得rt_device设备描述结构中有两个中断回调函数,I/O设备管理层也有两个函数接口分别用来设置这两个可由用户自定义的中断回调函数吗?
// .\rt-thread-4.0.1\include\rtdef.h
/* Device structure */
struct rt_device
{
......
/* device call back */
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;
......
};
// .\rt-thread-4.0.1\include\rtthread.h
rt_err_t
<