clinuxserial-porttermios

How to properly received serial data using C?


Update1: Short version: when serial device send 0x23 0x0 0x3b 0xa, my program can read all the 4 bytes (serial_getBiaststatus example). When serial devices send the second byte as 0x3, my device receive only the last 2 bytes (0x3b 0xa, serial_getLedstatus example)

Long version I have a serial device that answer for some commands to set and return status of some options, one of this options is a LED. To set options, I have to send:

0xFF, 0x55, 0x<ADDR>, 0x<DATA>

(device returns nothing after SET command)

And to read this options, I must send:

0xFF, 0xAA, 0x<ADDR>, 0xFF

and the device will return:

0x23, 0x<VALUE>, 0x3B, 0x0A

Besides these options, the device will constantly send data terminating with line feed. So I need to disable these data sending a special register (address 0x9) to disable this log datam otherwise I would not be able to read status from LED or another device option.

Ok, my problem is: when I set my LED status do 3 (0x3), I could not read status from device! My program read only 2 bytes from the device, instead of 4 bytes. If I use 0 or 1 value, the status is returned correctly! (4 bytes). I'm surre that the device is sending correct values (4 bytes, in this case, 0x23, 0x03, 0x3B, 0x0A because I have tested it with YAT terminal program in windows and the values are correct.....so it's not a device problem, but my program.

This is my code until now. I'm setting connection to serial device to non-canonical mode to set/read device options (specially because 'set' doesn't return anything, so I can't do read() and set timeout)...after setting options, I set the device connection to canonical mode and get the necessary data until the end of program. Everything is working, except when I need to use '3' in device options....the option itself if set on device (I can visually verify LED), but when I try to read this LED value, only the last 2 bytes are returned (0x3b 0xa), not the value (the second byte from 4).


int serial_set_interface_attribs(int fd, int canonical) {    
    pthread_mutex_lock(&m_serial);    
    if (tcgetattr(fd, &tty) < 0) {
        printf("Error from tcgetattr: %s\n", strerror(errno));
        pthread_mutex_unlock(&m_serial);
        return -1;
    }


    switch (serial_speed) {

        case 57600:
            cfsetospeed(&tty, (speed_t) B57600);
            cfsetispeed(&tty, (speed_t) B57600);
            jonis_log_level(2, "Serial device speed set to 57600\n");
            break;

        case 115200:
            cfsetospeed(&tty, (speed_t) B115200);
            cfsetispeed(&tty, (speed_t) B115200);
            jonis_log_level(2, "Serial device speed set to 115200\n");
            break;

        case 230400:
            cfsetospeed(&tty, (speed_t) B230400);
            cfsetispeed(&tty, (speed_t) B230400);
            jonis_log_level(2, "Serial device speed set to 230400\n");
            break;

        case 460800:
            cfsetospeed(&tty, (speed_t) B460800);
            cfsetispeed(&tty, (speed_t) B460800);
            jonis_log_level(2, "Serial device speed set to 460800\n");
            break;

        case 500000:
            cfsetospeed(&tty, (speed_t) B500000);
            cfsetispeed(&tty, (speed_t) B500000);
            jonis_log_level(2, "Serial device speed set to 500000\n");
            break;

        case 576000:
            cfsetospeed(&tty, (speed_t) B576000);
            cfsetispeed(&tty, (speed_t) B576000);
            jonis_log_level(2, "Serial device speed set to 576000\n");
            break;

        case 921600:
            cfsetospeed(&tty, (speed_t) B921600);
            cfsetispeed(&tty, (speed_t) B921600);
            jonis_log_level(2, "Serial device speed set to 921600\n");
            break;


        default:
            cfsetospeed(&tty, (speed_t) B921600);
            cfsetispeed(&tty, (speed_t) B921600);
            jonis_log_level(2, "Serial device speed set to 921600\n");

    }


    tty.c_cflag |= CLOCAL | CREAD;
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8; /* 8-bit characters */
    tty.c_cflag &= ~PARENB; /* no parity bit */
    tty.c_cflag &= ~CSTOPB; /* only need 1 stop bit */
    tty.c_cflag &= ~CRTSCTS; /* no hardware flowcontrol */

    if (canonical == 1) {
        jonis_log_level(1,"Interface in canonical mode\n");
        tty.c_lflag |= ICANON | ISIG; /* canonical input */
        tty.c_lflag &= ~(ECHO | ECHOE | ECHONL | IEXTEN);
    } else {
        jonis_log_level(1,"Interface in non-canonical mode\n");
        //Disable ECHO
        tty.c_lflag &= ~(ICANON);
        tty.c_lflag &= ~(ECHO | ECHOE);        
        tty.c_cc[VMIN] = 0;
        tty.c_cc[VTIME] = 10;

    }
    
    //tty.c_iflag &= ~IGNCR;  /* preserve carriage return - estava comentado */ 
    tty.c_iflag &= ~INPCK;
    tty.c_iflag &= ~(INLCR | ICRNL | IUCLC | IMAXBEL);
    tty.c_iflag &= ~(IXON | IXOFF | IXANY); /* no SW flowcontrol */

    tty.c_oflag &= ~OPOST;

    tty.c_cc[VEOL] = 0;
    tty.c_cc[VEOL2] = 0;
    tty.c_cc[VEOF] = 0x04;



    if (tcsetattr(fd, TCSANOW, &tty) != 0) {
        printf("Error from tcsetattr: %s\n", strerror(errno));        
        pthread_mutex_unlock(&m_serial);
        return -1;
    }    
    pthread_mutex_unlock(&m_serial);    
    return 0;
}

