eive-obsw/thirdparty/tas/uart.c
Robin Mueller 048cd89053
All checks were successful
EIVE/eive-obsw/pipeline/head This commit looks good
use scoped includes
2022-11-04 09:45:32 +01:00

604 lines
13 KiB
C

//**************************************************************************************
/*! \copyright: 2020-2021 Thales Alenia Space Deutschland GmbH
* \project: multiMIND
* \file: (name of source file: uart.c)
* \date: (20.05.2021)
* \author: (Sarthak Kelapure)
* \brief: (UART thread to collect data on serial interface)
* \language: (C)
**************************************************************************************
*/
#include "tas/uart.h"
#include "tas/hdlc.h"
#ifdef HDLC_ENABLE
#define HDLC_RX_STATE_IDLE (0u)
#define HDLC_RX_STATE_RECEIVING (1u)
#define HDLC_RX_STATE_ESCAPE (2u)
#endif
/**
* @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 ---------------
/**
* 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.
*/
static int serial_connect(serial_t* s, char device[], int baud);
/**
* Create the serial structure.
* Convenience method to allocate memory
* and instantiate objects.
* @return serial structure.
*/
static serial_t* serial_create();
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 ---------------
//Create serial object.
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 uart_destroy(serial_t* s)
{
free(s);
}
//Connect to serial device.
int serial_connect(serial_t* s, 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;
}
//Retrieve settings.
tcgetattr(s->fd, &oldtio);
//Set baud rate.
cfsetspeed(&oldtio, speed);
//Flush cache.
tcflush(s->fd, TCIFLUSH);
//Set UART settings, standard ones. 8N1
oldtio.c_cflag = (oldtio.c_cflag & ~CSIZE) | CS8; // 8-bit chars
// disable IGNBRK for mismatched speed tests; otherwise receive break
// as \000 chars
oldtio.c_iflag &= ~IGNBRK; // disable break processing
oldtio.c_lflag = 0; // no signaling chars, no echo,
// no canonical processing
oldtio.c_oflag = 0; // no remapping, no delays
oldtio.c_cc[VMIN] = 0; // read doesn't block
oldtio.c_cc[VTIME] = 5; // 0.5 seconds read timeout
oldtio.c_iflag &= ~(IXON | IXOFF | IXANY); // shut off xon/xoff ctrl
oldtio.c_iflag &= ~(IGNCR | ICRNL | INLCR); // CR and LF characters are not affected
oldtio.c_cflag |= (CLOCAL | CREAD);// ignore modem controls,
// enable reading
oldtio.c_cflag &= ~(PARENB | PARODD); // shut off parity
oldtio.c_cflag |= 0;
oldtio.c_cflag &= ~CSTOPB;
oldtio.c_cflag &= ~(020000000000);
//Apply settings.
if(tcsetattr(s->fd, TCSANOW, &oldtio) !=0){
printf("ERROR: serial settings failed\r\n");
return -1;
}
//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;
}
serial_t* uart_init(char device[], int baud)
{
serial_t* s = serial_create();
if(serial_connect(s, device, baud)< 0)
{
return NULL;
}
return s;
}
//Send data.
uint32_t uart_length_send(serial_t* s, uint8_t data[], int length)
{
// uint16_t ii;
// int res;
// for (ii = 0; ii < length; ii++)
// {
// res = write(s->fd, &data[ii], 1);
// }
int res = write(s->fd, data, length);
return res;
}
void uart_send(serial_t* s, uint8_t data)
{
char arr[1];
arr[0] = data;
write(s->fd, arr, 1);
}
//Determine characters available.
int uart_available(serial_t* s)
{
return buffer_available(s);
}
//Fetch a character.
char uart_get(serial_t* s)
{
char c = buffer_get(s);
return c;
}
int uart_length_get(serial_t* s, char* buff, int len, bool start_of_packet)
{
int ret = 0;
if (len > 0 && len < BUFF_SIZE)
{
#ifdef HDLC_ENABLE
uint8_t ch;
uint8_t hdlc_rx_state;
int rxb = 0;
if (start_of_packet)
hdlc_rx_state = HDLC_RX_STATE_IDLE;
else
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
while (rxb < len)
{
ch = uart_blocking_get(s);
switch (hdlc_rx_state)
{
case HDLC_RX_STATE_IDLE:
if (ch == HDLC_START_BYTE)
{
rxb = 0;
ret = 0;
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
}
break;
case HDLC_RX_STATE_RECEIVING:
if (ch == HDLC_START_BYTE)
{
rxb = 0;
ret = 0;
break;
}
if (ch == HDLC_ESC_BYTE)
{
hdlc_rx_state = HDLC_RX_STATE_ESCAPE;
break;
}
buff[rxb++] = ch;
ret++;
break;
case HDLC_RX_STATE_ESCAPE:
if (ch == HDLC_START_BYTE)
{
rxb = 0;
ret = 0;
break;
}
buff[rxb++] = ch ^ HDLC_ESCAPE_CHAR;
ret++;
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
break;
}
}
#else
for (int i=0;i<len;i++)
{
buff[i] = uart_blocking_get(s);
ret++;
}
#endif
}
return ret;
}
uint16_t uart_get_hdlc_packet(serial_t* s, uint8_t *buff, uint16_t buff_len)
{
uint8_t hdlc_rx_state = HDLC_RX_STATE_IDLE;
uint8_t ch;
uint16_t buff_pos = 0u;
while (1)
{
ch = uart_blocking_get(s);
switch (hdlc_rx_state)
{
case HDLC_RX_STATE_IDLE:
if (ch == HDLC_START_BYTE)
{
buff_pos = 0u;
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
}
break;
case HDLC_RX_STATE_RECEIVING:
if (ch == HDLC_START_BYTE)
{
buff_pos = 0u;
break;
}
if (ch == HDLC_END_BYTE)
{
if (buff_pos > 2u) // do not include HDLC CRC16
{
return buff_pos;
}
buff_pos = 0u;
hdlc_rx_state = HDLC_RX_STATE_IDLE;
break;
}
if (ch == HDLC_ESC_BYTE)
{
hdlc_rx_state = HDLC_RX_STATE_ESCAPE;
break;
}
if (buff_pos >= buff_len)
{
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
break;
}
buff[buff_pos++] = ch;
break;
case HDLC_RX_STATE_ESCAPE:
if ((ch == HDLC_START_BYTE) || (ch == HDLC_END_BYTE))
{
buff_pos = 0;
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
break;
}
if (buff_pos >= buff_len)
{
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
break;
}
buff[buff_pos++] = ch ^ HDLC_ESCAPE_CHAR;
hdlc_rx_state = HDLC_RX_STATE_RECEIVING;
break;
default:
buff_pos = 0u;
hdlc_rx_state = HDLC_RX_STATE_IDLE;
break;
}
}
}
char uart_blocking_get(serial_t* s)
{
while (uart_available(s) == 0);
return uart_get(s);
}
void uart_clear(serial_t* s)
{
//Clear the buffer.
while (buffer_available(s)) {
buffer_get(s);
}
tcflush(s->fd, TCIFLUSH);
}
//Close serial port.
int uart_close(serial_t* s)
{
//Stop thread.
serial_stop(s);
return 0;
}
void uart_deinit(serial_t* s){
uart_clear(s);
uart_close(s);
uart_destroy(s);
}
// --------------- Internal Functions --------------
//Stop serial listener thread.
static int serial_stop(serial_t* s)
{
s->running = 0;
return close(s->fd);
}
// 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 230400:
speed = B230400;
break;
case 460800:
speed = B460800;
break;
case 500000:
speed = B500000;
break;
case 576000:
speed = B576000;
break;
case 921600:
speed = B921600;
break;
case 1000000:
speed = B1000000;
break;
case 1152000:
speed = B1152000;
break;
case 1500000:
speed = B1500000;
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;
//Put data into buffer.
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 paramaters 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) {
uart_close(serial);
//raise(SIGLOST);
}
//Close file.
res = close(serial->fd);
return NULL;
}