#pragma once #include #include #include "gpio.h" #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 { volatile size_t head; volatile size_t tail; volatile uint8_t data[Size]; }; template struct Buffer { volatile size_t size; volatile 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; m_reading = true; const size_t newTail = (m_rxBuffer.tail + 1) % RX_BUFFER_SIZE; byte = m_rxBuffer.data[newTail]; m_rxBuffer.tail = newTail; m_reading = false; return true; } [[gnu::always_inline]] static void txByte(const uint8_t& byte) { if(m_txBuffer.size == TX_BUFFER_SIZE) { flushTx(); } m_txBuffer.data[m_txBuffer.size++] = byte; } [[gnu::always_inline]] static bool peek(uint8_t& byte) { if(m_rxBuffer.head == m_rxBuffer.tail) return false; m_reading = true; const size_t newTail = (m_rxBuffer.tail + 1) % RX_BUFFER_SIZE; byte = m_rxBuffer.data[newTail]; m_reading = false; return true; } [[gnu::always_inline]] static void flushTx() { if(m_txBuffer.size == 0) return; constexpr auto usbReady = []() { USBD_CDC_HandleTypeDef* hcdc = static_cast(hUsbDeviceFS.pClassData); return hcdc->TxState != 0; }; #ifdef INFO_LEDS HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(BLUE_LED_GPIO_Port, BLUE_LED_Pin, GPIO_PIN_RESET); #endif while(usbReady()) ; std::memcpy(const_cast(m_usbAsyncTxBuffer.data), const_cast(m_txBuffer.data), m_txBuffer.size); m_usbAsyncTxBuffer.size = m_txBuffer.size; m_txBuffer.size = 0; USBD_CDC_SetTxBuffer(&hUsbDeviceFS, const_cast(m_usbAsyncTxBuffer.data), m_usbAsyncTxBuffer.size); USBD_CDC_TransmitPacket(&hUsbDeviceFS); #ifdef INFO_LEDS HAL_GPIO_WritePin(BLUE_LED_GPIO_Port, BLUE_LED_Pin, GPIO_PIN_SET); #endif } private: static constexpr auto TX_BUFFER_SIZE = CDC_DATA_FS_OUT_PACKET_SIZE; static constexpr auto RX_BUFFER_SIZE = CDC_DATA_FS_IN_PACKET_SIZE; static Buffer m_txBuffer; static Buffer m_usbAsyncTxBuffer; static RingBuffer m_rxBuffer; static Buffer m_usbAsyncRxBuffer; static volatile bool m_reading; static int8_t CdcInit() { #ifdef INFO_LEDS HAL_GPIO_WritePin(RED_LED_GPIO_Port, RED_LED_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(BLUE_LED_GPIO_Port, BLUE_LED_Pin, GPIO_PIN_SET); #endif 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() { #ifdef INFO_LEDS HAL_GPIO_WritePin(RED_LED_GPIO_Port, RED_LED_Pin, GPIO_PIN_RESET); HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(BLUE_LED_GPIO_Port, BLUE_LED_Pin, GPIO_PIN_SET); #endif m_txBuffer.size = 0; m_usbAsyncTxBuffer.size = 0; m_rxBuffer.head = 0; m_rxBuffer.tail = 0; m_usbAsyncRxBuffer.size = 0; m_reading = false; 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) { #ifdef INFO_LEDS HAL_GPIO_WritePin(GREEN_LED_GPIO_Port, GREEN_LED_Pin, GPIO_PIN_SET); HAL_GPIO_WritePin(RED_LED_GPIO_Port, RED_LED_Pin, GPIO_PIN_RESET); #endif if(USBD_CDC_ReceivePacket(&hUsbDeviceFS) != USBD_OK) return USBD_FAIL; for(uint32_t i = 0; i < *length; ++i) { rxHandler(m_usbAsyncRxBuffer.data[i]); } #ifdef INFO_LEDS HAL_GPIO_WritePin(RED_LED_GPIO_Port, RED_LED_Pin, GPIO_PIN_SET); #endif return USBD_OK; } [[gnu::always_inline]] static inline void rxHandler(const volatile uint8_t& data) { const size_t newHead = (m_rxBuffer.head + 1) % RX_BUFFER_SIZE; // Overflow, but tail is being read if(newHead == m_rxBuffer.tail && m_reading) { // Throw away the data, because it cannot be received safely return; } // Overflow, overwrite oldest data else if(newHead == m_rxBuffer.tail) { const size_t newTail = (m_rxBuffer.tail + 1) % RX_BUFFER_SIZE; m_rxBuffer.tail = newTail; } m_rxBuffer.data[newHead] = data; m_rxBuffer.head = newHead; } }; template Buffer::TX_BUFFER_SIZE> VirtualComPort::m_txBuffer = {0, {0}}; template Buffer::TX_BUFFER_SIZE> VirtualComPort::m_usbAsyncTxBuffer = {0, {0}}; template RingBuffer::RX_BUFFER_SIZE> VirtualComPort::m_rxBuffer = {0, 0, {0}}; template Buffer::RX_BUFFER_SIZE> VirtualComPort::m_usbAsyncRxBuffer = {0, {0}}; template volatile bool VirtualComPort::m_reading = false; } // namespace uart::detail