Serial Programming

From miki
Jump to navigation Jump to search

References

How-To

Flush Serial Buffers

Use constant TCSAFLUSH for function tcsetattr (see [1] and [2]):

Constants for tcsetattr
Constant Description
TCSANOW Make changes now without waiting for data to complete
TCSADRAIN Wait until everything has been transmitted
TCSAFLUSH Flush input and output buffers and make the change

This can be done even on a serial port that is already configured:

  struct termios  options;
  tcgetattr(fd, &options);                   // Get current configuration of this port
  cfsetispeed(&options, B115200);            // Configure some port options (e.g. baud rate...)
  ...
  tcsetattr(fd, TCSANOW, &options);          // Apply the new settings immediately 
  ...                                        // Read / Write to the port...                       
  tcsetattr(fd, TCSAFLUSH, &options);        // Flush IO buffer

Tools

Minicom

Minicom (wikipedia) is a text-based modem control and terminal emulation program for Linux.

It can be used to debug / test serial connections, but unfortunately it is only ascii based and does not allow to send / receive easily data in hexadecimal format (except via sending / receiving files).

Minicom is in the repositories:

sudo apt-get install minicom

Cutecom

Cutecom is a handy and easy graphic tool to test/debug embedded devices controlled via serial link. This tool allows sending / receiving data in hexadecimal format (select Hex input and Hex output).

Cutecom is in the repositories:

sudo apt-get install cutecom

interceptty

interceptty is a program that sits between a serial port (or other tty-style device) and an application, and logs everything that passes through it.

SerLook

SerLook is a KDE application for inspecting data going over serial lines.

Commands

See Linux Commands#Serial for serial commands.

Async Serial IO on POSIX

Several motivations for async IO:

  • Don't block thread on read operation
  • Implement read timeout
  • Allow canceling IO from another thread

Async IO is not an easy business :-(. Here we list some caveats / ideas.

pthreads and async signal handlers

Apply extreme care when dealing with asyng signal handlers in multi-threaded environment, since pthreads functions are NOT async-signal safe (see POSIX). For instance, the following will likely dead-lock:

void signal_handler(int status)
{
  pthread_mutex_lock(& Ctx.Signal.mut);               // BAD!!! Might dead-lock
  Ctx.Signal.kicked = 1;
  pthread_cond_signal(& Ctx.Signal.cond);             // BAD!!! Might dead-lock
  pthread_mutex_unlock(& Ctx.Signal.mut);             // BAD!!! Might dead-lock
}

void setup(void)
{
  // ...
  saio.sa_handler = signal_handler;
  sigaction(SIGIO, &saio, NULL);
  // ...
}

Read with timeout (not limited to serial)

This can be done with select. Example below is copied from Non-blocking user input in loop without ncurses and from Set a timeout for reading stdin:

// if != 0, then there is data to be read on stdin
int kbhit()
{
  // timeout structure passed into select
  struct timeval tv;
  // fd_set passed into select
  fd_set fds;
  // Set up the timeout.  here we can wait for 1 second
  tv.tv_sec = 1;
  tv.tv_usec = 0;

  // Zero out the fd_set - make sure it's pristine
  FD_ZERO(&fds);
  // Set the FD that we want to read
  FD_SET(STDIN_FILENO, &fds); //STDIN_FILENO is 0
  // select takes the last file descriptor value + 1
  // in the fdset to check, the fdset for reads, 
  // writes, and errors.  We are only passing in reads.
  // the last parameter is the timeout.  
  // select will return if an FD is ready or the 
  // timeout has occurred
  select(STDIN_FILENO+1, &fds, NULL, NULL, &tv);
  // return 0 if STDIN is not ready to be read.
  return FD_ISSET(STDIN_FILENO, &fds);
}

Turn on/off canonical mode (otherwise kbhit will only react to ENTER key):

void nonblock(int state)
{
  struct termios ttystate;
 
  //get the terminal state
  tcgetattr(STDIN_FILENO, &ttystate);
 
  if (state==NB_ENABLE)
  {
    //turn off canonical mode
    ttystate.c_lflag &= ~ICANON;
    //minimum of number input read.
    ttystate.c_cc[VMIN] = 1;
  }
  else if (state==NB_DISABLE)
  {
    //turn on canonical mode
    ttystate.c_lflag |= ICANON;
  }
  //set the terminal attributes.
  tcsetattr(STDIN_FILENO, TCSANOW, &ttystate);

}
int main()
{
  char c;
  int i=0;
 
  nonblock(NB_ENABLE);
  while(!i)
  {
    usleep(1);
    i=kbhit();
    if (i!=0)
    {
      c=fgetc(stdin);
      if (c=='q')
        i=1;
      else
        i=0;
    }
 
    fprintf(stderr,"%d ",i);
  }
  printf("\n you hit %c. \n",c);
  nonblock(NB_DISABLE);
 
  return 0;
}

The same example with curses (from same links):

#include<stdio.h>
#include<curses.h>
#include<unistd.h>
 
int main ()
{
    int i=0;
 
    initscr();     //in ncurses
    timeout(0);
    while(!i)
    {
        usleep(1);
        i=getch();
        printw("%d ",i);
        if(i>0)
            i=1;
        else
            i=0;
    }
    endwin();
    printf("\nhitkb end\n");
    return 0;
}

Read with Timeout for Serial IO

See Serial Programming Guide for POSIX Operating Systems.

Troubleshooting

Shunt RXD/TXD pin

An easy way to test whether the serial line is working properly is simply to shunt the RXD and TXD line of the serial connector (2nd and 3rd pin on a typical DE9 RS232 connector). This way anything sent to the serial port is echoed back.

  1. Open a terminal connected to the serial port,
  2. Shunt RXD and TXD (2nd and 3rd pin),
  3. Type character and verify that the same character are echoed back.
  _______________________
 /   1               5   \
 \   o   o   o   o   o   /
  \    6           9    / 
   \   o   o   o   o   /
    \_________________/

On a D-sub DE9 RS232 connector, the top left pin is the 1st pin, the bottom right is the 9th pin. The pinout is as follows:

Pin Name Dir. Description Pin Name Dir. Description
1 CD Carrier Detect 6 DSR Data Set Ready
2 RXD Receive Data 7 RTS Request to Send
3 TXD Transmit Data 8 CTS Clear to Send
4 DTR Data Terminal Ready 9 RI Ring Indicator
5 GND System Ground