Question

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?

Was it helpful?

Solution

  1. Are you also setting the proper baud rate on the Windows side?
  2. Use an oscilloscope to check the actual data present on the wire(s). Debugging serial communications is what oscilloscopes were invented for. Almost. :)

OTHER TIPS

Your read function could easily explode. If you are near the end of the buffer, and you read more than the amount to fill it, you will copy past the end of the buffer, overwriting the stack.

On the Linux sending side, you should look into "raw mode", e.g., cfmakeraw(). This way you will not be bothered by the system "helping" you (like adding CR when you send a newline -- really screws up the binary data...). There's a way to do that in microsoft, but I forget how.

on windows side, did you set the DCBLength field in your DCB struct?

dcb.DCBlength = sizeof(dcb);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top