2
\$\begingroup\$

I am planning to write a reasonably feature rich serial library but wanted to cut my teeth on something fairly basic. This serial library is fairly simple. It is a thin wrapper on the UNIX open, close, read and write functions. It is blocking for simplicity and on error the caller can use the errno procedure to get further error info.

How can this be improved?

serial_port.h:

/* Blocking serial port class for unix platform. 
   Thin wrapper for blocking open, read, write and close.
   Functions return either zero or a +ve integer on success
   or -1 on error.  Query errno for further details on error.
*/
#ifndef SERIAL_PORT_H_
#define SERIAL_PORT_H_

#include <stdlib.h>

struct serial_port;
typedef struct serial_port serial_port_t;

/* returns port object if portname can be opened at selected baud rate */
serial_port_t* port_open(const char* portname, int baudrate);
/* returns characters written or -1 on error */
int port_write(serial_port_t* port, const char* data, size_t length);
/* returns characters read or -1 on error */
int port_read(serial_port_t* port, char* buffer, int buffer_size);
/* close port and free allocated resources */
int port_close(serial_port_t* port);

#endif // SERIAL_PORT_H_

serial_port.c:

/* blocking serial port class for unix platform */

#include "serial_port.h"

#include <unistd.h>  // posix api
#include <fcntl.h>   // file control operations
#include <termios.h> // terminal

struct serial_port {
  int fd;
  int baudrate;
  struct termios restore_tty;
  struct termios current_tty;
};

static int set_speed(struct termios tty, int speed) {
    speed_t sp;
    switch(speed) {
        case 1200: sp = B1200; break;
        case 1800: sp = B1800; break;
        case 2400: sp = B2400; break;
        case 4800: sp = B4800; break;
        case 9600: sp = B9600; break;
        case 19200: sp = B19200; break;
        case 38400: sp = B38400; break;
        case 57600: sp = B57600; break;
        case 115200: sp = B115200; break;
  default: 
    return -1; // unsupported
  }  

    int baudset = cfsetospeed(&tty, sp);
    baudset = cfsetispeed(&tty, sp);
    return baudset;
}

static int get_term(int fd, struct termios* ptty) {
  /* Upon successful completion, 0 shall be returned. Otherwise, -1 
     shall be returned and errno set to indicate the error. */
  return tcgetattr(fd, ptty);
}

/* configure tty */
static int set_terminal(int fd, struct termios* ptty) {
    ptty->c_cflag |= (CLOCAL | CREAD);    /* ignore modem controls */
    ptty->c_cflag &= ~CSIZE;
    ptty->c_cflag |= CS8;         /* 8-bit characters */
    ptty->c_cflag &= ~PARENB;     /* no parity bit */
    ptty->c_cflag &= ~CSTOPB;     /* only need 1 stop bit */
    ptty->c_cflag &= ~CRTSCTS;    /* no hardware flowcontrol */

    /* setup for non-canonical mode */
    ptty->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
    ptty->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
    ptty->c_oflag &= ~OPOST;

    /* fetch bytes as they become available */
    ptty->c_cc[VMIN] = 1;
    ptty->c_cc[VTIME] = 1;

    if (tcsetattr(fd, TCSANOW, ptty) != 0) {
      // Error from tcsetattr- use strerror(errno)
        return -1;
    }
    return 0;
}

serial_port_t* port_open(const char* portname, int baudrate) {
    int fd = open(portname, O_RDWR | O_NOCTTY);
    if (fd == -1) {
      return NULL;
    }

    serial_port_t* port = malloc(sizeof(serial_port_t));
    if (port) {
        port->fd = fd;
        port->baudrate = baudrate;
        // cache prior terminal settings for restore later
        if(get_term(port->fd, &port->current_tty) != 0) {
        // cache port - should we fai if not possible?
            port->restore_tty = port->current_tty;
        }
        // set port speed
        if(set_speed(port->current_tty, port->baudrate) != 0) {
      // error setting tty speed - should we fail?
        }
        // set terminal attribs
    if(set_terminal(port->fd, &port->current_tty) < 0) {
        // error configuring port - should we fail?
        }
    }
    return port;
}

int port_write(serial_port_t* port, const char* data, size_t length) {
    return write(port->fd, data, length);
}

int port_read(serial_port_t* port, char* buffer, int buffer_size) {
    return read(port->fd, buffer, buffer_size);
}

int port_close(serial_port_t* port) {
    if (tcsetattr(port->fd, TCSANOW, &port->restore_tty) < 0) {
        // error restoring attributes
    }
    int result = close(port->fd);
    free(port);
    return result;
}

main.c:

#include <stdio.h>

#include <string.h>  // strerror
#include <errno.h>   // errno

#include "serial_port.h"


// test serial library with modem
int main() {
    const char* portname = "/dev/ttyACM0";
    serial_port_t* port = port_open(portname, 9600);
    if(port) {
        printf("port %s successfully opened\n", portname);
        int result = port_write(port, "AT\r\n", 4);
        printf("writing AT to port - %d characters written\n", result);
        char buffer[100];
        result = port_read(port, buffer, 100);
        printf("reading port - %d characters read\n", result);
        if (result > 0) {
            for (int i = 0; i < result; i++) {
                printf("%2x ", (unsigned)buffer[i] & 0xFF);
            }
        }

        result = port_close(port);
        printf("port_close returned %d\n", result);
    } else {
        printf("Error opening port: %s\n", strerror(errno));
    }
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.