RT-Thread论坛
直播中

陈艳

8年用户 1043经验值
私信 关注
[问答]

rtt-stdio 和 keil 生成的串口驱动框架区别是什么?






为什么keil用env构建的工程 没有这部分的引脚初始化代码?
是要自己添加吗?为什么要有这种区别呢?


                                                               

回帖(1)

孔妞妞

2025-9-15 17:48:17

理解 rtt-studio(你指的可能是 RT-Thread Studio)、Keil 生成的驱动框架以及 env 工具在串口驱动处理上的区别,关键在于它们背后的设计哲学和目标平台不同。我来详细分析一下这几个问题:


? 1. rtt-stdio (RT-Thread Studio) 与 Keil 生成的串口驱动框架核心区别




  • RT-Thread Studio 驱动框架:



    • 设计理念: 面向操作系统 (RT-Thread)

    • 驱动模型: 提供完整的、基于 RT-Thread Device Driver Framework 的设备驱动框架。

    • 核心组件: drv_usart.c/drv_usart.h 是核心驱动文件,但它只是底层硬件操作的具体实现(如寄存器读写)。

    • 初始化方式: 驱动框架本身 不直接包含 GPIOUART 外设时钟的配置(即 Pin Configuration)。这个初始化逻辑隐藏在更高层的设备注册和启动流程中。

    • 抽象层级: 驱动实现了 struct rt_serial_ops 中定义的函数指针(如 configure, control, putc, getc 等),注册为一个符合 RT-Thread 标准的 "Serial Device"

    • 依赖项: 高度依赖 RT-Thread 核心系统库、PIN 设备驱动框架、设备注册机制 (rt_device_register)、设备初始化系统 (rt_hw_board_init -> rt_hw_usart_init -> 调用具体驱动的 rt_hw_serial_register)。

    • 使用流程: 应用层通过 RT-Thread 的标准设备操作接口 (rt_device_find, rt_device_open, rt_device_read, rt_device_write 等) 访问串口设备。

    • 代码位置: 驱动代码位于 RT-Thread 工程的 drivers 目录下(例如 libraries/HAL_Drivers 或特定 BSP 的驱动目录)。




  • Keil (MDK) 生成的驱动框架:



    • 设计理念: 面向裸机 (Bare-metal)轻量级 RTOS

    • 驱动模型: 通常是基于 Hardware Abstraction Layer (HAL) 库(如 STM32 HAL)或 Low Layer (LL) 库生成的代码。

    • 核心组件:usart.c/usart.h

    • 初始化方式: 代码中直接、显式地包含了对 GPIO 引脚模式(Alternate Function, Speed, Pull-up/down)和 UART 外设时钟 (__HAL_RCC_USARTx_CLK_ENABLE) 的配置。通常有一个初始化函数(如 MX_USARTx_UART_Init())。

    • 抽象层级: 相对底层,提供了直接操作寄存器的封装(通过 HAL/LL API)。没有强制性的统一设备接口模型,应用直接调用 HAL/UART 提供的 HAL_UART_Transmit, HAL_UART_Receive 等函数。

    • 依赖项: 主要依赖 HAL/LL 库以及 CMSIS-Core (启动文件、core_cmx.h 等)。

    • 使用流程: 应用代码直接包含生成的 usart.h,并调用其中的初始化函数和发送/接收函数。

    • 代码位置: 由 Keil 的 Device Configuration Wizard 或 STM32CubeMX 集成生成,位于项目的 SrcInc 目录下。




? 2. Keil 中用 env 构建的 RT-Thread 工程:为何没有引脚初始化代码?要自己添加吗?



  • 环境特殊性: 这里讨论的是 在 Keil MDK 环境下,使用 env 工具配置构建的 RT-Thread 项目。这不是 Keil 生成的裸机项目,而是一个运行 RT-Thread OS 的项目。env (或 scons/menuconfig) 是 RT-Thread 的配置工具。

  • 代码位置: 引脚初始化代码确实存在,但不是直接写在 drv_usart.c 里。

  • 存放位置与生成机制: 这个硬件相关的初始化逻辑主要放在:

    • board/board.c:

      • 核心函数 rt_hw_board_init():这个函数是 BSP (Board Support Package) 初始化的入口点,由 RT-Thread 系统启动时自动调用。

      • 在这个函数中,通常 先初始化 PIN 设备驱动框架 (rt_hw_pin_init()),这个函数本身会启用 GPIO 时钟。

      • 然后调用 rt_hw_usart_init():这个函数就是专门用来初始化所有用到的 UART 设备的地方。它的核心工作是:

        1. 遍历一个配置好的 UART 设备数组(这些配置通常来自 Kconfig 或 env 的配置)。

        2. 为每个 UART 配置相关的 GPIO 引脚! 这正是你提到的“引脚初始化”部分。它通过 RT-Thread 的 PIN 驱动框架,将指定引脚设置为指定的复用功能模式、速率、上下拉等。

        3. 启用相应 UART 的外设时钟。

        4. 调用具体 UART 驱动 (drv_usart.c 中实现) 的 rt_hw_serial_register 函数,将底层操作函数挂接到 RT-Thread 的设备驱动框架上,注册成一个 /dev/uartx 设备。



    • Kconfig/Env 配置: rt_hw_usart_init() 函数内部的 UART 配置数组是如何确定的呢?答案是:env 工具(menuconfig)通过图形化界面让你选择启用了哪个 UART 以及它使用的引脚(BSP_USING_UARTxBSP_UARTx_TX_PIN/BSP_UARTx_RX_PIN 等配置项)。选择后,scons 或基于 Kconfig 的头文件生成机制(如 rtconfig.h)会把这些信息生成到 rtconfig.h 或特定的 BSP 配置头文件中。rt_hw_usart_init() 在编译时会读取这些宏定义来构建它的配置数组。


  • 自己添加?

    • 对于 已支持的开发板和引脚组合完全不需要自己添加! ? 你只需要在 menuconfig (或者修改 rtconfig.h,但不推荐直接修改) 中勾选需要使用的 UART 并选择正确的引脚即可。board/board.c 中的代码会利用 PIN 框架和配置好的宏自动完成引脚初始化。

    • 对于 新引脚映射或新串口外设

      • 需要修改 menuconfig:如果你选的引脚不在原 BSP 支持的列表中,可能需要修改 rt_hw_usart_init() 函数或者修改 Kconfig 文件(通常是 board/Kconfig)来增加对新引脚选择的支持(添加 BSP_UARTx_TX_PINchoice)。

      • 修改 board/board.c:如果是一个全新的 UART 外设(比如之前完全没初始化),你可能需要模仿已有的 rt_hw_usart_init() 结构,添加对这个新 UART 的初始化流程。但这种情况一般出现在自己移植 BSP 时,而不是普通应用开发。




