support Windows in Serial.h|cpp

The serial code was Linux/Mac only and utilised termio/tcsetattr
functions that were not available on Windows platforms. This patch
ports the serial code to the Windows APIs using the same approach
as the Computrainer.cpp code.  It allows us to support old-style
serial download cables on Windows.  (We already supported them on
Mac and Linux.)
This commit is contained in:
Mark Liversedge
2010-01-09 13:21:59 +00:00
committed by Sean Rhea
parent 9920afdb4d
commit a89ebacac0
3 changed files with 196 additions and 3 deletions

View File

@@ -17,6 +17,15 @@
*/
#include "Serial.h"
#ifdef Q_OS_WIN32
// WIN32 includes
#include <windows.h>
#include <winbase.h>
#else
// Linux and Mac Includes
#include <assert.h>
#include <dirent.h>
#include <errno.h>
@@ -32,22 +41,39 @@
#include <sys/time.h>
#include <termios.h>
#include <unistd.h>
#endif
bool SerialRegistered = CommPort::addListFunction(&Serial::myListCommPorts);
#ifdef Q_OS_WIN32
Serial::Serial(const QString &path) : path(path), isOpen(false)
{
}
#else
Serial::Serial(const QString &path) : path(path), fd(-1)
{
}
#endif
Serial::~Serial()
{
if (fd >= 0)
#ifdef Q_OS_WIN32
if (isOpen == true) {
close();
}
#else
if (fd >= 0) close();
#endif
}
bool
Serial::open(QString &err)
{
#ifndef Q_OS_WIN32
//
// Linux and Mac OSX use stdio / termio / tcsetattr
//
assert(fd < 0);
fd = ::open(path.toAscii().constData(), O_RDWR | O_NOCTTY | O_NONBLOCK);
if (fd < 0) {
@@ -83,16 +109,81 @@ Serial::open(QString &err)
assert(0);
}
return true;
#else
//
// Windows uses CreateFile / DCB / SetCommState
//
DCB deviceSettings; // serial port settings baud rate et al
COMMTIMEOUTS timeouts; // timeout settings on serial ports
// if deviceFilename references a port above COM9
// then we need to open "\\.\COMX" not "COMX"
QString portSpec = "\\\\.\\" + path;
wchar_t deviceFilenameW[32]; // \\.\COM32 needs 9 characters, 32 should be enough?
MultiByteToWideChar(CP_ACP, 0, portSpec.toAscii(), -1, (LPWSTR)deviceFilenameW,
sizeof(deviceFilenameW));
// win32 commport API
fd = CreateFile (deviceFilenameW, GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_DELETE|FILE_SHARE_WRITE|FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (fd == INVALID_HANDLE_VALUE) return isOpen = false;
if (GetCommState (fd, &deviceSettings) == false) return isOpen = false;
// so we've opened the comm port lets set it up for
deviceSettings.BaudRate = CBR_9600;
deviceSettings.fParity = NOPARITY;
deviceSettings.ByteSize = 8;
deviceSettings.StopBits = ONESTOPBIT;
deviceSettings.EofChar = 0x0;
deviceSettings.ErrorChar = 0x0;
deviceSettings.EvtChar = 0x0;
deviceSettings.fBinary = TRUE;
deviceSettings.fRtsControl = 0x0;
deviceSettings.fOutxCtsFlow = FALSE;
if (SetCommState(fd, &deviceSettings) == false) {
CloseHandle(fd);
return isOpen = false;
}
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutConstant = 5000;
timeouts.ReadTotalTimeoutMultiplier = 50;
timeouts.WriteTotalTimeoutConstant = 5000;
timeouts.WriteTotalTimeoutMultiplier = 0;
SetCommTimeouts(fd, &timeouts);
return isOpen = true;
#endif
}
void
Serial::close()
{
#ifndef Q_OS_WIN32
assert(fd >= 0);
::close(fd);
fd = -1;
#else
if (isOpen == true) {
CloseHandle(fd);
isOpen = false;
}
#endif
}
#ifndef Q_OS_WIN32
//
// On Mac/Linux read/write timeout periods
// use timeval, whilst on Win32 the readfile
// API does this for you.
//
static struct timeval &
operator-=(struct timeval &left, const struct timeval &right)
{
@@ -114,10 +205,16 @@ operator<(struct timeval &left, const struct timeval &right)
return false;
return left.tv_usec < right.tv_usec;
}
#endif
int
Serial::read(void *buf, size_t nbyte, QString &err)
{
#ifndef Q_OS_WIN32
//
// Mac and Linux use select and timevals to
// do non-blocking reads with a timeout
//
assert(fd >= 0);
assert(nbyte > 0);
struct timeval start;
@@ -152,11 +249,27 @@ Serial::read(void *buf, size_t nbyte, QString &err)
assert(false);
}
return count;
#else
//
// Win32 API uses readfile
// which handles timeouts / ready read
//
DWORD cBytes;
int rc = ReadFile(fd, buf, nbyte, &cBytes, NULL);
if (rc) return (int)cBytes;
else return (-1);
#endif
}
int
Serial::write(void *buf, size_t nbyte, QString &err)
{
#ifndef Q_OS_WIN32
//
// Max and Linux use select() and write()
//
assert(fd >= 0);
fd_set fdset;
FD_ZERO(&fdset);
@@ -177,6 +290,20 @@ Serial::write(void *buf, size_t nbyte, QString &err)
if (result < 0)
err = QString("write: ") + strerror(errno);
return result;
#else
//
// Windows use the writefile WIN32 API
//
DWORD cBytes;
int rc = WriteFile(fd, buf, nbyte, &cBytes, NULL);
if (!rc) {
err = QString("write: error %1").arg(rc);
return -1;
} else {
return (int)cBytes; // how much was written?
}
#endif
}
QString
@@ -185,6 +312,10 @@ Serial::name() const
return QString("Serial: ") + path;
}
#ifndef Q_OS_WIN32
//
// Linux and Mac device enumerator matches wildcards in /dev
//
static int
find_devices(char *result[], int capacity)
{
@@ -207,6 +338,57 @@ find_devices(char *result[], int capacity)
}
return count;
}
#else
//
// Windows uses WIN32 API to open file to check existence
//
static int
find_devices(char *result[], int capacity)
{
// it is valid to have up to 255 com devices!
int count=0;
for (int i=1; i<256 && count < capacity; i++) {
// longCOM is in form "\\.\COMn" which is used by the API
// shortCOM is in form COMn which is familiar to users
QString shortCOM = QString("COM%1").arg(i);
QString longCOM = "\\\\.\\" + shortCOM;
wchar_t deviceFilenameW[32];
MultiByteToWideChar(CP_ACP, 0, longCOM.toAscii(), -1, (LPWSTR)deviceFilenameW,
sizeof(deviceFilenameW));
// Try to open the port
BOOL bSuccess = FALSE;
HANDLE hPort = CreateFile(deviceFilenameW, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
if (hPort == INVALID_HANDLE_VALUE) {
DWORD dwError = GetLastError();
// Check to see if the error was because some other app had the port open
// or a general and valid failure
if (dwError == ERROR_ACCESS_DENIED || dwError == ERROR_GEN_FAILURE ||
dwError == ERROR_SHARING_VIOLATION || dwError == ERROR_SEM_TIMEOUT)
bSuccess = TRUE;
} else {
// The port was opened successfully
bSuccess = TRUE;
// Don't forget to close the port, since we are going to do nothing with it anyway
CloseHandle(hPort);
}
// If success then add to the list
if (bSuccess) {
result[count] = (char*) malloc(shortCOM.length() + 1); // include '\0' terminator
strcpy(result[count], shortCOM.toLatin1().constData());
count++;
}
}
return count;
}
#endif
QVector<CommPortPtr>
Serial::myListCommPorts(QString &err)