void *serial_threadGetSerialData(void *argv) {

    AN_NOTUSED(argv);
    // signal handlers:
    signal(SIGINT, jonisSigintHandler);
    signal(SIGTERM, jonisSigtermHandler);
    
    if (pthread_mutex_init(&m_serial, NULL) != 0) {
        printf("\n mutex init failed\n");
        exit(EXIT_FAILURE);
    }
        
    if (serial_device == NULL) {
        jonis_log("Can't start serial communication. Device is null.\n");
        return NULL;
    }


    fd_serial = open(serial_device, O_RDWR | O_NOCTTY | O_SYNC);
    if (fd_serial < 0) {
        jonis_log("Error opening %s: %s\n", serial_device, strerror(errno));
        return NULL;
    }    
    
    
    // Configue LED and other settings           
    struct sdongle_version *tmp = serial_getDongleVersion();
    if (tmp == NULL) {
        jonis_log_level(1, "Function to get dongle FW version returned NULL!\n");
    } else {        
        jonis_log("Serial dongle FW version: %d.%d\n",tmp->major,tmp->minor);
    }                    

    // Set LED to 3    
    serial_setLedstatus(3); 
    // Read LED status from device
    serial_getLedstatus();

    if (serial_bias_t == 1) {
        // Enable Bias T
        serial_setBiaststatus(1);
        printf("Definido biast como 1!\n");
    } else {
        // Disabl Bias-T
        serial_setBiaststatus(0);
        printf("Definido biast como 0!\n");
    }
    (...)
    
    serial_enableData();
            
    /* simple canonical input */
    do {


        char buf[150];
        //unsigned char *p;
        int rdlen;

        rdlen = read(fd_serial, buf, sizeof (buf) - 1);
        if (rdlen > 0) {
            buf[rdlen] = 0;
            //printf("Read (main) %d bytes: ", rdlen);
            /* first display as hex numbers then ASCII */
            //for (p = buf; rdlen-- > 0; p++) {
            //    printf(" 0x%x", *p);
            //    if (*p < ' ')
            //        *p = '.';   /* replace any control chars */
            //}
            //printf("\n    \"%s\"\n\n", buf);
            //printf("%s", buf);

            // Proccess data
            //decodeRaw(buf);


        } else if (rdlen < 0) {
            jonis_log("Error from read: %d: %s\n", rdlen, strerror(errno));
        } else { /* rdlen == 0 */
            //printf("(main)Nothing read. EOF?\n");            
        }
        /* repeat read */
    } while (!MyProgram.exit);

    jonis_log_level(1,"Reseting Serial Dongle options.\n");    
    if (serial_setLedstatus(0) != 1) {
        jonis_log("Error reseting serial dongle LED status\n");
    }    
    
    if (fd_serial > -1) {
        close(fd_serial);
    }

    pthread_exit(EXIT_SUCCESS);

}

