#pragma once #include #include #include "usbd_cdc.h" #include "usbd_def.h" extern "C" { extern USBD_CDC_ItfTypeDef USBD_Interface_fops_FS; extern USBD_HandleTypeDef hUsbDeviceFS; } namespace uart::detail { template struct RingBuffer { size_t head; size_t tail; uint8_t data[Size]; }; template class VirtualComPort { public: static constexpr auto DATA_BITS = cfg::DATA_BITS; [[gnu::always_inline]] static void init() { USBD_Interface_fops_FS.Init = CdcInit; USBD_Interface_fops_FS.DeInit = CdcDeInit; USBD_Interface_fops_FS.Control = CdcControl; USBD_Interface_fops_FS.Receive = CdcReceive; } [[gnu::always_inline]] static bool rxByte(uint8_t& byte) { if(m_rxBuffer.head == m_rxBuffer.tail) return false; const size_t newTail = (m_rxBuffer.tail + 1) % RX_BUFFER_SIZE; byte = m_rxBuffer.data[newTail]; m_rxBuffer.tail = newTail; return true; } [[gnu::always_inline]] static void txByte(const uint8_t& byte) { const size_t newHead = (m_txBuffer.head + 1) % TX_BUFFER_SIZE; if(m_txBuffer.tail == newHead) { flushTx(); } m_txBuffer.data[newHead] = byte; m_txBuffer.head = newHead; } [[gnu::always_inline]] static bool peek(uint8_t& byte) { if(m_rxBuffer.head == m_rxBuffer.tail) return false; const size_t newTail = (m_rxBuffer.tail + 1) % RX_BUFFER_SIZE; byte = m_rxBuffer.data[newTail]; return true; } [[gnu::always_inline]] static void flushTx() { if(m_txBuffer.head == m_txBuffer.tail) { return; } constexpr auto usbReady = []() { USBD_CDC_HandleTypeDef* hcdc = static_cast(hUsbDeviceFS.pClassData); return hcdc->TxState != 0; }; constexpr auto txPacket = [usbReady](volatile uint8_t* buffer, size_t length) { USBD_CDC_SetTxBuffer(&hUsbDeviceFS, const_cast(buffer), length); USBD_CDC_TransmitPacket(&hUsbDeviceFS); while(!usbReady()) ; }; if(m_txBuffer.head > m_txBuffer.tail) { txPacket(&m_txBuffer.data[m_txBuffer.tail], m_txBuffer.head - m_txBuffer.tail); } else { txPacket(&m_txBuffer.data[m_txBuffer.tail], TX_BUFFER_SIZE - 1 - m_txBuffer.tail); txPacket(m_txBuffer.data, m_txBuffer.head + 1); } m_txBuffer.tail = m_txBuffer.head; } private: static constexpr auto TX_BUFFER_SIZE = 512; static constexpr auto RX_BUFFER_SIZE = 512; static volatile RingBuffer m_txBuffer; static volatile RingBuffer m_rxBuffer; static std::array m_usbAsyncRxBuffer; static int8_t CdcInit() { USBD_CDC_SetTxBuffer(&hUsbDeviceFS, const_cast(m_txBuffer.data), 0); USBD_CDC_SetRxBuffer(&hUsbDeviceFS, const_cast(m_usbAsyncRxBuffer.data())); return USBD_OK; } static int8_t CdcDeInit() { USBD_Interface_fops_FS.Init = nullptr; USBD_Interface_fops_FS.DeInit = nullptr; USBD_Interface_fops_FS.Control = nullptr; USBD_Interface_fops_FS.Receive = nullptr; return USBD_OK; } static int8_t CdcControl(uint8_t cmd, [[maybe_unused]] uint8_t* buf, [[maybe_unused]] uint16_t length) { switch(cmd) { case CDC_SEND_ENCAPSULATED_COMMAND: break; case CDC_GET_ENCAPSULATED_RESPONSE: break; case CDC_SET_COMM_FEATURE: break; case CDC_GET_COMM_FEATURE: break; case CDC_CLEAR_COMM_FEATURE: break; case CDC_SET_LINE_CODING: /*******************************************************************************/ /* Line Coding Structure */ /*-----------------------------------------------------------------------------*/ /* Offset | Field | Size | Value | Description */ /* 0 | dwDTERate | 4 | Number |Data terminal rate, in bits per second*/ /* 4 | bCharFormat | 1 | Number | Stop bits */ /* 0 - 1 Stop bit */ /* 1 - 1.5 Stop bits */ /* 2 - 2 Stop bits */ /* 5 | bParityType | 1 | Number | Parity */ /* 0 - None */ /* 1 - Odd */ /* 2 - Even */ /* 3 - Mark */ /* 4 - Space */ /* 6 | bDataBits | 1 | Number Data bits (5, 6, 7, 8 or 16). */ /*******************************************************************************/ break; case CDC_GET_LINE_CODING: break; case CDC_SET_CONTROL_LINE_STATE: break; case CDC_SEND_BREAK: break; default: break; } return USBD_OK; } static int8_t CdcReceive([[maybe_unused]] uint8_t* buf, uint32_t* length) { USBD_CDC_ReceivePacket(&hUsbDeviceFS); for(uint32_t i = 0; i < *length; ++i) { const auto byte = m_usbAsyncRxBuffer[i]; rxHandler(byte); } return USBD_OK; } [[gnu::always_inline]] static inline void rxHandler(const uint8_t& data) { const size_t newHead = (m_rxBuffer.head + 1) % RX_BUFFER_SIZE; if(newHead != m_rxBuffer.tail) { m_rxBuffer.data[newHead] = data; m_rxBuffer.head = newHead; } else { // TODO: Handle overflow } } }; template volatile RingBuffer::TX_BUFFER_SIZE> VirtualComPort::m_txBuffer = {0, 0, {0}}; template volatile RingBuffer::RX_BUFFER_SIZE> VirtualComPort::m_rxBuffer = {0, 0, {0}}; template std::array VirtualComPort::m_usbAsyncRxBuffer = {0}; } // namespace uart::detail