I have been tasked with the implementation of the ModBus protocol over a RS485 2-wire system. (Actually it's three wires, A/B and GND). ModBus is not the point though, but the step before that...simple I/O over the interface.
I am using the FTDI USB-RS485 converter to connect a Linux host (not interchangeable) to a Windows host (interchangeable with another Linux host, though I'd like to avoid that)
The encoding is supposed to be 19200, 8, n, 1. But it just doesn't seem to work.
I don't have the exact code handy, but on Linux I am doing this:
int fd = open("/dev/ttyS3", O_RDWR | O_CTTY);
if(fd == -1) return "Error while opening the port";
Next, I configure 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);
Parity and Flow Control are currently not planned, since the final result will connect to a low level board, where I need to take care of the signals myself. Furthermore there aren't any wires, which would allow 'unfettered communication'. (After all I don't want an XON/XOFF character to limit the byte range I can transmit)
All of these function go through properly and the data is set.
On Windows, I open the serial port like this:
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);
Parity is disabled, as well as flow control. Byte size is set to 8.
Edit: Since it has been asked, here is my code for the baudrate on Windows (from memory) SP.DCBlength= sizeof(SP); SP.BaudRate = 19200; SP.Parity = NOPARITY; SP.StopBits = ONESTOPBIT; SetCommState(hSerial, &SP);
Again, all of these functions run flawlessly.
Now, for the test case that's giving me a major headache.
On the Linux host, I create a byte buffer of 256 bytes size. This buffer is filled with the character values from 0-255...and then sent over the wire with write. At the same time, the other side is waiting with 'ReadFile' for data to arrive.
With this configuration, for both the 'other Linux host', as well as for the Windows host, 256 Bytes arrive...however it's NOT the numbers from 0-255, but something 00 06 etc.
I can get the Linuxhost to work, when I'm setting all members of the termios structure to 0 before setting the options I actually want. I'm guessing, it's because of the control characters...however if I do that, the Windows host either receives only 4 of 256 bytes.
As I said, unfortunately I don't have the code handy. If anyone has any idea from what point I could tackle this, I'd be very grateful. I will post more code, once I have access to it again.
How I'm implementing the read operation:
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
To be noted, I did set the timeouts, but can't remember the exact values.
Edit: Since I now have access to my work place again, here's the actual (current) code.
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;
}
As for the actual sending/receiving code:
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: %d\n", 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.\n\n", 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: %d\n", 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%02X\n", C);
}
#else
write(hSerial, &C, 1); //Sends one byte
#endif
close(hSerial);
}
return 0;
}
As for the Oscilloscope: I've tested both directions with sending. Both did their job quite admirably.
The signal of 0x55 is a constant Up/Down at the length of 50 microseconds (as it should, so setting the baud rate is no problem either).
So is there anything in my 'receive' code I'm doing wrong? Is the 'select' wrong?