504 lines
12 KiB
C
504 lines
12 KiB
C
|
/*
|
||
|
* A library for Linux serial communication
|
||
|
*
|
||
|
* Author: JoStudio, 2016
|
||
|
*
|
||
|
* Version: 0.5
|
||
|
*
|
||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||
|
* a copy of this software and associated documentation files (the
|
||
|
* "Software"), to deal in the Software without restriction, including
|
||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||
|
* the following conditions:
|
||
|
*
|
||
|
* The above copyright notice and this permission notice shall be
|
||
|
* included in all copies or substantial portions of the Software.
|
||
|
*
|
||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||
|
*/
|
||
|
|
||
|
#include "serial.h"
|
||
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <sys/stat.h>
|
||
|
#include <unistd.h>
|
||
|
#include <string.h>
|
||
|
#include <termios.h>
|
||
|
#include <sys/select.h>
|
||
|
#include <errno.h>
|
||
|
#include <string.h>
|
||
|
#include <fcntl.h>
|
||
|
|
||
|
#ifndef CMSPAR
|
||
|
#define CMSPAR 010000000000
|
||
|
#endif
|
||
|
|
||
|
|
||
|
|
||
|
/*
|
||
|
* convert baud_rate to speed_t
|
||
|
*/
|
||
|
static speed_t get_speed(unsigned int baud_rate)
|
||
|
{
|
||
|
switch (baud_rate) {
|
||
|
case 0:
|
||
|
return B0;
|
||
|
case 50:
|
||
|
return B50;
|
||
|
case 75:
|
||
|
return B75;
|
||
|
case 110:
|
||
|
return B110;
|
||
|
case 150:
|
||
|
return B150;
|
||
|
case 200:
|
||
|
return B200;
|
||
|
case 300:
|
||
|
return B300;
|
||
|
case 600:
|
||
|
return B600;
|
||
|
case 1200:
|
||
|
return B1200;
|
||
|
case 1800:
|
||
|
return B1800;
|
||
|
case 2400:
|
||
|
return B2400;
|
||
|
case 4800:
|
||
|
return B4800;
|
||
|
case 9600:
|
||
|
return B9600;
|
||
|
case 19200:
|
||
|
return B19200;
|
||
|
case 38400:
|
||
|
return B38400;
|
||
|
case 57600:
|
||
|
return B57600;
|
||
|
case 115200:
|
||
|
return B115200;
|
||
|
case 230400:
|
||
|
return B230400;
|
||
|
case 460800:
|
||
|
return B460800;
|
||
|
case 500000:
|
||
|
return B500000;
|
||
|
case 576000:
|
||
|
return B576000;
|
||
|
case 921600:
|
||
|
return B921600;
|
||
|
case 1000000:
|
||
|
return B1000000;
|
||
|
case 1152000:
|
||
|
return B1152000;
|
||
|
case 1500000:
|
||
|
return B1500000;
|
||
|
case 2000000:
|
||
|
return B2000000;
|
||
|
case 2500000:
|
||
|
return B2500000;
|
||
|
case 3000000:
|
||
|
return B3000000;
|
||
|
case 3500000:
|
||
|
return B3500000;
|
||
|
case 4000000:
|
||
|
return B4000000;
|
||
|
default:
|
||
|
//unsupported baud rate
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* set baud rate of serial port
|
||
|
*
|
||
|
* @param file_descriptor file descriptor of serial device
|
||
|
* @param baud_rate baud rate
|
||
|
*
|
||
|
* @return 1 if success
|
||
|
* return negative error code if fail
|
||
|
*/
|
||
|
int serial_set_baud_rate(int file_descriptor, int baud_rate) {
|
||
|
/* struct termios termio;
|
||
|
speed_t speed;
|
||
|
int fd = file_descriptor;
|
||
|
|
||
|
if ( fd < 0 ) {
|
||
|
return SERIAL_INVALID_FILE;
|
||
|
}
|
||
|
|
||
|
memset(&termio, 0, sizeof(termio));
|
||
|
|
||
|
//get old attribute
|
||
|
if (tcgetattr(fd, &termio)) {
|
||
|
return SERIAL_INVALID_RESOURCE;
|
||
|
}
|
||
|
|
||
|
//calculate baud rate
|
||
|
speed = get_speed(baud_rate);
|
||
|
if (speed == 0) {
|
||
|
return SERIAL_ERROR_BAUDRATE;
|
||
|
}
|
||
|
cfsetispeed(&termio, speed);
|
||
|
cfsetospeed(&termio, speed);
|
||
|
|
||
|
// set baud rate
|
||
|
if (tcsetattr(fd, TCSAFLUSH, &termio) < 0) {
|
||
|
return SERIAL_ERROR_BAUDRATE;
|
||
|
}*/
|
||
|
int iFd = file_descriptor;
|
||
|
int len,ret;
|
||
|
struct termios opt;
|
||
|
tcgetattr(iFd, &opt);
|
||
|
cfsetispeed(&opt, B115200);
|
||
|
cfsetospeed(&opt, B115200);
|
||
|
if (tcgetattr(iFd, &opt)<0) {
|
||
|
return -1;
|
||
|
}
|
||
|
opt.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
|
||
|
opt.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON);
|
||
|
opt.c_oflag &= ~(OPOST);
|
||
|
opt.c_cflag &= ~(CSIZE | PARENB | CBAUD);
|
||
|
opt.c_cflag |= (CS8 | B115200);
|
||
|
opt.c_cc[VMIN] = 255;
|
||
|
opt.c_cc[VTIME] = 5;
|
||
|
if (tcsetattr(iFd, TCSANOW, &opt)<0) {
|
||
|
return -1;
|
||
|
}
|
||
|
tcflush(iFd,TCIOFLUSH);
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* set serial attributes
|
||
|
*
|
||
|
* @param file_descriptor file descriptor of serial device
|
||
|
* @param flow_ctrl
|
||
|
* @param data_bits how many bits per data, could be 7, 8, 5
|
||
|
* @param parity which parity method, could be PARITY_NONE, PARITY_ODD, PARITY_EVEN
|
||
|
* @param stop_bits how many stop bits
|
||
|
*
|
||
|
* @return 1 if success.
|
||
|
* return negative error code if fail.
|
||
|
*/
|
||
|
int serial_set_attr(int file_descriptor, int data_bits, char parity, int stop_bits, int flow_ctrl)
|
||
|
{
|
||
|
struct termios termio;
|
||
|
int fd = file_descriptor;
|
||
|
|
||
|
if ( fd < 0 ) {
|
||
|
return SERIAL_INVALID_FILE;
|
||
|
}
|
||
|
|
||
|
memset(&termio, 0, sizeof(termio));
|
||
|
|
||
|
if (tcgetattr(fd, &termio)) {
|
||
|
return SERIAL_INVALID_RESOURCE;
|
||
|
}
|
||
|
|
||
|
//set flow control
|
||
|
switch(flow_ctrl) {
|
||
|
case FLOW_CONTROL_NONE :
|
||
|
termio.c_cflag &= ~CRTSCTS;
|
||
|
break;
|
||
|
case FLOW_CONTROL_HARDWARE :
|
||
|
termio.c_cflag |= CRTSCTS;
|
||
|
break;
|
||
|
case FLOW_CONTROL_SOFTWARE :
|
||
|
termio.c_cflag |= IXON | IXOFF | IXANY;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//set data bit
|
||
|
termio.c_cflag &= ~CSIZE;
|
||
|
switch (data_bits) {
|
||
|
case 8:
|
||
|
termio.c_cflag |= CS8;
|
||
|
break;
|
||
|
case 7:
|
||
|
termio.c_cflag |= CS7;
|
||
|
break;
|
||
|
case 6:
|
||
|
termio.c_cflag |= CS6;
|
||
|
break;
|
||
|
case 5:
|
||
|
termio.c_cflag |= CS5;
|
||
|
break;
|
||
|
default:
|
||
|
termio.c_cflag |= CS8;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//set stop bits
|
||
|
switch (stop_bits) {
|
||
|
case 1:
|
||
|
termio.c_cflag &= ~CSTOPB;
|
||
|
break;
|
||
|
case 2:
|
||
|
termio.c_cflag |= CSTOPB;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
//set parity bit
|
||
|
switch (parity) {
|
||
|
case PARITY_NONE:
|
||
|
termio.c_cflag &= ~(PARENB | PARODD);
|
||
|
break;
|
||
|
case PARITY_EVEN:
|
||
|
termio.c_cflag |= PARENB;
|
||
|
termio.c_cflag &= ~PARODD;
|
||
|
break;
|
||
|
case PARITY_ODD:
|
||
|
termio.c_cflag |= PARENB | PARODD;
|
||
|
break;
|
||
|
case PARITY_MARK:
|
||
|
termio.c_cflag |= PARENB | CMSPAR | PARODD;
|
||
|
break;
|
||
|
case PARITY_SPACE:
|
||
|
termio.c_cflag |= PARENB | CMSPAR;
|
||
|
termio.c_cflag &= ~PARODD;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (tcsetattr(fd, TCSAFLUSH, &termio) < 0) {
|
||
|
return SERIAL_ERROR_SETTING;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* open serial port
|
||
|
*
|
||
|
* @param device_filename device file name, such as "/dev/ttyS0"
|
||
|
* @param baund_rate could be 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300
|
||
|
*
|
||
|
* @return file descriptor which could be used in read() or write()
|
||
|
* return -1 if failed, and errno is set
|
||
|
*/
|
||
|
int serial_open_file(char *device_filename, int baud_rate) {
|
||
|
int fd;
|
||
|
struct termios termio;
|
||
|
|
||
|
if ((fd = open(device_filename, O_RDWR | O_NOCTTY )) == -1) {
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
memset(&termio, 0, sizeof(termio));
|
||
|
|
||
|
// setup the tty and the selected baud rate
|
||
|
// get current modes
|
||
|
if (tcgetattr(fd, &termio)) {
|
||
|
close(fd);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
// setup initial mode: 8 bit data, No parity, 1 stop bit, no echo, no flow control
|
||
|
cfmakeraw(&termio);
|
||
|
if (tcsetattr(fd, TCSAFLUSH, &termio) < 0) {
|
||
|
close(fd);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
if (serial_set_baud_rate(fd, baud_rate) != 1) {
|
||
|
close(fd);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
return fd;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* open serial port
|
||
|
*
|
||
|
* @param file_descriptor file descriptor of serial device
|
||
|
* @param timeout timeout in second
|
||
|
*
|
||
|
* @return 1 if success
|
||
|
* return negative error code if fail
|
||
|
*/
|
||
|
int serial_set_timeout(int file_descriptor, int timeout)
|
||
|
{
|
||
|
int fd = file_descriptor;
|
||
|
struct termios termio;
|
||
|
|
||
|
if (fd < 0) {
|
||
|
return SERIAL_INVALID_FILE;
|
||
|
}
|
||
|
|
||
|
|
||
|
// get current attributes
|
||
|
if (tcgetattr(fd, &termio)) {
|
||
|
return SERIAL_INVALID_RESOURCE;
|
||
|
}
|
||
|
|
||
|
//set read timeout
|
||
|
if (timeout > 0) {
|
||
|
timeout = timeout / 100;
|
||
|
if (timeout == 0)
|
||
|
timeout = 1;
|
||
|
}
|
||
|
termio.c_lflag &= ~ICANON; /* Set non-canonical mode */
|
||
|
termio.c_cc[VTIME] = timeout; /* Set timeout in tenth seconds */
|
||
|
if (tcsetattr(fd, TCSANOW, &termio) < 0) {
|
||
|
return SERIAL_ERROR_SETTING;
|
||
|
}
|
||
|
|
||
|
return 1;
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* open serial port
|
||
|
*
|
||
|
* @param port number of port, could be 0, 1 ... , the device file is /dev/ttyS0, /dev/ttyS1 respectively
|
||
|
* @param baund_rate could be 57600, 38400, 19200, 9600, 4800, 2400, 1200, 300
|
||
|
*
|
||
|
* @return file descriptor which could be used in read() or write()
|
||
|
* return -1 if failed, and errno is set
|
||
|
*/
|
||
|
int serial_open(char* port, int baud_rate) {
|
||
|
char buf[128];
|
||
|
|
||
|
snprintf(buf, sizeof(buf), "/dev/ttyS%d", port);
|
||
|
return serial_open_file(port, baud_rate);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* close serial port
|
||
|
*
|
||
|
* @param file_descriptor file descriptor of serial port device file
|
||
|
*
|
||
|
* @return 0 if success.
|
||
|
* return -1 if failed, and errno is set.
|
||
|
*/
|
||
|
int serial_close(int file_descriptor) {
|
||
|
return close(file_descriptor);
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* The function waits until all queued output to the terminal filedes has been transmitted.
|
||
|
*
|
||
|
* tcdrain() is called.
|
||
|
|
||
|
* @param file_descriptor file descriptor of serial port device file
|
||
|
*
|
||
|
* @return 0 if success.
|
||
|
* return -1 if fail, and errno is set.
|
||
|
*/
|
||
|
int serial_flush(int file_descriptor)
|
||
|
{
|
||
|
return tcdrain(file_descriptor);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* whether data is available
|
||
|
*
|
||
|
* @param file_descriptor file descriptor of serial port device file
|
||
|
* @param timeout_millisec timeout in millisecond
|
||
|
*
|
||
|
* @return 1 if data is available
|
||
|
* return 0 if data is not available
|
||
|
*/
|
||
|
int serial_data_available(int file_descriptor, unsigned int timeout_millisec)
|
||
|
{
|
||
|
int fd = file_descriptor;
|
||
|
struct timeval timeout;
|
||
|
fd_set readfds;
|
||
|
|
||
|
if (fd < 0) {
|
||
|
return SERIAL_INVALID_FILE;
|
||
|
}
|
||
|
|
||
|
if (timeout_millisec == 0) {
|
||
|
// no waiting
|
||
|
timeout.tv_sec = 0;
|
||
|
timeout.tv_usec = 0;
|
||
|
} else {
|
||
|
timeout.tv_sec = timeout_millisec / 1000;
|
||
|
timeout.tv_usec = (timeout_millisec % 1000) * 1000;
|
||
|
}
|
||
|
|
||
|
FD_ZERO(&readfds);
|
||
|
FD_SET(fd, &readfds);
|
||
|
|
||
|
if (select(fd + 1, &readfds, NULL, NULL, &timeout) > 0) {
|
||
|
return 1; // data is ready
|
||
|
} else {
|
||
|
return 0; // no data
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* send data
|
||
|
*
|
||
|
* @param file_descriptor file descriptor of serial port device file
|
||
|
* @param buffer buffer of data to send
|
||
|
* @param data_len data length
|
||
|
*
|
||
|
* @return positive integer of bytes sent if success.
|
||
|
* return -1 if failed.
|
||
|
*/
|
||
|
int serial_send(int file_descriptor, char *buffer, size_t data_len)
|
||
|
{
|
||
|
ssize_t len = 0;
|
||
|
len = write(file_descriptor, buffer, data_len);
|
||
|
if ( len == data_len ) {
|
||
|
return len;
|
||
|
} else {
|
||
|
tcflush(file_descriptor, TCOFLUSH);
|
||
|
return -1;
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* receive data
|
||
|
*
|
||
|
* @param file_descriptor file descriptor of serial port device file
|
||
|
* @param buffer buffer to receive data
|
||
|
* @param data_len max data length
|
||
|
*
|
||
|
* @return positive integer of bytes received if success.
|
||
|
* return -1 if failed.
|
||
|
*/
|
||
|
int serial_receive(int file_descriptor, char *buffer,size_t data_len)
|
||
|
{
|
||
|
int len,fs_sel;
|
||
|
fd_set fs_read;
|
||
|
struct timeval time;
|
||
|
int fd = file_descriptor;
|
||
|
|
||
|
FD_ZERO(&fs_read);
|
||
|
FD_SET(fd, &fs_read);
|
||
|
|
||
|
time.tv_sec = 10;
|
||
|
time.tv_usec = 0;
|
||
|
|
||
|
//use select() to achieve multiple channel communication
|
||
|
fs_sel = select(fd + 1, &fs_read, NULL, NULL, &time);
|
||
|
if ( fs_sel ) {
|
||
|
len = read(fd, buffer, data_len);
|
||
|
return len;
|
||
|
} else {
|
||
|
return -1;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|