0

My application is able to communicate with baud rates like 4800, 9600 and 115200 but can't with 14400 or 38400. I have to include asm/termios because I need struct termios2 since I'm going to use c_ispeed and c_ospeed members for any buad rate.

Also the second problem I encounter is that read function doesn't return afterVTIME. Do you know why this happens? Any help is appreciated. Thanks.

#include <asm/termios.h>

int serialDevice = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_SYNC);

serialSettings.baudRate = 4800;
serialSettings.dataBits = 8;
serialSettings.hardwareFlowControl = 0;
serialSettings.parity = 0;
serialSettings.parityOdd = 0;
serialSettings.stopBits = 1;
serialSettings.xonxoff = 1;

setSerialSettings(serialDevice, &serialSettings);
//-------------------------------------------------------
int8_t setSerialSettings(int serialDevice, Serial_Params_t *settings)
{
    struct termios2 tty;
    memset(&tty, 0, sizeof tty);

    // get current serial settings
    if (ioctl(serialDevice, TCGETS2, &tty) == -1)
    {
        sendLog("Can't get serial attributes | setSerialSettings", LOG_TYPE_ERROR);
        return FALSE;
    }

    // baudrate
    tty.c_cflag &= ~CBAUD;
    tty.c_cflag |= BOTHER;
    tty.c_ispeed = MAX(110, MIN(settings->baudRate, MAX_BAUDRATE));
    tty.c_ospeed = MAX(110, MIN(settings->baudRate, MAX_BAUDRATE));

    // enable input parity check
    tty.c_iflag |= INPCK;

    // data bits: CS5, CS6, CS7, CS8
    tty.c_cflag &= ~CSIZE;
    switch (settings->dataBits)
    {
    case 5:
        tty.c_cflag |= CS5;
        break;
    case 6:
        tty.c_cflag |= CS6;
        break;
    case 7:
        tty.c_cflag |= CS7;
        break;
    case 8:
    default:
        tty.c_cflag |= CS8;
        break;
    }

    // stop bit
    switch (settings->stopBits)
    {
    case 1:
    default:
        tty.c_cflag &= ~CSTOPB;
        break;
    case 2:
        tty.c_cflag |= CSTOPB;
    }

    // parity
    if (settings->parity == 1)
        tty.c_cflag |= PARENB;
    else
        tty.c_cflag &= ~PARENB;

    // odd/even parity
    if (settings->parityOdd == 1)
        tty.c_cflag |= PARODD;
    else
        tty.c_cflag &= ~PARODD;

    // flow control
    // XON/XOFF
    if (settings->xonxoff == 1)
        tty.c_iflag |= (IXON | IXOFF | IXANY);
    else
        tty.c_iflag &= ~(IXON | IXOFF | IXANY);

    // enable RTS/CTS
    if (settings->hardwareFlowControl == 1)
        tty.c_cflag |= CRTSCTS;
    else
        tty.c_cflag &= ~CRTSCTS;

    tty.c_cc[VMIN] = 1;            // return read function when receive 1 byte
    tty.c_cc[VTIME] = 10;          // 1 seconds read timeout (deciseconds)
    tty.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines

    // non-canonical mode
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);
    tty.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    tty.c_oflag &= ~OPOST;

    // flush port & apply attributes
    tcflush(serialDevice, TCIFLUSH);
    if (ioctl(serialDevice, TCSETS2, &tty) == -1)
    {
        sendLog("Can't set serial attributes | setSerialSettings", LOG_TYPE_ERROR);
        return FALSE;
    }
    return TRUE;
}
lurker
  • 56,987
  • 9
  • 69
  • 103
Mustafa Chelik
  • 2,113
  • 2
  • 17
  • 29
  • When you say you can't communicate at 14400 or 38400, what exactly do you mean? Does the `TCSETS2` ioctl fail or does communication just not work? If the latter, do I understand correctly that you have successfully tested communication at certain other speeds using this same code? – John Bollinger Mar 14 '19 at 19:48
  • It means that peer receives garbage bytes. `ioctl` returns 0 for both baudrates that works and doesn't. Yes exact same code works for 9600 or 115200 but it doesn't work for 14400 for example. (P.S. I do change baudrate for the peer as well). – Mustafa Chelik Mar 14 '19 at 20:04
  • 1
    *"My application is able to communicate can't ... [communicate] with 14400 or 38400"* - The documentation for the board or UART will tell you the supported speeds. Any number of tutorials on the web explain how to set a speed, and what the speed depends upon. You should add the supported speeds from the documentation to your question so we can say, *"the board or UART does not support the speed."* – jww Mar 15 '19 at 02:50

1 Answers1

3

My application is able to communicate with baud rates like 4800, 9600 and 115200 but can't with 14400 or 38400.

There is a pretty nice writeup for how custom serial speed setting works here: https://github.com/npat-efault/picocom/blob/master/termios2.txt.

In brief, given a struct termios2 identified by tty, to set both input and output speed to custom values, you must

  • ensure that tty.c_cflag & CBAUD == BOTHER. You appear to do this correctly.
  • set the desired output speed in tty.c_ospeed. You do this, too.
  • either

    • ensure that (tty.c_cflag >> IBSHIFT) & CBAUD == B0, in which case the output speed will also be used as the input speed, or
    • ensure that (tty.c_cflag >> IBSHIFT) & CBAUD == BOTHER, in which case tty.c_ispeed will be used as the input speed.


    You do not do either of those. I'm uncertain why this would cause incorrect communication for some speeds and not others, but the driver is reputed to play some interesting games with speed settings, and maybe you've stumbled across one.

As for

read function doesn't return after VTIME

I think you have incorrect expectations. You are setting VMIN and VTIME both to nonzero values. In this case, VTIME is the maximum inter-character time, not an overall read timeout. With these settings, a blocking read will wait indefinitely for the first character, then will keep reading subsequent characters, up to the requested number, as long as each one arrives within VTIME deciseconds of the previous one.

If you want an overall timeout on every read() call, then set VMIN to 0, and be prepared for some read() calls to read 0 bytes. As always, read() may also read a positive number of bytes but fewer than requested. That may be more likely to happen in this configuration than in the one you're presently using, depending on your choice of VTIME and the behavior of the peer.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • `(tty.c_cflag >> IBSHIFT) & CBAUD` is 0 after settings the baud rates. How can I make `(tty.c_cflag >> IBSHIFT) & CBAUD` to be equal to B0 or BOTHER ? like `tty.c_cflag = ( (tty.c_cflag >> IBSHIFT) & CBAUD) << IBSHIFT)`? About VTIME: I changed VMIN to 0 and now read returns immediately but I expect it to at least wait 1 second. – Mustafa Chelik Mar 14 '19 at 20:55
  • @MustafaChelik: `tty.c_cflag &= ~(CBAUD << IBSHIFT); tty.c_cflag |= (B0 << IBSHIFT);` – John Bollinger Mar 14 '19 at 20:57
  • As for the `read`, there is no combination of settings that instructs the driver to accumulate data for exactly a certain amount of time before returning the results. If that's really what you want, then set `VMIN` and `VTIME` both to 0, and control `read` timing on the userland side by inserting a delay before each `read()` call. That assumes you're calling `read()` directly, of course. – John Bollinger Mar 14 '19 at 21:01
  • Unfortunately your code didn't work. I went deep into this thing and finally I added `tty.c_cflag |= BOTHER << IBSHIFT;`. Just before `ioctl` set, I checked both `tty.c_cflag & CBAUD` and `(tty.c_cflag >> IBSHIFT) & CBAUD` values that they're equal to BOTHER (4096). Which means kernel should use `c_ispeed` and `c_ospeed` but it doesn't. Do you have any idea? – Mustafa Chelik Mar 14 '19 at 22:47
  • I tested some baud rates and all standard baud rates works (B110, B150, B300, B600, B1200, B2400, B4800, B9600, B9600, B19200, B38400, B57600, B115200). But when I want to use a custom baud rate that is not defined in `termios` it fails, like 14400 or 38400. The weird thing is I get the serial port settings right after set command and I can see that baud rates are the rates I want but I doesn't work. (I mean I receive garbage data). Is it possible that my Linux can't handle custom baud rates maybe? – Mustafa Chelik Mar 14 '19 at 23:08
  • I found this code but unfortunately it dodn't work either https://www.experts-exchange.com/questions/25858801/How-do-I-set-non-standard-baud-rate-in-Linux.html#answer30723158-20 – Mustafa Chelik Mar 14 '19 at 23:33
  • 2
    @MustafaChelik, you could consult this question and its answers: https://stackoverflow.com/q/4968529/2402272, but it looks like they're all pointing in the two directions you already know about. These are the ways to do it. If they aren't working for you then I doubt the problem is Linux itself, but it might be that your hardware doesn't support the speed you're requesting, or the peer doesn't support it, or it could be that your code is somehow flawed. There may be other possibilities. – John Bollinger Mar 15 '19 at 00:08
  • I got suspicious to my converter after your comment. I have 1 Chinese 1$ converter and 1 German DIGITUS converter. `ioctl(serialDevice, TIOCGSERIAL, &ser_info)` returns -1 for Chinese and 0 for DIGITUS. Since I have 1 DIGITUS, I can't test baud rate changes. So I ordered another DIGITUS. Once I get it, i'll test and post the result here. Thank you for your comments. EDIT: Linux recognizes the Chinese one as `ch341-uart converter` and DIGITUS as `FTDI USB Serial Device converter` ¯\\_(ツ)_/¯ – Mustafa Chelik Mar 15 '19 at 08:26
  • About VTIME, when I change VTIME and VMIN to 0, `read` returns immediately and my reading `while` goes wild. If I put a sleep, it will cause delay. The only reason that I don't want blocking `read` is that my main thread should be able to tell the serial port thread to stop and exit. If my main thread tells the serial port to stop and the serial port in `read`, my application will not exit. What if I close serial port from main thread? Will `read` return? I may test this. – Mustafa Chelik Mar 15 '19 at 09:53
  • 1
    UPDATE: I removed O_NONBLOCK, set VMIN to 0 and VTIME to 10. Now it returns after 1 second even there is no data. This is what I wanted. When there is no data, read returns 0 and errno is also 0. The VTIME thing is fixed thanks to you. I'll put update once I get new converter. – Mustafa Chelik Mar 15 '19 at 11:02
  • I got the DIGITUS serial converter but the thing is when I want to set 14400 baudrate, it sets to 14397 because of the formula. There is no way to set 14400 baudrate with `ss.custom_divisor = (ss.baud_base + (speed / 2)) / speed;` formula. Do you have any idea why I have to use the formula or do you know a solution for this? – Mustafa Chelik Mar 19 '19 at 10:45
  • @MustafaChelik, I am not familiar with the details of programming for the DIGITUS hardware, but a difference of 0.02% between target and actual baud rate ought not to interfere with communication. Serial hardware can generally adapt to significatly larger differences. – John Bollinger Mar 19 '19 at 12:01