Skip to content

Instantly share code, notes, and snippets.

@parsley72
Forked from ryankurte/uart.c
Last active May 4, 2021 02:00
Show Gist options
  • Save parsley72/e855d8e146827beeb3c0a9c4f82ce17f to your computer and use it in GitHub Desktop.
Save parsley72/e855d8e146827beeb3c0a9c4f82ce17f to your computer and use it in GitHub Desktop.
Simple unix serial implementation.This uses pthreads to receive and buffer incoming data for later use.
// https://gist.github.com/ryankurte/8143058
#include "uart.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <termios.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <signal.h>
#include <poll.h>
#include <errno.h>
/**
* @struct Serial device structure.
* Encapsulates a serial connection.
*/
struct serial_s {
int fd; //>! Connection file descriptor.
int state; //>! Signifies connection state.
int running; //>! Signifies thread state.
char rxbuff[BUFF_SIZE]; //>! Buffer for RX data.
int start, end; //>! Pointers to start and end of buffer.
pthread_t rx_thread; //>! Listening thread.
};
// --------------- Internal Functions ---------------
static int serial_resolve_baud(int baud);
/**
* Recieve data.
* Retrieves data from the serial device.
* @param s - serial structure.
* @param data - pointer to a buffer to read data into.
* @param maxLength - size of input buffer.
* @return amount of data recieved.
*/
static int serial_recieve(serial_t* obj, uint8_t data[], int maxLength);
/**
* @brief Serial Listener Thread.
* This blocks waiting for data to be recieved from the serial device,
* and calls the serial_callback method with appropriate context when
* data is recieved.
* Exits when close method is called, or serial error occurs.
* @param param - context passed from thread instantiation.
*/
static void *serial_data_listener(void *param);
/**
* @brief Start the serial threads.
* This spawns the listening and transmitting threads
* if they are not already running.
* @param s - serial structure.
* @return 0 on success, or -1 on error.
*/
static int serial_start(serial_t* s);
/**
* Stop serial listener thread.
* @param s - serial structure.
* @return 0;
*/
static int serial_stop(serial_t* s);
/**
* Callback to handle recieved data.
* Puts recieved data into the rx buffer.
* @param s - serial structure.
* @param data - data to be stored.
* @param length - length of recieved data.
*/
static void serial_rx_callback(serial_t* s, char data[], int length);
// Put character in rx buffer.
static int buffer_put(serial_t* s, char c)
{
//if there is space in the buffer
if ( s->end != ((s->start + BUFF_SIZE - 1) % BUFF_SIZE)) {
s->rxbuff[s->end] = c;
s->end ++;
s->end = s->end % BUFF_SIZE;
//printf("Put: %x start: %d, end: %d\r\n", c, s->start, s->end);
return 0; //No error
} else {
//buffer is full, this is a bad state
return -1; //Report error
}
}
// Get character from rx buffer.
static char buffer_get(serial_t* s)
{
char c = (char)0;
//if there is data to process
if (s->end != s->start) {
c = (s->rxbuff[s->start]);
s->start ++;
//wrap around
s->start = s->start % BUFF_SIZE;
} else {
}
//printf("Get: %x start: %d, end: %d\r\n", c, s->start, s->end);
return c;
}
// Get data available in the rx buffer.
static int buffer_available(serial_t* s)
{
return (s->end - s->start + BUFF_SIZE) % BUFF_SIZE;
}
// --------------- External Functions ---------------
serial_t* serial_create()
{
// Allocate serial object.
serial_t* s = malloc(sizeof(serial_t));
// Reconfigure buffer object.
s->start = 0;
s->end = 0;
// Return pointer.
return s;
}
void serial_destroy(serial_t* s)
{
free(s);
}
int serial_connect(serial_t* s, const char device[], int baud)
{
struct termios oldtio;
// Resolve baud.
int speed = serial_resolve_baud(baud);
if (speed < 0) {
printf("Error: Baud rate not recognized.\r\n");
return -1;
}
// Open device.
s->fd = open(device, O_RDWR | O_NOCTTY);
// Catch file open error.
if (s->fd < 0) {
perror(device);
return -2;
}
// https://blog.mbedded.ninja/programming/operating-systems/linux/linux-serial-ports-using-c-cpp/
// Retrieve settings.
tcgetattr(s->fd, &oldtio);
// Set baud rate.
cfsetspeed(&oldtio, speed);
// Control Modes (c_cflags)
// oldtio.c_cflag &= ~PARENB; // Clear parity bit, disabling parity (most common)
oldtio.c_cflag |= PARENB; // Set parity bit, enabling parity
oldtio.c_cflag &= ~PARODD; // If set, then parity for input and output is odd; otherwise even parity is used.
oldtio.c_cflag &= ~CSTOPB; // Clear stop field, only one stop bit used in communication (most common)
oldtio.c_cflag &= ~CSIZE; // Clear all the size bits, then use one of the statements below
oldtio.c_cflag |= CS8; // 8 bits per byte (most common)
oldtio.c_cflag &= ~CRTSCTS; // Disable RTS/CTS hardware flow control (most common)
// Local Modes (c_lflag)
// Disable Canonical Mode
oldtio.c_lflag &= ~ICANON;
// Disable Echo
oldtio.c_lflag &= ~ECHO; // Disable echo
oldtio.c_lflag &= ~ECHOE; // Disable erasure
oldtio.c_lflag &= ~ECHONL; // Disable new-line echo
// Disable Signal Chars
oldtio.c_lflag &= ~ISIG; // Disable interpretation of INTR, QUIT and SUSP
// CREAD and CLOCAL
oldtio.c_cflag |= CREAD | CLOCAL; // Turn on READ & ignore ctrl lines (CLOCAL = 1)
// Input Modes (c_iflag)
// Software Flow Control
oldtio.c_iflag &= ~(IXON | IXOFF | IXANY); // Turn off s/w flow ctrl
// Disable Special Handling Of Bytes On Receive
oldtio.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL); // Disable any special handling of received bytes
// Output Modes (c_oflag)
oldtio.c_oflag &= ~OPOST; // Prevent special interpretation of output bytes (e.g. newline chars)
oldtio.c_oflag &= ~ONLCR; // Prevent conversion of newline to carriage return/line feed
// Flush cache.
tcflush(s->fd, TCIFLUSH);
// Apply settings.
tcsetattr(s->fd, TCSANOW, &oldtio);
// Start listener thread.
int res = serial_start(s);
// Catch error.
if (res < 0) {
printf("Error: serial thread could not be spawned\r\n");
return -3;
}
// Indicate connection was successful.
s->state = 1;
return 0;
}
int serial_set_baud(serial_t* s, int baud)
{
struct termios oldtio;
// Resolve baud.
int speed = serial_resolve_baud(baud);
if (speed < 0) {
printf("Error: Baud rate not recognized.\r\n");
return -1;
}
// Retrieve settings.
tcgetattr(s->fd, &oldtio);
// Set baud rate.
cfsetspeed(&oldtio, speed);
// Flush cache.
tcflush(s->fd, TCIFLUSH);
// Apply settings.
tcsetattr(s->fd, TCSANOW, &oldtio);
return 0;
}
int serial_send(serial_t* s, const uint8_t data[], int length)
{
int res = write(s->fd, data, length);
return res;
}
void serial_put(serial_t* s, uint8_t data)
{
char arr[1];
arr[0] = data;
write(s->fd, arr, 1);
}
int serial_available(serial_t* s)
{
return buffer_available(s);
}
char serial_get(serial_t* s)
{
char c = buffer_get(s);
return c;
}
char serial_blocking_get(serial_t* s)
{
while (serial_available(s) == 0);
return serial_get(s);
}
void serial_clear(serial_t* s)
{
// Clear the buffer.
while (buffer_available(s)) {
buffer_get(s);
}
}
int serial_close(serial_t* s)
{
// Stop thread.
serial_stop(s);
return 0;
}
// --------------- Internal Functions --------------
// Stop serial listener thread.
static int serial_stop(serial_t* s)
{
s->running = 0;
return 0;
}
// Resolves standard baud rates to linux constants.
static int serial_resolve_baud(int baud)
{
int speed;
// Switch common baud rates to temios constants.
switch (baud) {
case 9600:
speed = B9600;
break;
case 19200:
speed = B19200;
break;
case 38400:
speed = B38400;
break;
case 57600:
speed = B57600;
break;
case 115200:
speed = B115200;
break;
case 460800:
speed = B460800;
break;
case 921600:
speed = B921600;
break;
case 2000000:
speed = B2000000;
break;
case 3000000:
speed = B3000000;
break;
default:
speed = -1;
break;
}
// Return.
return speed;
}
// Start serial listener.
static int serial_start(serial_t* s)
{
// Only start if it is not currently running.
if (s->running != 1) {
//Set running.
s->running = 1;
//Spawn thread.
int res;
res = pthread_create(&s->rx_thread, NULL, serial_data_listener, (void*) s);
if (res != 0) {
return -2;
}
// Return result.
return 0;
} else {
return -1;
}
}
// Recieve data.
static int serial_recieve(serial_t* s, uint8_t data[], int maxLength)
{
return read(s->fd, data, maxLength);
}
// Callback to store data in buffer.
static void serial_rx_callback(serial_t* s, char data[], int length)
{
// Put data into buffer.
int i;
for (i = 0; i < length; i++) {
buffer_put(s, data[i]);
}
}
// Serial data listener thread.
static void *serial_data_listener(void *param)
{
int res = 0;
int err = 0;
struct pollfd ufds;
uint8_t buff[BUFF_SIZE];
// Retrieve parameters and store locally.
serial_t* serial = (serial_t*) param;
int fd = serial->fd;
// Set up poll file descriptors.
ufds.fd = fd; // Attach socket to watch.
ufds.events = POLLIN; // Set events to notify on.
// Run until ended.
while (serial->running != 0) {
// Poll socket for data.
res = poll(&ufds, 1, POLL_TIMEOUT);
// If data was recieved.
if (res > 0) {
// Fetch the data.
int count = serial_recieve(serial, buff, BUFF_SIZE - 1);
// If data was recieved.
if (count > 0) {
// Pad end of buffer to ensure there is a termination symbol.
buff[count] = '\0';
// Call the serial callback.
serial_rx_callback(serial, (char *)buff, count);
// If an error occured.
} else if (count < 0) {
// Inform user and exit thread.
printf("Error: Serial disconnect\r\n");
err = 1;
break;
}
// If there was an error.
} else if (res < 0) {
// Inform user and exit thread.
printf("Error: Polling error in serial thread");
err = 1;
break;
}
// Otherwise, keep going around.
}
// If there was an error, close socket.
if (err) {
serial_close(serial);
//raise(SIGLOST);
}
// Close file.
/*res =*/ close(serial->fd);
return NULL;
}
// https://gist.github.com/ryankurte/8143058
#ifndef UART_H
#define UART_H
#define BUFF_SIZE 512
#define POLL_TIMEOUT 2000
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct serial_s serial_t;
/**
* Create the serial structure.
* Convenience method to allocate memory
* and instantiate objects.
* @return serial structure.
*/
serial_t* serial_create();
/**
* Destroy the serial structure
*/
void serial_destroy(serial_t* s);
/**
* Connect to a serial device.
* @param s - serial structure.
* @param device - serial device name.
* @param baud - baud rate for connection.
* @return -ve on error, 0 on success.
*/
int serial_connect(serial_t* s, const char device[], int baud);
/**
* Set baud rate.
* @param s - serial structure.
* @param baud - baud rate.
* @return -ve on error, 0 on success.
*/
int serial_set_baud(serial_t* s, int baud);
/**
* Send data.
* @param s - serial structure.
* @param data - character array to transmit.
* @param length - size of the data array.
*/
int serial_send(serial_t* s, const uint8_t data[], int length);
/**
* Send a single character.
* @param s - serial structure.
* @param data - single character to be sent.
*/
void serial_put(serial_t* s, uint8_t data);
/**
* Determine how much data is available
* in the serial buffer.
* @param s - serial structure.
* @return number of characters available.
*/
int serial_available(serial_t* s);
/**
* Fetch one char from the serial buffer.
* @param s - serial structure.
* @return character. Null if empty.
*/
char serial_get(serial_t* s);
/**
* Fetch one char from the serial buffer.
* Blocks until data becomes available.
* @param s - serial structure.
* @return character.
*/
char serial_blocking_get(serial_t* s);
/**
* Clear the serial buffer.
* @param s - serial structure.
*/
void serial_clear(serial_t* s);
/**
* Close the serial port.
* @param s - serial structure.
* @return value of close().
*/
int serial_close(serial_t* s);
#ifdef __cplusplus
}
#endif
#endif // UART_H
// https://gist.github.com/ryankurte/8143058
#ifndef UART_HPP
#define UART_HPP
#include "uart.h"
class Serial
{
public:
Serial()
{
_serial = serial_create();
}
~Serial()
{
serial_destroy(_serial);
}
int Connect(const char device[], int baud)
{
return serial_connect(_serial, device, baud);
}
int Set_Baud(int baud)
{
return serial_set_baud(_serial, baud);
}
int Send(const uint8_t data[], int length)
{
return serial_send(_serial, data, length);
}
void Put(uint8_t data)
{
return serial_put(_serial, data);
}
int Available()
{
return serial_available(_serial);
}
char Get()
{
return serial_get(_serial);
}
char Blocking_get()
{
return serial_blocking_get(_serial);
}
void Clear()
{
return serial_clear(_serial);
}
int Disconnect()
{
return serial_close(_serial);
}
private:
serial_t* _serial;
};
#endif // UART_HPP
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment