Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

tcsetattr() blocking time with TCSADRAIN flag is weird

I am coding with serial port in Linux.

And the requirement of the communication is 5ms inter-byte time.

And It requires me to change parity mode(even and odd) for each byte before write() call, according to what the byte's value is.

So I code like below(i describe code simply)

void setWakeupMode(int fd, bool mode) {
    struct termios tio;

    bzero(&tio, sizeof(tio));
    tcgetattr(fd, &tio);


    if (mode == false) {
        tio.c_cflag &= ~PARODD;
    } else if (mode == true) {
        tio.c_cflag |= PARODD;
    }

    if(tcsetattr(fd, TCSADRAIN, &tio) < 0){
        perror("tcsetattr Error");
    }
}

int main(){
    unsigned char a[2] = {0x01, 0x0F};

    write(fd, a, 1);

    setWakeupMode(fd, true);

    write(fd, a+1, 1);

}

But the code doesn't satisfy inter-byte time resulting in almost 20ms.

So i tried print exact time between each system call like below.

   int main(){
        unsigned char a[2] = {0x01, 0x0F};

        printf("write1 start : %s", /*time*/);
        write(fd, a, 1);

        printf("write1 end  : %s", /*time*/); 
        setWakeupMode(fd, true);

        printf("write2 start : %s", /*time*/);
        write(fd, a+1, 1);

        printf("write2 end : %s, /*time*/);
    }

and This is the result

write1 start : 34.755201
write1 end   : 34.756046
write2 start : 34.756587  
write2 end   : 34.757349  

This result suddenly satisfy the 5ms inter-byte time, resulting in 1ms inter-byte time.

So i tried several ways.

And finally i recognize that only when i print something right before tcsetattr(), inter-byte time is satisfied.

for example, if i remove printf("write1 end : %s, /*time*/); like below

   int main(){
        unsigned char a[2] = {0x01, 0x0F};

        printf("write1 start : %s", /*time*/);
        write(fd, a, 1);

        // printf("write1 end  : %s", /*time*/);  //remove this 
        setWakeupMode(fd, true);

        printf("write2 start : %s", /*time*/);
        write(fd, a+1, 1);

        printf("write2 end : %s", /*time*/);
    }

The result is surprisingly different, See the interval between write1 start and write2 start, It is 18ms.

write1 start : 40.210111
write2 start : 40.228332
write2 end   : 40.229187

If i use std::cout instead of printf, the result is same.

Why does this weird situration happen?

-------------------------------EDIT--------------------------------

Since i see some answers, some are misunderstanding my problem.

I am not worrying printf() overhead.

simply saying.

  1. I want to call write()s with 1byte But the interval between write()s must be within 5ms
  2. And Before calling write(), I have to change parity mode using tcsetattr()
  3. But The interval result is 18ms, being blocked at tcsetattr() almost time.
  4. But If i call printf() or std::cout right before tcsetattr(), the interval reduce to 1~2ms.

that is, somehow, calling printf before tcsetattr() make tcsetattr() return from blocking faster.

--------------------------Update----------------------------

I have progress on this problem.

I said that i had to put printf() or std::cout to make blocking time short on tcsetattr().

But It was not printing something to affect to that problem.

It just needed some delay, that is, if i put usleep(500) before calling tcsetattr(), it also make an affect on inter-byte-time reducing by 1~2ms, returning from tcsetattr() faster.

I assume, if i call tcsetattr() with TCSADRAIN flag, it wait until all data in serial buffer is transmitted, and change to the setting i want.

and it could make some delay.

but if i calling specifically delay, before i call tcsetattr(), the buffer state is already empty(because during the delay time, the data in serial buffer is transmitted), so that there is no blocking.

this is the scenario i assume, is it possible?

like image 736
SangminKim Avatar asked Oct 20 '22 20:10

SangminKim


1 Answers

this is the scenario i assume, is it possible?

You must be quite right. sawdust wrote:

uart_wait_until_sent() in drivers/tty/serial/serial_core.c explains why the interval between characters is quantized when a "drain" is involved: the serial port driver is polled for the TIOCSER_TEMP (transmitter empty) status using a delay with only millisecond resolution.

That's almost right, except that the polling cycle resolution is not milliseconds, but jiffies:

         while (!port->ops->tx_empty(port)) {
                 msleep_interruptible(jiffies_to_msecs(char_time));
                 …

So if HZ is 100 and char_time is 1 (the minimum), the status is checked every 10 ms. As you wrote:

but if i calling specifically delay, before i call tcsetattr(), the buffer state is already empty(because during the delay time, the data in serial buffer is transmitted), so that there is no blocking.

In other words, if you delay the process long enough that by the time it gets to the transmitter empty test !port->ops->tx_empty(port) the transmitter is already empty, no sleep occurs; otherwise it sleeps for at least 1 jiffy.

like image 107
Armali Avatar answered Oct 22 '22 12:10

Armali