/*! \file * \brief Communication via the \ref Serial interface (RS-232) */ #pragma once #include "../types.h" /*! \brief Serial interface. * \ingroup io * * This class provides a serial interface (COM1 - COM4) for communication with * the outside world. * * The first IBM PC used the external chip [8250 * UART](https://de.wikipedia.org/wiki/NSC_8250), whereas, in today's systems, * this functionality is commonly integrated into the motherboard chipset, but * remained compatible. * * \see [PC8250A Data Sheet](uart-8250a.pdf#page=11) (Registers on page 11) * \see [PC16550D Data Sheet](uart-16550d.pdf#page=16) (Successor, for optional * FIFO buffer, page 16) */ class Serial { public: /*! \brief COM-Port * * The serial interface and its hardware addresses. Modern desktop PCs * have, at most, a single, physical COM-port (`COM1`) */ enum class ComPort : uint16_t { COM1 = 0x3f8, COM2 = 0x2f8, COM3 = 0x3e8, COM4 = 0x2e8, }; /*! \brief Transmission speed * * The unit Baud describes the transmission speed in number of symbols per * seconds. 1 Baud therefore equals the transmission of 1 symbol per second. * The possible Baud rates are whole-number dividers of the clock frequency * of 115200 Hz.. */ enum class BaudRate : uint16_t { B300 = 384, B600 = 192, B1200 = 96, B2400 = 48, B4800 = 24, B9600 = 12, B19200 = 6, B38400 = 3, B57600 = 2, B115200 = 1, }; /*! \brief Number of data bits per character */ enum class DataBits : uint8_t { D5 = 0, D6 = 1, D7 = 2, D8 = 3, }; /*! \brief Number of stop bits per character */ enum class StopBits : uint8_t { S1 = 0, S1_5 = 4, S2 = 4, }; /*! \brief parity bit */ enum class Parity : uint8_t { NONE = 0, ODD = 8, EVEN = 24, MARK = 40, SPACE = 56, }; private: /*! \brief register index */ enum class RegisterIndex : uint8_t { RECEIVE_BUFFER = 0, TRANSMIT_BUFFER = 0, INTERRUPT_ENABLE = 1, DIVISOR_LOW = 0, DIVISOR_HIGH = 1, INTERRUPT_IDENT = 2, FIFO_CONTROL = 2, LINE_CONTROL = 3, MODEM_CONTROL = 4, LINE_STATUS = 5, MODEM_STATUS = 6 }; /*! \brief Mask for the respective register */ enum class RegisterMask : uint8_t { RECEIVED_DATA_AVAILABLE = 1 << 0, TRANSMITTER_HOLDING_REGISTER_EMPTY = 1 << 1, RECEIVER_LINE_STATUS = 1 << 2, MODEM_STATUS = 1 << 3, // Interrupt Ident Register INTERRUPT_PENDING = 1 << 0, ///< 0 means interrupt pending INTERRUPT_ID_0 = 1 << 1, INTERRUPT_ID_1 = 1 << 2, // FIFO Control Register ENABLE_FIFO = 1 << 0, ///< 0 means disabled ^= conforming to 8250a CLEAR_RECEIVE_FIFO = 1 << 1, CLEAR_TRANSMIT_FIFO = 1 << 2, DMA_MODE_SELECT = 1 << 3, TRIGGER_RECEIVE = 1 << 6, // Line Control Register // bits per character: 5 6 7 8 WORD_LENGTH_SELECT_0 = 1 << 0, // Setting Select0: 0 1 0 1 WORD_LENGTH_SELECT_1 = 1 << 1, // Setting Select1: 0 0 1 1 NUMBER_OF_STOP_BITS = 1 << 2, // 0 ≙ one stop bit, 1 ≙ 1.5/2 stop bits PARITY_ENABLE = 1 << 3, EVEN_PARITY_SELECT = 1 << 4, STICK_PARITY = 1 << 5, SET_BREAK = 1 << 6, DIVISOR_LATCH_ACCESS_BIT = 1 << 7, // DLAB // Modem Control Register DATA_TERMINAL_READY = 1 << 0, REQUEST_TO_SEND = 1 << 1, OUT_1 = 1 << 2, OUT_2 = 1 << 3, // must be set for interrupts! LOOP = 1 << 4, // Line Status Register DATA_READY = 1 << 0, // Set when there is a value in the receive buffer OVERRUN_ERROR = 1 << 1, PARITY_ERROR = 1 << 2, FRAMING_ERROR = 1 << 3, BREAK_INTERRUPT = 1 << 4, TRANSMITTER_HOLDING_REGISTER = 1 << 5, TRANSMITTER_EMPTY = 1 << 6, // Send buffer empty (ready to send) // Modem Status Register DELTA_CLEAR_TO_SEND = 1 << 0, DELTA_DATA_SET_READY = 1 << 1, TRAILING_EDGE_RING_INDICATOR = 1 << 2, DELTA_DATA_CARRIER_DETECT = 1 << 3, CLEAR_TO_SEND = 1 << 4, DATA_SET_READY = 1 << 5, RING_INDICATOR = 1 << 6, DATA_CARRIER_DETECT = 1 << 7 }; /*! \brief Read value from register * * * \param reg Register index * \return The value read from register */ uint8_t readReg(RegisterIndex reg); /*! \brief Write value to register * * * \param reg Register index * \param out value to be written */ void writeReg(RegisterIndex reg, uint8_t out); protected: /*! \brief Selected COM port */ const ComPort port; public: /*! \brief Constructor * * Creates a Serial object that encapsulates the used COM port, as well as * the parameters used for the serial connection. Default values are `8N1` * (8 bit, no parity bit, one stop bit) with 115200 Baud using COM1. * */ explicit Serial(ComPort port = ComPort::COM1, BaudRate baud_rate = BaudRate::B115200, DataBits data_bits = DataBits::D8, StopBits stop_bits = StopBits::S1, Parity parity = Parity::NONE); /*! \brief Write one byte to the serial interface * * * \param out Byte to be written * \return Byte written (or `-1` if writing byte failed) */ int write(uint8_t out); };