串行编程RS485

Serial programming RS485

本文关键字:RS485 编程      更新时间:2023-10-16

我的任务是通过RS485双线系统实现ModBus协议。(实际上是三根线,A/B和GND)。ModBus不是重点,但在此之前的步骤。。。接口上的简单I/O。

我使用FTDI USB-RS485转换器将Linux主机(不可互换)连接到Windows主机(可与另一台Linux主机互换,尽管我想避免这种情况)

编码应该是19200,8,n,1。但这似乎并不奏效。

我手头没有确切的代码,但在Linux上我正在做这件事:

int fd = open("/dev/ttyS3", O_RDWR | O_CTTY);
if(fd == -1) return "Error while opening the port";

接下来,我配置端口。

struct termios tty;
tcgetattr(fd, &tty);
cfsetispeed(&tty, B19200);
cfsetospeed(&tty, B19200);
tty.c_cflag  = CS8;              //Empties the cflags and sets the character width.
tty.c_cflag |= (CLOCAL | CREAD); //Sets 'recommended' options.
tty.c_lflag  = 0;
tty.c_iflag  = 0;
tty.c_oflag  = 0;
tcgetattr(fd, TCSANOW, &tty);

奇偶校验和流量控制目前还没有计划,因为最终结果将连接到一个低级别板,我需要自己处理信号。此外,这里没有任何电线,这将允许"不受限制的通信"。(毕竟,我不希望XON/XOFF字符限制我可以传输的字节范围)

所有这些功能都能正常运行,数据也就设置好了。

在Windows上,我打开串行端口如下:

DCB SP;
HANDLE hSerial = CreateFile("COM6", GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
if(hSerial == INVALID_HANDLE_VALUE) return "Error while opening the port";
GetCommState(hSerial, &SP);

奇偶校验和流量控制被禁用。字节大小设置为8。

编辑:既然有人问过,这是我在Windows上的baudrate代码(来自内存)SP.DCBlength=尺寸(SP);SP.BaudRate=19200;SP奇偶校验=无奇偶校验;SP.StopBits=ONESTOPBIT;SetCommState(hSerial,&SP);

同样,所有这些函数都可以完美地运行。

现在,对于让我头疼的测试用例。

在Linux主机上,我创建了一个256字节大小的字节缓冲区。该缓冲区填充了0-255…之间的字符值,然后通过导线发送写入。与此同时,另一方正在使用"ReadFile"等待数据到达。

使用此配置,对于"其他Linux主机"和Windows主机,256字节到达。。。然而,这不是0-255之间的数字,而是00 06等的数字。

当我在设置我真正想要的选项之前将termios结构的所有成员设置为0时,我可以让Linux主机工作。我猜,这是因为控制字符。。。但是,如果我这样做,Windows主机要么只接收256个字节中的4个。

正如我所说,不幸的是,我手头没有代码。如果有人知道我能从什么角度解决这个问题,我将不胜感激。一旦我能再次访问它,我会发布更多的代码。

我是如何实现读取操作的:

DWORD nBytes = 0;
char Buffer[256], *ptr = Buffer;
int Rem = 256;
while(Rem) {
ReadFile(hSerial, ptr, Rem, &nBytes, 0);
Rem -= nBytes;
ptr += nBytes;
}
//Evaluate Buffer

需要注意的是,我确实设置了超时,但记不清确切的值。

编辑:由于我现在可以再次访问我的工作场所,以下是实际(当前)代码。

const char *InitCOM(const char *TTY) {
struct termios tty;
hSerial = open(TTY, O_RDWR | O_NOCTTY | O_NDELAY);
if(hSerial == -1) return "Opening of the port failed";
fcntl(hSerial, F_SETFL, 0);
if(tcgetattr(hSerial, &tty) != 0) return "Getting the parameters failed.";
if(cfsetispeed(&tty, B19200) != 0 || cfsetospeed(&tty, B19200) != 0) return "Setting the baud rate failed.";

//CFlags
//Note: I am full aware, that there's an '=', and that it makes the '&=' obsolete, but they're in there for the sake of completeness.
tty.c_cflag  = (tty.c_cflag & ~CSIZE) | CS8;    //8-bit characters
tty.c_cflag |= (CLOCAL | CREAD);und erlaubt 'Lesen'.
tty.c_cflag &= ~(PARENB | PARODD);          
tty.c_cflag &= ~CSTOPB;
tty.c_cflag &= ~CRTSCTS;                    
//Input Flags
tty.c_iflag     &= ~IGNBRK;             
tty.c_iflag &= ~(IXON | IXOFF | IXANY);         
//Local Flags
tty.c_lflag  = 0;                   
//Output Flags
tty.c_oflag  = 0;
//Control-Characters
tty.c_cc[VMIN]   = 0;
tty.c_cc[VTIME]  = 5;
if(tcsetattr(hSerial, TCSAFLUSH, &tty) != 0) return "Setting the new parameters failed";
return NULL;

}

对于实际发送/接收代码:

int main(int argc, char* argv[]) {
#if defined FOR_PC
const char *err = InitCOM("/dev/ttyUSB0");
#else
const char *err = InitCOM("/dev/ttyS3");
#endif
if(err) printf("Error while initalizing: %s ErrNum: %dn", err, errno);
else {
/*unsigned char C[256];    //Original code with the array
int nBytes;
#ifdef FOR_PC
int Rem = 256, ReqCount = 0;
unsigned char *ptr = C;
while(Rem > 0) {    
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
nBytes = read(hSerial, ptr, Rem);
if(nBytes > 0) {
Rem -= nBytes;
ptr += nBytes;
++ReqCount;
}
}
printf("Number of received Bytes: %d in %d sends.nn", 256 - Rem, ReqCount);
for(int i = 0; i < 256; ++i) {
printf("%02X ", C[i]);
if((i%32) == 31) printf("n");
}
#else
for(int i = 0; i < 256; ++i) C[i] = i;
nBytes = write(hSerial, C, 256);
printf("nWritten Bytes: %dn", nBytes);
#endif*/
//Single-Byte Code
unsigned char C = 0x55;
#ifdef FOR_PC
while(true) {   //Keeps listening
fd_set fds;
FD_ZERO(&fds);
FD_SET(hSerial, &fds);
select(hSerial+1, &fds, NULL, NULL, NULL);
read(hSerial, &C, 1);
printf("Received value 0x%02Xn", C);
}
#else
write(hSerial, &C, 1);  //Sends one byte
#endif
close(hSerial);
}
return 0;

}

至于示波器:我已经测试了发送的两个方向。两人的工作都做得非常出色。

0x55的信号是一个50微秒长度的恒定上/下(这是应该的,所以设置波特率也没有问题)。

那么,我的"接收"代码中有什么我做错了吗?"选择"错误吗?

  1. 您是否也在Windows端设置了正确的波特率
  2. 使用示波器检查导线上的实际数据。示波器的发明就是为了调试串行通信。差不多。:)

您的读取函数很容易爆炸。如果您接近缓冲区的末尾,并且您读取的数据超过了填充缓冲区的量,那么您将复制到缓冲区的末端,从而覆盖堆栈。

在Linux发送端,您应该查看"原始模式",例如cfmakeraw()。这样你就不会被系统"帮助"你所困扰(比如当你发送换行符时添加CR——真的把二进制数据搞砸了…)。在微软有一种方法可以做到这一点,但我忘了怎么做了。

在windows端,您是否在DCB结构中设置了DCBLength字段?

dcb.DCBlength = sizeof(dcb);