【发布时间】:2018-12-05 07:32:11
【问题描述】:
我正在尝试通过一个 USB 到 RS232 到 RS485 端口以波特率 38400、1 个起始位、8 个数据位、无奇偶校验和 1 个停止位与多个 Modbus RTU 设备通信。
与一台 Modbus RTU 设备的通讯流程如下:
- 向设备发送 8 个字节;
- 等待设备回复;
- 接收 23 个字节的回复。
根据我的计算和数字示波器,发送8个字节需要2.083ms,接收23个字节需要5.99ms,Modbus RTU设备的响应时间约为1.3ms。所以通信过程的时间总共花费了9.373ms。 但在我的测试程序中,我发现平均通信时间约为 15 毫秒(平均 10000 次)。我想知道额外的 5 毫秒是从哪里来的,我该如何优化我的程序来减少这个时间。
提前致谢!
测试程序如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
void print_hex_buf(unsigned char *buffer, int size)
{
for (int i=0; i<size; i++)
{
printf("%02x ", buffer[i]);
}
printf("\n");
}
void diff_time(struct timeval t1, struct timeval t2, struct timeval *diff)
{
time_t sec;
suseconds_t usec;
//time in two different days
if (t1.tv_sec > t2.tv_sec)
sec = t2.tv_sec + 24*60*60 - t1.tv_sec;
else
sec = t2.tv_sec - t1.tv_sec;
usec = t2.tv_usec - t1.tv_usec;
if (usec < 0)
{
sec -= 1;
usec += 1000000;
}
diff->tv_sec = sec;
diff->tv_usec = usec;
}
int serial_write(int uart_fd, char *buffer, int size)
{
int count = 0;
count = write(uart_fd, buffer, size);
return count;
}
int serial_read(int uart_fd, char *buffer, int size)
{
int count = 0;
int bytes_read = 0;
int read_retry = 0;
fd_set fds_read;
struct timeval timeout;
FD_ZERO(&fds_read);
FD_SET(uart_fd, &fds_read);
timeout.tv_sec = 0;
timeout.tv_usec = 500000; //500ms
int ret = select(uart_fd + 1, &fds_read, NULL, NULL, &timeout);
if (ret > 0 && FD_ISSET(uart_fd, &fds_read))
{
count = read(uart_fd, buffer, size);
bytes_read = (count > 0)?count:0;
while (bytes_read < size && read_retry++ < 500)
{
count = read(uart_fd, buffer+bytes_read, size-bytes_read);
bytes_read += (count > 0)?count:0;
if (bytes_read >= size)
break;
}
}
else
{
printf("Failed to from uart!\n");
return -1;
}
return bytes_read;
}
int main(int argc, char** argv)
{
int fd;
struct termios opt;
int count;
unsigned char send_buf[] = { 0x01, 0x04, 0x00, 0x00, 0x00, 0x09, 0x30, 0x0c};
unsigned char buffer[256];
int iteration = 0;
int delay_ms = 0;
int err_count = 0;
int cycle = 0;
suseconds_t average_time = 0;
setbuf(stdout, NULL);
if (argc != 3)
{
printf("Usage: testuart [uart device] [iteration]\n");
return 0;
}
iteration = atoi(argv[2]);
fd = open(argv[1], O_RDWR | O_NOCTTY | O_NDELAY);
if (fd == -1)
{
printf("Failed to open port: %s\n", argv[1]);
return -1;
}
if (tcgetattr(fd, &opt) != 0)
{
printf("Failed to get uart attribute!\n");
return -1;
}
opt.c_cflag = B38400|CS8|CREAD|CLOCAL;
opt.c_iflag = IGNPAR;
opt.c_cflag &= ~PARENB;
opt.c_cflag &= ~PARODD;
opt.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
opt.c_oflag &= ~OPOST;
opt.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
tcflush(fd, TCIFLUSH);
if (tcsetattr(fd, TCSANOW, &opt) != 0)
{
printf("Failed to setup serial port!\n");
close(fd);
return -1;
}
while (cycle++ < iteration)
{
printf("Send hex command:\n");
print_hex_buf(send_buf, 8);
struct timeval tm_start;
struct timeval tm_end;
struct timeval tm_diff;
gettimeofday(&tm_start, NULL);
count = serial_write(fd, send_buf, 8);
if (count != 8)
{
printf("Failed to write 8 bytes!\n");
close(fd);
return -1;
}
count = serial_read(fd, buffer, 23);
if (count <= 0)
{
printf("serial read returns %d\n", count);
close(fd);
return -1;
}
gettimeofday(&tm_end, NULL);
diff_time(tm_start, tm_end, &tm_diff);
print_hex_buf(buffer, count);
printf("serial communication costs %ld.%06ld seconds.\n",
tm_diff.tv_sec, tm_diff.tv_usec);
average_time = ((average_time*(cycle-1))+tm_diff.tv_usec)/cycle;
}
printf("%d times, average time in usec is %ld\n", cycle-1, average_time);
close(fd);
return 0;
}
【问题讨论】:
-
我的平台操作系统是OpenWrt,Linux内核4.14.37。
-
您计算的时间是您的应用程序永远无法达到的数字。您测量的总时间包括系统开销和进程暂停。其中一些时间可以减少。请参阅stackoverflow.com/questions/4667141/… 您的内核是否配置了抢占式?使用非阻塞 I/O 可能没有您想象的那么有用。你的 RS485 全双工吗?
-
您是否有理由在部分读取后继续调用
read()而不是再次调用select()?这会给你一个更正确的实现;你不知道 500 次重试需要多长时间。它可能非常快。 -
@sawdust 感谢您的回复!内核配置了“No Forced Preemption (Server)”。使用非阻塞 I/O 只是为了防止 Modbus RTU 设备由于某种原因没有回复。我的 RS485 是半双工的。
-
@janm 在我添加选择功能之前使用了 500 次重试(每次重试延迟 1 毫秒),我忘了删除它。当 select 函数返回时,第一个 read 函数可以工作,所以它不会进入 while 循环。 Modbus RTU 设备只在我发送命令后回复一次。
标签: linux linux-kernel serial-port