Serial port terminal raw mode in C

Thread Starter

nsaspook

Joined Aug 27, 2009
7,299
I'm using a RPi3 to interface serial data over the network in my MBMC project. The terminal interface guts in UNIX/Linux is one of subjects you want to avoid usually by using a higher level interface but I just wanted a simple C program to do the work so I have total control at every byte from the buffer.

Most of the time there is a large amount of processing on the raw serial port data to make it look to programs like an old school terminal for compatibility to decades old programs. All of that can be turned off to get a one byte in/one byte out binary interface.

The GNU C manual set has a very good rundown of the process, structures and functions.
https://ftp.gnu.org/old-gnu/Manuals/glibc-2.2.3/html_chapter/libc_17.html

Here is a fairly simple somewhat hacky example of how it works.
https://github.com/nsaspook/tserv
C:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/* 
 * File:   main.c
 * Author: root
 *
 * Created on April 16, 2020, 12:28 PM
 */
#define _DEFAULT_SOURCE // for BSDisms in GCC
#include "tserv.h"

int serial_port, file_port;
struct termios tty, tty_backup;
struct termios tty_opts_backup, tty_opts_raw;
bool raw_set1 = false, raw_set2 = false;

void reset_input_mode(void)
{
    if (raw_set1)
        tcsetattr(serial_port, TCSANOW, &tty_backup);
    if (raw_set2)
        tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_backup);
}

/*
 * MBMC UNIX time server and data logger
 */
int main(int argc, char** argv)
{
    int t_set = false;
    char junk;

#ifdef __x86_64__ // for PC
    serial_port = open("/dev/ttyS0", O_RDWR);
#else // for RPi
    serial_port = open("/dev/ttyUSB0", O_RDWR);
#endif
    if (serial_port < 0) {
        printf("Port Error %i from open: %s\r\n", errno, strerror(errno));
        return errno;
    }
    memset(&tty, 0, sizeof tty); // make sure it's clear
    memset(&tty_backup, 0, sizeof tty_backup);

    // Read in existing settings, and handle any error
    if (tcgetattr(serial_port, &tty) != 0) {
        printf("TTY Error %i from tcgetattr tty: %s\r\n", errno, strerror(errno));
        return errno;
    }
    if (tcgetattr(serial_port, &tty_backup) != 0) {
        printf("TTY Error %i from tcgetattr tty_backup: %s\r\n", errno, strerror(errno));
        return errno;
    }
    raw_set1 = true; // restore on signal exit
    atexit(reset_input_mode); // restore the original terminal modes before exiting or terminating with a signal.

    /*
     * most of this could be done using cfmakeraw like in tserv.c
     */

    tty.c_cflag &= ~PARENB;
    tty.c_cflag &= ~CSTOPB;
    tty.c_cflag &= ~CSIZE;
    tty.c_cflag |= CS8;
    tty.c_cflag |= CREAD | CLOCAL;

    tty.c_lflag &= ~ICANON;
    tty.c_lflag &= ~ECHO;
    tty.c_lflag &= ~ECHOE;
    tty.c_lflag &= ~ECHONL;
    tty.c_lflag &= ~ISIG;
    tty.c_iflag &= ~(IXON | IXOFF | IXANY);
    tty.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL);

    tty.c_oflag &= ~OPOST;
    tty.c_oflag &= ~ONLCR;

    tty.c_cc[VTIME] = 10; // Wait for up to 1s, returning as soon as any data is received.
    tty.c_cc[VMIN] = 0;

    // Set in/out baud rate to be 115200
    cfsetispeed(&tty, B115200);
    cfsetospeed(&tty, B115200);

    // Save tty settings, also checking for error
    if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
        printf("TTY set Error %i from tcsetattr: %s\n", errno, strerror(errno));
        return errno;
    }
    // clear the receive buffer
    while (read(serial_port, &junk, 1));

    while (true) {
        if (t_set) {
            get_log(1);
        } else {
            t_set = get_log(0);
        }
    }
    raw_set1 = false;
    close(serial_port);
    return(EXIT_SUCCESS);
}
C:
/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */

/* 
 * File:   tserv.h
 * Author: root
 *
 * Created on April 17, 2020, 9:49 AM
 */

#ifndef TSERV_H
#define TSERV_H

#ifdef __cplusplus
extern "C" {
#endif

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fcntl.h> 
#include <errno.h>
#include <termios.h>
#include <unistd.h>
#include <time.h>
#include <stdbool.h>

#define LOG_FILE "/media/disk/minicom1.txt"

    int get_log(int);
    char get_one_char(void);
    void reset_input_mode(void);
#ifdef __cplusplus
}
#endif

#endif /* TSERV_H */
C:
// C library headers
#define _DEFAULT_SOURCE
#include "logger/tserv.h"

extern int serial_port, file_port;
extern struct termios tty_opts_backup, tty_opts_raw;
extern bool raw_set2;

const char cmd_text[][32] = {
    " ",
    " AC Charger ON",
    " AC Charger OFF",
    " System Data",
};

char get_one_char(void)
{
    struct termios tty_opts_backup, tty_opts_raw;

    if (!isatty(STDIN_FILENO)) {
        printf("Error: stdin is not a TTY\n");
        exit(1);
    }

    // Back up current TTY settings
    tcgetattr(STDIN_FILENO, &tty_opts_backup);
    raw_set2 = true; // restore on signal exit

    // Change TTY settings to raw mode
    cfmakeraw(&tty_opts_raw);
    tty_opts_raw.c_cc[VMIN] = 0;
    tty_opts_raw.c_cc[VTIME] = 1;
    tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_raw);

    // Read characters from stdin
    int c = '\0';

    read(STDIN_FILENO, &c, 1);

    // Restore previous TTY settings
    raw_set2 = false;
    tcsetattr(STDIN_FILENO, TCSANOW, &tty_opts_backup);
    return c;
}

/*
 * read from the serial port, check for time T command and log data to storage file
 */
int get_log(int mode)
{
    char read_buf[256], msg[256], c, k;
    int num_bytes, rcode = 0;

    num_bytes = read(serial_port, &read_buf[0], sizeof(read_buf));
    if (num_bytes < 0) {
        printf("Error reading: %s\r\n", strerror(errno));
        return errno;
    }

    file_port = open(LOG_FILE, O_RDWR | O_APPEND);
    if (file_port < 0) {
        printf("File Error %i from open: %s\r\n", errno, strerror(errno));
        return errno;
    } else {
        if (num_bytes) {
            printf("Read %i bytes. Received message: %c\r\n", num_bytes, read_buf[0]);
            if (read_buf[0] == 'T') { // process the T server time command
                int k = 0;
                // Write to serial port
                sprintf(msg, "T%lut", time(0)); // format UNIX time in ASCII
                do {
                    write(serial_port, &msg[k], 1); // use gaps between bytes for slow controller time processing
                    usleep(1000);
                } while (k++ < strlen(msg));

                printf("Send time %s\r\n", msg);
                rcode = 1;
            } else { // just write the serial buffer to the log file
                write(file_port, &read_buf[0], num_bytes);
            }
            c = get_one_char();
            if (c == 'V' || c == 'v' || c == '#') {// check for valid commands

                switch (c) {
                case 'V':
                    k = 1;
                    break;
                case 'v':
                    k = 2;
                    break;
                case '#':
                    k = 3;
                    break;
                default:
                    k = 0;
                    break;
                }
                write(serial_port, &c, 1);
                printf("\r\nSend Command %c %s\r\n", c, cmd_text[(int) k]);
            }
        }

        close(file_port);
    }
    return rcode;
}
 
Top