RT-Thread uart串口设备驱动代码结构剖析

蔚落 2022-10-30 15:28 444阅读 0赞

硬件测试平台:正点原子潘多拉STM32L4开发板
OS内核版本:4.0.0

注意:下面的示例代码是从原子提供的例程中摘录,因此可能与最新的RT-Thread源码有出入(因为RT-Thread源码在不断的开发维护中)
下面摘录的例程中,关键位置我给出了注释

下面开始正文:

RT-Thread的Finsh串口控制台有个标志性的开头打印信息如下:

  1. \ | /
  2. - RT - Thread Operating System
  3. / | \ 4.0.0 build Dec 18 2018
  4. 2006 - 2018 Copyright by rt-thread team

在components.c中找到rtthread_startup()函数

  1. int rtthread_startup(void)
  2. {
  3. rt_hw_interrupt_disable();
  4. /* board level initialization * NOTE: please initialize heap inside board initialization. */
  5. rt_hw_board_init();
  6. /* show RT-Thread version */
  7. rt_show_version();

rt_show_version();就是打印这段开头信息的。

  1. /** * This function will show the version of rt-thread rtos */
  2. void rt_show_version(void)
  3. {
  4. rt_kprintf("\n \\ | /\n");
  5. rt_kprintf("- RT - Thread Operating System\n");
  6. rt_kprintf(" / | \\ %d.%d.%d build %s\n",
  7. RT_VERSION, RT_SUBVERSION, RT_REVISION, __DATE__);
  8. rt_kprintf(" 2006 - 2018 Copyright by rt-thread team\n");
  9. }
  10. RTM_EXPORT(rt_show_version);

显然,既然能串口打印了,那么必然在这之前已经初始化了该串口,那么rt_show_version()之前的rt_hw_board_init()函数里一定初始化了串口。

  1. void rt_hw_board_init()
  2. {
  3. //...略
  4. /* Second initialize the serial port, so we can use rt_kprintf right away */
  5. #ifdef RT_USING_SERIAL
  6. stm32_hw_usart_init();
  7. #endif
  8. #ifdef RT_USING_CONSOLE
  9. rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
  10. #endif
  11. //...略
  12. }
  13. int stm32_hw_usart_init(void)
  14. {
  15. struct stm32_uart* uarts[] =
  16. {
  17. #ifdef BSP_USING_UART1
  18. &uart1,
  19. #endif
  20. #ifdef BSP_USING_UART2
  21. &uart2,
  22. #endif
  23. #ifdef BSP_USING_UART3
  24. &uart3,
  25. #endif
  26. #ifdef BSP_USING_LPUART1
  27. &lpuart1,
  28. #endif
  29. };
  30. int i;
  31. for (i = 0; i < sizeof(uarts) / sizeof(uarts[0]); i++)
  32. {
  33. struct stm32_uart *uart = uarts[i];
  34. rt_err_t result;
  35. /* register UART device */
  36. result = rt_hw_serial_register(&uart->serial,
  37. uart->uart_name,
  38. #ifdef BSP_UART_USING_DMA_RX
  39. RT_DEVICE_FLAG_DMA_RX |
  40. #endif
  41. RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
  42. uart);
  43. RT_ASSERT(result == RT_EOK);
  44. (void)result;
  45. }
  46. return 0;
  47. }

stm32_hw_usart_init()这个函数注册了使用到的串口设备到设备框架层,注册完就可以直接使用内核通用接口函数操作设备了,比如开启设备,读设备,写设备,通过BSP_USING_UART1这类宏控来控制将要使用哪几个串口。注意,这里只是注册,并没完成真正的底层硬件初始化。真正的完成底层硬件初始化在rt_console_set_device(RT_CONSOLE_DEVICE_NAME);这里面。即调用了打开设备才是完成了串口硬件初始化串口才能正常使用。

  1. /* UART1 device driver structure */
  2. static struct stm32_uart uart1 =
  3. {
  4. { USART1}, // UART_HandleTypeDef UartHandle;
  5. USART1_IRQn, // IRQn_Type irq;
  6. #ifdef BSP_UART_USING_DMA_RX
  7. USART1_RX_DMA_IRQN, // IRQn_Type dma_irq;
  8. 0, // rt_size_t last_index;
  9. // DMA_HandleTypeDef hdma_rx;
  10. { USART1_RX_DMA_CHANNEL, { USART1_RX_DMA_REUQEST}},
  11. #endif
  12. "uart1", // char * uart_name;
  13. // struct rt_serial_device serial;
  14. { { 0}, &stm32_uart_ops, RT_SERIAL_CONFIG_DEFAULT}
  15. };

stm32_uart类型如下:

  1. /* STM32 uart driver */
  2. struct stm32_uart
  3. {
  4. UART_HandleTypeDef UartHandle;
  5. IRQn_Type irq;
  6. #ifdef BSP_UART_USING_DMA_RX
  7. IRQn_Type dma_irq;
  8. rt_size_t last_index;
  9. DMA_HandleTypeDef hdma_rx;
  10. #endif
  11. char * uart_name;
  12. struct rt_serial_device serial;
  13. };

struct rt_serial_device 类型如下:

  1. struct rt_serial_device
  2. {
  3. struct rt_device parent;
  4. const struct rt_uart_ops *ops;
  5. struct serial_configure config;
  6. void *serial_rx;
  7. void *serial_tx;
  8. };
  9. typedef struct rt_serial_device rt_serial_t;

&stm32_uart_ops 对应 const struct rt_uart_ops *ops;
RT_SERIAL_CONFIG_DEFAULT 对应 struct serial_configure config;

stm32_uart_ops这个结构体实现接口层和底层的连接。
RT_SERIAL_CONFIG_DEFAULT 这个是串口默认配置。

  1. /* Default config for serial_configure structure */
  2. #define RT_SERIAL_CONFIG_DEFAULT \ { \ BAUD_RATE_115200, /* 115200 bits/s */ \
  3. DATA_BITS_8, /* 8 databits */ \
  4. STOP_BITS_1, /* 1 stopbit */ \
  5. PARITY_NONE, /* No parity */ \
  6. BIT_ORDER_LSB, /* LSB first sent */ \
  7. NRZ_NORMAL, /* Normal mode */ \
  8. RT_SERIAL_RB_BUFSZ, /* Buffer size */ \
  9. 0 \
  10. }
  11. //一一对应关系如下
  12. struct serial_configure
  13. {
  14. rt_uint32_t baud_rate; /* 波特率 */
  15. rt_uint32_t data_bits :4; /* 数据位 */
  16. rt_uint32_t stop_bits :2; /* 停止位 */
  17. rt_uint32_t parity :2; /* 奇偶校验位 */
  18. rt_uint32_t bit_order :1; /* 高位在前或者低位在前 */
  19. rt_uint32_t invert :1; /* 模式 */
  20. rt_uint32_t bufsz :16; /* 接收数据缓冲区大小 */
  21. rt_uint32_t reserved :6; /* 保留位 */
  22. };

