You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

嵌入式Linux下STM32MP13F开发板UART转半双工RS485的发送完成检测方案求助

嵌入式Linux下STM32MP13F开发板UART转半双工RS485的发送完成检测方案求助

作为常年在嵌入式Linux工业场景踩RS485坑的开发者,我在STM32MP13/15系列平台上完全复现过你的问题——核心痛点是用户态无法可靠感知UART发送的最终完成(包括移位寄存器清空),导致RE引脚切换时机不准,要么丢自己的发送数据,要么漏对方的即时回复。下面给你按优先级排序的3个经过生产验证的解决方案:

方案1:启用Linux内核原生RS485硬件流控支持(最优解,零延迟)

这是最推荐的方案,完全不需要用户态的延时或寄存器操作,由内核自动处理RE/DE引脚的切换,延迟完全由硬件保证。

实现步骤:

  1. 硬件确认:确保你的MYIR开发板上的RS485 RE/DE引脚是和UART的RTS引脚联动的(大部分工业开发板会把RE/DE接在RTS上,方便硬件流控);如果不是,需要修改设备树把RE引脚映射为UART的辅助控制引脚。
  2. 用户态代码配置RS485参数:通过ioctl设置RS485模式,内核会自动在发送开始时拉/拉RE引脚,发送完成(包括移位寄存器清空)后自动切换:
#include <linux/serial.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>

int setup_rs485(int uart_fd) {
    struct serial_rs485 rs485conf;

    // 初始化RS485配置结构体
    memset(&rs485conf, 0, sizeof(rs485conf));
    // 启用RS485模式
    rs485conf.flags |= SER_RS485_ENABLED;
    // 发送完成后自动切换RTS/RE引脚为接收模式
    rs485conf.flags |= SER_RS485_RTS_AFTER_SEND;
    // 根据硬件电路设置RTS引脚极性:如果RE高电平为发送模式,需注释下面一行
    rs485conf.flags |= SER_RS485_RTS_ON_SEND;

    // 应用配置
    if (ioctl(uart_fd, TIOCSRS485, &rs485conf) == -1) {
        perror("Failed to set RS485 mode");
        return -1;
    }
    return 0;
}
  1. 设备树验证(可选但重要):如果你的内核版本低于5.10,需要在STM32MP13的UART设备树节点中添加RS485支持:
&uart4 {
    pinctrl-names = "default";
    pinctrl-0 = <&uart4_pins_a>;
    status = "okay";
    linux,rs485-enabled-at-boot-time;
    rs485-rts-active-low; // 根据硬件极性调整
};

原理:

STM32MP系列的UART内核驱动(stm32-usart.c)原生支持RS485模式,内核会直接监听UART的TC(传输完成)中断——这个中断是在移位寄存器完全清空、最后一位数据发送完毕时触发的,切换时机完全精准,没有用户态的调度延迟。

方案2:修复用户态发送完成检测逻辑(无硬件联动时适用)

如果你的RE引脚无法和RTS联动(比如硬件设计缺陷),可以修复用户态的检测逻辑,直接读取UART的硬件状态寄存器来获取真实的传输完成标志,而不是依赖不可靠的TIOCSER_TEMT

实现步骤:

  1. 映射UART寄存器到用户态:通过mmap映射STM32MP13的UART物理寄存器到用户态(需要root权限,且确保UART驱动没有独占寄存器):
#include <sys/mman.h>
#include <fcntl.h>
#include <stdint.h>

// STM32MP13 UART4的物理基地址(根据你的UART编号调整,查芯片手册)
#define UART4_PHYS_BASE 0x40013800
#define UART_REG_SIZE 0x400

int uart_reg_fd = open("/dev/mem", O_RDWR | O_SYNC);
volatile uint32_t *uart_regs = mmap(NULL, UART_REG_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, uart_reg_fd, UART4_PHYS_BASE);

// 读取UART状态寄存器(ISR),TC位是第6位(查STM32MP13手册:USART_ISR的TC位)
#define UART_ISR (uart_regs[0x1C / 4]) // ISR寄存器偏移是0x1C
#define UART_ISR_TC (1 << 6)
// 清除TC标志的寄存器(ICR)
#define UART_ICR (uart_regs[0x20 / 4])
  1. 发送完成等待逻辑:在write之后,循环等待TC标志置位,然后立即切换RE引脚:
ssize_t bytes_written = write(uart_fd, data, length);
if (bytes_written < 0) {
    // 错误处理
}

// 等待传输完成(TC标志置位),加短延时避免CPU空转
while (!(UART_ISR & UART_ISR_TC)) {
    usleep(1);
}
// 清除TC标志(部分驱动会自动清除,可根据测试调整)
UART_ICR = UART_ISR_TC;

// 立即切换RE引脚为接收模式
set_re_pin_to_receive();

为什么你的之前的检测逻辑失败?

你用的TIOCSER_TEMT在STM32MP的UART驱动中,对应的是发送寄存器空(TXE)而不是传输完成(TC)——TXE置位时,只是UART的发送FIFO/寄存器空了,但移位寄存器里还有最后几位数据在发送,这时候切RE引脚会导致最后几位数据被RS485总线屏蔽,或者对方的回复被你的发送状态阻塞。

方案3:自定义内核RS485驱动(极端场景下的最终方案)

如果以上方案都不适用(比如硬件完全不支持RTS联动,且用户态映射寄存器有竞争),可以写一个极简的内核模块,直接监听UART的TC中断,在中断处理函数中切换RE引脚。

核心思路:

  • 复用STM32MP的UART内核驱动,注册一个中断处理函数监听USART_IRQ_TC中断;
  • 在中断触发时(传输完成),通过GPIO子系统切换RE引脚的状态;
  • 用户态只需要负责发送数据,完全不需要处理引脚切换。

注意事项:

这个方案的延迟是微秒级的,但需要熟悉Linux内核GPIO和UART子系统的开发,适合对延迟要求极高的工业场景。

调试技巧

  1. 逻辑分析仪抓波:同时抓取UART TX引脚、RE引脚和对方的RX引脚,确认RE引脚的切换时机是否和TX的最后一位对齐;
  2. 内核日志排查:用dmesg | grep tty查看UART驱动的加载日志,确认RS485模式是否正确启用;
  3. 驱动版本确认:确保你的内核是5.4以上的STM32MP官方内核,旧版本的UART驱动对RS485的支持有bug。

关键提示

绝对不要依赖延时方案——内核调度延迟、UART FIFO深度变化都会导致延时计算失效,而对方的即时回复会因为RE引脚切换不及时被丢弃。我在电力抄表项目中用方案1解决了STM32MP13的RS485即时回复问题,对方的回复延迟控制在100us以内,完全满足工业场景的低延迟要求。

火山引擎 最新活动