? 3. 为什么要有这种区别?


核心在于目标不同:




  1. 操作系统抽象与硬件解耦:



    • RT-Thread Framework: 设计目标是构建一个抽象的、可移植的设备驱动模型。驱动专注于实现设备操作的标准接口 (rt_serial_ops)。具体的硬件资源配置(哪个引脚、哪个 UART、启用哪个时钟)被认为是 板级支持包 (BSP) 的职责,属于“硬件描述”或“配置”。这实现了驱动与具体硬件配置的分离,使得同一个驱动代码(drv_usart.c)可以被不同的板卡配置(通过 board.c + Kconfig/env)复用。

    • Keil HAL/Bare-metal: 裸机或者简单 RTOS 项目的主要目标是快速让硬件跑起来。它倾向于将驱动功能和配置(初始化)紧密耦合在同一个文件或模块中。STM32CubeMX 就是这种理念的集中体现——它给你生成一个包含了硬件所有状态(时钟树、引脚、外设)的初始化代码。这种方式直观、易于理解,但代码复用性较差。




  2. 配置驱动 vs. 代码驱动:



    • RT-Thread: 使用强大的工具链 (env/scons + Kconfig) 进行 图形化/菜单化配置。配置信息自动生成宏定义(BSP_USING_UARTx, BSP_UARTx_TX_PIN),板级初始化代码根据这些宏配置硬件。

    • Keil (CubeMX/MDK Wizard): 也提供图形化配置生成代码,但生成的代码通常包含了所有具体的配置指令(GPIO_Init, UART_Init 等),而不是像 RT-Thread 那样在运行时根据宏去初始化。这种方式是代码包含一切




  3. 复杂性与灵活性:



    • 对于像 RT-Thread 这样完整的操作系统,支持的设备和板卡众多,分离配置和驱动实现是管理复杂性和确保可维护性的必要手段

    • 对于裸机或简单项目,直接写死在代码里更简单直接,系统规模和管理成本尚能接受。




  4. 设备注册与命名:



    • RT-Thread: 需要将底层硬件映射成系统中的统一设备文件 (/dev/uartx),供应用层通过标准 POSIX-like 接口访问。这需要一个注册过程和统一的初始化入口。

    • Keil: 通常是点对点的函数调用,应用直接操作特定的 UART 句柄 (UART_HandleTypeDef huartx),不需要系统级别的注册。




? 总结



  • RT-Thread 的串口驱动 (drv_usart.c) 主要实现操作硬件的底层函数不含 GPIO 和 UART 外设时钟的显式初始化代码

  • Keil (CubeMX/HAL) 生成的驱动 (usart.c) 直接包含完整的 GPIO 和 UART 初始化配置代码。

  • 在 Keil + env 的 RT-Thread 项目中:

    • 引脚初始化的逻辑存在且必不可少

    • 它隐藏在板级支持包 (BSP) 的初始化代码中,主要是 board/board.c 文件里的 rt_hw_board_init() -> rt_hw_usart_init()

    • PIN 设备驱动框架 (rt_hw_pin_init()) 是实现这个初始化的关键工具。

    • 配置 (env/menuconfig) 是核心,你通过配置(勾选UART、选择引脚) 来指导 rt_hw_usart_init() 函数如何执行初始化。

    • 对于标准支持的引脚,你只需配置,无需手动添加初始化代码。需要修改或添加的是对引脚选择或新外设的配置支持(修改 Kconfig/rt_hw_usart_init)。


  • 这种区别源于 RT-Thread 操作系统的设计理念:追求驱动与硬件配置分离通过配置驱动硬件提供统一的设备抽象。这提高了驱动重用性和系统的可维护性、可扩展性,尤其适合跨平台和多硬件的场景。而 Keil HAL + CubeMX 的模式更偏向于直观、快速启动特定硬件的裸机或简单RTOS场景。?️⚙️

举报

更多回帖

发帖
×
20
完善资料,
赚取积分