int serial_disableData(void) {

    if (fd_serial < 0) {
        jonis_log("Error disabling serial data: invalid serial connection.\n");
        return -1;
    }

    char disable_data[] = {0xFF, 0x55, 0x09, 0x01};

    serial_set_interface_attribs(fd_serial, 0);    
    jonis_log_level(1, "Canonical mode set to 0\n");    
    pthread_mutex_lock(&m_serial);
    
    int wlen = 0;

    wlen = write(fd_serial, disable_data, 4);
    if (wlen != 4) {
        jonis_log("Error from write: %d, %d\n", wlen, errno);
        pthread_mutex_unlock(&m_serial);
        return -1;
    }
    tcdrain(fd_serial); /* delay for output */
    
    usleep(100000);
    
    // Clear current buffer
    char buf[9000] = {0};
    int total_cleared = 0;

    int rdlen = 0;
    rdlen = read(fd_serial, buf, sizeof (buf) - 1);
    total_cleared = rdlen;
    jonis_log_level(1, "Clearing buffer. Current counter: %d. Returned: %d\n", total_cleared, rdlen);
    while (rdlen != 0) {
        rdlen = read(fd_serial, buf, sizeof (buf) - 1);
        total_cleared = total_cleared + rdlen;
        usleep(100000);
        jonis_log_level(1, "Clearing buffer. Current counter: %d. Returned: %d\n", total_cleared, rdlen);
    }

    jonis_log_level(1, "Finished sending 'disable_log' command do serial device! Data length in buffer: '%d'\n", total_cleared);
    pthread_mutex_unlock(&m_serial);
    return 1;

}

int serial_enableData(void) {

    if (fd_serial < 0) {
        jonis_log("Error enabling serial data: invalid serial connection.\n");
        return -1;
    }

    serial_set_interface_attribs(fd_serial, 1);
    pthread_mutex_lock(&m_serial);
    char disable_data[] = {0xFF, 0x55, 0x09, 0x00};

    int wlen = 0;

    wlen = write(fd_serial, disable_data, 4);
    if (wlen != 4) {
        jonis_log("Error from write: %d, %d\n", wlen, errno);
        pthread_mutex_unlock(&m_serial);
        return -1;
    }
    tcdrain(fd_serial); /* delay for output */

    jonis_log_level(1, "Finished sending 'enable_log' command do serial device!\n");

    pthread_mutex_unlock(&m_serial);
    return 1;

}

struct sdongle_version *serial_getDongleVersion(void) {

    if (fd_serial < 0) {
        jonis_log("Error getting serial data: invalid serial connection.\n");
        return NULL;
    }

    if (serial_disableData() < 0) {
        jonis_log("Could not disable data\n");
        return NULL;
    }

    char disable_data[] = {0xFF, 0xAA, 0x0F, 0xFF};
    int wlen = 0;

    wlen = write(fd_serial, disable_data, 4);
    if (wlen != 4) {
        jonis_log("Error from write: %d, %d\n", wlen, errno);
        return NULL;
    }
    tcdrain(fd_serial); /* delay for output */
    usleep(100000);

    char buf[150];
    char *p;
    int rdlen;

    //rdlen = read(fd_serial, buf, sizeof (buf) - 1);
    //if (rdlen > 0) {
    while ((rdlen = read(fd_serial, buf, sizeof (buf) - 1)) < 4 || !Modes.exit) {    
        buf[rdlen] = 0;
        //printf("(Version) Read '%d' bytes: ", rdlen);
        if (rdlen == 4 && buf[0] == 0x23 && buf[2] == 0x3b && buf[3] == 0xa) {
            jonis_log_level(1, "Version string found!\n");
            int minor = buf[1] << 2 >> 2;
            int major = buf[1] >> 6;
            jonis_log_level(1, "Minor version: %d, Major version: %d\n", minor, major);

            struct sdongle_version *ver = malloc(sizeof (struct sdongle_version));
            ver->major = major;
            ver->minor = minor;
            return ver;
        }

        /* first display as hex numbers then ASCII */
        for (p = buf; rdlen-- > 0; p++) {
            //printf(" 0x%x", *p);            
        }
        //printf("\n    (version)Value: \"%s\"\n\n", buf);


    }

