253 lines
8.1 KiB
C++
253 lines
8.1 KiB
C++
#pragma once
|
|
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
|
|
#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<size_t Size>
|
|
struct RingBuffer {
|
|
volatile size_t head;
|
|
volatile size_t tail;
|
|
volatile uint8_t data[Size];
|
|
};
|
|
|
|
template<size_t Size>
|
|
struct Buffer {
|
|
volatile size_t size;
|
|
volatile uint8_t data[Size];
|
|
};
|
|
|
|
template<class cfg>
|
|
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<USBD_CDC_HandleTypeDef*>(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<uint8_t*>(m_usbAsyncTxBuffer.data), const_cast<const uint8_t*>(m_txBuffer.data), m_txBuffer.size);
|
|
m_usbAsyncTxBuffer.size = m_txBuffer.size;
|
|
m_txBuffer.size = 0;
|
|
|
|
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, const_cast<uint8_t*>(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<TX_BUFFER_SIZE> m_txBuffer;
|
|
static Buffer<TX_BUFFER_SIZE> m_usbAsyncTxBuffer;
|
|
|
|
static RingBuffer<RX_BUFFER_SIZE> m_rxBuffer;
|
|
static Buffer<RX_BUFFER_SIZE> 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<uint8_t*>(m_txBuffer.data), 0);
|
|
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, const_cast<uint8_t*>(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<class cfg>
|
|
Buffer<VirtualComPort<cfg>::TX_BUFFER_SIZE> VirtualComPort<cfg>::m_txBuffer = {0, {0}};
|
|
|
|
template<class cfg>
|
|
Buffer<VirtualComPort<cfg>::TX_BUFFER_SIZE> VirtualComPort<cfg>::m_usbAsyncTxBuffer = {0, {0}};
|
|
|
|
template<class cfg>
|
|
RingBuffer<VirtualComPort<cfg>::RX_BUFFER_SIZE> VirtualComPort<cfg>::m_rxBuffer = {0, 0, {0}};
|
|
|
|
template<class cfg>
|
|
Buffer<VirtualComPort<cfg>::RX_BUFFER_SIZE> VirtualComPort<cfg>::m_usbAsyncRxBuffer = {0, {0}};
|
|
|
|
template<class cfg>
|
|
volatile bool VirtualComPort<cfg>::m_reading = false;
|
|
|
|
} // namespace uart::detail
|