官网介绍 I/O 设备模型框架如下图:
但看到官网写道 :“设备驱动层是一组驱使硬件设备工作的程序,实现访问硬件设备的功能。它负责创建和注册 I/O 设备,对于操作逻辑简单的设备,可以不经过设备驱动框架层,直接将设备注册到 I/O 设备管理器中”, 这句话不是很理解,因为我在 PIN 设备源码分析 中并没有看到 设备驱动框架层。直到我分析 UART 设备源码的时候才了解 设备驱动框架层。
本篇文章就学习一下 RT-Thread 的 UART 设备驱动。
裸跑时的 UART 驱动代码
在程序裸跑时,我们对 UART 的驱动一般流程如下:
1,配置 UART 参数,如果用到中断/DMA,也在这里配置;
2,初始化 UART 用到的 I/O 口;
3,在主函数中执行 1,2 两条,相当于完成 UART 串口的初始化功能。
4,如果用到中断/DMA,实现中断服务例程。
下面以 STM32 的 HAL 库来实际看一下:
void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 115200;
huart1.Init.WordLength = UART_WORDLENGTH_8B;
huart1.Init.StopBits = UART_STOPBITS_1;
huart1.Init.Parity = UART_PARITY_NONE;
huart1.Init.Mode = UART_MODE_TX_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
HAL_UART_Receive_IT(&huart1, &Uart1_Recv, 1);
}
上面函数说是配置 USART1 相关参数,如波特率为115200,数据位为8位,1位停止位……,然后调用 HAL_UART_Init() 对其初始化,在 HAL_UART_Init() 函数中会调用 HAL_UART_MspInit(huart)函数,这个函数内容如下:
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
if(uartHandle->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* USART1 clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = MCU1_U1_TX_Pin|MCU1_U1_RX_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 DMA Init */
/* USART1_RX Init */
hdma_usart1_rx.Instance = DMA1_Channel5;
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_rx.Init.Mode = DMA_NORMAL;
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_HIGH;
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
/* USART1_TX Init */
hdma_usart1_tx.Instance = DMA1_Channel4;
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_usart1_tx.Init.Mode = DMA_NORMAL;
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_MEDIUM;
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
{
_Error_Handler(__FILE__, __LINE__);
}
__HAL_LINKDMA(uartHandle,hdmatx,hdma_usart1_tx);
//USART1->CR1 |= 0x00000010; //开启串口空闲中断
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
else if(uartHandle->Instance==USART2)
{
//...
}
else if(uartHandle->Instance==USART3)
{
//....
}
}
在 HAL_UART_MspInit() 函数中会初始化串口用到的 IO 串,配置中断/DMA 等(如果用到的话)。
然后需要用户自行实现中断服务例程:
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
用户实现需要实现的是 HAL_UART_IRQHandler(&huart1); 这个回调函数。
上面的代码是 STM32 HAL 库的相关实现方式,不同的芯片方式也不尽相同,但基本流程一样。
RT-Thread 中的 UART 驱动代码
rt_hw_usart_init() 函数
我们知道,RT-Thread 的启动流程是:先调用 components.c 文件中的 rtthread_startup() 函数,此函数接着调用 rt_hw_board_init() 函数(定义在 drv_common.c 文件中),而 rt_hw_board_init() 函数中会调用 rt_hw_usart_init() 函数,此函数就是串口初始化函数,它定义在 drv_usart.c 文件中。用下面图片可以更清楚的看出调用流程:
接下来就看看 rt_hw_usart_init() 此函数初始化了什么? 函数在 drv_uart.c 文件中,内容如下:
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;
/* 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;
}
第 3 行,看下 uart_obj 是什么?内容如下:
static struct stm32_uart uart_obj[sizeof(uart_config) / sizeof(uart_config[0])] = {0};
uart_obj 是 stm32_uart 类型的结构体数组,其数组的长度为:sizeof(uart_config)/sizeof(uart_config[0]),那先来看看这个 uart_config 是什么?
static struct stm32_uart_config uart_config[] =
{
#ifdef BSP_USING_UART1
UART1_CONFIG,
#endif
#ifdef BSP_USING_UART2
UART2_CONFIG,
#endif
#ifdef BSP_USING_UART3
UART3_CONFIG,
#endif
#ifdef BSP_USING_UART4
UART4_CONFIG,
#endif
#ifdef BSP_USING_UART5
UART5_CONFIG,
#endif
#ifdef BSP_USING_UART6
UART6_CONFIG,
#endif
#ifdef BSP_USING_UART7
UART7_CONFIG,
#endif
#ifdef BSP_USING_UART8
UART8_CONFIG,
#endif
#ifdef BSP_USING_LPUART1
LPUART1_CONFIG,
#endif
};
这就很明显了,比如我现在利用 env 工具使用了串口1和串口2 ,那这时 uart_config 的内容相当于如下:
static struct stm32_uart_config uart_config[2] =
{
UART1_CONFIG,
UART2_CONFIG,
};
而此时 uart_obj[sizeof(uart_config) / sizeof(uart_config[0])]
就相当于 uart_obj[2]
而上面结构体里面的 UART1_CONFIG, UART2_CONFIG, 的内容如下:
#if defined(BSP_USING_UART2)
#ifndef UART2_CONFIG
#define UART2_CONFIG \
{ \
.name = "uart2", \
.Instance = USART2, \
.irq_type = USART2_IRQn, \
}
#endif /* UART2_CONFIG */
其中 .Instance = USART2,这里的 USART2 定义为:
#define USART2 ((USART_TypeDef *)USART2_BASE)
#define USART2_BASE (APB1PERIPH_BASE + 0x00004400UL)
#define APB1PERIPH_BASE PERIPH_BASE
#define PERIPH_BASE 0x40000000UL /*!< Peripheral base address in the alias region */
最终可以得出 USART2 = 0x400004400UL,这个是硬件 USART2 外设的偏移地址。
而 .irq_type = USART2_IRQn 中的 USART2_IRQn 定义为:
USART2_IRQn = 38, /*!< USART2 global Interrupt */
这两个变量的值都是与 STM32 芯片型号有关,不同的芯片,其地址可能不同。
现在是搞清了 uart_obj[] 这个结构体数组的长度了,接下来再看一下它的结构体类型 stm32_uart 是什么?
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_rx;
struct
{
DMA_HandleTypeDef handle;
} dma_tx;
#endif
rt_uint16_t uart_dma_flag;
struct rt_serial_device serial;
};
可以看出 stm32_uart 是串口结构体对象,它把关于串口相关的所有配置都集合到这个结构体中了。一个stm32_uart 就可以完整的表示一个串口设备。
其中成员变量 UART_HandleTypeDef handle 是 STM32 官方定义的 串口结构体;成员变量 stm32_uart_config 是串口参数配置,内容如下:
/* stm32 config class */
struct stm32_uart_config
{
const char *name;
USART_TypeDef *Instance;
IRQn_Type irq_type;
struct dma_config *dma_rx;
struct dma_config *dma_tx;
};
stm32_uart 结构体成员变量 rt_uint16_t uart_dma_flag 指示是否使用串口 dma 功能。
stm32_uart 结构体成员变量 rt_serial_device serial 是属于设备驱动框架层的设备对象,其定义在 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;
rt_serial_device 结构体成员 parent 是继承于内核对象;rt_serial_device 结构体成员 ops 是相关操作函数;rt_serial_device 结构体成员 config 为串口配置参数,内容如下:
struct serial_configure
{
rt_uint32_t baud_rate;
rt_uint32_t data_bits :4;
rt_uint32_t stop_bits :2;
rt_uint32_t parity :2;
rt_uint32_t bit_order :1;
rt_uint32_t invert :1;
rt_uint32_t bufsz :16;
rt_uint32_t reserved :6;
};
扯的太远了,都快忘记最开始在干嘛了,所以,画个图帮忙梳理一下:
OK,现在接着看 rt_hw_usart_init(),话说上面看到此函数的第 3 行,现在接着往下看第4行。第4行内容如下:
struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
RT_SERIAL_CONFIG_DEFAULT 所定义的内容如下:
/* Default config for serial_configure structure */
#define RT_SERIAL_CONFIG_DEFAULT \
{ \
BAUD_RATE_115200, /* 115200 bits/s */ \
DATA_BITS_8, /* 8 databits */ \
STOP_BITS_1, /* 1 stopbit */ \
PARITY_NONE, /* No parity */ \
BIT_ORDER_LSB, /* LSB first sent */ \
NRZ_NORMAL, /* Normal mode */ \
RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
0 \
}
接下来第7行,调用函数 stm32_uart_get_dma_config(),此函数内容如下:
static void stm32_uart_get_dma_config(void)
{
#ifdef BSP_USING_UART1
uart_obj[UART1_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART1_RX_USING_DMA
uart_obj[UART1_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
static struct dma_config uart1_dma_rx = UART1_DMA_RX_CONFIG;
uart_config[UART1_INDEX].dma_rx = &uart1_dma_rx;
#endif
#ifdef BSP_UART1_TX_USING_DMA
uart_obj[UART1_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
static struct dma_config uart1_dma_tx = UART1_DMA_TX_CONFIG;
uart_config[UART1_INDEX].dma_tx = &uart1_dma_tx;
#endif
#endif
#ifdef BSP_USING_UART2
uart_obj[UART2_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART2_RX_USING_DMA
uart_obj[UART2_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
static struct dma_config uart2_dma_rx = UART2_DMA_RX_CONFIG;
uart_config[UART2_INDEX].dma_rx = &uart2_dma_rx;
#endif
#ifdef BSP_UART2_TX_USING_DMA
uart_obj[UART2_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
static struct dma_config uart2_dma_tx = UART2_DMA_TX_CONFIG;
uart_config[UART2_INDEX].dma_tx = &uart2_dma_tx;
#endif
#endif
#ifdef BSP_USING_UART3
uart_obj[UART3_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART3_RX_USING_DMA
uart_obj[UART3_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
static struct dma_config uart3_dma_rx = UART3_DMA_RX_CONFIG;
uart_config[UART3_INDEX].dma_rx = &uart3_dma_rx;
#endif
#ifdef BSP_UART3_TX_USING_DMA
uart_obj[UART3_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
static struct dma_config uart3_dma_tx = UART3_DMA_TX_CONFIG;
uart_config[UART3_INDEX].dma_tx = &uart3_dma_tx;
#endif
#endif
#ifdef BSP_USING_UART4
uart_obj[UART4_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART4_RX_USING_DMA
uart_obj[UART4_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
static struct dma_config uart4_dma_rx = UART4_DMA_RX_CONFIG;
uart_config[UART4_INDEX].dma_rx = &uart4_dma_rx;
#endif
#ifdef BSP_UART4_TX_USING_DMA
uart_obj[UART4_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
static struct dma_config uart4_dma_tx = UART4_DMA_TX_CONFIG;
uart_config[UART4_INDEX].dma_tx = &uart4_dma_tx;
#endif
#endif
#ifdef BSP_USING_UART5
uart_obj[UART5_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART5_RX_USING_DMA
uart_obj[UART5_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
static struct dma_config uart5_dma_rx = UART5_DMA_RX_CONFIG;
uart_config[UART5_INDEX].dma_rx = &uart5_dma_rx;
#endif
#ifdef BSP_UART5_TX_USING_DMA
uart_obj[UART5_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
static struct dma_config uart5_dma_tx = UART5_DMA_TX_CONFIG;
uart_config[UART5_INDEX].dma_tx = &uart5_dma_tx;
#endif
#endif
#ifdef BSP_USING_UART6
uart_obj[UART6_INDEX].uart_dma_flag = 0;
#ifdef BSP_UART6_RX_USING_DMA
uart_obj[UART6_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
static struct dma_config uart6_dma_rx = UART6_DMA_RX_CONFIG;
uart_config[UART6_INDEX].dma_rx = &uart6_dma_rx;
#endif
#ifdef BSP_UART6_TX_USING_DMA
uart_obj[UART6_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_TX;
static struct dma_config uart6_dma_tx = UART6_DMA_TX_CONFIG;
uart_config[UART6_INDEX].dma_tx = &uart6_dma_tx;
#endif
#endif
}
这里假如我们只用到了串口2,则此函数去掉条件编译后内容如下:
static void stm32_uart_get_dma_config(void)
{
#ifdef BSP_USING_UART2
uart_obj[UART2_INDEX].uart_dma_flag = 0; //这里值为0说明不使用dma
//#ifdef BSP_UART2_RX_USING_DMA
// uart_obj[UART2_INDEX].uart_dma_flag |= RT_DEVICE_FLAG_DMA_RX;
// static struct dma_config uart2_dma_rx = UART2_DMA_RX_CONFIG;
// uart_config[UART2_INDEX].dma_rx = &uart2_dma_rx;
#endif
}
接下来第9 ~ 22 是 for 循环,循环初始化 uart_obj[] 里面的每一个串口。假如 uart_obj[] 里面只有一个串口2,且不使用 dma 功能,那经过此循环后,uart_obj[0] 这个串口2 的各项内容如下:
然后就是调用 rt_hw_serial_register() 函数,向内核注册这个串口设备。
rt_hw_serial_register() 函数
由上一小节知道,rt_hw_usart_init() 调用了 rt_hw_serial_register() 函数。调用如下:
/* 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);
此函数定义在 serial.c 文件中,接下来就是看看这个函数内容:
/*
* 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
device->init = rt_serial_init;
device->open = rt_serial_open;
device->close = rt_serial_close;
device->read = rt_serial_read;
device->write = rt_serial_write;
device->control = rt_serial_control;
#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;
}
此函数一旦执行成功,那上一小节中的 图1 中的 .parent 变量会有初始化的内容,如下图:
接下来调用 rt_device_register() 函数向内核注册这个串口设备,内核用一个双向链表来管理注册到内核中的设备。
现在梳理一下函数调用过程:
I/O 设备管理接口
通过上一节的内容了解了串口设备相关初始化,及如何注册到内核,这基本上不用用户操作,只要用 env 工具配置好要使用的串口,RT-Thread 会自动给你注册好。现在用户想要操作已注册好的串口该怎么办勒?
RT-Thread 提供的 I/O 设备管理接口来帮助用户访问串口硬件,相关接口如下所示:
函数 | 描述 |
---|---|
rt_device_find() | 查找设备 |
rt_device_open() | 打开设备 |
rt_device_read() | 读取数据 |
rt_device_write() | 写入数据 |
rt_device_control() | 控制设备 |
rt_device_set_rx_indicate() | 设置接收回调函数 |
rt_device_set_tx_complete() | 设备发送完成回调函数 |
rt_device_close() | 关闭设备 |
关于如何利用 I/O 设备管理接口操作串口设备,请参考官方文档 设备和驱动——UART设备
其实 RT-Thread 提供的 I/O 设备管理接口是统一的,不针对某一种设备,意思就是说如果外设是个 ADC 使用 rt_device_open() 等函数对 ADC 进行访问,如果外设是个串口,也是使用这些接口。但除了某些特别简单的外设,如 GPIO 口,比如想访问一个 GPIO 口,RT-Thread 提供的接口是 rt_pin_xxx()。
以下列出了当前 RT-Thread 操作系统中所有的 I/O 设备管理接口(在源码的 rtthread.h 文件中):
/*
* device (I/O) system interface
*/
rt_device_t rt_device_find(const char *name);
rt_err_t rt_device_register(rt_device_t dev,
const char *name,
rt_uint16_t flags);
rt_err_t rt_device_unregister(rt_device_t dev);
rt_device_t rt_device_create(int type, int attach_size);
void rt_device_destroy(rt_device_t device);
rt_err_t rt_device_init_all(void);
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_err_t
rt_device_set_tx_complete(rt_device_t dev,
rt_err_t (*tx_done)(rt_device_t dev, void *buffer));
rt_err_t rt_device_init(rt_device_t dev);
rt_err_t rt_device_open(rt_device_t dev, rt_uint16_t oflag);
rt_err_t rt_device_close(rt_device_t dev);
rt_size_t rt_device_read(rt_device_t dev,
rt_off_t pos,
void *buffer,
rt_size_t size);
rt_size_t rt_device_write(rt_device_t dev,
rt_off_t pos,
const void *buffer,
rt_size_t size);
rt_err_t rt_device_control(rt_device_t dev, int cmd, void *arg);
但并不是所有设备都实现了上面的所有接口,不同的设备只用实现对自己有用的接口。
这里分析一下 RT-Thread 是如何提供统一的 I/O 设备管理接口来对不同的设备进行访问的。
rt_device_xxx() 函数
这里的 rt_device_xxx() 指的是除了 rt_device_find()、rt_device_create()、rt_device_init_all() 这三个函数外的其它函数。
我们会发现 rt_deivce_xxx() 函数有一个共同点,那就是这些函数的第一个参数是 rt_device_t dev。我们知道 rt_device_t 是 struct rt_device 是 RT-Thread 描述一个设备的通用结构体,所有设备都继承于 rt_device 。
现在以 rt_device_init() 函数来学习一下,RT-Thread 是如何使用 rt_device_init() 来操作硬件设备的。
首先上 rt_device_init() 函数源码:
/**
* This function will initialize the specified device
*
* @param dev the pointer of device driver structure
*
* @return the result
*/
rt_err_t rt_device_init(rt_device_t dev)
{
rt_err_t result = RT_EOK;
RT_ASSERT(dev != RT_NULL);
/* get device_init handler */
if (device_init != RT_NULL)
{
if (!(dev->flag & RT_DEVICE_FLAG_ACTIVATED))
{
result = device_init(dev);
if (result != RT_EOK)
{
rt_kprintf("To initialize device:%s failed. The error code is %d\n",
dev->parent.name, result);
}
else
{
dev->flag |= RT_DEVICE_FLAG_ACTIVATED;
}
}
}
return result;
}
可以看到 rt_device_init() 函数就是调用了 device_init(dev) 函数,所以来看一下 device_init() 这个函数是怎么操作硬件的。
在 device.c 文件中有这样的宏定义:
#define device_init (dev->init)
#define device_open (dev->open)
#define device_close (dev->close)
#define device_read (dev->read)
#define device_write (dev->write)
#define device_control (dev->control)
看到这此宏定义,我想是不用解释了,所有的迷雾在看到这几个宏定义的一瞬间都烟消云散了。结合串口,看第二节中的图二,是否嘴角已上扬~
到此,问题就转换成了 rt_serial_init、rt_serial_open、rt_serial_close… … 这类函数是怎么操作硬件的。现在还是以 rt_serial_init() 函数为例说明:
/* RT-Thread Device Interface */
/*
* This function initializes serial device.
*/
static rt_err_t rt_serial_init(struct rt_device *dev)
{
rt_err_t result = RT_EOK;
struct rt_serial_device *serial;
RT_ASSERT(dev != RT_NULL);
serial = (struct rt_serial_device *)dev;
/* initialize rx/tx */
serial->serial_rx = RT_NULL;
serial->serial_tx = RT_NULL;
/* apply configuration */
if (serial->ops->configure)
result = serial->ops->configure(serial, &serial->config);
return result;
}
看到这个函数被定义为 static 所以这个函数用户是不能直接调用的,再看第 19 行,即 result = serial->ops->configure(serial, &serial->config)
是不是又拨开迷雾。对,serial->ops 的一系列函数指针在 rt_hw_usart_init() 的时候都已经指向了 stm32_uart_ops,有下图为证:
而 stm32_uart_ops 定义为下图:
所以最终 rt_serial_init() 调用了 stm32_configure() ,而其它函数也是一样的。
现在就来看一下 stm32_configure() 函数的实现方式:
static rt_err_t stm32_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
struct stm32_uart *uart;
RT_ASSERT(serial != RT_NULL);
RT_ASSERT(cfg != RT_NULL);
uart = rt_container_of(serial, struct stm32_uart, serial);
uart->handle.Instance = uart->config->Instance;
uart->handle.Init.BaudRate = cfg->baud_rate;
uart->handle.Init.HwFlowCtl = UART_HWCONTROL_NONE;
uart->handle.Init.Mode = UART_MODE_TX_RX;
uart->handle.Init.OverSampling = UART_OVERSAMPLING_16;
switch (cfg->data_bits)
{
case DATA_BITS_8:
uart->handle.Init.WordLength = UART_WORDLENGTH_8B;
break;
case DATA_BITS_9:
uart->handle.Init.WordLength = UART_WORDLENGTH_9B;
break;
default:
uart->handle.Init.WordLength = UART_WORDLENGTH_8B;
break;
}
switch (cfg->stop_bits)
{
case STOP_BITS_1:
uart->handle.Init.StopBits = UART_STOPBITS_1;
break;
case STOP_BITS_2:
uart->handle.Init.StopBits = UART_STOPBITS_2;
break;
default:
uart->handle.Init.StopBits = UART_STOPBITS_1;
break;
}
switch (cfg->parity)
{
case PARITY_NONE:
uart->handle.Init.Parity = UART_PARITY_NONE;
break;
case PARITY_ODD:
uart->handle.Init.Parity = UART_PARITY_ODD;
break;
case PARITY_EVEN:
uart->handle.Init.Parity = UART_PARITY_EVEN;
break;
default:
uart->handle.Init.Parity = UART_PARITY_NONE;
break;
}
if (HAL_UART_Init(&uart->handle) != HAL_OK)
{
return -RT_ERROR;
}
return RT_EOK;
}
可以看到,stm32_configure() 设备串口参数与裸机是一样的,而第 54 行的 HAL_UART_Init() 函数会调用 HAL_UART_MspInit() 函数对串口用到的 IO 口作初始化,代码如下:
void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(huart->Instance==USART1)
{
/* USER CODE BEGIN USART1_MspInit 0 */
/* USER CODE END USART1_MspInit 0 */
/* Peripheral clock enable */
__HAL_RCC_USART1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**USART1 GPIO Configuration
PA9 ------> USART1_TX
PA10 ------> USART1_RX
*/
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USART1 interrupt Init */
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
/* USER CODE BEGIN USART1_MspInit 1 */
/* USER CODE END USART1_MspInit 1 */
}
else
{
//其它串口IO的初始化代码(如果使用了其它串口)
}
}