再看stm32_uart_ops

  1. static const struct rt_uart_ops stm32_uart_ops =
  2. {
  3. stm32_configure,
  4. stm32_control,
  5. stm32_putc,
  6. stm32_getc,
  7. };

这里的stm32_configure函数就实现了STM32串口的底层初始化流程,包含HAL_UART_Init函数调用。
stm32_control函数是开启和清楚接收中断的,以及串口DMA配置。
限于篇幅这几个函数不放在这里了。

  1. void USART1_IRQHandler(void)
  2. {
  3. /* enter interrupt */
  4. rt_interrupt_enter();
  5. uart_isr(&uart1.serial);
  6. /* leave interrupt */
  7. rt_interrupt_leave();
  8. }
  9. void USART1_RX_DMA_IRQHandler(void)
  10. {
  11. /* enter interrupt */
  12. rt_interrupt_enter();
  13. HAL_DMA_IRQHandler(uart1.UartHandle.hdmarx);
  14. /* leave interrupt */
  15. rt_interrupt_leave();
  16. }

串口中断和串口接收DMA中断也在drv_usart.c文件里
uart_isr(&uart1.serial);主要是清除中断标志位

用过HAL库的都知道,外设的GPIO初始化,外设时钟使能,NVIC配置 都在HAL_UART_MspInit函数中完成,HAL_UART_MspInit函数也在drv_usart.c文件里

  1. void HAL_UART_MspInit(UART_HandleTypeDef *huart)

同时将由于不同的GPIO可能导致的不同的端口配置和复用通道,DMA通道都用宏封装

  1. #ifdef BSP_USING_UART1
  2. #define USART1_RX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
  3. #define USART1_TX_GPIO_CLK_ENABLE() __HAL_RCC_GPIOA_CLK_ENABLE()
  4. /* Definition for USART1 Pins */
  5. #define USART1_TX_PIN GPIO_PIN_9
  6. #define USART1_TX_GPIO_PORT GPIOA
  7. #define USART1_TX_AF GPIO_AF7_USART1
  8. #define USART1_RX_PIN GPIO_PIN_10
  9. #define USART1_RX_GPIO_PORT GPIOA
  10. #define USART1_RX_AF GPIO_AF7_USART1
  11. #define USART1_RX_DMA_CHANNEL DMA1_Channel5
  12. #define USART1_RX_DMA_REUQEST DMA_REQUEST_2
  13. #define USART1_RX_DMA_IRQN DMA1_Channel5_IRQn
  14. #define USART1_RX_DMA_IRQHandler DMA1_Channel5_IRQHandler
  15. #endif

上面的串口1使用的是默认配置RT_SERIAL_CONFIG_DEFAULT,
如果要开其他串口或是要改配置比如改波特率,可以仿照RT_SERIAL_CONFIG_DEFAULT这个宏再封装一个出来改对应的项,赋值到对应串口的设备结构体中去,如static struct stm32_uart uart1