    return NULL;

}


int serial_getLedstatus(void) {

    if (fd_serial < 0) {
        jonis_log("Error getting serial data: invalid serial connection.\n");
        return -1;
    }

    if (serial_disableData() < 0) {
        jonis_log("Could not disable data\n");
        return -1;
    }

    char led_status_data[] = {0xFF, 0xAA, 0x8, 0xFF};

    int wlen = 0;

    wlen = write(fd_serial, led_status_data, 4);
    if (wlen != 4) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd_serial); /* delay for output */
    sleep(1);

    char buf[150] = {0};
    char *p = NULL;
    int rdlen = 0;
    int tries = 0;
    
    while (((rdlen = read(fd_serial, buf, sizeof (buf) - 1)) < 4 && !Modes.exit) && tries <= 5) {
        jonis_log_level(1,"(led) Read '%d' bytes: \n", rdlen);
        tries++;
        // Force rwrite command
        wlen = write(fd_serial, led_status_data, 4);
        tcdrain(fd_serial);
        sleep(1);        
        buf[rdlen] = 0;                
        /* first display as hex numbers then ASCII */
        for (p = buf; rdlen-- > 0; p++) {
            if (debug_level >= 1) { 
                printf(" 0x%x", *p); 
            }
        }
        if (debug_level >= 1) { 
            printf("\n");
            jonis_log_level(1,"    (led)Value: \"%s\"\n\n", buf);
        }
    } 
    
    if (rdlen > 0) {
        jonis_log_level(1,"Debug -> Received chars in LED Status\n");
        jonis_log_level(1,"(led) Read '%d' bytes: ", rdlen);        
        buf[rdlen] = 0;                
        /* first display as hex numbers then ASCII */
        for (p = buf; rdlen-- > 0; p++) {
            if (debug_level >= 1) { 
                printf(" 0x%x", *p);            
            }
        }
        if (debug_level >= 1) { 
            printf("\n");
            jonis_log_level(1,"    (led)Value: \"%s\"\n\n", buf);
        }
    }
    
    jonis_log_level(1,"LED Status (function): %d\n",buf[1]);
    return buf[1];

}


int serial_setLedstatus(char led_status) {

    
    if (fd_serial < 0) {
        jonis_log("Error getting serial data: invalid serial connection.\n");
        return -1;
    }

    if (serial_disableData() < 0) {
        jonis_log("Could not disable data\n");
        return -1;
    }    
    
    char led_data[] = {0xFF, 0x55, 0x08, led_status};
    int wlen = 0;
    wlen = write(fd_serial, led_data, 4);
    if (wlen != 4) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd_serial); /* delay for output */
    //serial_getLedstatus();
    return 1;
}

int serial_getBiaststatus(void) {

    if (fd_serial < 0) {
        jonis_log("Error getting serial data: invalid serial connection.\n");
        return -1;
    }

    if (serial_disableData() < 0) {
        jonis_log("Could not disable data\n");
        return -1;
    }

    char bias_status_data[] = {0xFF, 0xAA, 0x07, 0xFF};

    int wlen = 0;

    wlen = write(fd_serial, bias_status_data, 4);
    if (wlen != 4) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd_serial); /* delay for output */
    usleep(100000);

    char buf[150];
    char *p;
    int rdlen;

    rdlen = read(fd_serial, buf, sizeof (buf) - 1);
    if (rdlen > 0) {
        buf[rdlen] = 0;
        printf("(biast) Read '%d' bytes: ", rdlen);
        
        /* first display as hex numbers then ASCII */
        for (p = buf; rdlen-- > 0; p++) {
            printf(" 0x%x", *p);            
        }
        printf("\n    (biast)Value: \"%s\"\n\n", buf);


    } else if (rdlen < 0) {
        jonis_log("Error from read: %d: %s\n", rdlen, strerror(errno));
    } else { /* rdlen == 0 */
        //printf("(led)Nothing read. EOF?\n");        
    }    
    jonis_log_level(1,"BIAS-T Status (function): %d\n",buf[1]);
    return buf[1];

}


