Simple Serial Port Windows Interface
$begingroup$
I implemented a very simple blocking uart interface. Most of the serial instruments I interface with are master/slave and I have to wait on data to proceed forward. I am contemplating add some template code to accept any container iterator, but for now I will probably use array or vector. Also, I open the serial port immediately in the constructor and only close it in the destructor. If I don't keep it open some of the other calls will not perform their intended function.
Things I implemented that I'm working on understanding better
- Pimpl Idiom
- Exception Handling
- RAII
- Constructor defaults
SerialPort.h
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <chrono>
namespace Lite
{
class SerialPort
{
public:
enum class PARITY { NONE, ODD, EVEN, MARK, SPACE };
enum class STOPBITS { ONE, ONEPT5, TWO };
enum class FLOWCONTROL { SW, HW, NONE };
SerialPort(const std::string port = "COM1",
const uint32_t baudRate = 9600,
const PARITY parity = PARITY::NONE,
const STOPBITS stopBit = STOPBITS::ONE,
const FLOWCONTROL flowControl = FLOWCONTROL::NONE,
const uint8_t databits = 8);
~SerialPort();
SerialPort(const SerialPort&) = delete;
SerialPort(SerialPort&&) = delete;
SerialPort& operator=(const SerialPort&) = delete;
SerialPort& operator=(SerialPort&&) = delete;
size_t ReadBytes(uint8_t* data, const size_t bytesToRead);
size_t SendBytes(const uint8_t* data, const size_t bytesToSend);
// thinking of adding a container template version of read and send
void SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout);
void FlushRx();
void FlushTx();
size_t CurrentRxQueueSize();
size_t CurrentTxQueueSize();
size_t CurrentRxInQueue();
size_t CurrentTxInQueue();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
class SerialPortRuntimeException
: public std::runtime_error
{
public:
SerialPortRuntimeException(const char *message)
: std::runtime_error(message) {}
};
class SerialPortRangeError
: public std::range_error
{
public:
SerialPortRangeError(const char *message)
: std::range_error(message) {}
};
}
SerialPort.cpp
#if defined(_WIN32)
#include <Windows.h>
#include <CommCtrl.h>
#include <sstream>
#include <string>
#include <stdexcept>
#include "SerialPort.h"
struct Lite::SerialPort::Impl
{
BYTE ParityValue(const SerialPort::PARITY parity);
BYTE StopBitsValue(const SerialPort::STOPBITS stopBits);
DCB& FlowControlValue(const SerialPort::FLOWCONTROL, DCB& dcb);
std::string GetCommError();
HANDLE m_hCommPort;
};
std::string Lite::SerialPort::Impl::GetCommError()
{
LPSTR lpMsgBuf;
DWORD errCode = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errCode,
0, // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
std::stringstream temp;
temp << lpMsgBuf;
return temp.str();
}
Lite::SerialPort::SerialPort(const std::string name, const uint32_t baudRate,
const PARITY parity, const STOPBITS stopBit, const FLOWCONTROL flowControl,
const uint8_t databits)
: m_impl(std::make_unique <Impl>())
{
m_impl->m_hCommPort = CreateFile(name.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0
);
if(m_impl->m_hCommPort == INVALID_HANDLE_VALUE) {
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
}
DCB dcb;
dcb.DCBlength = sizeof(DCB);
if(!GetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
dcb.BaudRate = baudRate;
dcb.ByteSize = databits;
dcb.Parity = m_impl->ParityValue(parity);
dcb.StopBits = m_impl->StopBitsValue(stopBit);
m_impl->FlowControlValue(flowControl, dcb);
if (!SetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
Lite::SerialPort::~SerialPort()
{
CloseHandle(m_impl->m_hCommPort);
return;
}
size_t Lite::SerialPort::ReadBytes(uint8_t* data, const size_t bytesToRead)
{
auto tempBytesToRead = bytesToRead;
if (tempBytesToRead > MAXDWORD)
tempBytesToRead = MAXDWORD;
DWORD bytesRead;
if (!ReadFile(m_impl->m_hCommPort, &data,
tempBytesToRead, &bytesRead, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return bytesRead;
}
size_t Lite::SerialPort::SendBytes(const uint8_t* data, const size_t bytesToSend)
{
auto tempBytesToSend = bytesToSend;
if (tempBytesToSend > MAXDWORD)
tempBytesToSend = MAXDWORD;
DWORD sentSize;
if(!WriteFile(m_impl->m_hCommPort, &data,
tempBytesToSend, &sentSize, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return sentSize;
}
void Lite::SerialPort::SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout)
{
COMMTIMEOUTS to;
if (!GetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
to.ReadIntervalTimeout = readIntervalTimeout.count();
to.ReadTotalTimeoutMultiplier = 0;
to.ReadTotalTimeoutConstant = timeOut.count();
if(!SetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushRx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_RXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushTx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_TXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
size_t Lite::SerialPort::CurrentRxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentRxQueue;
}
size_t Lite::SerialPort::CurrentTxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentTxQueue;
}
size_t Lite::SerialPort::CurrentRxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cs.cbInQue;
}
size_t Lite::SerialPort::CurrentTxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw std::runtime_error(m_impl->GetCommError().c_str());
return cs.cbOutQue;
}
BYTE Lite::SerialPort::Impl::ParityValue(const SerialPort::PARITY parity)
{
BYTE temp;
switch (parity)
{
case SerialPort::PARITY::NONE:
temp = NOPARITY;
break;
case SerialPort::PARITY::ODD:
temp = ODDPARITY;
break;
case SerialPort::PARITY::EVEN:
temp = EVENPARITY;
break;
case SerialPort::PARITY::MARK:
temp = MARKPARITY;
break;
case SerialPort::PARITY::SPACE:
temp = SPACEPARITY;
break;
default:
throw SerialPortRangeError("Parity Enum out of Range");
break;
}
return temp;
}
BYTE Lite::SerialPort::Impl::StopBitsValue(const SerialPort::STOPBITS stopBits)
{
BYTE temp;
switch (stopBits)
{
case SerialPort::STOPBITS::ONE:
temp = ONESTOPBIT;
break;
case SerialPort::STOPBITS::ONEPT5:
temp = ONE5STOPBITS;
break;
case SerialPort::STOPBITS::TWO:
temp = TWOSTOPBITS;
break;
default:
throw SerialPortRangeError("Stop Bits Enum out of range");
break;
}
return temp;
}
DCB& Lite::SerialPort::Impl::FlowControlValue(const SerialPort::FLOWCONTROL flowcontrol,
DCB& dcb)
{
switch (flowcontrol)
{
case Lite::SerialPort::FLOWCONTROL::SW:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = true;
dcb.fInX = true;
break;
case Lite::SerialPort::FLOWCONTROL::HW:
dcb.fOutxCtsFlow = true;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fOutX = false;
dcb.fInX = false;
break;
case Lite::SerialPort::FLOWCONTROL::NONE:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = false;
dcb.fInX = false;
break;
default:
throw SerialPortRangeError("Flow Control Enum out of range");
break;
}
return dcb;
}
#endif
main.cpp
#include <iostream>
#include "SerialPort.h"
void HandleException()
{
try { throw; }
catch (const Lite::SerialPortRuntimeException &e)
{
std::cerr << e.what() << 'n'; // catches standard error message
}
catch (const Lite::SerialPortRangeError &e)
{
std::cerr << e.what() << 'n';; // catches standard error message
}
return;
}
int main()
{
try
{
Lite::SerialPort sp{ "COM5", 4800 };
sp.FlushRx();
sp.FlushTx();
std::cout << "rx queue size: " << sp.CurrentRxQueueSize() << 'n';
std::cout << "rx in queue size: " << sp.CurrentRxInQueue() << 'n';
sp.SetRxTimeouts(std::chrono::milliseconds(1000),
std::chrono::milliseconds(1000));
std::vector<uint8_t> writeBuf(100);
sp.SendBytes(writeBuf.data(), writeBuf.size());
std::vector<uint8_t> test(sp.CurrentRxQueueSize());
test.resize(sp.ReadBytes(test.data(), 100));
sp.FlushRx();
sp.FlushTx();
}
catch (...)
{
HandleException();
}
}
c++ object-oriented windows interface serial-port
$endgroup$
add a comment |
$begingroup$
I implemented a very simple blocking uart interface. Most of the serial instruments I interface with are master/slave and I have to wait on data to proceed forward. I am contemplating add some template code to accept any container iterator, but for now I will probably use array or vector. Also, I open the serial port immediately in the constructor and only close it in the destructor. If I don't keep it open some of the other calls will not perform their intended function.
Things I implemented that I'm working on understanding better
- Pimpl Idiom
- Exception Handling
- RAII
- Constructor defaults
SerialPort.h
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <chrono>
namespace Lite
{
class SerialPort
{
public:
enum class PARITY { NONE, ODD, EVEN, MARK, SPACE };
enum class STOPBITS { ONE, ONEPT5, TWO };
enum class FLOWCONTROL { SW, HW, NONE };
SerialPort(const std::string port = "COM1",
const uint32_t baudRate = 9600,
const PARITY parity = PARITY::NONE,
const STOPBITS stopBit = STOPBITS::ONE,
const FLOWCONTROL flowControl = FLOWCONTROL::NONE,
const uint8_t databits = 8);
~SerialPort();
SerialPort(const SerialPort&) = delete;
SerialPort(SerialPort&&) = delete;
SerialPort& operator=(const SerialPort&) = delete;
SerialPort& operator=(SerialPort&&) = delete;
size_t ReadBytes(uint8_t* data, const size_t bytesToRead);
size_t SendBytes(const uint8_t* data, const size_t bytesToSend);
// thinking of adding a container template version of read and send
void SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout);
void FlushRx();
void FlushTx();
size_t CurrentRxQueueSize();
size_t CurrentTxQueueSize();
size_t CurrentRxInQueue();
size_t CurrentTxInQueue();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
class SerialPortRuntimeException
: public std::runtime_error
{
public:
SerialPortRuntimeException(const char *message)
: std::runtime_error(message) {}
};
class SerialPortRangeError
: public std::range_error
{
public:
SerialPortRangeError(const char *message)
: std::range_error(message) {}
};
}
SerialPort.cpp
#if defined(_WIN32)
#include <Windows.h>
#include <CommCtrl.h>
#include <sstream>
#include <string>
#include <stdexcept>
#include "SerialPort.h"
struct Lite::SerialPort::Impl
{
BYTE ParityValue(const SerialPort::PARITY parity);
BYTE StopBitsValue(const SerialPort::STOPBITS stopBits);
DCB& FlowControlValue(const SerialPort::FLOWCONTROL, DCB& dcb);
std::string GetCommError();
HANDLE m_hCommPort;
};
std::string Lite::SerialPort::Impl::GetCommError()
{
LPSTR lpMsgBuf;
DWORD errCode = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errCode,
0, // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
std::stringstream temp;
temp << lpMsgBuf;
return temp.str();
}
Lite::SerialPort::SerialPort(const std::string name, const uint32_t baudRate,
const PARITY parity, const STOPBITS stopBit, const FLOWCONTROL flowControl,
const uint8_t databits)
: m_impl(std::make_unique <Impl>())
{
m_impl->m_hCommPort = CreateFile(name.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0
);
if(m_impl->m_hCommPort == INVALID_HANDLE_VALUE) {
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
}
DCB dcb;
dcb.DCBlength = sizeof(DCB);
if(!GetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
dcb.BaudRate = baudRate;
dcb.ByteSize = databits;
dcb.Parity = m_impl->ParityValue(parity);
dcb.StopBits = m_impl->StopBitsValue(stopBit);
m_impl->FlowControlValue(flowControl, dcb);
if (!SetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
Lite::SerialPort::~SerialPort()
{
CloseHandle(m_impl->m_hCommPort);
return;
}
size_t Lite::SerialPort::ReadBytes(uint8_t* data, const size_t bytesToRead)
{
auto tempBytesToRead = bytesToRead;
if (tempBytesToRead > MAXDWORD)
tempBytesToRead = MAXDWORD;
DWORD bytesRead;
if (!ReadFile(m_impl->m_hCommPort, &data,
tempBytesToRead, &bytesRead, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return bytesRead;
}
size_t Lite::SerialPort::SendBytes(const uint8_t* data, const size_t bytesToSend)
{
auto tempBytesToSend = bytesToSend;
if (tempBytesToSend > MAXDWORD)
tempBytesToSend = MAXDWORD;
DWORD sentSize;
if(!WriteFile(m_impl->m_hCommPort, &data,
tempBytesToSend, &sentSize, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return sentSize;
}
void Lite::SerialPort::SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout)
{
COMMTIMEOUTS to;
if (!GetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
to.ReadIntervalTimeout = readIntervalTimeout.count();
to.ReadTotalTimeoutMultiplier = 0;
to.ReadTotalTimeoutConstant = timeOut.count();
if(!SetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushRx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_RXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushTx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_TXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
size_t Lite::SerialPort::CurrentRxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentRxQueue;
}
size_t Lite::SerialPort::CurrentTxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentTxQueue;
}
size_t Lite::SerialPort::CurrentRxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cs.cbInQue;
}
size_t Lite::SerialPort::CurrentTxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw std::runtime_error(m_impl->GetCommError().c_str());
return cs.cbOutQue;
}
BYTE Lite::SerialPort::Impl::ParityValue(const SerialPort::PARITY parity)
{
BYTE temp;
switch (parity)
{
case SerialPort::PARITY::NONE:
temp = NOPARITY;
break;
case SerialPort::PARITY::ODD:
temp = ODDPARITY;
break;
case SerialPort::PARITY::EVEN:
temp = EVENPARITY;
break;
case SerialPort::PARITY::MARK:
temp = MARKPARITY;
break;
case SerialPort::PARITY::SPACE:
temp = SPACEPARITY;
break;
default:
throw SerialPortRangeError("Parity Enum out of Range");
break;
}
return temp;
}
BYTE Lite::SerialPort::Impl::StopBitsValue(const SerialPort::STOPBITS stopBits)
{
BYTE temp;
switch (stopBits)
{
case SerialPort::STOPBITS::ONE:
temp = ONESTOPBIT;
break;
case SerialPort::STOPBITS::ONEPT5:
temp = ONE5STOPBITS;
break;
case SerialPort::STOPBITS::TWO:
temp = TWOSTOPBITS;
break;
default:
throw SerialPortRangeError("Stop Bits Enum out of range");
break;
}
return temp;
}
DCB& Lite::SerialPort::Impl::FlowControlValue(const SerialPort::FLOWCONTROL flowcontrol,
DCB& dcb)
{
switch (flowcontrol)
{
case Lite::SerialPort::FLOWCONTROL::SW:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = true;
dcb.fInX = true;
break;
case Lite::SerialPort::FLOWCONTROL::HW:
dcb.fOutxCtsFlow = true;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fOutX = false;
dcb.fInX = false;
break;
case Lite::SerialPort::FLOWCONTROL::NONE:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = false;
dcb.fInX = false;
break;
default:
throw SerialPortRangeError("Flow Control Enum out of range");
break;
}
return dcb;
}
#endif
main.cpp
#include <iostream>
#include "SerialPort.h"
void HandleException()
{
try { throw; }
catch (const Lite::SerialPortRuntimeException &e)
{
std::cerr << e.what() << 'n'; // catches standard error message
}
catch (const Lite::SerialPortRangeError &e)
{
std::cerr << e.what() << 'n';; // catches standard error message
}
return;
}
int main()
{
try
{
Lite::SerialPort sp{ "COM5", 4800 };
sp.FlushRx();
sp.FlushTx();
std::cout << "rx queue size: " << sp.CurrentRxQueueSize() << 'n';
std::cout << "rx in queue size: " << sp.CurrentRxInQueue() << 'n';
sp.SetRxTimeouts(std::chrono::milliseconds(1000),
std::chrono::milliseconds(1000));
std::vector<uint8_t> writeBuf(100);
sp.SendBytes(writeBuf.data(), writeBuf.size());
std::vector<uint8_t> test(sp.CurrentRxQueueSize());
test.resize(sp.ReadBytes(test.data(), 100));
sp.FlushRx();
sp.FlushTx();
}
catch (...)
{
HandleException();
}
}
c++ object-oriented windows interface serial-port
$endgroup$
add a comment |
$begingroup$
I implemented a very simple blocking uart interface. Most of the serial instruments I interface with are master/slave and I have to wait on data to proceed forward. I am contemplating add some template code to accept any container iterator, but for now I will probably use array or vector. Also, I open the serial port immediately in the constructor and only close it in the destructor. If I don't keep it open some of the other calls will not perform their intended function.
Things I implemented that I'm working on understanding better
- Pimpl Idiom
- Exception Handling
- RAII
- Constructor defaults
SerialPort.h
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <chrono>
namespace Lite
{
class SerialPort
{
public:
enum class PARITY { NONE, ODD, EVEN, MARK, SPACE };
enum class STOPBITS { ONE, ONEPT5, TWO };
enum class FLOWCONTROL { SW, HW, NONE };
SerialPort(const std::string port = "COM1",
const uint32_t baudRate = 9600,
const PARITY parity = PARITY::NONE,
const STOPBITS stopBit = STOPBITS::ONE,
const FLOWCONTROL flowControl = FLOWCONTROL::NONE,
const uint8_t databits = 8);
~SerialPort();
SerialPort(const SerialPort&) = delete;
SerialPort(SerialPort&&) = delete;
SerialPort& operator=(const SerialPort&) = delete;
SerialPort& operator=(SerialPort&&) = delete;
size_t ReadBytes(uint8_t* data, const size_t bytesToRead);
size_t SendBytes(const uint8_t* data, const size_t bytesToSend);
// thinking of adding a container template version of read and send
void SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout);
void FlushRx();
void FlushTx();
size_t CurrentRxQueueSize();
size_t CurrentTxQueueSize();
size_t CurrentRxInQueue();
size_t CurrentTxInQueue();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
class SerialPortRuntimeException
: public std::runtime_error
{
public:
SerialPortRuntimeException(const char *message)
: std::runtime_error(message) {}
};
class SerialPortRangeError
: public std::range_error
{
public:
SerialPortRangeError(const char *message)
: std::range_error(message) {}
};
}
SerialPort.cpp
#if defined(_WIN32)
#include <Windows.h>
#include <CommCtrl.h>
#include <sstream>
#include <string>
#include <stdexcept>
#include "SerialPort.h"
struct Lite::SerialPort::Impl
{
BYTE ParityValue(const SerialPort::PARITY parity);
BYTE StopBitsValue(const SerialPort::STOPBITS stopBits);
DCB& FlowControlValue(const SerialPort::FLOWCONTROL, DCB& dcb);
std::string GetCommError();
HANDLE m_hCommPort;
};
std::string Lite::SerialPort::Impl::GetCommError()
{
LPSTR lpMsgBuf;
DWORD errCode = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errCode,
0, // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
std::stringstream temp;
temp << lpMsgBuf;
return temp.str();
}
Lite::SerialPort::SerialPort(const std::string name, const uint32_t baudRate,
const PARITY parity, const STOPBITS stopBit, const FLOWCONTROL flowControl,
const uint8_t databits)
: m_impl(std::make_unique <Impl>())
{
m_impl->m_hCommPort = CreateFile(name.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0
);
if(m_impl->m_hCommPort == INVALID_HANDLE_VALUE) {
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
}
DCB dcb;
dcb.DCBlength = sizeof(DCB);
if(!GetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
dcb.BaudRate = baudRate;
dcb.ByteSize = databits;
dcb.Parity = m_impl->ParityValue(parity);
dcb.StopBits = m_impl->StopBitsValue(stopBit);
m_impl->FlowControlValue(flowControl, dcb);
if (!SetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
Lite::SerialPort::~SerialPort()
{
CloseHandle(m_impl->m_hCommPort);
return;
}
size_t Lite::SerialPort::ReadBytes(uint8_t* data, const size_t bytesToRead)
{
auto tempBytesToRead = bytesToRead;
if (tempBytesToRead > MAXDWORD)
tempBytesToRead = MAXDWORD;
DWORD bytesRead;
if (!ReadFile(m_impl->m_hCommPort, &data,
tempBytesToRead, &bytesRead, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return bytesRead;
}
size_t Lite::SerialPort::SendBytes(const uint8_t* data, const size_t bytesToSend)
{
auto tempBytesToSend = bytesToSend;
if (tempBytesToSend > MAXDWORD)
tempBytesToSend = MAXDWORD;
DWORD sentSize;
if(!WriteFile(m_impl->m_hCommPort, &data,
tempBytesToSend, &sentSize, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return sentSize;
}
void Lite::SerialPort::SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout)
{
COMMTIMEOUTS to;
if (!GetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
to.ReadIntervalTimeout = readIntervalTimeout.count();
to.ReadTotalTimeoutMultiplier = 0;
to.ReadTotalTimeoutConstant = timeOut.count();
if(!SetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushRx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_RXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushTx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_TXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
size_t Lite::SerialPort::CurrentRxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentRxQueue;
}
size_t Lite::SerialPort::CurrentTxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentTxQueue;
}
size_t Lite::SerialPort::CurrentRxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cs.cbInQue;
}
size_t Lite::SerialPort::CurrentTxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw std::runtime_error(m_impl->GetCommError().c_str());
return cs.cbOutQue;
}
BYTE Lite::SerialPort::Impl::ParityValue(const SerialPort::PARITY parity)
{
BYTE temp;
switch (parity)
{
case SerialPort::PARITY::NONE:
temp = NOPARITY;
break;
case SerialPort::PARITY::ODD:
temp = ODDPARITY;
break;
case SerialPort::PARITY::EVEN:
temp = EVENPARITY;
break;
case SerialPort::PARITY::MARK:
temp = MARKPARITY;
break;
case SerialPort::PARITY::SPACE:
temp = SPACEPARITY;
break;
default:
throw SerialPortRangeError("Parity Enum out of Range");
break;
}
return temp;
}
BYTE Lite::SerialPort::Impl::StopBitsValue(const SerialPort::STOPBITS stopBits)
{
BYTE temp;
switch (stopBits)
{
case SerialPort::STOPBITS::ONE:
temp = ONESTOPBIT;
break;
case SerialPort::STOPBITS::ONEPT5:
temp = ONE5STOPBITS;
break;
case SerialPort::STOPBITS::TWO:
temp = TWOSTOPBITS;
break;
default:
throw SerialPortRangeError("Stop Bits Enum out of range");
break;
}
return temp;
}
DCB& Lite::SerialPort::Impl::FlowControlValue(const SerialPort::FLOWCONTROL flowcontrol,
DCB& dcb)
{
switch (flowcontrol)
{
case Lite::SerialPort::FLOWCONTROL::SW:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = true;
dcb.fInX = true;
break;
case Lite::SerialPort::FLOWCONTROL::HW:
dcb.fOutxCtsFlow = true;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fOutX = false;
dcb.fInX = false;
break;
case Lite::SerialPort::FLOWCONTROL::NONE:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = false;
dcb.fInX = false;
break;
default:
throw SerialPortRangeError("Flow Control Enum out of range");
break;
}
return dcb;
}
#endif
main.cpp
#include <iostream>
#include "SerialPort.h"
void HandleException()
{
try { throw; }
catch (const Lite::SerialPortRuntimeException &e)
{
std::cerr << e.what() << 'n'; // catches standard error message
}
catch (const Lite::SerialPortRangeError &e)
{
std::cerr << e.what() << 'n';; // catches standard error message
}
return;
}
int main()
{
try
{
Lite::SerialPort sp{ "COM5", 4800 };
sp.FlushRx();
sp.FlushTx();
std::cout << "rx queue size: " << sp.CurrentRxQueueSize() << 'n';
std::cout << "rx in queue size: " << sp.CurrentRxInQueue() << 'n';
sp.SetRxTimeouts(std::chrono::milliseconds(1000),
std::chrono::milliseconds(1000));
std::vector<uint8_t> writeBuf(100);
sp.SendBytes(writeBuf.data(), writeBuf.size());
std::vector<uint8_t> test(sp.CurrentRxQueueSize());
test.resize(sp.ReadBytes(test.data(), 100));
sp.FlushRx();
sp.FlushTx();
}
catch (...)
{
HandleException();
}
}
c++ object-oriented windows interface serial-port
$endgroup$
I implemented a very simple blocking uart interface. Most of the serial instruments I interface with are master/slave and I have to wait on data to proceed forward. I am contemplating add some template code to accept any container iterator, but for now I will probably use array or vector. Also, I open the serial port immediately in the constructor and only close it in the destructor. If I don't keep it open some of the other calls will not perform their intended function.
Things I implemented that I'm working on understanding better
- Pimpl Idiom
- Exception Handling
- RAII
- Constructor defaults
SerialPort.h
#pragma once
#include <memory>
#include <vector>
#include <string>
#include <chrono>
namespace Lite
{
class SerialPort
{
public:
enum class PARITY { NONE, ODD, EVEN, MARK, SPACE };
enum class STOPBITS { ONE, ONEPT5, TWO };
enum class FLOWCONTROL { SW, HW, NONE };
SerialPort(const std::string port = "COM1",
const uint32_t baudRate = 9600,
const PARITY parity = PARITY::NONE,
const STOPBITS stopBit = STOPBITS::ONE,
const FLOWCONTROL flowControl = FLOWCONTROL::NONE,
const uint8_t databits = 8);
~SerialPort();
SerialPort(const SerialPort&) = delete;
SerialPort(SerialPort&&) = delete;
SerialPort& operator=(const SerialPort&) = delete;
SerialPort& operator=(SerialPort&&) = delete;
size_t ReadBytes(uint8_t* data, const size_t bytesToRead);
size_t SendBytes(const uint8_t* data, const size_t bytesToSend);
// thinking of adding a container template version of read and send
void SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout);
void FlushRx();
void FlushTx();
size_t CurrentRxQueueSize();
size_t CurrentTxQueueSize();
size_t CurrentRxInQueue();
size_t CurrentTxInQueue();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
class SerialPortRuntimeException
: public std::runtime_error
{
public:
SerialPortRuntimeException(const char *message)
: std::runtime_error(message) {}
};
class SerialPortRangeError
: public std::range_error
{
public:
SerialPortRangeError(const char *message)
: std::range_error(message) {}
};
}
SerialPort.cpp
#if defined(_WIN32)
#include <Windows.h>
#include <CommCtrl.h>
#include <sstream>
#include <string>
#include <stdexcept>
#include "SerialPort.h"
struct Lite::SerialPort::Impl
{
BYTE ParityValue(const SerialPort::PARITY parity);
BYTE StopBitsValue(const SerialPort::STOPBITS stopBits);
DCB& FlowControlValue(const SerialPort::FLOWCONTROL, DCB& dcb);
std::string GetCommError();
HANDLE m_hCommPort;
};
std::string Lite::SerialPort::Impl::GetCommError()
{
LPSTR lpMsgBuf;
DWORD errCode = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM |
FORMAT_MESSAGE_IGNORE_INSERTS,
NULL,
errCode,
0, // Default language
(LPTSTR)&lpMsgBuf,
0,
NULL
);
std::stringstream temp;
temp << lpMsgBuf;
return temp.str();
}
Lite::SerialPort::SerialPort(const std::string name, const uint32_t baudRate,
const PARITY parity, const STOPBITS stopBit, const FLOWCONTROL flowControl,
const uint8_t databits)
: m_impl(std::make_unique <Impl>())
{
m_impl->m_hCommPort = CreateFile(name.c_str(),
GENERIC_READ | GENERIC_WRITE,
0,
0,
OPEN_EXISTING,
0,
0
);
if(m_impl->m_hCommPort == INVALID_HANDLE_VALUE) {
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
}
DCB dcb;
dcb.DCBlength = sizeof(DCB);
if(!GetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
dcb.BaudRate = baudRate;
dcb.ByteSize = databits;
dcb.Parity = m_impl->ParityValue(parity);
dcb.StopBits = m_impl->StopBitsValue(stopBit);
m_impl->FlowControlValue(flowControl, dcb);
if (!SetCommState(m_impl->m_hCommPort, &dcb))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
Lite::SerialPort::~SerialPort()
{
CloseHandle(m_impl->m_hCommPort);
return;
}
size_t Lite::SerialPort::ReadBytes(uint8_t* data, const size_t bytesToRead)
{
auto tempBytesToRead = bytesToRead;
if (tempBytesToRead > MAXDWORD)
tempBytesToRead = MAXDWORD;
DWORD bytesRead;
if (!ReadFile(m_impl->m_hCommPort, &data,
tempBytesToRead, &bytesRead, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return bytesRead;
}
size_t Lite::SerialPort::SendBytes(const uint8_t* data, const size_t bytesToSend)
{
auto tempBytesToSend = bytesToSend;
if (tempBytesToSend > MAXDWORD)
tempBytesToSend = MAXDWORD;
DWORD sentSize;
if(!WriteFile(m_impl->m_hCommPort, &data,
tempBytesToSend, &sentSize, NULL))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return sentSize;
}
void Lite::SerialPort::SetRxTimeouts(const std::chrono::milliseconds& timeOut,
const std::chrono::milliseconds& readIntervalTimeout)
{
COMMTIMEOUTS to;
if (!GetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
to.ReadIntervalTimeout = readIntervalTimeout.count();
to.ReadTotalTimeoutMultiplier = 0;
to.ReadTotalTimeoutConstant = timeOut.count();
if(!SetCommTimeouts(m_impl->m_hCommPort, &to))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushRx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_RXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
void Lite::SerialPort::FlushTx()
{
if (!PurgeComm(m_impl->m_hCommPort, PURGE_TXCLEAR))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return;
}
size_t Lite::SerialPort::CurrentRxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentRxQueue;
}
size_t Lite::SerialPort::CurrentTxQueueSize()
{
COMMPROP cp;
if (!GetCommProperties(m_impl->m_hCommPort, &cp))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cp.dwCurrentTxQueue;
}
size_t Lite::SerialPort::CurrentRxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw SerialPortRuntimeException(m_impl->GetCommError().c_str());
return cs.cbInQue;
}
size_t Lite::SerialPort::CurrentTxInQueue()
{
COMSTAT cs;
DWORD error;
if (!ClearCommError(m_impl->m_hCommPort, &error, &cs))
throw std::runtime_error(m_impl->GetCommError().c_str());
return cs.cbOutQue;
}
BYTE Lite::SerialPort::Impl::ParityValue(const SerialPort::PARITY parity)
{
BYTE temp;
switch (parity)
{
case SerialPort::PARITY::NONE:
temp = NOPARITY;
break;
case SerialPort::PARITY::ODD:
temp = ODDPARITY;
break;
case SerialPort::PARITY::EVEN:
temp = EVENPARITY;
break;
case SerialPort::PARITY::MARK:
temp = MARKPARITY;
break;
case SerialPort::PARITY::SPACE:
temp = SPACEPARITY;
break;
default:
throw SerialPortRangeError("Parity Enum out of Range");
break;
}
return temp;
}
BYTE Lite::SerialPort::Impl::StopBitsValue(const SerialPort::STOPBITS stopBits)
{
BYTE temp;
switch (stopBits)
{
case SerialPort::STOPBITS::ONE:
temp = ONESTOPBIT;
break;
case SerialPort::STOPBITS::ONEPT5:
temp = ONE5STOPBITS;
break;
case SerialPort::STOPBITS::TWO:
temp = TWOSTOPBITS;
break;
default:
throw SerialPortRangeError("Stop Bits Enum out of range");
break;
}
return temp;
}
DCB& Lite::SerialPort::Impl::FlowControlValue(const SerialPort::FLOWCONTROL flowcontrol,
DCB& dcb)
{
switch (flowcontrol)
{
case Lite::SerialPort::FLOWCONTROL::SW:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = true;
dcb.fInX = true;
break;
case Lite::SerialPort::FLOWCONTROL::HW:
dcb.fOutxCtsFlow = true;
dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
dcb.fOutX = false;
dcb.fInX = false;
break;
case Lite::SerialPort::FLOWCONTROL::NONE:
dcb.fOutxCtsFlow = false;
dcb.fRtsControl = RTS_CONTROL_DISABLE;
dcb.fOutX = false;
dcb.fInX = false;
break;
default:
throw SerialPortRangeError("Flow Control Enum out of range");
break;
}
return dcb;
}
#endif
main.cpp
#include <iostream>
#include "SerialPort.h"
void HandleException()
{
try { throw; }
catch (const Lite::SerialPortRuntimeException &e)
{
std::cerr << e.what() << 'n'; // catches standard error message
}
catch (const Lite::SerialPortRangeError &e)
{
std::cerr << e.what() << 'n';; // catches standard error message
}
return;
}
int main()
{
try
{
Lite::SerialPort sp{ "COM5", 4800 };
sp.FlushRx();
sp.FlushTx();
std::cout << "rx queue size: " << sp.CurrentRxQueueSize() << 'n';
std::cout << "rx in queue size: " << sp.CurrentRxInQueue() << 'n';
sp.SetRxTimeouts(std::chrono::milliseconds(1000),
std::chrono::milliseconds(1000));
std::vector<uint8_t> writeBuf(100);
sp.SendBytes(writeBuf.data(), writeBuf.size());
std::vector<uint8_t> test(sp.CurrentRxQueueSize());
test.resize(sp.ReadBytes(test.data(), 100));
sp.FlushRx();
sp.FlushTx();
}
catch (...)
{
HandleException();
}
}
c++ object-oriented windows interface serial-port
c++ object-oriented windows interface serial-port
edited 1 hour ago
Jamal♦
30.3k11116226
30.3k11116226
asked 1 hour ago
Eddie C.Eddie C.
124
124
add a comment |
add a comment |
0
active
oldest
votes
Your Answer
StackExchange.ifUsing("editor", function () {
return StackExchange.using("mathjaxEditing", function () {
StackExchange.MarkdownEditor.creationCallbacks.add(function (editor, postfix) {
StackExchange.mathjaxEditing.prepareWmdForMathJax(editor, postfix, [["\$", "\$"]]);
});
});
}, "mathjax-editing");
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "196"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: false,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: null,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211971%2fsimple-serial-port-windows-interface%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
0
active
oldest
votes
0
active
oldest
votes
active
oldest
votes
active
oldest
votes
Thanks for contributing an answer to Code Review Stack Exchange!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
Use MathJax to format equations. MathJax reference.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fcodereview.stackexchange.com%2fquestions%2f211971%2fsimple-serial-port-windows-interface%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown