cserial-portembedded-linuxbinary-datatermios

How to read a binary data over serial terminal in C program?


I read followed links and other sources, but didn't find answer for my question.

Binary data over serial terminal

Data gets corrupted during transmission over the serial port

I communicate with my embedded device through a serial port. By default, embedded Linux uses this port as a terminal. But I want to transfer also binary data (service packets) through the port. My /etc/inittab file has a "getty" call: console::respawn:/sbin/getty 115200 ttyS0

I also have /etc/passwd file with string where "admin" user launch my "cli" application after log in: admin:8Mt/Jtxcyg8AY:1000:0:admin:/tmp:/tmp/cli

My default ttyS0 settings before running the program is:

~ # stty -a
speed 115200 baud;stty: standard input
 line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = ^J;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon ixoff
-iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon -iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
-echoctl echoke
~ #

So, in my cli program I do the following:

main ()
{
    ...
    system("stty erase ^H);
    system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter in non-canonical (raw) mode

    // What function do I need to use here to retrieve binary data (also symbols that > 0x7F) from /dev/ttyS0?

    system("stty -F /dev/ttyS0 icrnl ixon ixoff opost isig icanon echo");   // go back to canonical mode

    ...

    exit(0);
}

I have attempted read() function (with unsigned char buffer) to get binary data, but failed to receive correct data. I also preliminarily open /dev/ttyS0 again to get file_descriptor & use read() func.

My program sends 3 bytes: 0xAA, 0x02, 0xFE. But in syslog I always see that device receives incorrect symbols: 0x98, 0xE6, 0x18.

What is the matter? How to get correct binary data?

A whole code that I am testing at the moment.

#include "cli.h"
#include "glb_vars.h"

/******************************************
 *** Definitions
 ******************************************/
#define APPLICATION_NAME    "cli"
#define SERIALPORT_IS_CONSOLE

/******************************************
 *** Constants
 ******************************************/
const char dev_name[] = DEV_NAME;
const char lineminstr[] = "\t--------------------------------------------------------\n";

/******************************************
 *** Internal Function Declarations
 ******************************************/
CLI_RETVAL cliInit(void);
CLI_RETVAL cliClose(void);
void cliWorkLoop(Term_callback_t **term);

/******************************************
 *** External Function Declarations
 ******************************************/
extern void Vectors_init(Term_callback_t **vec);
extern char** Menu_completion(const char * text, int start, int end);


/****************************************************************************/
int file_descr, max_fd;
struct termios tty, orig_tty;
fd_set work_set;

/****************************************************************************/
/*!
 *  \brief  Init cli
 *
 *  \return  success or failure
 *  \retval  CLI_SUCCESS, CLI_FAILURE
 *
 *  \ingroup CLI
 */
/****************************************************************************/
CLI_RETVAL cliInit(void)
{
    long spd;

    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    signal(SIGTERM, SIG_IGN);
    signal(SIGABRT, SIG_IGN);
    signal(SIGQUIT, SIG_IGN);
    signal(SIGILL, SIG_IGN);

//  system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter in non-canonical mode
//  system("stty -a");
//  sleep(1);

#ifdef SERIALPORT_IS_CONSOLE
    file_descr = STDIN_FILENO;
    SYS_LOG_DEBUG("SERIALPORT IS CONSOLE");
#else
    SYS_LOG_DEBUG("SERIALPORT IS NOT CONSOLE");
    file_descr = open("/dev/ttyS0", O_RDWR | O_ASYNC | O_NDELAY);
    if (file_descr == -1) {
        // Could not open the port
        perror("unable to open /dev/ttyS0");
        exit(1);
    }
#endif

    if(tcgetattr(file_descr, &tty) < 0)
    {
        perror("unable to get tty attributes");
        exit(1);
    }
    // backup tty, make it raw and apply changes
    orig_tty = tty;

    spd = B115200;
    cfsetospeed(&tty, (speed_t)spd);
    cfsetispeed(&tty, (speed_t)spd);

    cfmakeraw(&tty);

    tty.c_cc[VMIN] = 1;
    tty.c_cc[VTIME] = 10;

    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CRTSCTS;    /* no HW flow control? */
    tty.c_cflag |= CLOCAL | CREAD;
    tcsetattr(file_descr, TCSANOW, &tty);

//  // update local mode flags
//  tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
////    // renew control mode flags
////    tty.c_cflag &= ~(CBAUD | CSIZE | CSTOPB | PARENB | PARODD);
////    tty.c_cflag |= (BAUD | DATABITS | STOPBITS | PARITYON | PARITY);
//  // select 'raw' output mode
//  tty.c_oflag &= ~OPOST;
//  // disable mapping for input mode
//  tty.c_iflag &= ~(INLCR | ICRNL);
//
//
//  if(tcsetattr(file_descr, TCSAFLUSH, &tty) < 0)
//  {
//      perror("unable to set tty attributes");
//      exit(1);
//  }
//
    // Setup fd_set
    FD_ZERO(&work_set);
    FD_SET(file_descr, &work_set);
    max_fd = file_descr + 1;

    /* Readline lib init */
    // Define application name for readline library
    rl_readline_name = APPLICATION_NAME;
    // Update Pointer to alternative function to create matches.
    rl_attempted_completion_function = Menu_completion;
    // Start readline with reading /etc/inputrc file
    using_history();
    stifle_history(CLI_MAX_HISTORY_SIZE);


    // Some other initialization code
    // ...
    // ...

    return CLI_SUCCESS;
}

/****************************************************************************/
/*!
 *  \brief  Close cli
 *
 *  \return  success or failure
 *  \retval  CLI_SUCCESS, CLI_FAILURE
 *
 *  \ingroup CLI
 */
/****************************************************************************/
CLI_RETVAL cliClose(void)
{

//  system("stty -F /dev/ttyS0 icrnl ixon ixoff opost isig icanon echo");   // enter in canonical mode

    tcsetattr(file_descr, TCSANOW, &orig_tty);
//  if(tcsetattr(file_descr, TCSAFLUSH, &orig_tty) < 0)
//  {
//      perror("unable to set orig_tty attributes");
//      exit(1);
//  }
    close(file_descr);

    return CLI_SUCCESS;
}


/****************************************************************************/
/*!
 *  \brief  Main cli processing loop
 *
 *  \no return
 *
 *  \ingroup CLI
 */
/****************************************************************************/
void cliWorkLoop(Term_callback_t **term)
{
    Term_callback_t *cur_term;
    int8 *commandString;
    uint8 ret = CLI_REFRESH, no_prompt;

    char prompt_str[20];

    while (1) {

        cur_term = *term;
        global_cmd_compl_pointer = cur_term->cmd_list;

        commandString = NULL;
        sprintf(prompt_str, "%s:~> ", dev_name);

        if(ret == CLI_REFRESH) {
            CLEAR_SCR();
            if(cur_term->out != NULL) {
                cur_term->out(term, commandString, &ret);
                no_prompt = ret;
            }
            CURSOR_DOWN();
        }

        int n;
        struct timeval timeout;
        uint8 tmpBuf[32];

        while (1)
        {
            // Setup Timeout
            timeout.tv_sec = 60;
            timeout.tv_usec = 0;
            // Wait for new connections
            n = select(max_fd, &work_set, NULL, NULL, &timeout);
            if (n < 0)
            {
                perror("select #2 failed");
                break;
            }
            if (n > 0)
            {
                /* У нас есть ввод */
                if (FD_ISSET(file_descr, &work_set))
                {
                    if (read(file_descr, tmpBuf, 10) < 0) {
                        perror("cannot read");
                        exit(1);
                    }
                    else
                    {
                        SYS_LOG_DEBUG("READ first 4 chars: 0x%X,0x%X,0x%X,0x%X", tmpBuf[0], tmpBuf[1], tmpBuf[2], tmpBuf[3]);
                    }
                }
                break;
            }
        }
//
//
//      n = read(file_descr, tmpBuf, 5);
//      if (n > 0) {
//          unsigned char   *p = tmpBuf;
//
//          while (n-- > 0)
//              printf(" 0x%x", *p++);
//          printf("\r\n");
//      } else {
//          printf("failed to read: %d\r\n", n);
//      }
//
//
        exit(0);
    }

    CLEAR_SCR();
    return;
}


/****************************************************************************/
/*!
 *  \brief Main cli function
 *
 *  \param[in]      argc - argument number.
 *  \param[in,out]  argv - argument values entered by user.
 *
 *  \return  success or failure
 *  \retval  EXIT_SUCCESS, EXIT_FAILURE
 *
 *
 *  \ingroup CLI
 */
/****************************************************************************/
int main(int argc, char *argv[])
{
    Term_callback_t *term;
    char logname[16];
    FILE *fp;


    /* Set mask for file operation */
    umask(0);

    system("stty erase ^H");
    openlog("cli", LOG_CONS, LOG_USER);

    /* Write cli start log */
    syslog(LOG_NOTICE, "Console startup. Software version: %s", VERSION);
    /* Find login name */
    strcpy(logname, "noname");
    if ((fp = popen( "whoami", "r" )) == NULL)
    {
        SYS_LOG_ERR("Can't open process for \"whoami\" command.");
    } else
    {
        fgets(logname, 16, fp);
        pclose(fp);
    }
    SYS_LOG_INFO("Console is entered by \"%s\".", logname); //getenv("USER")

    /* Console initialization */
    if (cliInit() != CLI_SUCCESS) {
        SYS_LOG_CRIT("CLI init failed");
        return EXIT_FAILURE;
    }

    Vectors_init(&term);

    /* Console work loop */
    cliWorkLoop(&term);
    cliClose();

    /* Exiting from cli */
    SYS_LOG_INFO("\"%s\" exited from console.", logname);

    return EXIT_SUCCESS;
}

Solution

  • system("stty erase ^H);
    system("stty -F /dev/ttyS0 -icrnl -ixon -ixoff -opost -isig -icanon -echo");    // enter into non-canonical (raw) mode
    

    This would be insufficient code (and poor coding practice per POSIX conventions) to put the serial port into raw or non-canonical mode.
    The easiest method in C and Linux is to use the function cfmakeraw() which is available in both the GNU libc and uClibc libraries. Use the man page to obtain the details on the termios structure members that are modified by cfmakeraw().
    Beware that cfmakeraw() will setup the serial port in raw mode for a data length of 8 bits and no parity, for a total character frame of 10 bits (assuming one stop bit).

    The preferred method is preserving a copy of the termios stucture (for restoration on program exit) and only modifying the required flag bits (rather than writing the complete structure members).

    REVISION

    Code that works on my ARM SoC is:

    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/un.h>
    #include <unistd.h>
    #include <errno.h>
    #include <fcntl.h>
    #include <sys/types.h>
    #include <sys/ioctl.h>
    #include <sys/stat.h>
    #include <sys/syslog.h>
    #include <termios.h>
    
    #define SERIALPORT_IS_CONSOLE
    
    main()
    {
        struct termios  tty;
        struct termios  savetty;
        speed_t     spd;
        unsigned int    sfd;
        unsigned char   buf[80];
        int     reqlen = 79;
        int     rc;
        int     rdlen;
        int     pau = 0;
    
    #ifdef SERIALPORT_IS_CONSOLE
        sfd = STDIN_FILENO;
    #else
        sfd = open("/dev/ttyS1", O_RDWR | O_NOCTTY);
    #endif
        if (sfd < 0) {
            syslog(LOG_DEBUG, "failed to open: %d, %s", sfd, strerror(errno));
            exit (-1);
        }
        syslog(LOG_DEBUG, "opened sfd=%d for reading", sfd);
    
        rc = tcgetattr(sfd, &tty);
        if (rc < 0) {
            syslog(LOG_DEBUG, "failed to get attr: %d, %s", rc, strerror(errno));
            exit (-2);
        }
        savetty = tty;    /* preserve original settings for restoration */
    
        spd = B115200;
        cfsetospeed(&tty, (speed_t)spd);
        cfsetispeed(&tty, (speed_t)spd);
    
        cfmakeraw(&tty);
    
        tty.c_cc[VMIN] = 1;
        tty.c_cc[VTIME] = 10;
    
        tty.c_cflag &= ~CSTOPB;
        tty.c_cflag &= ~CRTSCTS;    /* no HW flow control? */
        tty.c_cflag |= CLOCAL | CREAD;
        rc = tcsetattr(sfd, TCSANOW, &tty);
        if (rc < 0) {
            syslog(LOG_DEBUG, "failed to set attr: %d, %s", rc, strerror(errno));
            exit (-3);
        }
    
        do {
            unsigned char   *p = buf;
        
            rdlen = read(sfd, buf, reqlen);
            if (rdlen > 0) {
                if (*p == '\r')
                    pau = 1;
                syslog(LOG_DEBUG, "read: %d, 0x%x 0x%x 0x%x", \
                         rdlen, *p, *(p + 1), *(p + 2));
            } else {
                syslog(LOG_DEBUG, "failed to read: %d, %s", rdlen, strerror(errno));
            }
        } while (!pau);
    
        tcsetattr(sfd, TCSANOW, &savetty);
        close(sfd);
        exit (0);
    }
    

    The compiled program is loaded & executed on the target board.

    From the host side of the serial comm link, the file seq.bin with the following contents is sent:

    $ od -t x1 seq.bin
    0000000 aa 02 fe
    0000003
    

    Then "ABC" is typed on the host (which is running the minicom terminal emulator program), followed by a carriage return. The program terminates on the target, and the syslog is then examined:

    # tail /var/log/messages                                                        
    Sep xx xx:xx:42 atmel_soc user.info kernel: EXT3 FS on nvsram, internal journal 
    Sep xx xx:xx:42 atmel_soc user.info kernel: EXT3-fs: mounted filesystem with or.
    Sep xx xx:xx:42 atmel_soc user.info kernel: kjournald starting.  Commit intervas
    Sep xx xx:xx:18 atmel_soc auth.info login[431]: root login on 'ttyS0'           
    Sep xx xx:xx:04 atmel_soc user.debug syslog: opened sfd=0 for reading           
    Sep xx xx:xx:14 atmel_soc user.debug syslog: read: 3, 0xaa 0x2 0xfe             
    Sep xx xx:xx:50 atmel_soc user.debug syslog: read: 1, 0x41 0x2 0xfe              
    Sep xx xx:xx:51 atmel_soc user.debug syslog: read: 1, 0x42 0x2 0xfe              
    Sep xx xx:xx:51 atmel_soc user.debug syslog: read: 1, 0x43 0x2 0xfe              
    Sep xx xx:xx:52 atmel_soc user.debug syslog: read: 1, 0xd 0x2 0xfe              
    # 
    

    The binary data has been received intact.

    Note that since this is raw mode and the typed chars were entered relatively slowly, the read() returns "partial" data and the user program would be responsible for buffering/assembling the data into complete "messages". If the messages are of fixed length, then c_cc[VMIN]member could be set to the message length. But beware of message framing issues and complications when frame sync is lost!