Implemented templated vcp wrapper

This commit is contained in:
2020-06-29 00:33:14 +02:00
parent 7d75537427
commit 70e3d52dea
9 changed files with 752 additions and 341 deletions

View File

@@ -0,0 +1,158 @@
#pragma once
#include <cctype>
#include <cstdint>
#include <cstdlib>
#include <cstring>
namespace detail {
constexpr auto ENDL = "\r\n";
constexpr auto HELP_CMD = "help";
constexpr auto READ_CMD = "read";
constexpr auto VERSION_CMD = "version";
constexpr auto VERSION = "1.0";
static inline bool substringEquals(const char* str1, const char* str2, const size_t& size)
{
return (std::strncmp(str1, str2, size) == 0);
}
static inline bool stringEquals(const char* str1, const char* str2, const size_t& size)
{
if(size == std::strlen(str2)) {
return substringEquals(str1, str2, size);
}
return false;
}
} // namespace detail
template<class Uart>
class Terminal {
public:
static void init()
{
m_serial.init();
m_serial << detail::ENDL;
printVersion();
m_serial << detail::ENDL << "$ ";
}
static void callback()
{
if(receiveInput()) {
parseInput();
}
}
private:
static constexpr auto INPUT_BUFFER_SIZE = 128;
static constexpr auto BACKSPACE = uint8_t{0x7f};
static constexpr auto CTRL_C = uint8_t{0x03};
static Uart m_serial;
static char m_inputBuffer[INPUT_BUFFER_SIZE];
static uint16_t m_inputSize;
static bool receiveInput()
{
uint8_t inputByte;
while(m_serial.rxByte(inputByte)) {
if(std::isprint(inputByte) || inputByte == CTRL_C) {
m_inputBuffer[m_inputSize++] = inputByte;
// Handle Ctrl + C
if(inputByte == CTRL_C) {
m_serial << "^C" << detail::ENDL;
return true;
}
// Echo
else {
m_serial << static_cast<char>(inputByte);
}
}
// Handle backspace
if(inputByte == BACKSPACE && m_inputSize > 0) {
m_serial << "\b \b";
--m_inputSize;
}
// Handle line terminator
else if(inputByte == '\r' || inputByte == '\n') {
// Consume possible second line terminator
if(m_serial.peek(inputByte) && (inputByte == '\r' || inputByte == '\n')) {
m_serial.rxByte(inputByte);
}
m_serial << detail::ENDL;
return true;
}
if(m_inputSize >= INPUT_BUFFER_SIZE) {
m_serial << detail::ENDL << "WARNING: Terminal input buffer overflow!" << detail::ENDL;
return true;
}
}
return false;
}
static void parseInput()
{
if(m_inputSize) {
if(m_inputBuffer[m_inputSize - 1] == CTRL_C) {
handleCtrlC();
}
else {
if(detail::substringEquals(m_inputBuffer, detail::HELP_CMD, m_inputSize)) {
printHelp();
}
else if(detail::substringEquals(m_inputBuffer, detail::READ_CMD, m_inputSize)) {
readSensor();
}
else if(detail::substringEquals(m_inputBuffer, detail::VERSION_CMD, m_inputSize)) {
printVersion();
}
else {
printUnknown();
}
}
}
m_inputSize = 0;
m_serial << "$ ";
}
static void handleCtrlC() { m_serial << "Abort!" << detail::ENDL; }
static void printHelp()
{
m_serial << "AdaptiveBrightness command overview: " << detail::ENDL;
m_serial << detail::HELP_CMD << " .......: prints this help message" << detail::ENDL;
m_serial << detail::READ_CMD << " .......: reads and displays all LDR values" << detail::ENDL;
m_serial << detail::VERSION_CMD << " ....: displays firmware version" << detail::ENDL;
}
static void readSensor() { m_serial << "Sensor values: 1 2 3" << detail::ENDL; }
static void printVersion() { m_serial << "AdaptiveBrightness v" << detail::VERSION << detail::ENDL; }
static void printUnknown()
{
m_serial << "Unknown command \"";
for(uint16_t i = 0; i < m_inputSize; ++i) {
m_serial << static_cast<char>(m_inputBuffer[i]);
}
m_serial << "\"" << detail::ENDL;
}
};
template<class Uart>
char Terminal<Uart>::m_inputBuffer[INPUT_BUFFER_SIZE];
template<class Uart>
uint16_t Terminal<Uart>::m_inputSize = 0;

