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

2023-05-16

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

}

总结

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【RT-Thread】UART 设备源码分析 的相关文章

  • 转帖:Bash实现“多线程”的例子

    以下文章转自 http hi baidu com wwy1640 blog item a74d8c50a272b86384352443 html Bash实现 多线程 的例子 2007 04 25 22 59 bin bash 2006 7
  • CreateEvent人工重置事件对象

    include
  • 线程进程协程的实现代码

    单线程 import time def run print hello world time sleep 1 if name main for i in range 5 run 多线程 import threading import tim
  • JAVA多线程--信号量(Semaphore)

    简介 信号量 Semaphore 有时被称为信号灯 是在多线程环境下使用的一种设施 它负责协调各个线程 以保证它们能够正确 合理的使用公共资源 一个计数信号量 从概念上讲 信号量维护了一个许可集 如有必要 在许可可用前会阻塞每一个 acqu
  • STM32双串口

    STM32双串口的使用 最近老是需要stm32通过串口去跟WiFi模块 蓝牙模块 openmv进行数据交互 然后需要用到stm32的串口调试 就把这个程序整理成一个工程 方便调试 实验目的 外设模块 WiFi模块 蓝牙模块 openmv 发
  • Java-多线程-给线程命名

    Java 多线程 给线程命名 在Java中 通过继承Thread创建的线程 有以下两种方式可以给线程命名 通过构造器命名 因为线程类继承自Thread类 所有也继承了Thread的name属性 可以通过super的方法调用父类构造器 将na
  • CreateRemoteThread的使用(转载)

    先解释一下远程进程 其实就是要植入你的代码的进程 相对于你的工作进程 如果叫本地进程的话 它就叫远程进程 可理解为宿主 首先介绍一下我们的主要工具CreateRemoteThread 这里先将函数原型简单介绍以下 CreateRemoteT
  • 如何终止一个无限循环线程和 程序退出时销毁线程

    http zhidao baidu com question 299079849 html android 启动了一个子线程 这个子线程是一个死循环 不成的打印 Hello 现在要实现点击一个Button 让这个子线程终止 用什么方法啊 s
  • POSIX线程:API

    一 线程创建与取消 1 线程创建 1 1 线程与进程 相对进程而言 线程是一个更加接近于执行体的概念 它可以与同进程中的其他线程共享数据 但拥有自己的栈空间 拥有独立的执行序列 在串行程序基础上引入线程和进程是为了提高程序的并发度 从而提高
  • 多线程之创建工作者线程和用户界面线程区别

    转帖 部分原创 1 工作者线程倾向于琐碎的处理 与它不同的是 用户界面线程具有自己的界面而且实际上类似于运行其他应用程序 创建线程而不是其他应用程序的好处是线程可与应用程序共享程序空间 这样可以简化线程与应用程序共享数据的功能 2 典型情况
  • Java调用Win API

    官方网站 http jawinproject sourceforge net 把lib文件夹下的jawin jar和jawin stubs jar放到 JAVA HOME jre lib ext 目录下 把bin文件夹下的jawin dll
  • QT实现多线程,以及子线程调用主线程方法与变量

    实现思路 第一步需要将子线程声明为主线程的友元类 第二步是将主线程类对象的地址通过信号槽传递给子线程中创建的对象 使得子线程能访问主线程的数据的 1 子线程 displayresult h 头文件 伪代码 include tabwindow
  • STM32CubeMX HAL库串口+DMA+IDLE空闲中断不定长度数据接收和发送

    本文开发环境 MCU型号 STM32F103ZET6 IDE环境 MDK 5 29 代码生成工具 STM32CubeMx 5 3 0 HAL库版本 STM32Cube FW F1 V1 8 0 STM32Cube MCU Package f
  • Java线程(Thread)生命周期的6种状态

    当线程被创建并启动以后 它既不是一启动就进入了执行状态 也不是一直处于执行状态 在线程的生命周期中 可能处于不同的状态 java lang Thread State 列举出了这6种线程状态 线程状态 导致状态发生条件 New 新建 线程刚被
  • Gem5 中与 ARM 裸机的 UART 通信

    我目前正在使用 Gem5 我必须通过 UART 从我的主机访问 ARMv8 裸机选项 所以我尝试了很多方法 但我还没有准备好 您能否让我知道 如何在裸机类型编程中将主机的串行端口映射到 ARMv8 的串行端口 任何帮助 将不胜感激 工作设置
  • 英特尔伽利略裸机 UART

    我想编写一些 hello world 程序裸机申请于英特尔伽利略木板 当然 使用 UEFI 打印文本 到 UART 1 效果很好 但我想 手动 访问 UART 而不需要 UEFI 的任何帮助 在 QEMU 中我的代码运行良好 h file
  • GATT 配置文件和 UART 服务

    我是开发通过蓝牙连接到外围设备的移动应用程序的新手 我搜索到 GATT 是用于蓝牙LE 通信的相关配置文件 但我们的客户建议我们使用 UART 服务 现在我很困惑 1 这两件事是如何关联的 2 我们是否必须选择其中之一 如果是的话 每一个的
  • 如何将 microbit 与 BLE 连接并监听按钮按下事件?

    2021 年 11 月 28 日编辑 如果您需要使用蓝牙低功耗将 microbit 连接到计算机 并在单击按钮时执行操作 直接跳并跟随 ukBaz https stackoverflow com users 7721752 ukbaz的回答
  • 跨线程操作无效:从创建它的线程以外的线程访问控制“textBox1”[重复]

    这个问题在这里已经有答案了 我想使用 UART 将温度值从微控制器发送到 C 接口并显示温度Label Content 这是我的微控制器代码 while 1 key scan get value of temp if Usart Data
  • Android Things 5​​.1 - 9 位 UART

    我正在尝试使用 Android Things 支持 9 位 UART 当我尝试将数据大小设置为 9 时 我收到 IO 异常 唯一有效的配置是 7 位和 8 位 我知道可以使用奇偶校验错误中断进行 9 位模拟 但在 Android 上 我没有

