11

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?

Mike
  • 47,263
  • 29
  • 113
  • 177
ATaylor
  • 2,598
  • 2
  • 17
  • 25
  • As unwind suggests, check the baud rate configuration. – Picarus Sep 21 '12 at 13:26
  • Please post the Windows code setting up the baudrate and calling SetCommState. – Lundin Sep 21 '12 at 13:50
  • '4' is always a suspicious number on 32-bit systems, (sizeof(char*)). – Martin James Sep 21 '12 at 14:04
  • Just some things you could try: add the code `SP.DCBlength = sizeof(DCB);`. Also try `SP.fParity = 0;` which disables parity entirely. `SP.ByteSize = 8;` for 8 data bits. – Lundin Sep 21 '12 at 14:06
  • @MartinJames I'm afraid I don't understand. Are you referring to the number of bytes I received during my tests? – ATaylor Sep 21 '12 at 14:07
  • @Lundin Ah, thank you for reminding me. I already did that. (The sizeof). The fParity I have yet to try though. Thank you. – ATaylor Sep 21 '12 at 14:07
  • @ATaylor I think he is hinting that your sizeof call might be incorrect, perhaps it is taking the size of a pointer and not an array. – Lundin Sep 21 '12 at 14:07
  • @Lundin No, that's not it. Both write, as well as ReadFile work with 256 as size parameter. The write operation also reports 256 getting written, however only 4 bytes arrive on the other side (under particular conditions) – ATaylor Sep 21 '12 at 14:10
  • @Lundin - something like that, yes. Nearly every time there's a question on SO that contains '4', (or, more recently, '8'), it's that issue. Likewise in my own code, if some buffer turns up with only 4 bytes in it, that's what I look for first! – Martin James Sep 21 '12 at 14:10
  • @MartinJames Yes, I understand what you're saying and am thankful for your input. However as I already stated, I'm not passing 4 at any point of my program. – ATaylor Sep 21 '12 at 14:11
  • @ATaylor - OK, but it was a good try! – Martin James Sep 21 '12 at 14:11
  • Not related to your problem, but on Windows, it's worth writing the port name as "\\.\COM6" (`"\\\\.\\COM6"` as a string literal), otherwise you'll have mysterious failures with two-digit port numbers. – Ben Voigt Sep 21 '12 at 14:13
  • @BenVoigt Ah, I didn't know that. I shall try to remember it. – ATaylor Sep 21 '12 at 14:15
  • OK, next - are you reading the port in a loop? 'When reading from a communications device, the behavior of ReadFile is determined by the current communication time-out as set and retrieved by using the SetCommTimeouts and GetCommTimeouts functions. Unpredictable results can occur if you fail to set the time-out values. For more information about communication time-outs, see COMMTIMEOUTS.' – Martin James Sep 21 '12 at 14:17
  • @BenVoigt It isn't really applicable when he hard codes the COM port number :) But if you write a generic driver you should indeed do as you descibe, ie: `sprintf(port, "\\\\.\\COM%d", port_number);` – Lundin Sep 21 '12 at 14:18
  • @MartinJames Actually I do set the timeouts. Unfortunately I can't remember the values I'm using. (Which is why I didn't post them) Also, I am using ReadFile as a 'single read' setup (reading the entire traffic in one go). However you may be able to make something of the actual reading code. Hold on. – ATaylor Sep 21 '12 at 14:21
  • 2
    The thing is, ReadFile() has an annoying tendency to return early, having not read as many bytes as you would wish. That's why it has that 'lpNumberOfBytesRead' parameter, so you can find out how much of your buffer it has read this time and, if necessary, issue another ReadFile() call. – Martin James Sep 21 '12 at 14:26
  • @MartinJames Which is what I'm doing (check my latest edit. 'read' has the same habit, btw.) – ATaylor Sep 21 '12 at 14:27
  • Oh - you're adding the 'ptr' on each loop but you're not reading at the 'ptr'. – Martin James Sep 21 '12 at 14:34
  • Also make sure to check the result of ReadFile. If it is FALSE, then call GetLastError(). – Lundin Sep 21 '12 at 14:40
  • @MartinJames Stupid reciting code form memory...of course you're right, I fixed it to match the actual code. And error checking is actually in place in the actual code. – ATaylor Sep 21 '12 at 14:42
  • OK, now I'm stuck too. I would expect it to work:( – Martin James Sep 21 '12 at 14:53
  • @MartinJames So did I. But I'll try out the oscilloscope approach and the fParity flag on Monday. Who knows, maybe the problem is somewhere completely else after all. – ATaylor Sep 21 '12 at 14:55
  • Yes. unwind/Lundin are spot-on, you have to split this problem up to find out whether its the tx, rx or dodgy cables/connectors etc. Which reminds me - must give unwind a +1 :) – Martin James Sep 21 '12 at 15:17
  • Did you try to send just one byte? Does it work? Also, could you detail the wiring between the two hosts ? It should be A-->A, B-->B, GND-->GND – Alexandre Vinçon Sep 23 '12 at 08:58
  • @AlexandreVinçon Yes, I did try sending one byte. 0x55 to be precise. The wiring is plain chaos, because we need to go from a RJ-45 connector, which is sitting in a plug which leads to a platine, which then will forward it to the actual RS485 chip...in any case, we're pretty sure that we've got the wiring down now, with A on A, B on B and GND on GND. Also, we are getting signals, which look similar, but are not evaluated correctly. (Right now, I send 0x55 and get two times 0x00) – ATaylor Sep 24 '12 at 13:03
  • 1
    Ok, now the problem you have is that you are developping the sender and the receiver at the same time. I suggest that you try to split the problem in two: get any third-party modbus device that is proven to work with RS485 (buy one if you have to!). Make your sending host work with this device and then you'll know its functionnal. Then do the same with your receiving host. – Alexandre Vinçon Sep 24 '12 at 19:13