上面提到,实际打开串口完成硬件初始化配置是在rt_console_set_device函数中

  1. #ifdef RT_USING_CONSOLE
  2. rt_console_set_device(RT_CONSOLE_DEVICE_NAME);
  3. #endif
  4. rt_device_t rt_console_set_device(const char *name)
  5. {
  6. rt_device_t new, old;
  7. /* save old device */
  8. old = _console_device;
  9. /* find new console device */
  10. new = rt_device_find(name);
  11. if (new != RT_NULL)
  12. {
  13. if (_console_device != RT_NULL)
  14. {
  15. /* close old console device */
  16. rt_device_close(_console_device);
  17. }
  18. /* set new console device */
  19. rt_device_open(new, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);
  20. _console_device = new;
  21. }
  22. return old;
  23. }

#define RT_CONSOLE_DEVICE_NAME “uart1”
这个宏定义在rtconfig.h中,定义了使用哪个串口作为控制台的端口。这里使用的是串口1,可以根据需要进行修改

在rt_console_set_device函数中,完成了打开串口设备的动作 rt_device_open(new, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);
rt_device_open函数中调用了device_init和device_open,
#define device_init (dev->init)
#define device_open (dev->open)
实际上是 (dev->init)和(dev->open)

在rt_hw_serial_register函数中device->init = rt_serial_init;
在rt_serial_init函数中看到了serial->ops->configure(serial, &serial->config); 前面说了,configure这个函数指针指向了stm32_configure,这个里面调用了HAL_UART_Init函数完成串口初始化配置。

  1. rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
  2. const char *name,
  3. rt_uint32_t flag,
  4. void *data)
  5. {
  6. rt_err_t ret;
  7. struct rt_device *device;
  8. RT_ASSERT(serial != RT_NULL);
  9. device = &(serial->parent);
  10. device->type = RT_Device_Class_Char;
  11. device->rx_indicate = RT_NULL;
  12. device->tx_complete = RT_NULL;
  13. #ifdef RT_USING_DEVICE_OPS
  14. device->ops = &serial_ops;
  15. #else
  16. device->init = rt_serial_init;
  17. device->open = rt_serial_open;
  18. device->close = rt_serial_close;
  19. device->read = rt_serial_read;
  20. device->write = rt_serial_write;
  21. device->control = rt_serial_control;
  22. #endif
  23. device->user_data = data;
  24. /* register a character device */
  25. ret = rt_device_register(device, name, flag);
  26. #if defined(RT_USING_POSIX)
  27. /* set fops */
  28. device->fops = &_serial_fops;
  29. #endif
  30. return ret;
  31. }
  32. static rt_err_t rt_serial_init(struct rt_device *dev)
  33. {
  34. rt_err_t result = RT_EOK;
  35. struct rt_serial_device *serial;
  36. RT_ASSERT(dev != RT_NULL);
  37. serial = (struct rt_serial_device *)dev;
  38. /* initialize rx/tx */
  39. serial->serial_rx = RT_NULL;
  40. serial->serial_tx = RT_NULL;
  41. /* apply configuration */
  42. if (serial->ops->configure)
  43. result = serial->ops->configure(serial, &serial->config);
  44. return result;
  45. }

本文通篇讲了用于控制台的串口1如何注册到系统中并完成了初始化,其余串口也是同理。
主要关注两个问题,1是如何修改某个串口的波特率,2是如果增加串口。
如果底层驱动做好的情况下,实际上直接改宏控就能完成增加串口,env工具可以快速完成配置,也可以手动改rtconfig.h文件。

也可以先全部默认的参数,然后在任务中重新配置,比如:
如下步骤可以实现对应的配置,比如波特率115200改成9600

  1. #define SAMPLE_UART_NAME "uart2" /* 串口设备名称 */
  2. static rt_device_t serial; /* 串口设备句柄 */
  3. struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT; /* 初始化配置参数 */
  4. /* step1:查找串口设备 */
  5. serial = rt_device_find(SAMPLE_UART_NAME);
  6. /* step2:修改串口配置参数 */
  7. config.baud_rate = BAUD_RATE_9600; //修改波特率为 9600
  8. config.data_bits = DATA_BITS_8; //数据位 8
  9. config.stop_bits = STOP_BITS_1; //停止位 1
  10. config.bufsz = 128; //修改缓冲区 buff size 为 128
  11. config.parity = PARITY_NONE; //无奇偶校验位
  12. /* step3:控制串口设备。通过控制接口传入命令控制字,与控制参数 */
  13. rt_device_control(serial, RT_DEVICE_CTRL_CONFIG, &config);
  14. /* step4:打开串口设备。以中断接收及轮询发送模式打开串口设备 */
  15. rt_device_open(serial, RT_DEVICE_FLAG_INT_RX);

RT-Thread 官方串口配置文档

发表评论

表情:
评论列表 (有 0 条评论,444人围观)

还没有评论,来说两句吧...

相关阅读