随机推荐

  • 编码

    UTF 8 UTF 8以字节为单位对Unicode进行编码 从Unicode到UTF 8的编码方式如下 xff1a Unicode编码 十六进制 UTF 8 字节流 二进制 000000 00007F0xxxxxxx000080 0007F
  • tensorflow-gpu1.14 + Win10 + CUDA10.0 + CUDNN7.5.0 + Python3.6 + VS2015安装

    最近学习深度学习 xff0c 在配置环境中的过程中遇到很多问题 xff0c 在这进行总结 xff0c 希望对大家有帮助 一 整个软件安装配置过程 xff0c 很多博客写的很详细 xff0c 附上链接 xff1b https blog csd
  • VS2015下配置海康威视SDK

    1网络摄像头可以在官网下载到SDK开发包 xff0c 进入海康威视官网 xff0c 选择何时的版本 xff0c 点击下载 https www hikvision com cn download 61 html 下载完成进行解压 解压完成 x
  • 如何提升串口响应速度

    最近负责编写公司的工厂模式指令集 xff0c 碰到了一些代码之外的问题 xff0c 困扰了我很久 因为综测那边对串口响应速度的要求很高 xff0c 要求从上位机下发指令开始到上位机接收到完整回复 xff0c 整个过程的响应速度要达到几十个m
  • rv1126 SDK简单编译

    rv1126 SDK简单编译 在工程的根目录下执行命令 source envsetup sh 会出现很多选项 xff0c 选择 rockchip rv1126 rv1109 spi nand 这个选项 xff0c 输入93 我的FLASH是
  • socket套接字编程之UDP协议封装

    1 UDP 协议特点 xff1a 传输层协议 无连接 不可靠传输 面向数据报 2 封装之前先将清楚几个要点 xff1a 2 1网络字节序 xff1a 注意设备的大小端 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出 接收主机把
  • C语言 使用调用函数的方法,将两个字符串连接起来

    因本人才疏学浅 xff0c 见识浅薄 xff0c 有不当之处望指正 xff0c 谢谢 xff01 这次用调用函数的方法 xff0c 连接两个字符串 在被调函数中可以说明形参数组的大小 xff0c 也可以不说明形参数组的大小 例如cat st
  • STL容器特点对比

    1 分类 序列式容器 xff08 sequential container xff09 vector list array deque forward list关联式容器 xff08 associative container xff09
  • 【RT-Thread】PIN 设备源码分析

    目录 1 获取引脚编号2 设置引脚模式3 设置引脚电平4 绑定 PIN 中断回调函数5 使能引脚中断6 总结7 PIN 设备使用示例 关于 RT Thread 的 PIN 设备驱动应用层面的介绍可以直接参考 RT Thread 的官网 xf
  • 基于VSCode的Linux内核调试环境搭建

    1 安装开发工具 span class token function sudo span span class token function apt span span class token function install span b
  • STM32定时器周期任务函数编写

    在STM32中我们对定时器的周期任务有一定的需求 xff0c 但在使用过程中 xff0c 需要将一些任务写到中断中 xff0c 中断函数会看上去比较复杂 xff0c 并且会有好多标志位 以流水灯为例 xff0c 周期为1s 介绍一下我自己的
  • stm32寄存器封装

    文章目录 前言 一 版本一 二 版本二 三 版本三 前言 本文记录的是用stm32开发的时候 一些底层的寄存器封装 固件库是如何帮我们完成这些工作的 一 版本一 代码如下 示例 span class token comment 外设基地址
  • 使用TI的MSP430实现一个单片机与上位机的数传系统。(西安电子科技大学综合应用开发实验)

    题目要求 xff1a 目标 xff1a 智能控制系统 利用单片机 xff08 开发平台任选 xff09 设计并编程实现一个单片机与上位机的数传系统 要求 xff1a 对单片机和PC 手机 单片机之间的通信进行设计 如果大作业没有设计通信部分
  • NVIDIA Jetson Xavier NX 控制GPIO

    NVIDIA Jetson Xavier NX 控制GPIO 文章目录 NVIDIA Jetson Xavier NX 控制GPIO前言一 简介二 代码实例1 gpio h2 gpio cpp 三 拓展 前言 在linux系统中以文件io的
  • NVIDIA Jetson Xavier NX禁用上电自启,使用按键开关机

    NVIDIA Jetson Xavier NX禁用上电自启 xff0c 使用按键开关机 文章目录 NVIDIA Jetson Xavier NX禁用上电自启 xff0c 使用按键开关机前言一 原理二 拓展 前言 NX默认上电自启 xff0c
  • Linux系统设置共享文件夹

    Linux系统设置共享文件夹 文章目录 Linux系统设置共享文件夹一 设置原理二 设置步骤1 安装samba2 创建 设置共享文件夹 三 测试 一 设置原理 基于Ubuntu16 04 xff0c 采用在线安装samba库的方式设置共享文
  • Linux:复位USB设备

    Linux xff1a 复位USB设备 文章目录 Linux xff1a 复位USB设备前言一 基本原理二 代码实例总结 前言 在Ubuntu16 04下开发SDR设备数据处理程序时 xff0c msi sdr设备有时运行几个小时后就会出现
  • Ubuntu Terminal终端默认常用快捷键总结

    Ubuntu Terminal终端默认常用快捷键总结 Ubuntu Terminal终端快捷键默认设置如下 xff0c 不同的发行版本可能有所出入 xff0c 以下快捷键在Ubuntu18 04LTS下可用 1 文件 快捷键说 明Ctrl
  • 基于c++ boost库实现进程管理

    基于c 43 43 boost库实现进程管理 1 前言 基于c 43 43 boost库与Terminator终端 xff0c 实现启动进程 进程运行状态监听 自动重启进程 杀死进程 设置进程环境变量等基础功能 2 原理 启动 杀死进程基于
  • 【RT-Thread】UART 设备源码分析

    官网介绍 I O 设备模型框架如下图 xff1a 但看到官网写道 设备驱动层是一组驱使硬件设备工作的程序 xff0c 实现访问硬件设备的功能 它负责创建和注册 I O 设备 xff0c 对于操作逻辑简单的设备 xff0c 可以不经过设备驱动