15

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;
}
phuclv
  • 37,963
  • 15
  • 156
  • 475
Bakir
  • 193
  • 1
  • 2
  • 9
  • Your serial port is setup for canonical input, i.e. lines of ASCII text. Either switch to *raw mode* or transform the binary data at both ends. Look at `uuencode` and `uudecode` for transmission of binary data over an ASCII medium. It's the traditional way of sending binary data in text-only e-mail and USENET postings. Another method of sending binary or "special" chars is to use an "escape" character to prefix "special" char and normalizing the byte to a valid ASCII char. BTW `-parenb -parodd cs8` will produce 11-bit character frames. "No parity" is typical when using 8-bit data chars. – sawdust Sep 15 '12 at 22:30
  • Thank you sawdust! You are right, the second variant is a more convenient method to use. But I have an old software that communicate with previous embedded devices and doesn't support it. There is every likelihood that we will have to change it to support a new one. Even so, why can't I get an special char when going in a raw mode (please see mentioned code) using an read() func? – Bakir Sep 17 '12 at 05:19

1 Answers1

12
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!

Community
  • 1
  • 1
sawdust
  • 16,103
  • 3
  • 40
  • 50
  • I have used this function and got the same result :) – Bakir Sep 18 '12 at 04:31
  • So, I checked your code. Unfortunately I got the same picture. – Bakir Sep 18 '12 at 05:24
  • I think I didn't fully complete preparing of tty config. Previously I wrote RS232toETH program and used all these tools and they worked. But in that case I commented the launch of console in linux startup. There was only my application that used /dev/ttyS0. Many thanks sawdust! :) You gave me an idea. I think I will try workaround method (uuencode and uudecode / escape chars). Thank you for your time. – Bakir Sep 18 '12 at 05:39
  • Are you explicitly re-opening the console to obtain a file descriptor, `sdf`, or have you tried simply using `sfd = STDIN_FILENO` in place of the `open()`? Please review the changes made to the code sample above. – sawdust Sep 18 '12 at 23:29
  • I explicitly open the console to get a file des. I will try this. – Bakir Sep 21 '12 at 08:22
  • So, I tried again. But failed to get success (. I inserted conditional statement and it says for some reason that SERIALPORT IS NOT CONSOLE. And, simple read() doesnt work in my code, that's why I used fd set with read() func in loop to get symbols. I obtained the same: 0x98, 0xE6, 0x18. Thank you very much!!! – Bakir Sep 21 '12 at 10:24
  • Some real code would probably be more comprehensible than your textual synopsis. FYI "SERIALPORT_IS_CONSOLE" is a made-up conditional compilation symbol; you could either simply hardcode the `sfd = STDIN_FILENO`, or install `#define SERIALPORT_IS_CONSOLE` at the beginning, or add `-DSERIALPORT_IS_CONSOLE` as a gcc compile-line switch. Have you done any sanity tests, like send something different like "ABC" (i.e. 0x41 0x42 0x43) for the 3 bytes? What is this "syslog" that is reporting the "bad data"? – sawdust Sep 21 '12 at 19:04
  • I inserted "SERIALPORT_IS_CONSOLE" definition, but it didn't affect to receiving of the symbols. And I did sanity tests, for example, I sent regular chars and got the correct symbols on the receive side. I use a system log through /var/log/messages. I added a whole code in my question. Please look above. Best regards. – Bakir Sep 22 '12 at 09:30
  • My answer has been revised to include a complete working example for receiving binary data over a serial port. There are (at least) two items that you should fix in your code. (1) Since you are having difficulties, you should be checking the return values for all libc/system calls. (2) The degenerate case of *read()* returning 0 bytes (or any short read for that matter) is not detected, and the log will report garbage values. Good luck. – sawdust Sep 24 '12 at 00:33
  • 1
    It works!!! I am ashamed of myself. The cause of all that is my inattention! :( The program that tests my com-app. was configured to use 57600 bit rate when I needed 115200. Both codes are correct. I'm very very sorry for this. Many thanks sawdust! :) `Jan 1 00:03:42 FM2 user.debug cli: read: 3, 0xaa 0x2 0xfe Jan 1 00:05:44 FM2 user.debug cli: read: 1, 0x41 0x2 0xfe Jan 1 00:05:45 FM2 user.debug cli: read: 1, 0x42 0x2 0xfe Jan 1 00:05:46 FM2 user.debug cli: read: 1, 0x43 0x2 0xfe Jan 1 00:05:50 FM2 user.debug cli: read: 1, 0xd 0x2 0xfe` – Bakir Sep 26 '12 at 05:02