int serial_setBiaststatus(char biast_status) {

    if (fd_serial < 0) {
        jonis_log("Error getting serial data: invalid serial connection.\n");
        return -1;
    }

    if (serial_disableData() < 0) {
        jonis_log("Could not disable data\n");
        return -1;
    }

    char biast_data[] = {0xFF, 0x55, 0x07, biast_status};
    int wlen = 0;
    wlen = write(fd_serial, biast_data, 4);
    if (wlen != 4) {
        printf("Error from write: %d, %d\n", wlen, errno);
    }
    tcdrain(fd_serial); /* delay for output */
    usleep(100000);
    
    if (serial_getBiaststatus() == biast_status) {    
        return 1;
    } else {
        return 0;
    }

}

And this is the returned text (note only 2 bytes received from LED status):

[2022-04-28 09:22:00] [serial_set_interface_attribs]  Interface in non-canonical mode
[2022-04-28 09:22:00] [serial_disableData]  Canonical mode set to 0
[2022-04-28 09:22:00] [serial_disableData]  Clearing buffer. Current counter: 1. Returned: 1
[2022-04-28 09:22:01] [serial_disableData]  Clearing buffer. Current counter: 1. Returned: 0
[2022-04-28 09:22:01] [serial_disableData]  Finished sending 'disable_log' command do serial device! Data length in buffer: '1'
[2022-04-28 09:22:01] [serial_getDongleVersion]  Version string found!
[2022-04-28 09:22:01] [serial_getDongleVersion]  Minor version: 34, Major version: 0
[2022-04-28 09:22:01]  Serial dongle FW version: 0.34
[2022-04-28 09:22:01] [serial_set_interface_attribs]  Interface in non-canonical mode
[2022-04-28 09:22:01] [serial_disableData]  Canonical mode set to 0
[2022-04-28 09:22:02] [serial_disableData]  Clearing buffer. Current counter: 0. Returned: 0
[2022-04-28 09:22:02] [serial_disableData]  Finished sending 'disable_log' command do serial device! Data length in buffer: '0'
[2022-04-28 09:22:02] [serial_set_interface_attribs]  Interface in non-canonical mode
[2022-04-28 09:22:02] [serial_disableData]  Canonical mode set to 0
[2022-04-28 09:22:03] [serial_disableData]  Clearing buffer. Current counter: 0. Returned: 0
[2022-04-28 09:22:03] [serial_disableData]  Finished sending 'disable_log' command do serial device! Data length in buffer: '0'
[2022-04-28 09:22:04] [serial_getLedstatus]  (led) Read '2' bytes:
 0x3b 0xa
[2022-04-28 09:22:05] [serial_getLedstatus]      (led)Value: ";
"

[2022-04-28 09:22:05] [serial_getLedstatus]  (led) Read '2' bytes:
 0x3b 0xa
[2022-04-28 09:22:06] [serial_getLedstatus]      (led)Value: ";
"

[2022-04-28 09:22:06] [serial_getLedstatus]  (led) Read '2' bytes:
 0x3b 0xa
[2022-04-28 09:22:07] [serial_getLedstatus]      (led)Value: ";
"

[2022-04-28 09:22:07] [serial_getLedstatus]  (led) Read '2' bytes:
 0x3b 0xa
[2022-04-28 09:22:08] [serial_getLedstatus]      (led)Value: ";
"

[2022-04-28 09:22:08] [serial_getLedstatus]  (led) Read '2' bytes:
 0x3b 0xa
[2022-04-28 09:22:09] [serial_getLedstatus]      (led)Value: ";
"

