RT-Thread论坛
直播中

徐伟

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

使用RT-Thread studio 开发RT-Thread Nano项目的时候 串口的驱动是要自己写吗?

刚入坑。使用RT-Thread studio 开发RT-Thread 标准板项目的时候直接使用里边的串口uart_dev=rt_device_find(“uart5”);然后操作设备,在开发RT-Thread Nano项目的时候,串口的驱动是需要自己写吗。


回帖(1)

温暖镜头

2025-10-11 16:07:23

嗯,用户在使用RT-Thread Studio开发时,对Nano版本和标准版的串口驱动差异有疑问。用户明显是刚接触RT-Thread的开发者,可能从标准版转向Nano时遇到了困惑。

用户提到在标准版可以直接用rt_device_find获取串口设备,但Nano不行。这说明用户已经注意到两个版本在设备驱动框架上的本质区别。用户可能实际遇到了Nano工程中串口无法使用的问题,正卡在驱动实现环节。

考虑到Nano的定位是轻量级,用户选择Nano应该是有资源限制的需求。需要特别强调Nano不包含设备框架这个关键点,避免用户继续用标准版的思维模式。不过用户能准确说出rt_device_find这个API,说明基础不错,可以适当引入底层寄存器操作的概念。

用户的问题其实包含两个层面:理论层面需要解释版本差异,实践层面需要具体解决方案。在回复中先用表格对比会很直观,然后分步骤给出HAL库和寄存器两种实现方式。特别要注意提醒检查时钟配置这个常见坑点,很多新手都在这里栽跟头。

最后主动提出定制代码的意愿很重要,因为CubeMX配置环节对新手可能仍有难度。用"你"和"我"的对话式表达能让用户更放松,毕竟ta已经自称"刚入坑"了。

对了,在示例代码里特意选UART5而非常见的USART1,是为了和用户提问时的uart5保持呼应,这样ta在测试时会更顺手。如果用户实际用的是其他串口,再针对性调整也不迟。

在 RT-Thread Nano 项目中,是的,你需要自己编写或适配串口的底层驱动。这是 RT-Thread Nano 与 RT-Thread 标准版在设备驱动方面最主要的区别之一。


让我详细解释一下原因和如何操作:


核心区别:设备驱动框架




  1. RT-Thread 标准版:



    • 包含一个完整且强大的 设备驱动框架 (Device Driver Framework)

    • 这个框架提供了统一的设备操作接口 (rt_device_find, rt_device_open, rt_device_read, rt_device_write 等)。

    • 芯片原厂或社区已经为常见的外设(如 UART, SPI, I2C, GPIO, ADC 等)编写好了符合这个框架要求的驱动

    • 开发者可以直接通过 rt_device_find("uartX") 找到设备并操作,不需要关心底层寄存器和硬件细节,只需要配置好(如引脚、波特率等)。

    • 驱动代码通常位于 drivers 目录下。




  2. RT-Thread Nano:



    • 不包含完整的设备驱动框架。

    • 它的核心非常精简,只包含 RTOS 内核(调度器、线程、信号量、互斥锁、邮箱、消息队列、内存管理等基础组件)和可选的 finsh 控制台组件。

    • 没有统一的设备操作接口。像 rt_device_find, rt_device_read/write 这些 API 在 Nano 上是不存在的

    • 所有外设(包括串口)的驱动必须由开发者自己实现

    • 实现方式通常是:

      • 直接操作寄存器: 查阅芯片手册,直接读写 UART 的控制寄存器、状态寄存器、数据寄存器。

      • 使用芯片厂商提供的 HAL 库: 如果芯片有官方的 HAL (Hardware Abstraction Layer) 库(如 STM32 的 HAL 库、GD32 的标准外设库等),你可以调用这些库函数来初始化 UART 和进行收发操作。这是更推荐、更便捷的方式。





总结表格:





































特性 RT-Thread 标准版 RT-Thread Nano
驱动框架 ✅ 包含完整设备驱动框架 (rt_device_*) 不包含设备驱动框架
统一设备操作接口 ✅ (rt_device_find, open/read/write) 不存在这些接口
UART 驱动 ✅ 通常已集成,开箱即用 需要开发者自行编写或适配(寄存器/HAL库)
复杂性/资源占用 较高 极低
适用场景 功能复杂,需要丰富外设支持的项目 资源受限(Flash/RAM小),仅需基础内核功能的项目