3 Answers3

5
  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. :)
unwind
  • 391,730
  • 64
  • 469
  • 606
  • 1
    If you don't have an oscilloscope use the hyper terminal application in Windows. It works and is easy to setup. Maybe you need to use rs232 instead but this will help you to discard a problem on cables and connectors. – Picarus Sep 21 '12 at 13:27
  • Yes, I'm setting the Baud rate on windows. I'm also pretty sure, that the cable works (otherwise the Linux example with both termios set to 0 before setting the baud rate and stuff wouldn't work, would it?) Unfortunately, I don't have RS232 available. – ATaylor Sep 21 '12 at 13:33
  • 2
    @ATaylor You really, really need to use an oscilloscope when doing hardware-related programming such as this. Because right now you don't know if it is the Linux side, the Windows side or the hardware causing trouble. If you could measure a proper UART data signal with the correct baudrate leaving the Linux machine, then the problem is in the receiver, otherwise it is in the transmitter or the hardware. You can't assume that the Linux version is correct because it can speak "with itself", because then you can set the baudrate and data format to anything and it will still work. – Lundin Sep 21 '12 at 14:23
  • @Lundin Yeah, I will try the oscilloscope first thing on Monday...I'm not much of a hardware guy, which is why I'm having so much trouble with this issue. – ATaylor Sep 21 '12 at 14:25
  • 4
    @ATaylor Neither am I, I consider myself a 99% software guy. But still I consider an oscilloscope to be a mandatory tool for embedded programming. – Lundin Sep 21 '12 at 14:34
  • ..though I recently wasted half a day trying to work out why my keypad scan pulses were not being issued. Turned out they were, but I'd knocked the 'Times 10' knob and the pulses were off the screen:(( – Martin James Sep 21 '12 at 14:56
  • Just some anecdotal experience about RS232: Some of the USB-Serial Adapters do some buffering of their own, throwing off timings. It's good enough for the RS232 stuff they are usually intended for (telephone systems, modems and some printers) but may have problems with real hardware that's picky about that stuff. YMMV, +1 to the oscilloscope. – Michael Stum Sep 21 '12 at 20:36
  • Thanks to the oscillocope, we were able to ascertain, that the signal leaves the Linux platform quite splendidly. We also verified, that the sending from the other side works. So the cables are okay. Which leaves me in a tight spot, because that means it's a software fault. Still +1 for the Oscilloscope hint. – ATaylor Sep 24 '12 at 09:51
  • While I haven't been able to solve the problem as of yet, the Oscilloscope helped me ascertain, that the problem is most likely beyond my control (with the USB-Converter the signal is flawless, with the actual hardware, it's distorted and wrong...with the same code, mind you). So you're getting the 'Correct' mark. Thank you again. – ATaylor Sep 26 '12 at 09:12
0

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.

bobwki
  • 794
  • 6
  • 22
0

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

dcb.DCBlength = sizeof(dcb);
Gianluca Ghettini
  • 11,129
  • 19
  • 93
  • 159
  • While I appreciate the input, the question has long been answered. As it turned out, if I recall correctly, I was given false configuration information from the get go by the manufacturer of the Linux embedded device. – ATaylor May 15 '13 at 13:44
  • ah ok, I told you to set DCBlength just because is a very common pitfall when it comes to serial I/O on Windows :) I messed up with the DCB configuration struct for countless hours... – Gianluca Ghettini May 15 '13 at 14:00