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 agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.