dl-fldigi/src/rigcontrol/serial.cxx

690 wiersze
18 KiB
C++

// ----------------------------------------------------------------------------
// serial.cxx - Serial I/O class
//
// Copyright (C) 2007-2010
// Dave Freese, W1HKJ
//
// This file is part of fldigi.
//
// Fldigi is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Fldigi is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with fldigi. If not, see <http://www.gnu.org/licenses/>.
// ----------------------------------------------------------------------------
#include <config.h>
#include <string>
#include "serial.h"
#include "debug.h"
LOG_FILE_SOURCE(debug::LOG_RIGCONTROL);
#ifndef __MINGW32__
#include <cstdio>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <memory>
using namespace std;
Cserial::Cserial() {
device = "/dev/ttyS0";
baud = 1200;
timeout = 50; //msec
retries = 5;
rts = dtr = false;
rtsptt = dtrptt = false;
rtscts = false;
status = 0;
stopbits = 2;
fd = -1;
}
Cserial::~Cserial() {
ClosePort();
}
///////////////////////////////////////////////////////
// Function name : Cserial::OpenPort
// Description : Opens the port specified by strPortName
// Return type : BOOL
// Argument : c_string strPortName
///////////////////////////////////////////////////////
bool Cserial::OpenPort() {
#ifdef __CYGWIN__
com_to_tty(device);
#endif
int oflags = O_RDWR | O_NOCTTY | O_NDELAY;
# ifdef HAVE_O_CLOEXEC
oflags = oflags | O_CLOEXEC;
# endif
if ((fd = open( device.c_str(), oflags)) < 0)
return false;
// save current port settings
tcflush (fd, TCIFLUSH);
tcgetattr (fd, &oldtio);
newtio = oldtio;
// 8 data bits
newtio.c_cflag &= ~CSIZE;
newtio.c_cflag |= CS8;
// enable receiver, set local mode
newtio.c_cflag |= (CLOCAL | CREAD);
// no parity
newtio.c_cflag &= ~PARENB;
if (stopbits == 1)
// 1 stop bit
newtio.c_cflag &= ~CSTOPB;
else
// 2 stop bit
newtio.c_cflag |= CSTOPB;
if (rtscts)
// h/w handshake
newtio.c_cflag |= CRTSCTS;
else
// no h/w handshake
newtio.c_cflag &= ~CRTSCTS;
// raw input
newtio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
// raw output
newtio.c_oflag &= ~OPOST;
// software flow control disabled
newtio.c_iflag &= ~IXON;
// do not translate CR to NL
newtio.c_iflag &= ~ICRNL;
switch(baud) {
case 300:
speed = B300;
break;
case 1200:
speed = B1200;
break;
case 2400:
speed = B2400;
break;
case 4800:
speed = B4800;
break;
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;
default:
speed = B1200;
}
cfsetispeed(&newtio, speed);
cfsetospeed(&newtio, speed);
tcsetattr (fd, TCSANOW, &newtio);
ioctl(fd, TIOCMGET, &status);
origstatus = status;
if (dtr)
status |= TIOCM_DTR; // set the DTR bit
else
status &= ~TIOCM_DTR; // clear the DTR bit
if (rtscts == false) { // rts OK for ptt if RTSCTS not used
if (rts)
status |= TIOCM_RTS; // set the RTS bit
else
status &= ~TIOCM_RTS; // clear the RTS bit
}
ioctl(fd, TIOCMSET, &status);
return true;
}
///////////////////////////////////////////////////////
// Function name : Cserial::setPTT
// Return type : void
///////////////////////////////////////////////////////
void Cserial::SetPTT(bool b)
{
if (fd < 0) {
LOG_DEBUG("PTT fd < 0");
return;
}
if (dtrptt || rtsptt) {
ioctl(fd, TIOCMGET, &status);
LOG_DEBUG("H/W PTT %d, status %X", b, status);
if (b == true) { // ptt enabled
if (dtrptt && dtr) status &= ~TIOCM_DTR; // toggle low
if (dtrptt && !dtr) status |= TIOCM_DTR; // toggle high
if (rtscts == false) {
if (rtsptt && rts) status &= ~TIOCM_RTS; // toggle low
if (rtsptt && !rts) status |= TIOCM_RTS; // toggle high
}
} else { // ptt disabled
if (dtrptt && dtr) status |= TIOCM_DTR; // toggle high
if (dtrptt && !dtr) status &= ~TIOCM_DTR; // toggle low
if (rtscts == false) {
if (rtsptt && rts) status |= TIOCM_RTS; // toggle high
if (rtsptt && !rts) status &= ~TIOCM_RTS; // toggle low
}
}
LOG_DEBUG("Status %02X, %s", status & 0xFF, uint2bin(status, 8));
ioctl(fd, TIOCMSET, &status);
}
LOG_DEBUG("No PTT specified");
}
///////////////////////////////////////////////////////
// Function name : Cserial::ClosePort
// Description : Closes the Port
// Return type : void
///////////////////////////////////////////////////////
void Cserial::ClosePort()
{
if (fd < 0) return;
LOG_DEBUG("Serial port closed, fd = %d", fd);
ioctl(fd, TIOCMSET, &origstatus);
tcsetattr (fd, TCSANOW, &oldtio);
close(fd);
fd = -1;
return;
}
bool Cserial::IOselect ()
{
fd_set rfds;
struct timeval tv;
int retval;
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
tv.tv_sec = timeout/1000;
tv.tv_usec = (timeout % 1000) * 1000;
retval = select (FD_SETSIZE, &rfds, (fd_set *)0, (fd_set *)0, &tv);
if (retval <= 0) // no response from serial port or error returned
return false;
return true;
}
///////////////////////////////////////////////////////
// Function name : Cserial::ReadBuffer
// Description : Reads upto nchars from the selected port
// Return type : # characters received
// Argument : pointer to buffer; # chars to read
///////////////////////////////////////////////////////
int Cserial::ReadBuffer (unsigned char *buf, int nchars)
{
if (fd < 0) return 0;
int retnum, nread = 0;
while (nchars > 0) {
if (!IOselect()) {
return nread;
}
retnum = read (fd, (char *)(buf + nread), nchars);
if (retnum < 0)
return 0;//nread;
if (retnum == 0)
return nread;
nread += retnum;
nchars -= retnum;
}
return nread;
}
///////////////////////////////////////////////////////
// Function name : Cserial::WriteBuffer
// Description : Writes a string to the selected port
// Return type : BOOL
// Argument : BYTE by
///////////////////////////////////////////////////////
int Cserial::WriteBuffer(unsigned char *buff, int n)
{
if (fd < 0) return 0;
int ret = write (fd, buff, n);
return ret;
}
///////////////////////////////////////////////////////
// Function name : Cserial::FlushBuffer
// Description : flushes the pending rx chars
// Return type : void
///////////////////////////////////////////////////////
void Cserial::FlushBuffer()
{
if (fd < 0)
return;
tcflush (fd, TCIFLUSH);
}
#else // __MINGW32__
using namespace std;
///////////////////////////////////////////////////////
// Function name : Cserial::OpenPort
// Description : Opens the port specified by strPortName
// Return type : BOOL
// Argument : CString strPortName
///////////////////////////////////////////////////////
BOOL Cserial::OpenPort()
{
string COMportname = "//./";
tty_to_com(device);
COMportname += device;
hComm = CreateFile(COMportname.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0);
if(hComm == INVALID_HANDLE_VALUE) {
LOG_ERROR("Invalid handle");
return FALSE;
}
if (!ConfigurePort( baud, 8, FALSE, NOPARITY, stopbits)) {
CloseHandle(hComm);
hComm = INVALID_HANDLE_VALUE;
LOG_ERROR("Could not configure port");
return FALSE;
}
FlushBuffer();
return TRUE;
}
///////////////////////////////////////////////////////
// Function name : Cserial::ClosePort
// Description : Closes the Port
// Return type : void
///////////////////////////////////////////////////////
void Cserial::ClosePort()
{
if (hComm) {
bPortReady = SetCommTimeouts (hComm, &CommTimeoutsSaved);
CloseHandle(hComm);
hComm = INVALID_HANDLE_VALUE;
}
return;
}
int Cserial::ReadData (unsigned char *buf, int nchars)
{
if (!hComm)
return FALSE;
DWORD dwRead = 0;
ReadFile(hComm, buf, nchars, &dwRead, NULL);
return (int) dwRead;
}
BOOL Cserial::ReadByte(unsigned char & by)
{
static BYTE byResByte[2];
if (ReadData(byResByte, 1) == 1) {
by = byResByte[0];
return true;
}
return false;
}
void Cserial::FlushBuffer()
{
unsigned char c;
while (ReadByte(c) == true);
}
///////////////////////////////////////////////////////
// Function name : Cserial::WriteByte
// Description : Writes a Byte to teh selected port
// Return type : BOOL
// Argument : BYTE by
///////////////////////////////////////////////////////
BOOL Cserial::WriteByte(UCHAR by)
{
if (!hComm)
return FALSE;
nBytesWritten = 0;
if (WriteFile(hComm,&by,1,&nBytesWritten,NULL)==0)
return FALSE;
return TRUE;
}
///////////////////////////////////////////////////////
// Function name : Cserial::WriteBuffer
// Description : Writes a string to the selected port
// Return type : BOOL
// Argument : BYTE by
///////////////////////////////////////////////////////
int Cserial::WriteBuffer(unsigned char *buff, int n)
{
if (!hComm)
return -1;
WriteFile (hComm, buff, n, &nBytesWritten, NULL);
if (!nBytesWritten) {
LOG_DEBUG("Reopening comm port");
ClosePort();
OpenPort();
WriteFile (hComm, buff, n, &nBytesWritten, NULL);
}
return nBytesWritten;
}
///////////////////////////////////////////////////////
// Function name : Cserial::SetCommunicationTimeouts
// Description : Sets the timeout for the selected port
// Return type : BOOL
// Argument : DWORD ReadIntervalTimeout
// Argument : DWORD ReadTotalTimeoutMultiplier
// Argument : DWORD ReadTotalTimeoutConstant
// Argument : DWORD WriteTotalTimeoutMultiplier
// Argument : DWORD WriteTotalTimeoutConstant
///////////////////////////////////////////////////////
BOOL Cserial::SetCommunicationTimeouts(
DWORD ReadIntervalTimeout, // msec
DWORD ReadTotalTimeoutMultiplier,
DWORD ReadTotalTimeoutConstant,
DWORD WriteTotalTimeoutMultiplier,
DWORD WriteTotalTimeoutConstant
)
{
CommTimeouts.ReadIntervalTimeout = ReadIntervalTimeout;
CommTimeouts.ReadTotalTimeoutMultiplier = ReadTotalTimeoutMultiplier;
CommTimeouts.ReadTotalTimeoutConstant = ReadTotalTimeoutConstant;
CommTimeouts.WriteTotalTimeoutConstant = WriteTotalTimeoutConstant;
CommTimeouts.WriteTotalTimeoutMultiplier = WriteTotalTimeoutMultiplier;
LOG_DEBUG("\n\
Read Interval Timeout............... %8ld %8ld\n\
Read Total Timeout Multiplier....... %8ld %8ld\n\
Read Total Timeout Constant Timeout. %8ld %8ld\n\
Write Total Timeout Constant........ %8ld %8ld\n\
Write Total Timeout Multiplier...... %8ld %8ld",
CommTimeoutsSaved.ReadIntervalTimeout,
CommTimeouts.ReadIntervalTimeout,
CommTimeoutsSaved.ReadTotalTimeoutMultiplier,
CommTimeouts.ReadTotalTimeoutMultiplier,
CommTimeoutsSaved.ReadTotalTimeoutConstant,
CommTimeouts.ReadTotalTimeoutConstant,
CommTimeoutsSaved.WriteTotalTimeoutConstant,
CommTimeouts.WriteTotalTimeoutConstant,
CommTimeoutsSaved.WriteTotalTimeoutMultiplier,
CommTimeouts.WriteTotalTimeoutMultiplier);
bPortReady = SetCommTimeouts (hComm, &CommTimeouts);
if(bPortReady ==0) {
CloseHandle(hComm);
hComm = INVALID_HANDLE_VALUE;
return FALSE;
}
return TRUE;
}
/*
* ReadIntervalTimeout
*
* The maximum time allowed to elapse between the arrival of two bytes on the
* communications line, in milliseconds. During a ReadFile operation, the time
* period begins when the first byte is received. If the interval between the
* arrival of any two bytes exceeds this amount, the ReadFile operation is
* completed and any buffered data is returned. A value of zero indicates that
* interval time-outs are not used.
*
* A value of MAXDWORD, combined with zero values for both the
* ReadTotalTimeoutConstant and ReadTotalTimeoutMultiplier members, specifies
* that the read operation is to return immediately with the bytes that have
* already been received, even if no bytes have been received.
*
* ReadTotalTimeoutMultiplier
*
* The multiplier used to calculate the total time-out period for read
* operations, in milliseconds. For each read operation, this value is
* multiplied by the requested number of bytes to be read.
*
* ReadTotalTimeoutConstant
*
* A constant used to calculate the total time-out period for read operations,
* in milliseconds. For each read operation, this value is added to the product
* of the ReadTotalTimeoutMultiplier member and the requested number of bytes.
*
* A value of zero for both the ReadTotalTimeoutMultiplier and
* ReadTotalTimeoutConstant members indicates that total time-outs are not
* used for read operations.
*
* WriteTotalTimeoutMultiplier
*
* The multiplier used to calculate the total time-out period for write
* operations, in milliseconds. For each write operation, this value is
* multiplied by the number of bytes to be written.
*
* WriteTotalTimeoutConstant
*
* A constant used to calculate the total time-out period for write operations,
* in milliseconds. For each write operation, this value is added to the product
* of the WriteTotalTimeoutMultiplier member and the number of bytes to be
* written.
*
* A value of zero for both the WriteTotalTimeoutMultiplier and
* WriteTotalTimeoutConstant members indicates that total time-outs are not
* used for write operations.
*
* Remarks
*
* If an application sets ReadIntervalTimeout and ReadTotalTimeoutMultiplier to
* MAXDWORD and sets ReadTotalTimeoutConstant to a value greater than zero and
* less than MAXDWORD, one of the following occurs when the ReadFile function
* is called:
*
* If there are any bytes in the input buffer, ReadFile returns immediately
* with the bytes in the buffer.
*
* If there are no bytes in the input buffer, ReadFile waits until a byte
* arrives and then returns immediately.
*
* If no bytes arrive within the time specified by ReadTotalTimeoutConstant,
* ReadFile times out.
*/
BOOL Cserial::SetCommTimeout() {
return SetCommunicationTimeouts (
MAXDWORD, // Read Interval Timeout
MAXDWORD, // Read Total Timeout Multiplier
10, // Read Total Timeout Constant
50, // Write Total Timeout Constant
5 // Write Total Timeout Multiplier
);
}
///////////////////////////////////////////////////////
// Function name : ConfigurePort
// Description : Configures the Port
// Return type : BOOL
// Argument : DWORD BaudRate
// Argument : BYTE ByteSize
// Argument : DWORD fParity
// Argument : BYTE Parity
// Argument : BYTE StopBits
///////////////////////////////////////////////////////
BOOL Cserial::ConfigurePort(DWORD BaudRate,
BYTE ByteSize,
DWORD dwParity,
BYTE Parity,
BYTE StopBits)
{
if((bPortReady = GetCommState(hComm, &dcb))==0) {
LOG_ERROR("GetCommState error on %s", device.c_str());
CloseHandle(hComm);
hComm = INVALID_HANDLE_VALUE;
return FALSE;
}
dcb.BaudRate = BaudRate;
dcb.ByteSize = ByteSize;
dcb.Parity = Parity ;
dcb.StopBits = (StopBits == 1 ? ONESTOPBIT : TWOSTOPBITS);
dcb.fBinary = TRUE;
dcb.fDsrSensitivity = FALSE;
dcb.fParity = dwParity;
dcb.fOutX = FALSE;
dcb.fInX = FALSE;
dcb.fNull = FALSE;
dcb.fAbortOnError = TRUE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
if (dtr)
dcb.fDtrControl = DTR_CONTROL_ENABLE;
else
dcb.fDtrControl = DTR_CONTROL_DISABLE;
dcb.fDsrSensitivity = FALSE;
if (rtscts)
dcb.fRtsControl = RTS_CONTROL_ENABLE;
else {
if (rts)
dcb.fRtsControl = RTS_CONTROL_ENABLE;
else
dcb.fRtsControl = RTS_CONTROL_DISABLE;
}
bPortReady = SetCommState(hComm, &dcb);
if(bPortReady == 0) {
CloseHandle(hComm);
hComm = INVALID_HANDLE_VALUE;
LOG_ERROR("Port not available");
return FALSE;
}
if ( (bPortReady = GetCommTimeouts (hComm, &CommTimeoutsSaved) ) == 0)
return FALSE;
return SetCommTimeout();
}
///////////////////////////////////////////////////////
// Function name : Cserial::setPTT
// Return type : void
///////////////////////////////////////////////////////
void Cserial::SetPTT(bool b)
{
if ( !(dtrptt || rtsptt) )
return;
if(hComm == INVALID_HANDLE_VALUE) {
LOG_ERROR("Invalid handle");
return;
}
LOG_DEBUG("PTT = %d, DTRptt = %d, DTR = %d, RTSptt = %d, RTS = %d",
b, dtrptt, dtr, rtsptt, rts);
if (b == true) { // ptt enabled
if (dtrptt && dtr)
dcb.fDtrControl = DTR_CONTROL_DISABLE;
if (dtrptt && !dtr)
dcb.fDtrControl = DTR_CONTROL_ENABLE;
if (rtscts == false) {
if (rtsptt && rts)
dcb.fRtsControl = RTS_CONTROL_DISABLE;
if (rtsptt && !rts)
dcb.fRtsControl = RTS_CONTROL_ENABLE;
}
} else { // ptt disabled
if (dtrptt && dtr)
dcb.fDtrControl = DTR_CONTROL_ENABLE;
if (dtrptt && !dtr)
dcb.fDtrControl = DTR_CONTROL_DISABLE;
if (rtscts == false) {
if (rtsptt && rts)
dcb.fRtsControl = RTS_CONTROL_ENABLE;
if (rtsptt && !rts)
dcb.fRtsControl = RTS_CONTROL_DISABLE;
}
}
SetCommState(hComm, &dcb);
}
#endif //__MINGW32__
#ifdef __WOE32__
#include <sstream>
#include "re.h"
// convert COMx to /dev/ttySy with y = x - 1
void com_to_tty(string& port)
{
re_t re("com([0-9]+)", REG_EXTENDED | REG_ICASE);
if (!(re.match(port.c_str()) && re.nsub() == 2))
return;
stringstream ss;
int n;
ss << re.submatch(1);
ss >> n;
if (--n < 0)
n = 0;
ss.clear(); ss.str("");
ss << "/dev/ttyS" << n;
ss.seekp(0);
port = ss.str();
}
// convert /dev/ttySx to COMy with y = x + 1
void tty_to_com(string& port)
{
re_t re("/dev/tty.([0-9]+)", REG_EXTENDED | REG_ICASE);
if (!(re.match(port.c_str()) && re.nsub() == 2))
return;
stringstream ss;
int n;
ss << re.submatch(1);
ss >> n;
ss.clear(); ss.str("");
ss << "COM" << n + 1;
ss.seekp(0);
port = ss.str();
}
#endif