树莓派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, ¶m) != 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
相关产品推荐
相关产品推荐

