Link to home
Start Free TrialLog in
Avatar of ichigokurosaki
ichigokurosakiFlag for Afghanistan

asked on

Read from USB in Ubuntu with C

I'm using my pc with Ubuntu 11.04 to read data from an USB device mounted on /dev/ttyACM0.

I can read it by using bash utility like cat or tail, for example:

@bash> tail -f /dev/ttyACM0
read: Hello World sent from USB!
..
@bash>cat /dev/ttyACM0
read: Hello World sent from USB!

Open in new window


Since, I have to integrate the USB read and write functionality in my C program, i searched on google and I've find a C code sample to read from serial port (i want to read arduino data on Ubuntu via USB).

The problem is that this code sample works great only if i previous use cat or tail command, otherwise, if I do not use these commands, it is not capable to read from USB.

For example, if i directly call ./code_sample it does not display any information; on the contrary, if I call tail command and then I stop it and call ./code_sample, now it is able to print on screen the information received from USB.

Why it happens?

This is the code:
#include <stdio.h>    /* Standard input/output definitions */
#include <stdlib.h> 
#include <stdint.h>   /* Standard types */
#include <string.h>   /* String function definitions */
#include <unistd.h>   /* UNIX standard function definitions */
#include <fcntl.h>    /* File control definitions */
#include <errno.h>    /* Error number definitions */
#include <termios.h>  /* POSIX terminal control definitions */
#include <sys/ioctl.h>
#include <getopt.h>

void usage(void);
int serialport_init(const char* serialport, int baud);
int serialport_writebyte(int fd, uint8_t b);
int serialport_write(int fd, const char* str);
int serialport_read_until(int fd, char* buf, char until);

void usage(void) {
    printf("Usage: arduino-serial -p <serialport> [OPTIONS]\n"
    "\n"
    "Options:\n"
    "  -h, --help                   Print this help message\n"
    "  -p, --port=serialport        Serial port Arduino is on\n"
    "  -b, --baud=baudrate          Baudrate (bps) of Arduino\n"
    "  -s, --send=data              Send data to Arduino\n"
    "  -r, --receive                Receive data from Arduino & print it out\n"
    "  -n  --num=num                Send a number as a single byte\n"
    "  -d  --delay=millis           Delay for specified milliseconds\n"
    "\n"
    "Note: Order is important. Set '-b' before doing '-p'. \n"
    "      Used to make series of actions:  '-d 2000 -s hello -d 100 -r' \n"
    "      means 'wait 2secs, send 'hello', wait 100msec, get reply'\n"
    "\n");
}

int main(int argc, char *argv[]) 
{
    int fd = 0;
    char serialport[256];
    int baudrate = B9600;  // default
    char buf[256];
    int rc,n;

    if (argc==1) {
        usage();
        exit(EXIT_SUCCESS);
    }

    /* parse options */
    int option_index = 0, opt;
    static struct option loptions[] = {
        {"help",       no_argument,       0, 'h'},
        {"port",       required_argument, 0, 'p'},
        {"baud",       required_argument, 0, 'b'},
        {"send",       required_argument, 0, 's'},
        {"receive",    no_argument,       0, 'r'},
        {"num",        required_argument, 0, 'n'},
        {"delay",      required_argument, 0, 'd'}
    };
    
    while(1) {
        opt = getopt_long (argc, argv, "hp:b:s:rn:d:",
                           loptions, &option_index);
        if (opt==-1) break;
        switch (opt) {
        case '0': break;
        case 'd':
            n = strtol(optarg,NULL,10);
            usleep(n * 1000 ); // sleep milliseconds
            break;
        case 'h':
            usage();
            break;
        case 'b':
            baudrate = strtol(optarg,NULL,10);
            break;
        case 'p':
            strcpy(serialport,optarg);
            fd = serialport_init(optarg, baudrate);
            if(fd==-1) return -1;
            break;
        case 'n':
            n = strtol(optarg, NULL, 10); // convert string to number
            rc = serialport_writebyte(fd, (uint8_t)n);
            if(rc==-1) return -1;
            break;
        case 's':
            strcpy(buf,optarg);
            rc = serialport_write(fd, buf);
            if(rc==-1) return -1;
            break;
        case 'r':
            serialport_read_until(fd, buf, '\n');
            printf("read: %s\n",buf);
            break;
        }
    }

    exit(EXIT_SUCCESS);    
} // end main
    
