libdivecomputer/serial_win32.c
2007-10-29 09:22:19 +00:00

487 lines
9.9 KiB
C

#include <stddef.h>
#include <windows.h>
#include "serial.h"
#include <stdio.h>
#define TRACE(expr) \
{ \
DWORD error = GetLastError (); \
fprintf (stderr, "TRACE %s:%d: ", __FILE__, __LINE__); \
if (expr) \
fprintf (stderr, "%s: %s\n", expr, serial_errmsg ()); \
else \
fprintf (stderr, "%s\n", serial_errmsg ()); \
SetLastError (error); \
}
struct serial {
/*
* The file descriptor corresponding to the serial port.
*/
HANDLE hFile;
/*
* Serial port settings are saved into this variables immediately
* after the port is opened. These settings are restored when the
* serial port is closed.
*/
DCB dcb;
COMMTIMEOUTS timeouts;
};
//
// Error reporting.
//
int serial_errcode ()
{
return GetLastError ();
}
const char* serial_errmsg ()
{
static char buffer[256] = {0};
unsigned int size = sizeof (buffer) / sizeof (char);
DWORD errcode = GetLastError ();
DWORD rc = FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
NULL, errcode, 0, buffer, size, NULL);
// Remove certain characters ('\r', '\n' and '.')
// at the end of the error message.
while (rc > 0 && (
buffer[rc-1] == '\n' ||
buffer[rc-1] == '\r' ||
buffer[rc-1] == '.')) {
buffer[rc-1] = '\0';
rc--;
}
if (rc) {
return buffer;
} else {
return NULL;
}
}
//
// Open the serial port.
//
int
serial_open (serial** out, const char* name)
{
if (out == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
// Allocate memory.
struct serial *device = malloc (sizeof (struct serial));
if (device == NULL) {
TRACE ("malloc");
return -1; // ERROR_OUTOFMEMORY (Not enough storage is available to complete this operation)
}
// Open the device.
device->hFile = CreateFile (name,
GENERIC_READ | GENERIC_WRITE, 0,
NULL, // No security attributes.
OPEN_EXISTING,
0, // Non-overlapped I/O.
NULL);
if (device->hFile == INVALID_HANDLE_VALUE) {
TRACE ("CreateFile");
free (device);
return -1;
}
// Retrieve the current communication settings and timeouts,
// to be able to restore them when closing the device.
// It is also used to check if the obtained handle
// represents a serial device.
if (!GetCommState (device->hFile, &device->dcb) ||
!GetCommTimeouts (device->hFile, &device->timeouts)) {
TRACE ("GetCommState/GetCommTimeouts");
CloseHandle (device->hFile);
free (device);
return -1;
}
*out = device;
return 0;
}
//
// Close the serial port.
//
int
serial_close (serial* device)
{
if (device == NULL)
return 0;
// Restore the initial communication settings and timeouts.
if (!SetCommState (device->hFile, &device->dcb) ||
!SetCommTimeouts (device->hFile, &device->timeouts)) {
TRACE ("SetCommState/SetCommTimeouts");
CloseHandle (device->hFile);
free (device);
return -1;
}
// Close the device.
if (!CloseHandle (device->hFile)) {
TRACE ("CloseHandle");
free (device);
return -1;
}
// Free memory.
free (device);
return 0;
}
//
// Configure the serial port (baudrate, databits, parity, stopbits and flowcontrol).
//
int
serial_configure (serial *device, int baudrate, int databits, int parity, int stopbits, int flowcontrol)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
// Retrieve the current settings.
DCB dcb = {0};
if (!GetCommState (device->hFile, &dcb)) {
TRACE ("GetCommState");
return -1;
}
dcb.fBinary = TRUE; // Enable Binary Transmission
// Baudrate.
switch (baudrate) {
case 110: dcb.BaudRate = CBR_110; break;
case 300: dcb.BaudRate = CBR_300; break;
case 600: dcb.BaudRate = CBR_600; break;
case 1200: dcb.BaudRate = CBR_1200; break;
case 2400: dcb.BaudRate = CBR_2400; break;
case 4800: dcb.BaudRate = CBR_4800; break;
case 9600: dcb.BaudRate = CBR_9600; break;
case 14400: dcb.BaudRate = CBR_14400; break;
case 19200: dcb.BaudRate = CBR_19200; break;
case 38400: dcb.BaudRate = CBR_38400; break;
case 57600: dcb.BaudRate = CBR_57600; break;
case 115200: dcb.BaudRate = CBR_115200; break;
case 128000: dcb.BaudRate = CBR_128000; break;
case 256000: dcb.BaudRate = CBR_256000; break;
default:
return -1;
}
// Character size.
if (databits >= 5 && databits <= 8)
dcb.ByteSize = databits;
else
return -1;
// Parity checking.
switch (parity) {
case SERIAL_PARITY_NONE: // No parity
dcb.Parity = NOPARITY;
dcb.fParity = FALSE;
break;
case SERIAL_PARITY_EVEN: // Even parity
dcb.Parity = EVENPARITY;
dcb.fParity = TRUE;
break;
case SERIAL_PARITY_ODD: // Odd parity
dcb.Parity = ODDPARITY;
dcb.fParity = TRUE;
break;
default:
return -1;
}
// Stopbits.
switch (stopbits) {
case 1: // One stopbit
dcb.StopBits = ONESTOPBIT;
break;
case 2: // Two stopbits
dcb.StopBits = TWOSTOPBITS;
break;
default:
return -1;
}
// Flow control.
switch (flowcontrol) {
case SERIAL_FLOWCONTROL_NONE: // No flow control.
dcb.fInX = FALSE;
dcb.fOutX = FALSE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
break;
case SERIAL_FLOWCONTROL_HARDWARE: // Hardware (RTS/CTS) flow control.
dcb.fInX = FALSE;
dcb.fOutX = FALSE;
dcb.fOutxCtsFlow = TRUE;
dcb.fOutxDsrFlow = TRUE;
dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
break;
case SERIAL_FLOWCONTROL_SOFTWARE: // Software (XON/XOFF) flow control.
dcb.fInX = TRUE;
dcb.fOutX = TRUE;
dcb.fOutxCtsFlow = FALSE;
dcb.fOutxDsrFlow = FALSE;
dcb.fDtrControl = DTR_CONTROL_ENABLE;
dcb.fRtsControl = RTS_CONTROL_ENABLE;
break;
default:
return -1;
}
// Flush the input and output buffers.
if (!PurgeComm (device->hFile, PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR)) {
TRACE ("PurgeComm");
return -1;
}
// Apply the new settings.
if (!SetCommState (device->hFile, &dcb)) {
TRACE ("SetCommState");
return -1;
}
return 0;
}
//
// Configure the serial port (timeouts).
//
int
serial_set_timeout (serial* device, long timeout)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
// Retrieve the current timeouts.
COMMTIMEOUTS timeouts = {0};
if (!GetCommTimeouts (device->hFile, &timeouts)) {
TRACE ("GetCommTimeouts");
return -1;
}
// Update the settings.
if (timeout < 0) {
// Blocking mode.
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
} else if (timeout == 0) {
// Non-blocking mode.
timeouts.ReadIntervalTimeout = MAXDWORD;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = 0;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
} else {
// Standard timeout mode.
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutMultiplier = 0;
timeouts.ReadTotalTimeoutConstant = timeout;
timeouts.WriteTotalTimeoutMultiplier = 0;
timeouts.WriteTotalTimeoutConstant = 0;
}
// Activate the new timeouts.
if (!SetCommTimeouts (device->hFile, &timeouts)) {
TRACE ("SetCommTimeouts");
return -1;
}
return 0;
}
int
serial_read (serial* device, void* data, unsigned int size)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
DWORD dwRead = 0;
if (!ReadFile (device->hFile, data, size, &dwRead, NULL)) {
TRACE ("ReadFile");
return -1;
}
return dwRead;
}
int
serial_write (serial* device, const void* data, unsigned int size)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
DWORD dwWritten = 0;
if (!WriteFile (device->hFile, data, size, &dwWritten, NULL)) {
TRACE ("WriteFile");
return -1;
}
return dwWritten;
}
int
serial_flush (serial* device, int queue)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
DWORD flags = 0;
switch (queue) {
case SERIAL_QUEUE_INPUT:
flags = PURGE_RXABORT | PURGE_RXCLEAR;
case SERIAL_QUEUE_OUTPUT:
flags = PURGE_TXABORT | PURGE_TXCLEAR;
default:
flags = PURGE_RXABORT | PURGE_RXCLEAR | PURGE_TXABORT | PURGE_TXCLEAR;
}
if (!PurgeComm (device->hFile, flags)) {
TRACE ("PurgeComm");
return -1;
}
return 0;
}
int
serial_drain (serial* device)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
return 0;
}
int
serial_send_break (serial* device)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
if (!SetCommBreak (device->hFile)) {
TRACE ("SetCommBreak");
return -1;
}
Sleep (250);
if (!ClearCommBreak (device->hFile)) {
TRACE ("ClearCommBreak");
return -1;
}
return 0;
}
int
serial_set_dtr (serial* device, int level)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
int status = (level ? SETDTR : CLRDTR);
if (!EscapeCommFunction (device->hFile, status)) {
TRACE ("EscapeCommFunction");
return -1;
}
return 0;
}
int
serial_set_rts (serial* device, int level)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
int status = (level ? SETRTS : CLRRTS);
if (!EscapeCommFunction (device->hFile, status)) {
TRACE ("EscapeCommFunction");
return -1;
}
return 0;
}
int
serial_get_received (serial* device)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
COMSTAT stats = {0};
if (!ClearCommError (device->hFile, NULL, &stats)) {
TRACE ("ClearCommError");
return -1;
}
return stats.cbInQue;
}
int
serial_get_transmitted (serial* device)
{
if (device == NULL)
return -1; // ERROR_INVALID_PARAMETER (The parameter is incorrect)
COMSTAT stats = {0};
if (!ClearCommError (device->hFile, NULL, &stats)) {
TRACE ("ClearCommError");
return -1;
}
return stats.cbOutQue;
}
int
serial_sleep (unsigned long timeout)
{
Sleep (timeout);
return 0;
}
int
serial_timer ()
{
return GetTickCount ();
}