【RT-Thread】UART 设备源码分析

官网介绍 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的初始化代码(如果使用了其它串口)
  }

}

总结

在这里插入图片描述

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值