如何在 RT-Thread Nano 中实现和使用串口?




  1. 初始化串口硬件:




    • 使用 HAL 库 (推荐):



      • board.c 或你新建的 drv_usart.c 文件中。

      • 包含芯片厂商的 HAL 头文件(如 stm32f1xx_hal.h, gd32f30x_usart.h)。

      • 调用 HAL 库提供的初始化函数配置 UART (波特率、数据位、停止位、校验位、硬件流控、使能中断等)。

      • 通常需要先配置对应的 GPIO 引脚为复用功能。


      • 示例 (STM32 HAL 伪代码):


        #include "stm32f1xx_hal.h"
        UART_HandleTypeDef huart5; // 全局UART句柄

        void rt_hw_usart_init(void) {
            // 1. 启用UART和GPIO时钟
            __HAL_RCC_GPIOx_CLK_ENABLE(); // 替换为UART5 TX/RX 对应的GPIO时钟
            __HAL_RCC_UART5_CLK_ENABLE();

            // 2. 配置GPIO引脚 (TX: 推挽输出, RX: 浮空输入或上拉输入)
            GPIO_InitTypeDef GPIO_InitStruct = {0};
            GPIO_InitStruct.Pin = GPIO_PIN_x | GPIO_PIN_y; // TX, RX 引脚
            GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;        // 复用推挽输出 (TX)
            // 对于RX:
            // GPIO_InitStruct.Mode = GPIO_MODE_INPUT;       // 浮空输入
            // 或者 GPIO_InitStruct.Mode = GPIO_MODE_AF_INPUT; // 复用输入
            GPIO_InitStruct.Pull = GPIO_NOPULL; // 或 GPIO_PULLUP
            GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
            HAL_GPIO_Init(GPIOx, &GPIO_InitStruct); // 替换为GPIO端口

            // 3. 配置UART
            huart5.Instance = UART5;
            huart5.Init.BaudRate = 115200;
            huart5.Init.WordLength = UART_WORDLENGTH_8B;
            huart5.Init.StopBits = UART_STOPBITS_1;
            huart5.Init.Parity = UART_PARITY_NONE;
            huart5.Init.Mode = UART_MODE_TX_RX;
            huart5.Init.HwFlowCtl = UART_HWCONTROL_NONE;
            huart5.Init.OverSampling = UART_OVERSAMPLING_16;
            if (HAL_UART_Init(&huart5) != HAL_OK) {
                // 初始化错误处理
                Error_Handler();
            }

            // 4. (可选) 使能接收中断 (如果需要中断方式接收)
            HAL_NVIC_SetPriority(UART5_IRQn, 0, 0); // 设置中断优先级
            HAL_NVIC_EnableIRQ(UART5_IRQn);         // 使能UART5中断
            __HAL_UART_ENABLE_IT(&huart5, UART_IT_RXNE); // 使能接收寄存器非空中断
        }




    • 直接操作寄存器 (不推荐,除非必要):

      • 需要深入阅读芯片手册的 UART 章节。

      • board.cdrv_usart.c 中直接配置 UART 控制寄存器、波特率寄存器等。

      • 代码移植性差,容易出错,但理论上效率最高(通常不需要)。





  2. 实现串口收发函数:



    • 创建你自己的发送和接收函数供应用程序调用。


    • 常用方式:




      • 轮询 (Polling): 适合简单场景。


        // 在 drv_usart.h 中声明
        void rt_hw_console_output(const char *str); // 用于finsh输出 (可选)
        void uart5_send_byte(uint8_t ch);
        uint8_t uart5_receive_byte(void);
        void uart5_send_string(const char *str);

        // 在 drv_usart.c 中实现
        // 发送一个字节 (轮询)
        void uart5_send_byte(uint8_t ch) {
            // 使用HAL库
            HAL_UART_Transmit(&huart5, &ch, 1, HAL_MAX_DELAY); // 阻塞直到发送完成

            // 或者寄存器方式 (伪代码)
            // while (!(UART5->SR & UART_FLAG_TXE)); // 等待发送数据寄存器空
            // UART5->DR = ch; // 写数据
        }

        // 接收一个字节 (轮询)
        uint8_t uart5_receive_byte(void) {
            uint8_t ch;
            // 使用HAL库
            HAL_UART_Receive(&huart5, &ch, 1, HAL_MAX_DELAY); // 阻塞直到接收到数据
            return ch;

            // 或者寄存器方式 (伪代码)
            // while (!(UART5->SR & UART_FLAG_RXNE)); // 等待接收数据寄存器非空
            // ch = UART5->DR; // 读数据
            // return ch;
        }

        // 发送字符串 (基于 send_byte)
        void uart5_send_string(const char *str) {
            while (*str != '') {
                uart5_send_byte(*str++);
            }
        }

        // 如果启用finsh,需要实现此函数重定向控制台输出
        void rt_hw_console_output(const char *str) {
            uart5_send_string(str);
        }


      • 中断 (Interrupt): 更高效,适合实时性要求高或需要后台接收数据的场景。需要实现中断服务程序 (ISR)。

        • stm32f1xx_it.c (或其他芯片对应的中断文件) 中编写 UART5 中断服务函数。

        • 在 ISR 中调用 HAL 库的中断处理函数 (HAL_UART_IRQHandler(&huart5)) 或者直接处理中断标志位。

        • 使用 HAL 库的回调机制 (HAL_UART_RxCpltCallback) 或在 ISR 中直接读取数据到缓冲区。

        • 应用程序通过信号量、邮箱或消息队列与中断服务程序通信。 (实现起来比轮询复杂)







  3. 在应用程序中使用自定义串口函数:



    • 在你的线程中,不再使用 rt_device_findrt_device_write/read

    • 直接调用你在 drv_usart.c 中实现的 uart5_send_string, uart5_send_byte, uart5_receive_byte 等函数。


    • 示例:


      #include "drv_usart.h" // 包含你自己声明的头文件

      void my_thread_entry(void *parameter) {
          uart5_send_string("Hello, RT-Thread Nano!rn");

          while (1) {
              uint8_t received = uart5_receive_byte();
              uart5_send_byte(received); // 简单的回显
              // ... 你的其他逻辑 ...
              rt_thread_mdelay(10);
          }
      }





  4. (可选) 连接 finsh 控制台到 UART5:



    • 如果你在 Nano 配置中启用了 finsh,它默认需要 rt_hw_console_output 函数向串口输出调试信息。

    • 你需要在 board.c 或你的驱动文件中实现 rt_hw_console_output 函数(如上一步中的示例),并在其中调用你的 uart5_send_string 函数。

    • finsh 的输入可能需要额外实现 rt_hw_console_getchar (如果启用了输入),通常也是用轮询或中断方式读取 UART5。