int serialport_writebyte( int fd, uint8_t b)
{
    int n = write(fd,&b,1);
    if( n!=1)
        return -1;
    return 0;
}

int serialport_write(int fd, const char* str)
{
    int len = strlen(str);
    int n = write(fd, str, len);
    if( n!=len ) 
        return -1;
    return 0;
}

int serialport_read_until(int fd, char* buf, char until)
{
    char b[1];
    int i=0;
    do { 
        int n = read(fd, b, 1);  // read a char at a time
        if( n==-1) return -1;    // couldn't read
        if( n==0 ) {
            usleep( 10 * 1000 ); // wait 10 msec try again
            continue;
        }
        buf[i] = b[0]; i++;
    } while( b[0] != until );

    buf[i] = 0;  // null terminate the string
    return 0;
}

// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
// and a baud rate (bps) and connects to that port at that speed and 8N1.
// opens the port in fully raw mode so you can send binary data.
// returns valid fd, or -1 on error
int serialport_init(const char* serialport, int baud)
{
    struct termios toptions;
    int fd;
    
    //fprintf(stderr,"init_serialport: opening port %s @ %d bps\n",
    //        serialport,baud);

    fd = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY);
    if (fd == -1)  {
        perror("init_serialport: Unable to open port ");
        return -1;
    }
    
    if (tcgetattr(fd, &toptions) < 0) {
        perror("init_serialport: Couldn't get term attributes");
        return -1;
    }
    speed_t brate = baud; // let you override switch below if needed
    switch(baud) {
    case 4800:   brate=B4800;   break;
    case 9600:   brate=B9600;   break;
#ifdef B14400
    case 14400:  brate=B14400;  break;
#endif
    case 19200:  brate=B19200;  break;
#ifdef B28800
    case 28800:  brate=B28800;  break;
#endif
    case 38400:  brate=B38400;  break;
    case 57600:  brate=B57600;  break;
    case 115200: brate=B115200; break;
    }
    cfsetispeed(&toptions, brate);
    cfsetospeed(&toptions, brate);

    // 8N1
    toptions.c_cflag &= ~PARENB;
    toptions.c_cflag &= ~CSTOPB;
    toptions.c_cflag &= ~CSIZE;
    toptions.c_cflag |= CS8;
    // no flow control
    toptions.c_cflag &= ~CRTSCTS;

    toptions.c_cflag |= CREAD | CLOCAL;  // turn on READ & ignore ctrl lines
    toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl

    toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    toptions.c_oflag &= ~OPOST; // make raw

    // see: http://unixwiz.net/techtips/termios-vmin-vtime.html
    toptions.c_cc[VMIN]  = 0;
    toptions.c_cc[VTIME] = 20;
    
    if( tcsetattr(fd, TCSANOW, &toptions) < 0) {
        perror("init_serialport: Couldn't set term attributes");
        return -1;
    }

    return fd;
}

Open in new window

arduino-serial.c
Avatar of Duncan Roe
Duncan Roe
Flag of Australia image

Personally I would not use O_NDELAY, rather I would use select() or poll() if there was anything useful I could do in the absence of serial input. (some people say use O_NDELAY anyway, but use select() / poll() first - that's fine with me, I expect I'll wish I'd done that one day:)
Also I think you have VMIN and VTIME wrong way round - I use VMIN=1 and VTIME=0 always.

I suspect tail cat &c. do not use termios at all - you could verify this with strace. If they don't, then maybe neither should you.Possibly the USB serial driver ignores termios settings once there has been some i/o. Otherwise it's still a mystery why it works after you use them.

USB serial devices don't usually care about baud rate in my experience

The settings when the device is first plugged in may be fine. Use stty -F /dev/ttyACM0 -a to check this (likely needs to be root or use sudo)
Avatar of ichigokurosaki

ASKER

I changed VMIN and VTIME as your suggestion, but nothing changed.

This is the strace result:

console@pc:~/Desktop$ strace tail -f /dev/ttyACM0
execve("/usr/bin/tail", ["tail", "-f", "/dev/ttyACM0"], [/* 38 vars */]) = 0
brk(0)                                  = 0x9648000
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77c1000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY)      = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=100105, ...}) = 0
mmap2(NULL, 100105, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77a8000
close(3)                                = 0
access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\220o\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1434180, ...}) = 0
mmap2(NULL, 1444360, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x27e000
mprotect(0x3d8000, 4096, PROT_NONE)     = 0
mmap2(0x3d9000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x15a) = 0x3d9000
mmap2(0x3dc000, 10760, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x3dc000
close(3)                                = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77a7000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb77a78d0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0x3d9000, 8192, PROT_READ)     = 0
mprotect(0x8054000, 4096, PROT_READ)    = 0
mprotect(0x201000, 4096, PROT_READ)     = 0
munmap(0xb77a8000, 100105)              = 0
brk(0)                                  = 0x9648000
brk(0x9669000)                          = 0x9669000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=8318400, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb75a7000
mmap2(NULL, 4096, PROT_READ, MAP_PRIVATE, 3, 0x7ed) = 0xb77c0000
close(3)                                = 0
open("/dev/ttyACM0", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFCHR|0660, st_rdev=makedev(166, 0), ...}) = 0
read(3, "o world!\r\n1650 Hello world!\r\n165"..., 8192) = 4004
read(3, " Hello world!\r\n1861 Hello world!"..., 8192) = 353
read(3, "d!\r\n1879 Hello world!\r\n1880 Hell"..., 8192) = 363
read(3, " Hello world!\r\n31592 Hello world", 8192) = 32
read(3, "!\r\n31593 Hello world!\r\n31594 Hel"..., 8192) = 125

Open in new window


and this is the stty result:

speed 115200 baud; line = 0;
eof = ^A; min = 1; time = 0;
-brkint -icrnl -imaxbel
-opost -onlcr
-isig -icanon -iexten -echo -echoe -echok -echoctl -echoke

Open in new window


So, do you think the problem is with Termios?
What i should do in order to solve it?
tail absolutely doesn't use termios. I would just open the device and read from it.
Was that really the output from stty -F /dev/ttyACM0 -a? I get a little less from stty -F /dev/ttyS0 and a great deal more if I append -a. Certainly I get min and time: 1 and 0 respectively.
I wouldn't try setting the baud rate if I felt I really had to use tcsetattr but why do you think you need to use it?
This is the result of stty -F /dev/ttyACM0 -s:

console@pc:~/Desktop$ stty -F /dev/ttyACM0 -a
speed 115200 baud; rows 0; columns 0; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^A; eol = <undef>;
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

Open in new window


I tested the same program on my Mac with Snow Leopard and it works great so it doesn't work only on my Ubuntu.

The baud rate setting is because I have to read data sent by Arduino board to my pc; in Arduino you can specify the baud rate of the port, so I use the baud rate in the program in order to change the setting according on the Arduino script.

Just a question: Can i just open and read the file in /dev/ttyACM0 in order to read the data sent from Arduino in C? In this way i can avoid all these problem and get my program more easier.
I've almost solved the problem and now the program is able to read data from USB without having to call tail or cat before.

Now, the only problem is that when i stop the program by pressing "q" or "ctrl+z", it is not possible to use the shell anymore because it doesn't take input from keyboard anymore and i have to close it and open the shell again.

I did this change in my code during the port initialization:

int serialport_init(const char* serialport)
{
     struct termios tio;
     struct termios stdio;

    int fd;
       
    memset(&stdio,0,sizeof(stdio));
        stdio.c_iflag=0;
        stdio.c_oflag=0;
        stdio.c_cflag=0;
        stdio.c_lflag=0;
        stdio.c_cc[VMIN]=1;
        stdio.c_cc[VTIME]=0;
        tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
        tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);
        fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);       // make the reads non-blocking
 
 
        memset(&tio,0,sizeof(tio));
     // 8N1
    tio.c_cflag &= ~PARENB;
    tio.c_cflag &= ~CSTOPB;
    tio.c_cflag &= ~CSIZE;
    tio.c_cflag |= CS8;
    // no flow control
    tio.c_cflag &= ~CRTSCTS;

    tio.c_cflag |= CREAD | CLOCAL;  // turn on READ & ignore ctrl lines
    tio.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl

    tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    tio.c_oflag &= ~OPOST; // make raw

 
       fd = open(serialport, O_RDWR | O_NONBLOCK | O_NOCTTY); 
    if (fd == -1)  {
        perror("init_serialport: Unable to open port ");
        return -1;
    }   
        cfsetospeed(&tio,B115200);            // 115200 baud
        cfsetispeed(&tio,B115200);            // 115200 baud
 
        tcsetattr(fd,TCSANOW,&tio);

    return fd;
}