[2022-04-28 09:22:09] [serial_getLedstatus]  (led) Read '2' bytes:
 0x3b 0xa
[2022-04-28 09:22:10] [serial_getLedstatus]      (led)Value: ";
"

[2022-04-28 09:22:10] [serial_getLedstatus]  Debug -> Received chars in LED Status
[2022-04-28 09:22:10] [serial_getLedstatus]  (led) Read '2' bytes:  0x3b 0xa
[2022-04-28 09:22:10] [serial_getLedstatus]      (led)Value: ";
"

[2022-04-28 09:22:10] [serial_getLedstatus]  LED Status (function): 10
[2022-04-28 09:22:10] [serial_set_interface_attribs]  Interface in non-canonical mode
[2022-04-28 09:22:10] [serial_disableData]  Canonical mode set to 0
[2022-04-28 09:22:11] [serial_disableData]  Clearing buffer. Current counter: 0. Returned: 0
[2022-04-28 09:22:11] [serial_disableData]  Finished sending 'disable_log' command do serial device! Data length in buffer: '0'
[2022-04-28 09:22:12] [serial_set_interface_attribs]  Interface in non-canonical mode
[2022-04-28 09:22:12] [serial_disableData]  Canonical mode set to 0
[2022-04-28 09:22:13] [serial_disableData]  Clearing buffer. Current counter: 0. Returned: 0
[2022-04-28 09:22:13] [serial_disableData]  Finished sending 'disable_log' command do serial device! Data length in buffer: '0'
[2022-04-28 09:22:13] [serial_set_interface_attribs]  Interface in non-canonical mode
[2022-04-28 09:22:13] [serial_disableData]  Canonical mode set to 0
[2022-04-28 09:22:14] [serial_disableData]  Clearing buffer. Current counter: 0. Returned: 0
[2022-04-28 09:22:14] [serial_disableData]  Finished sending 'disable_log' command do serial device! Data length in buffer: '0'
[2022-04-28 09:22:14] [serial_set_interface_attribs]  Interface in non-canonical mode
[2022-04-28 09:22:14] [serial_disableData]  Canonical mode set to 0
[2022-04-28 09:22:15] [serial_disableData]  Clearing buffer. Current counter: 0. Returned: 0
[2022-04-28 09:22:15] [serial_disableData]  Finished sending 'disable_log' command do serial device! Data length in buffer: '0'
(biast) Read '4' bytes:  0x23 0x0 0x3b 0xa
    (biast)Value: "#"

[2022-04-28 09:22:15] [serial_getBiaststatus]  BIAS-T Status (function): 0
[2022-04-28 09:22:15] [serial_set_interface_attribs]  Interface in canonical mode
[2022-04-28 09:22:15] [serial_enableData]  Finished sending 'enable_log' command do serial device!


Again: the device is returning the correct number of bytes (4), even if the value os the option is 3, but my program can't ready all these bytes, only last 2. What am I missing here? Tks!


Solution

  • Ok, my problem is: when I set my LED status do 3 (0x3), I could not read status from device!
    ...
    I'm surre that the device is sending correct values (4 bytes, in this case, 0x23, 0x03, 0x3B, 0x0A ...

    0x03 is also the ASCII control code named ETX.

    The default value for the termios special character VINTR is 0x03, which is also known as ETX and Ctrl-C.
    When the termios ISIG attribute of c_lflag is set, then receiving the VINTR character will cause the process to get a SIGINT signal, and that character is removed from the input buffer.


    if (canonical == 1) {
        ...
        tty.c_lflag |= ICANON | ISIG; /* canonical input */
        ...
    } else {
        ...
        tty.c_lflag &= ~(ICANON);
        tty.c_lflag &= ~(ECHO | ECHOE);        
        ...
    }
    

    Your program sets the ISIG attribute for "canonical" mode, and otherwise leaves that attribute in c_lflag untouched.
    Apparently VINTR still has its default value of 0x03.

    The net effect is that the ISIG attribute is always enabled, and therefore receiving 0x03 will always be unreadable.