View File

@@ -0,0 +1,329 @@
#pragma once
#include <limits>
#include <type_traits>
#include <cstdint>
#include "uart_config.hpp"
#include "uart_vcp.hpp"
namespace uart {
namespace detail {
template<typename...>
struct always_false : std::false_type {
};
template<typename... Ts>
inline constexpr auto always_false_v = always_false<Ts...>::value;
template<typename T, T Limit, size_t Base>
static constexpr size_t cntDigits()
{
T num = Limit;
size_t cnt = 0;
do {
num /= Base;
++cnt;
} while(num > 0);
return cnt;
}
template<typename T, size_t Base>
static constexpr size_t maxNumDigits()
{
constexpr T MinVal = std::numeric_limits<T>::min();
constexpr T MaxVal = std::numeric_limits<T>::max();
constexpr T MinDigits = cntDigits<T, MinVal, Base>();
constexpr T MaxDigits = cntDigits<T, MaxVal, Base>();
return (MinDigits < MaxDigits) ? MaxDigits : MinDigits;
}
} // namespace detail
template<class Driver>
class Uart {
public:
// Constructing a uart object does not initialize the driver to allow different specializations with the same
// back-end to exists at the same time
// Note that init must be called every time when switching specializations with the same back-end
Uart() = default;
// Moving and copying uart objects is not supported
Uart(const Uart&) = delete;
Uart(Uart&&) = delete;
Uart& operator=(const Uart&) = delete;
Uart& operator=(Uart&&) = delete;
// Before using the uart init must be called
static void init() { Driver::init(); }
static void txByte(const uint8_t& byte) { Driver::txByte(byte); }
static bool rxByte(uint8_t& byte) { return Driver::rxByte(byte); }
static bool peek(uint8_t& byte) { return Driver::peek(byte); }
static bool peek() { return Driver::peek(); }
static void flushTx() { Driver::flushTx(); }
static void txString(const char* str)
{
static_assert(Driver::DATA_BITS == DataBits::EIGHT, "Strings are only supported with 8 data bits");
while(char ch = *str++)
txByte(ch);
}
template<typename T, size_t Base = 10, size_t Padding = 0, char PadChar = '0', bool LowerCase = true>
static void txNumber(const T& val)
{
static_assert(std::is_integral_v<T>, "Only supported on integral types");
static_assert(Base >= 2, "Numbers with base less than 2 make no sense");
static_assert(Base <= 16, "Numbers with base higher than 16 are not supported");
static_assert(Padding <= detail::maxNumDigits<T, Base>(), "Cannot pad more than maximum length of number");
constexpr char AlphaChar = (LowerCase) ? 'a' : 'A';
constexpr size_t NumDigits = detail::maxNumDigits<T, Base>();
T digits = val;
if(digits < 0) {
digits = -digits;
txByte('-');
}
uint8_t buffer[NumDigits];
uint8_t* bufEnd = buffer + NumDigits - 1;
do {
const uint8_t lastDigit = digits % Base;
*bufEnd-- = (lastDigit < 10) ? ('0' + lastDigit) : (AlphaChar + lastDigit - 10);
digits /= Base;
} while(digits > 0);
if(Padding > 0) {
size_t strLen = buffer + NumDigits - (bufEnd + 1);
if(Padding > strLen) {
for(size_t i = Padding; i > strLen && bufEnd >= buffer; --i) {
*bufEnd-- = PadChar;
}
}
}
for(uint8_t* buf = bufEnd + 1; buf < buffer + NumDigits; ++buf) {
txByte(*buf);
}
}
//////////////////////////////////////////////////////////////////////////
// Output stream overloads
Uart& operator<<(const char* str)
{
txString(str);
return *this;
}
Uart& operator<<(const char& val)
{
txByte(val);
return *this;
}
Uart& operator<<(const signed char& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const unsigned char& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const short& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const unsigned short& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const int& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const unsigned int& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const long& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const unsigned long& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const long long& val)
{
txNumber(val);
return *this;
}
Uart& operator<<(const unsigned long long& val)
{
txNumber(val);
return *this;
}
template<typename... Ts>
Uart& operator<<(float) const
{
static_assert(detail::always_false_v<Ts...>, "Not supported by hardware");
}
template<typename... Ts>
Uart& operator<<(double) const
{
static_assert(detail::always_false_v<Ts...>, "Not supported by hardware");
}
template<typename... Ts>
Uart& operator<<(long double) const
{
static_assert(detail::always_false_v<Ts...>, "Not supported by hardware");
}
Uart& operator<<(const bool& val)
{
txString(val ? "true" : "false");
return *this;
}
Uart& operator<<(const void* val)
{
txString("0x");
txNumber<uint32_t, 16, 4, '0', false>(reinterpret_cast<uint32_t>(val));
return *this;
}
//////////////////////////////////////////////////////////////////////////
// Input stream overloads
template<typename... Ts>
Uart& operator>>(char&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(unsigned char&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(short&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(unsigned short&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(int&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(unsigned int&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(long&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(unsigned long&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(long long&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(unsigned long long&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(float&) const
{
static_assert(detail::always_false_v<Ts...>, "Not supported by hardware");
}
template<typename... Ts>
Uart& operator>>(double&) const
{
static_assert(detail::always_false_v<Ts...>, "Not supported by hardware");
}
template<typename... Ts>
Uart& operator>>(long double&) const
{
static_assert(detail::always_false_v<Ts...>, "Not supported by hardware");
}
template<typename... Ts>
Uart& operator>>(bool&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
template<typename... Ts>
Uart& operator>>(const void*&) const
{
static_assert(detail::always_false_v<Ts...>, "Not implemented");
}
};
template<typename cfg = Config<>>
using Vcp = Uart<detail::VirtualComPort<cfg>>;
} // namespace uart

View File

@@ -0,0 +1,32 @@
#pragma once
namespace uart {
enum class DataBits {
FIVE,
SIX,
SEVEN,
EIGHT,
NINE,
};
enum class StopBits {
ONE,
TWO,
};
enum class Parity {
NONE,
ODD,
EVEN,
};
template<uint32_t baudRate = 9600, DataBits dataBits = DataBits::EIGHT, Parity parity = Parity::NONE, StopBits stopBits = StopBits::ONE>
struct Config {
static constexpr auto BAUD_RATE = baudRate;
static constexpr auto DATA_BITS = dataBits;
static constexpr auto PARITY = parity;
static constexpr auto STOP_BITS = stopBits;
};
} // namespace uart

View File

@@ -0,0 +1,209 @@
#pragma once
#include <array>
#include <cstdint>
#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 {
size_t head;
size_t tail;
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;
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<USBD_CDC_HandleTypeDef*>(hUsbDeviceFS.pClassData);
return hcdc->TxState != 0;
};
constexpr auto txPacket = [usbReady](volatile uint8_t* buffer, size_t length) {
USBD_CDC_SetTxBuffer(&hUsbDeviceFS, const_cast<uint8_t*>(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<TX_BUFFER_SIZE> m_txBuffer;
static volatile RingBuffer<RX_BUFFER_SIZE> m_rxBuffer;
static std::array<volatile uint8_t, 64> m_usbAsyncRxBuffer;
static int8_t CdcInit()
{
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()
{
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<class cfg>
volatile RingBuffer<VirtualComPort<cfg>::TX_BUFFER_SIZE> VirtualComPort<cfg>::m_txBuffer = {0, 0, {0}};
template<class cfg>
volatile RingBuffer<VirtualComPort<cfg>::RX_BUFFER_SIZE> VirtualComPort<cfg>::m_rxBuffer = {0, 0, {0}};
template<class cfg>
std::array<volatile uint8_t, 64> VirtualComPort<cfg>::m_usbAsyncRxBuffer = {0};
} // namespace uart::detail

View File

@@ -38,11 +38,11 @@
* @brief For Usb device.
* @{
*/
/** @defgroup USBD_CDC_IF USBD_CDC_IF
* @brief Usb VCP device module
* @{
*/
*/
/** @defgroup USBD_CDC_IF_Exported_Defines USBD_CDC_IF_Exported_Defines
* @brief Defines.
@@ -92,21 +92,6 @@ extern USBD_CDC_ItfTypeDef USBD_Interface_fops_FS;
/* USER CODE BEGIN EXPORTED_VARIABLES */
/* USER CODE END EXPORTED_VARIABLES */
/**
* @}
*/
/** @defgroup USBD_CDC_IF_Exported_FunctionsPrototype USBD_CDC_IF_Exported_FunctionsPrototype
* @brief Public functions declaration.
* @{
*/
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len);
/* USER CODE BEGIN EXPORTED_FUNCTIONS */
/* USER CODE END EXPORTED_FUNCTIONS */
/**