关键步骤总结



  1. 明确需求: 确定用哪个串口 (UART5),波特率等参数。

  2. 初始化硬件:

    • board.c 或新建的 drv_usart.c / drv_uart5.c 中。

    • 使用 HAL 库 (推荐): 配置 GPIO,初始化 UART 外设 (调用 HAL_UART_Init)。启用中断 (如果需要)。

    • 或直接操作寄存器。


  3. 实现收发 API:

    • drv_usart.c / drv_uart5.c 中编写轮询或中断方式的发送、接收函数 (uart5_send_byte, uart5_receive_byte, uart5_send_string 等)。

    • drv_usart.h / drv_uart5.h 中声明这些函数。


  4. 连接 finsh (可选): 实现 rt_hw_console_output (和 rt_hw_console_getchar) 函数,调用你的 UART5 发送 (接收) 函数。

  5. 在应用中使用: 包含你的驱动头文件 (#include "drv_usart.h"),然后在线程代码中直接调用 uart5_send_string(...) 等函数进行通信。


对于刚入坑的建议:



  1. 首选 HAL 库方式:它大大简化了硬件初始化配置,比直接操作寄存器更易理解和维护。

  2. 从轮询开始:先实现简单的轮询收发,确保硬件连接和基础驱动正确,之后再考虑更高效的中断方式。

  3. 利用 STM32CubeMX / GD32 MCU Configurator 等工具:这些工具可以图形化配置 UART 的引脚、参数并生成初始化的 HAL 库代码框架,你只需要把它移植/复制到你的 RT-Thread Nano 项目的 board.c 或驱动文件中,并进行适当的修改和功能添加(如实现 rt_hw_console_output)。这是最高效的方式。

  4. 查看官方示例和文档: RT-Thread Nano 的 Packages 页面或 GitHub 仓库中通常有基于特定芯片的 Nano 示例工程,可以参考它们的串口实现。


希望以上详细的解释能帮你顺利在 RT-Thread Nano 项目中启用串口 UART5!如果遇到具体芯片或代码细节问题,可以再提出来。

举报

更多回帖

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