0

I am trying to read data from a serial port and have taken sample code from here:

http://tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html

The code sample is:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>

/* baudrate settings are defined in <asm/termbits.h>, which is
included by <termios.h> */
#define BAUDRATE B9600     
//B38400            
/* change this definition for the correct port */
#define MODEMDEVICE "/dev/ttyACM0"
#define _POSIX_SOURCE 1 /* POSIX compliant source */

#define FALSE 0
#define TRUE 1

volatile int STOP=FALSE; 

main()
{
  printf("starting program\n");

          int fd,c, res;
          struct termios oldtio,newtio;
          char buf[255];
        /* 
          Open modem device for reading and writing and not as controlling tty
          because we don't want to get killed if linenoise sends CTRL-C.
        */
         fd = open(MODEMDEVICE, O_RDWR | O_NOCTTY ); 
         if (fd <0) {perror(MODEMDEVICE); exit(-1); }

         printf("fd=%d\n", fd);

         tcgetattr(fd,&oldtio); /* save current serial port settings */
         bzero(&newtio, sizeof(newtio)); /* clear struct for new port settings */

        /* 
          BAUDRATE: Set bps rate. You could also use cfsetispeed and cfsetospeed.
          CRTSCTS : output hardware flow control (only used if the cable has
                    all necessary lines. See sect. 7 of Serial-HOWTO)
          CS8     : 8n1 (8bit,no parity,1 stopbit)
          CLOCAL  : local connection, no modem contol
          CREAD   : enable receiving characters
        */
         newtio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;

        /*
          IGNPAR  : ignore bytes with parity errors
          ICRNL   : map CR to NL (otherwise a CR input on the other computer
                    will not terminate input)
          otherwise make device raw (no other input processing)
        */
         newtio.c_iflag = IGNPAR | ICRNL;

        /*
         Raw output.
        */
         newtio.c_oflag = 0;

        /*
          ICANON  : enable canonical input
          disable all echo functionality, and don't send signals to calling program
        */
         newtio.c_lflag = ICANON;

        /* 
          initialize all control characters 
          default values can be found in /usr/include/termios.h, and are given
          in the comments, but we don't need them here
        */
         newtio.c_cc[VINTR]    = 0;     /* Ctrl-c */ 
         newtio.c_cc[VQUIT]    = 0;     /* Ctrl-\ */
         newtio.c_cc[VERASE]   = 0;     /* del */
         newtio.c_cc[VKILL]    = 0;     /* @ */
         newtio.c_cc[VEOF]     = 4;     /* Ctrl-d */
         newtio.c_cc[VTIME]    = 0;     /* inter-character timer unused */
         newtio.c_cc[VMIN]     = 1;     /* blocking read until 1 character arrives */
         newtio.c_cc[VSWTC]    = 0;     /* '\0' */
         newtio.c_cc[VSTART]   = 0;     /* Ctrl-q */ 
         newtio.c_cc[VSTOP]    = 0;     /* Ctrl-s */
         newtio.c_cc[VSUSP]    = 0;     /* Ctrl-z */
         newtio.c_cc[VEOL]     = 0;     /* '\0' */
         newtio.c_cc[VREPRINT] = 0;     /* Ctrl-r */
         newtio.c_cc[VDISCARD] = 0;     /* Ctrl-u */
         newtio.c_cc[VWERASE]  = 0;     /* Ctrl-w */
         newtio.c_cc[VLNEXT]   = 0;     /* Ctrl-v */
         newtio.c_cc[VEOL2]    = 0;     /* '\0' */

        /* 
          now clean the modem line and activate the settings for the port
        */
         tcflush(fd, TCIFLUSH);
         tcsetattr(fd,TCSANOW,&newtio);
         printf("tcsetattr returned %d\n", res);
         printf("just before while STOP loop\n");        
        /*
          terminal settings done, now handle input
          In this example, inputting a 'z' at the beginning of a line will 
          exit the program.
        */
         while (STOP==FALSE) {     /* loop until we have a terminating condition */
         /* read blocks program execution until a line terminating character is 
            input, even if more than 255 chars are input. If the number
            of characters read is smaller than the number of chars available,
            subsequent reads will return the remaining chars. res will be set
            to the actual number of characters actually read */
             printf("just before read\n");
             res = read(fd,buf,255); 
            printf("just after read\n");

            buf[res]=0;             /* set end of string, so we can printf */
            printf(":%s:%d\n", buf, res);
            if (buf[0]=='z') STOP=TRUE;
         }
         /* restore the old port settings */
         tcsetattr(fd,TCSANOW,&oldtio);
        }

And I compiled and run like this:

sudo ./a.out

But the only output I get is:

starting program
fd=3
tcsetattr returned 0
just before while STOP loop
just before read

I tried ringing the modem using my mobile but nothing.

if I do a ls /dev/ttyA* I do see /dev/ttyACM0

Do I need to configure the port somehow?

The modem is a Conexant 93010 voice modem. It definitely works. There is a slight complication that I am running ubuntu in a VMWare VM. But I do connect in the VM, and when I connect the device I do see ttyACM0.

UPDATE

I have found in testing that I DO get output but it is very delayed. For example if I ring in I eventually get RING displayed. But I have to continually ring in via modem, cut the line then ring again. After 15 rings I see output. I changed read to only read 5 characters at a time. Why the delay?

If I change the read call to read 10 characters at a time it is same behaviour. It takes 15 - 16 rings before I see any data printed on the screen (all those RING's).

I run serial code on Windows and there is no delay. So it is not the hardware.

Angus Comber
  • 9,316
  • 14
  • 59
  • 107
  • check return values of functions like read and/or tcsetattr, btw do you have selinux installed/activated? – OznOg Mar 17 '19 at 12:43
  • I don't think I have selinux installed. its standard ubuntu. just blocks on read – Angus Comber Mar 17 '19 at 12:47
  • @OznOg edited to show return of tcsetattr – Angus Comber Mar 17 '19 at 12:54
  • there is a off by one in case res is 255 @ buf[res]=0; and worse if read returns -1 – OznOg Mar 17 '19 at 13:56
  • ca you 'talk' to your modem with software like minicom ? – OznOg Mar 17 '19 at 14:01
  • @OznOg - yes minicom works – Angus Comber Mar 17 '19 at 14:35
  • The slow speed is probably due to `B9600`. You should probably configure your terminal with `cfmakeraw`. Then, set `tty.c_cflag |= CLOCAL;` and possibly `tty.c_cflag |= CRTSCTS;`. Finally, use input and output speed `B115200`. Also see [How to open, read, and write from serial port in C?](https://stackoverflow.com/q/6947413/608639) and [open function doesn't return for serial port](https://stackoverflow.com/a/55158900/608639) and [Reading and writing to serial port in C on Linux](https://stackoverflow.com/q/18108932/608639). – jww Mar 17 '19 at 23:42
  • *"I ... have taken sample code from here..."* -- Unfortunately you're copying code that is not POSIX-compliant (i.e. it is not portable), so while that code may have performed okay for the author, there is no guarantee that the code will also perform in your situation. See [Setting Terminal Modes Properly](http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_12.html#SEC237) and [Serial Programming Guide for POSIX Operating Systems](http://www.cmrr.umn.edu/~strupp/serial.html) – sawdust Mar 18 '19 at 01:52
  • *"The modem is a Conexant ..."* -- For (POSIX) code that reportedly works with a Conexant modem see https://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c/38318768#comment96180431_38318768 – sawdust Mar 19 '19 at 23:13

1 Answers1

-1

For historical reasons, there are a lot of settings in the serial driver layer that will do different pre-processing functions. It looks like your problem comes in here:

newtio.c_lflag = ICANON;
...
newtio.c_cc[VEOF]     = 4;     /* Ctrl-d */
...
newtio.c_cc[VEOL]     = 0;     /* '\0' */
....
newtio.c_cc[VEOL2]    = 0;     /* '\0' */

According to the documentation for termios:

Canonical and noncanonical mode

The setting of the ICANON canon flag in c_lflag determines whether the terminal is operating in canonical mode (ICANON set) or noncanon‐ ical mode (ICANON unset). By default, ICANON is set.

In canonical mode:

  • Input is made available line by line. An input line is available when one of the line delimiters is typed (NL, EOL, EOL2; or EOF at the start of line). Except in the case of EOF, the line delimiter is included in the buffer returned by read(2).

So it sounds like what is happening here is that the kernel buffer is eventually filling up and sending information back to you, or you're getting one of the control characters(perhaps spuriously).

Anyway, try setting newio.c_lflag to 0; that should clear the canonical input mode. This related issue may help as well.


I will generally clear out all termios settings as follows, and then set as appropriate:

struct termios newio;
if( tcgetattr(fd, &newio) < 0 ){
   /* Error checking */
}
newio.c_iflag |= IGNBRK;
newio.c_iflag &= ~BRKINT;
newio.c_iflag &= ~ICRNL;
newio.c_oflag = 0;
newio.c_lflag = 0;
newio.c_cc[VTIME] = 0;
newio.c_cc[VMIN] = 1;
if( tcsetattr( fd, TCSANOW, &newio ) < 0 ) {
    /* Error checking */
}

The above settings are the same as I use in both my C library and Java libraries, if you don't want to do the serial port settings on your own.

rm5248
  • 2,590
  • 3
  • 17
  • 16
  • Downvote for suggesting code that is not POSIX-compliant. See [Setting Terminal Modes Properly](http://www.chemie.fu-berlin.de/chemnet/use/info/libc/libc_12.html#SEC237) – sawdust Mar 19 '19 at 04:47
  • @sawdust I never say anything about POSIX compliance at all, but for clarification I have edited the latter half of the answer. – rm5248 Mar 19 '19 at 11:29
  • Your code is still not portable, although it's not as bad as what the OP copied from. There are other users that have commented that POSIX-compliant code *"works better"* than hard assignments like your example. See https://stackoverflow.com/questions/6947413/how-to-open-read-and-write-from-serial-port-in-c/38318768#comment91608949_38318768 FYI I once replaced non-POSIX termios code like the OP's in a public-domain program, and then had a significant improvement and reliable transfers. – sawdust Mar 19 '19 at 23:03