You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

树莓派ttyAMA0串口读取延迟过高问题优化咨询

树莓派软PLC RS485串口读取延迟优化方案

问题描述

我在树莓派上搭建软PLC,已为PLC任务预留单独核心,延迟可控制在100微秒以内。该PLC需以1Mbps速率进行RS485通信,为避免USB转换器带来额外延迟,使用了树莓派板载串口。目前使用write(SerialId, buf, len)向RS485发送数据时,耗时最高为85微秒;但使用int bytes = read(SerialId, RxBuf, sizeof RxBuf)读取数据时表现极差,耗时可达10毫秒以上。已将串口设置为非阻塞模式且缓冲区足够大,每次读取都会超时,实测读取耗时从几微秒到10毫秒不等。

串口初始化代码:

void SetupSerial(){
    char *portname = "/dev/ttyAMA0";
    int speed=B1000000;
    SerialId = open (portname, O_RDWR | O_NOCTTY | O_SYNC | O_NONBLOCK);
    if (SerialId < 0){
        printf ("FastPLC: Error %d opening %s: %s\n", errno, portname, strerror (errno));
        exit(errno);
    }
    printf ("FastPLC: %s is open\n", portname);
    struct termios tty;
    if (tcgetattr (SerialId, &tty) != 0)    {
        printf ("FastPLC: Error %d from tcgetattr", errno);
        exit(errno);
    }
    cfsetospeed (&tty, speed);
    cfsetispeed (&tty, speed);
    cfmakeraw(&tty);
    tty.c_cc[VTIME] = 0;                //nonblocking
    tty.c_cc[VMIN] = 0;
    if (tcsetattr (SerialId, TCSANOW, &tty) != 0)   {
            printf ("FastPLC: Error %d from tcgetattr", errno);
            exit(errno);
    }
}

优化方案

1. 扩大串口接收缓冲区

默认串口接收缓冲区较小,在1Mbps速率下易引发频繁中断或数据溢出,直接拉低读取响应速度。通过ioctl调整缓冲区大小:

#include <linux/serial.h>

// 在tcsetattr之前添加以下代码:
struct serial_struct ser_info;
if (ioctl(SerialId, TIOCGSERIAL, &ser_info) == 0) {
    ser_info.rx_buffer_size = 16384; // 设置为16KB,可根据实际需求调整
    if (ioctl(SerialId, TIOCSSERIAL, &ser_info) != 0) {
        printf("FastPLC: Failed to set rx buffer size\n");
    }
}

2. 明确禁用流控机制

cfmakeraw可能未完全关闭流控,手动禁用硬件/软件流控避免额外延迟:

// 在cfmakeraw之后添加:
tty.c_cflag &= ~CRTSCTS; // 禁用硬件流控
tty.c_iflag &= ~(IXON | IXOFF | IXANY); // 禁用软件流控

3. 优化VMIN/VTIME参数(适配非阻塞模式)

当前VMIN=0+VTIME=0会让read立即返回,无数据时频繁调用会触发系统调度延迟。若允许等待至少1字节数据,可调整为:

tty.c_cc[VMIN] = 1;  // 等待至少1字节再返回
tty.c_cc[VTIME] = 0; // 因O_NONBLOCK,无数据时仍会立即返回-1

若必须保持纯非阻塞,建议用poll替代轮询read,仅当有数据时才执行读取:

#include <poll.h>

struct pollfd fds[1];
fds[0].fd = SerialId;
fds[0].events = POLLIN;

// 等待数据,超时时间设为1ms(可按需调整)
int ret = poll(fds, 1, 1);
if (ret > 0 && (fds[0].revents & POLLIN)) {
    int bytes = read(SerialId, RxBuf, sizeof(RxBuf));
    // 处理读取到的数据
}

4. 启用实时调度策略

利用预留的单独核心,将PLC进程设置为实时优先级,避免被低优先级进程抢占:

#include <sched.h>

struct sched_param param;
param.sched_priority = 90; // 实时优先级范围1-99,数值越高优先级越高
if (sched_setscheduler(0, SCHED_FIFO, &param) != 0) {
    printf("FastPLC: Failed to set real-time scheduler\n");
}
// 绑定进程到预留核心(假设核心编号为3)
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(3, &cpuset);
if (sched_setaffinity(0, sizeof(cpu_set_t), &cpuset) != 0) {
    printf("FastPLC: Failed to set CPU affinity\n");
}

5. 禁用串口控制台服务

确保板载串口未被系统控制台占用,停止并禁用getty服务:

sudo systemctl stop serial-getty@ttyAMA0.service
sudo systemctl disable serial-getty@ttyAMA0.service

同时修改/boot/cmdline.txt,移除包含console=ttyAMA0的参数,重启树莓派生效。

6. 调整串口FIFO触发阈值

树莓派UART支持FIFO触发级别设置,减少中断次数以降低系统开销:

# 设置FIFO触发阈值为1/2满(可选值:1/4、1/2、3/4、满,需硬件支持)
stty -F /dev/ttyAMA0 fifo 1

内容的提问来源于stack exchange,提问作者eSlavko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 18:23:14