void serialport_read_until(int fd)
{
     while (c!='q')
        {
                if (read(fd,&c,1)>0)        write(STDOUT_FILENO,&c,1);              // if new data is available on the serial port, print it out
                if (read(STDIN_FILENO,&c,1)>0)  write(fd,&c,1);                     // if new data is available on the console, send it to the serial port
        }

}

Open in new window


What can i do in order to avoid the shell blocking?
May be the problem is on lines 15 and 16:

 
tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);

Open in new window


because sometimes it happens that the program does not catch the "q" key and i have to terminate the program by pressing ctrl+c.
Ctrl-Z only stops the program - it's still there as a stopped process.
You only need one tcsetattr call. Definitely I would keep TCSAFLUSH and lose the other. Why would your program be making that call if it hasn't read 'q'? (I'm assuming that code snippet resets to original settings).
If you leave the terminal settings in an odd state, it is quite likely that the shell can't make sense of its input. Make a note of the pty you are running on before you start (ps will show it). In another window, use stty -F to display the shell's settings before you start. If it hangs after, check its settings to see if they are right. If you used Ctrl-Z, you will need to kill the program from another window.
The problem is that if I keep TCSAFLUSH, the program is not able to read 'q' key anymore and i must use CTRL-Z to stop the program; then, i have to kill the process from the shell.

I do not know why when the program start to receive data from USB port, it is not able to read 'q' key anymore and it can't stop.
The program wasn't able to read 'q' because i was using getchar() to read it:

     while ( (c = getchar()) != 'q')
        {
                if (read(fd,&c,1)>0)        write(STDOUT_FILENO,&c,1);              // if new data is available on the serial port, print it out
                if (read(STDIN_FILENO,&c,1)>0)  write(fd,&c,1);                     // if new data is available on the console, send it to the serial port
        }

Open in new window


However, the problem is that when the program correctly reads 'q' key, it terminates but when i type in the terminal, i'm not able to see what i write.

This is the whole code i'm using:

int main(int argc, char *argv[]) 
{
    int fd = 0;
    char serialport[256];
    int baudrate = B9600;  // default
    char buf[256];
    int rc,n;

    if (argc==1) {
        usage();
        exit(EXIT_SUCCESS);
    }

    /* parse options */
    int option_index = 0, opt;
    static struct option loptions[] = {
        {"help",       no_argument,       0, 'h'},
        {"port",       required_argument, 0, 'p'},
        {"baud",       required_argument, 0, 'b'},
        {"send",       required_argument, 0, 's'},
        {"receive",    no_argument,       0, 'r'},
        {"num",        required_argument, 0, 'n'},
        {"delay",      required_argument, 0, 'd'}
    };
    
    while(1) {
        opt = getopt_long (argc, argv, "hp:b:s:rn:d:",
                           loptions, &option_index);
        if (opt==-1) break;
        switch (opt) {
        case '0': break;
        case 'd':
            n = strtol(optarg,NULL,10);
            usleep(n * 1000 ); // sleep milliseconds
            break;
        case 'h':
            usage();
            break;
        case 'b':
            baudrate = strtol(optarg,NULL,10);
            break;
        case 'p':
            strcpy(serialport,optarg);
            fd = serialport_init(optarg, baudrate);
            if(fd==-1) return -1;
            break;
        case 'n':
            n = strtol(optarg, NULL, 10); // convert string to number
            rc = serialport_writebyte(fd, (uint8_t)n);
            if(rc==-1) return -1;
            break;
        case 's':
            strcpy(buf,optarg);
            rc = serialport_write(fd, buf);
            if(rc==-1) return -1;
            break;
        case 'r':
            serialport_read_until(fd);
            break;
        }
    }

    exit(EXIT_SUCCESS);    
} // end main
    
int serialport_writebyte( int fd, uint8_t b)
{
    int n = write(fd,&b,1);
    if( n!=1)
        return -1;
    return 0;
}

int serialport_write(int fd, const char* str)
{
    int len = strlen(str);
    int n = write(fd, str, len);
    if( n!=len ) 
        return -1;
    return 0;
}

int serialport_read_until(int fd)
{
     while (c !='q')
        {
                if (read(fd,&c,1)>0)        write(STDOUT_FILENO,&c,1);              // if new data is available on the serial port, print it out
                if (read(STDIN_FILENO,&c,1)>0)  write(fd,&c,1);                     // if new data is available on the console, send it to the serial port
        }
    return 0;
}

// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
// and a baud rate (bps) and connects to that port at that speed and 8N1.
// opens the port in fully raw mode so you can send binary data.
// returns valid fd, or -1 on error
int serialport_init(const char* serialport, int baud)
{
     struct termios tio;
     struct termios stdio;

    int fd;
    
    //fprintf(stderr,"init_serialport: opening port %s @ %d bps\n",
    //        serialport,baud);

       
    memset(&stdio,0,sizeof(stdio));
        stdio.c_iflag=0;
        stdio.c_oflag=0;
        stdio.c_cflag=0;
        stdio.c_lflag=0;
        stdio.c_cc[VMIN]=0;
        stdio.c_cc[VTIME]=1;
       tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
       tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);
        fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);       // make the reads non-blocking
 
 
 
 
        memset(&tio,0,sizeof(tio));
     // 8N1
    tio.c_cflag &= ~PARENB;
    tio.c_cflag &= ~CSTOPB;
    tio.c_cflag &= ~CSIZE;
    tio.c_cflag |= CS8;
    // no flow control
    tio.c_cflag &= ~CRTSCTS;

    tio.c_cflag |= CREAD | CLOCAL;  // turn on READ & ignore ctrl lines
    tio.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl

    tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    tio.c_oflag &= ~OPOST; // make raw

 
       fd = open(serialport, O_RDWR | O_NONBLOCK | O_NOCTTY); 
    if (fd == -1)  {
        perror("init_serialport: Unable to open port ");
        return -1;
    }   
        cfsetospeed(&tio,B115200);            // 115200 baud
        cfsetispeed(&tio,B115200);            // 115200 baud
 
        tcsetattr(fd,TCSANOW,&tio);

    return fd;
}

Open in new window

You must reset the termios settings of stdout (your terminal) to what they were when the program started before leaving the program. That means you must save a copy of toptions as soon as you read it with tcgetattr and re-assert it with tcsetattr as you exit (you can use the atexit function to do it - this would cater for multiple exit points).
I tryed to do as you suggested: I saved the current settings with tcgetattr and i restored it at the exit with atexit, but the problem is still here because shell does not show what i type after the program termination.

Can you check the code i've posted below, please?
May be, I did some kind of errors which i'm not able to detect (i'm not very good in programming).

void usage(void);
int serialport_init(const char* serialport, int baud);
int serialport_writebyte(int fd, uint8_t b);
int serialport_write(int fd, const char* str);
int serialport_read_until(int fd);
struct termios oldtio;
int fd;

void close_fd(){       <- I RESTORE THE PREVIOS SETTINGS
	printf("\nOK\n");
	tcsetattr(fd,TCSANOW,&oldtio);
	}

void usage(void) {
    printf("Usage: arduino-serial -p <serialport> [OPTIONS]\n"
    "\n"
    "Options:\n"
    "  -h, --help                   Print this help message\n"
    "  -p, --port=serialport        Serial port Arduino is on\n"
    "  -b, --baud=baudrate          Baudrate (bps) of Arduino\n"
    "  -s, --send=data              Send data to Arduino\n"
    "  -r, --receive                Receive data from Arduino & print it out\n"
    "  -n  --num=num                Send a number as a single byte\n"
    "  -d  --delay=millis           Delay for specified milliseconds\n"
    "\n"
    "Note: Order is important. Set '-b' before doing '-p'. \n"
    "      Used to make series of actions:  '-d 2000 -s hello -d 100 -r' \n"
    "      means 'wait 2secs, send 'hello', wait 100msec, get reply'\n"
    "\n");
}

