1
\$\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

  1. Pimpl Idiom
  2. Exception Handling
  3. RAII (close handle on destruction)
  4. Constructor defaults

Any comments/improvements are welcome.

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();
    }
}
\$\endgroup\$

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Browse other questions tagged or ask your own question.