int main(int argc, char *argv[]) 
{
    int fd = 0;
    char serialport[256];
	int baudrate = B9600;  // default
    char buf[256];
    int rc,n;
	
    atexit (close_fd);
		
    if (argc==1) {
        usage();
        exit(EXIT_SUCCESS);
    }
	
    /* parse options */
    int option_index = 0, opt;
    static struct option loptions[] = {
        {"help",       no_argument,       0, 'h'},
        {"port",       required_argument, 0, 'p'},
        {"baud",       required_argument, 0, 'b'},
        {"send",       required_argument, 0, 's'},
        {"receive",    no_argument,       0, 'r'},
        {"num",        required_argument, 0, 'n'},
        {"delay",      required_argument, 0, 'd'}
    };
    
    while(1) {
        opt = getopt_long (argc, argv, "hp:b:s:rn:d:",
                           loptions, &option_index);
        if (opt==-1) break;
        switch (opt) {
			case '0': break;
			case 'd':
				n = strtol(optarg,NULL,10);
				usleep(n * 1000 ); // sleep milliseconds
				break;
			case 'h':
				usage();
				break;
			case 'b':
				baudrate = strtol(optarg,NULL,10);
				break;
			case 'p':
				strcpy(serialport,optarg);
				fd = serialport_init(optarg, baudrate);
				if(fd==-1) return -1;
				break;
			case 'n':
				n = strtol(optarg, NULL, 10); // convert string to number
				rc = serialport_writebyte(fd, (uint8_t)n);
				if(rc==-1) return -1;
				break;
			case 's':
				strcpy(buf,optarg);
				rc = serialport_write(fd, buf);
				if(rc==-1) return -1;
				break;
			case 'r':
				serialport_read_until(fd);
				break;
        }
    }
	
    exit(EXIT_SUCCESS);    
} // end main

int serialport_writebyte( int fd, uint8_t b)
{
    int n = write(fd,&b,1);
    if( n!=1)
        return -1;
    return 0;
}

int serialport_write(int fd, const char* str)
{
    int len = strlen(str);
    int n = write(fd, str, len);
    if( n!=len ) 
        return -1;
    return 0;
}

int serialport_read_until(int fd)
{
	unsigned char c='D';
	while (c !='q')
	{
		if (read(fd,&c,1)>0)        write(STDOUT_FILENO,&c,1);              // if new data is available on the serial port, print it out
		if (read(STDIN_FILENO,&c,1)>0)  write(fd,&c,1);                     // if new data is available on the console, send it to the serial port
	}
   
	return 0;
	
}

// takes the string name of the serial port (e.g. "/dev/tty.usbserial","COM1")
// and a baud rate (bps) and connects to that port at that speed and 8N1.
// opens the port in fully raw mode so you can send binary data.
// returns valid fd, or -1 on error
int serialport_init(const char* serialport, int baud)
{
	struct termios tio;
	struct termios stdio;
	
 
    
    //fprintf(stderr,"init_serialport: opening port %s @ %d bps\n",
    //        serialport,baud);
	
	tcgetattr(fd,&oldtio); <-- I SAVE THE CURRENT SETTINGS, IS IT CORRECT?
	
    memset(&stdio,0,sizeof(stdio));
	stdio.c_iflag=0;
	stdio.c_oflag=0;
	stdio.c_cflag=0;
	stdio.c_lflag=0;
	stdio.c_cc[VMIN]=0;
	stdio.c_cc[VTIME]=1;
	tcsetattr(STDOUT_FILENO,TCSANOW,&stdio);
	tcsetattr(STDOUT_FILENO,TCSAFLUSH,&stdio);
	fcntl(STDIN_FILENO, F_SETFL, O_NONBLOCK);       // make the reads non-blocking
	
	
	
	
	memset(&tio,0,sizeof(tio));
	// 8N1
    tio.c_cflag &= ~PARENB;
    tio.c_cflag &= ~CSTOPB;
    tio.c_cflag &= ~CSIZE;
    tio.c_cflag |= CS8;
    // no flow control
    tio.c_cflag &= ~CRTSCTS;
	
    tio.c_cflag |= CREAD | CLOCAL;  // turn on READ & ignore ctrl lines
    tio.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl
	
    tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw
    tio.c_oflag &= ~OPOST; // make raw
	
	
	fd = open(serialport, O_RDWR | O_NONBLOCK | O_NOCTTY); 
    if (fd == -1)  {
        perror("init_serialport: Unable to open port ");
        return -1;
    }   
	cfsetospeed(&tio,B115200);            // 115200 baud
	cfsetispeed(&tio,B115200);            // 115200 baud
	
	tcsetattr(fd,TCSANOW,&tio);
	
    return fd;
}

Open in new window

close_fd looks OK. Do you see the OK message?
Did you try checking your shell's  termios settings before / after running your program as I suggested in http:#a37409584 ?
ASKER CERTIFIED SOLUTION
Avatar of Duncan Roe
Duncan Roe
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Also you should check the return code from tcsetattr and print a message if it failed
Thanks a lot for your advices!

Finally, my code works great!
I had to save and restore the correct configuration before closing the program.
Thanks for your help!