390 lines
9.4 KiB
C++
390 lines
9.4 KiB
C++
#pragma once
|
|
|
|
#include "../clock.hpp"
|
|
|
|
#include <stdint.h>
|
|
|
|
#include "utils.hpp"
|
|
|
|
#define FORCE_INLINE __attribute__((always_inline))
|
|
|
|
namespace uart {
|
|
|
|
enum class Mode {
|
|
ASYNCHRONOUS,
|
|
ASYNCHRONOUS_2X,
|
|
SYNCHRONOUS_MASTER,
|
|
SYNCHRONOUS_SLAVE,
|
|
SPI,
|
|
};
|
|
|
|
enum class Driven {
|
|
INTERRUPT,
|
|
BLOCKING,
|
|
};
|
|
|
|
namespace detail {
|
|
|
|
using reg_ptr_t = volatile uint8_t *;
|
|
|
|
template <uintptr_t Address>
|
|
static inline reg_ptr_t getRegPtr()
|
|
{
|
|
return reinterpret_cast<reg_ptr_t>(Address);
|
|
}
|
|
|
|
template <typename data_t, uint8_t Size>
|
|
struct RingBuffer {
|
|
uint8_t head;
|
|
uint8_t tail;
|
|
data_t buf[Size];
|
|
};
|
|
|
|
template <class Registers, typename CtrlFlagsA, typename CtrlFlagsB, typename CtrlFlagsC, class cfg, Driven driven,
|
|
Mode mode>
|
|
class Hardware {
|
|
public:
|
|
static void init() FORCE_INLINE
|
|
{
|
|
constexpr auto baudVal = calcBaud();
|
|
|
|
*getRegPtr<Registers::BAUD_REG_H_ADDR>() = static_cast<uint8_t>(baudVal >> 8);
|
|
*getRegPtr<Registers::BAUD_REG_L_ADDR>() = static_cast<uint8_t>(baudVal);
|
|
|
|
constexpr auto dataBitsVal = calcDataBits();
|
|
constexpr auto parityVal = calcParity();
|
|
constexpr auto stopBitsVal = calcStopBits();
|
|
constexpr auto modeVal = calcMode();
|
|
constexpr auto enableRx = calcRxState<true>();
|
|
constexpr auto enableTx = calcTxState<true>();
|
|
constexpr auto interruptVal = calcInterrupt();
|
|
|
|
constexpr uint8_t controlRegB = dataBitsVal.regBVal | enableRx | enableTx | interruptVal;
|
|
constexpr uint8_t controlRegC = dataBitsVal.regCVal | parityVal | stopBitsVal | modeVal;
|
|
|
|
*getRegPtr<Registers::CTRL_STAT_REG_B_ADDR>() = controlRegB;
|
|
*getRegPtr<Registers::CTRL_STAT_REG_C_ADDR>() = controlRegC;
|
|
}
|
|
|
|
static bool rxByteBlocking(typename cfg::data_t &byte) FORCE_INLINE
|
|
{
|
|
if (*getRegPtr<Registers::CTRL_STAT_REG_A_ADDR>() & (1 << CtrlFlagsA::RECEIVE_COMPLETE)) {
|
|
byte = *getRegPtr<Registers::IO_REG_ADDR>();
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static typename cfg::data_t rxByteInterrupt() FORCE_INLINE
|
|
{
|
|
return *getRegPtr<Registers::IO_REG_ADDR>();
|
|
}
|
|
|
|
static bool txEmpty() FORCE_INLINE
|
|
{
|
|
return *getRegPtr<Registers::CTRL_STAT_REG_A_ADDR>() & (1 << CtrlFlagsA::DATA_REG_EMPTY);
|
|
}
|
|
|
|
static bool txComplete() FORCE_INLINE
|
|
{
|
|
return *getRegPtr<Registers::CTRL_STAT_REG_A_ADDR>() & (1 << CtrlFlagsA::TRANSMIT_COMPLETE);
|
|
}
|
|
|
|
static void clearTxComplete() FORCE_INLINE
|
|
{
|
|
*getRegPtr<Registers::CTRL_STAT_REG_A_ADDR>() |= (1 << CtrlFlagsA::TRANSMIT_COMPLETE);
|
|
}
|
|
|
|
static void txByteBlocking(const typename cfg::data_t &byte) FORCE_INLINE
|
|
{
|
|
while (!txEmpty())
|
|
;
|
|
|
|
*getRegPtr<Registers::IO_REG_ADDR>() = byte;
|
|
}
|
|
|
|
static void txByteInterrupt(volatile const typename cfg::data_t &byte) FORCE_INLINE
|
|
{
|
|
*getRegPtr<Registers::IO_REG_ADDR>() = byte;
|
|
}
|
|
|
|
static bool peekBlocking() FORCE_INLINE
|
|
{
|
|
if (*getRegPtr<Registers::CTRL_STAT_REG_A_ADDR>() & (1 << CtrlFlagsA::RECEIVE_COMPLETE)) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void enableDataRegEmptyInt() FORCE_INLINE
|
|
{
|
|
*getRegPtr<Registers::CTRL_STAT_REG_B_ADDR>() |= (1 << CtrlFlagsB::DATA_REG_EMPTY_INT_ENABLE);
|
|
}
|
|
|
|
static void disableDataRegEmptyInt() FORCE_INLINE
|
|
{
|
|
*getRegPtr<Registers::CTRL_STAT_REG_B_ADDR>() &= ~(1 << CtrlFlagsB::DATA_REG_EMPTY_INT_ENABLE);
|
|
}
|
|
|
|
private:
|
|
struct DataBitsVal {
|
|
uint8_t regCVal = 0;
|
|
uint8_t regBVal = 0;
|
|
};
|
|
|
|
static constexpr auto calcBaud()
|
|
{
|
|
// The actual formula is (F_CPU / (16 * baudRate)) - 1, but this one has the advantage of rounding correctly
|
|
constexpr auto baudVal = (F_CPU + 8 * cfg::BAUD_RATE) / (16 * cfg::BAUD_RATE) - 1;
|
|
return baudVal;
|
|
}
|
|
|
|
static constexpr auto calcDataBits()
|
|
{
|
|
DataBitsVal dataBitsVal;
|
|
|
|
switch (cfg::DATA_BITS) {
|
|
case DataBits::FIVE:
|
|
dataBitsVal.regCVal = 0;
|
|
break;
|
|
case DataBits::SIX:
|
|
dataBitsVal.regCVal = (1 << CtrlFlagsC::CHAR_SIZE_0);
|
|
break;
|
|
case DataBits::SEVEN:
|
|
dataBitsVal.regCVal = (1 << CtrlFlagsC::CHAR_SIZE_1);
|
|
break;
|
|
case DataBits::EIGHT:
|
|
dataBitsVal.regCVal = (1 << CtrlFlagsC::CHAR_SIZE_1) | (1 << CtrlFlagsC::CHAR_SIZE_0);
|
|
break;
|
|
case DataBits::NINE:
|
|
dataBitsVal.regCVal = (1 << CtrlFlagsC::CHAR_SIZE_1) | (1 << CtrlFlagsC::CHAR_SIZE_0);
|
|
dataBitsVal.regBVal = (1 << CtrlFlagsB::CHAR_SIZE_2);
|
|
break;
|
|
}
|
|
|
|
return dataBitsVal;
|
|
}
|
|
|
|
static constexpr auto calcParity()
|
|
{
|
|
uint8_t parityVal = 0;
|
|
|
|
if (cfg::PARITY == Parity::EVEN)
|
|
parityVal = (1 << CtrlFlagsC::PARITY_MODE_1);
|
|
else if (cfg::PARITY == Parity::ODD)
|
|
parityVal = (1 << CtrlFlagsC::PARITY_MODE_1) | (1 << CtrlFlagsC::PARITY_MODE_0);
|
|
|
|
return parityVal;
|
|
}
|
|
|
|
static constexpr auto calcStopBits()
|
|
{
|
|
uint8_t stopBitsVal = 0;
|
|
|
|
if (cfg::STOP_BITS == StopBits::TWO)
|
|
stopBitsVal = (1 << CtrlFlagsC::STOP_BIT_SEL);
|
|
|
|
return stopBitsVal;
|
|
}
|
|
|
|
static constexpr auto calcMode()
|
|
{
|
|
static_assert(mode != Mode::SPI, "SPI mode can not be used with uart");
|
|
|
|
uint8_t modeVal = 0;
|
|
|
|
if (mode == Mode::SYNCHRONOUS_MASTER || mode == Mode::SYNCHRONOUS_SLAVE) {
|
|
modeVal = (1 << CtrlFlagsC::MODE_SEL_0);
|
|
}
|
|
|
|
return modeVal;
|
|
}
|
|
|
|
template <bool enable>
|
|
static constexpr auto calcRxState()
|
|
{
|
|
uint8_t enableVal = 0;
|
|
|
|
if (enable)
|
|
enableVal = (1 << CtrlFlagsB::RX_ENABLE);
|
|
|
|
return enableVal;
|
|
}
|
|
|
|
template <bool enable>
|
|
static constexpr auto calcTxState()
|
|
{
|
|
uint8_t enableVal = 0;
|
|
|
|
if (enable)
|
|
enableVal = (1 << CtrlFlagsB::TX_ENABLE);
|
|
|
|
return enableVal;
|
|
}
|
|
|
|
static constexpr auto calcInterrupt()
|
|
{
|
|
uint8_t interruptVal = 0;
|
|
|
|
if (driven == Driven::INTERRUPT)
|
|
interruptVal = (1 << CtrlFlagsB::RX_INT_ENABLE);
|
|
|
|
return interruptVal;
|
|
}
|
|
};
|
|
|
|
template <class Registers, typename CtrlFlagsA, typename CtrlFlagsB, typename CtrlFlagsC, class cfg, Mode mode>
|
|
class BlockingHardware {
|
|
public:
|
|
using data_t = typename cfg::data_t;
|
|
static constexpr auto DATA_BITS = cfg::DATA_BITS;
|
|
|
|
static void init() FORCE_INLINE
|
|
{
|
|
HardwareImpl::init();
|
|
}
|
|
|
|
static void txByte(const data_t &byte) FORCE_INLINE
|
|
{
|
|
HardwareImpl::txByteBlocking(byte);
|
|
}
|
|
|
|
static bool rxByte(data_t &byte) FORCE_INLINE
|
|
{
|
|
return HardwareImpl::rxByteBlocking(byte);
|
|
}
|
|
|
|
static bool peek(data_t &) FORCE_INLINE
|
|
{
|
|
static_assert(util::always_false_v<data_t>, "Peek with data is not supported in blocking mode");
|
|
return false;
|
|
}
|
|
|
|
static bool peek() FORCE_INLINE
|
|
{
|
|
return HardwareImpl::peekBlocking();
|
|
}
|
|
|
|
static void flushTx() FORCE_INLINE
|
|
{
|
|
while (!HardwareImpl::txEmpty())
|
|
;
|
|
while (!HardwareImpl::txComplete())
|
|
;
|
|
HardwareImpl::clearTxComplete();
|
|
}
|
|
|
|
private:
|
|
using HardwareImpl = Hardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, Driven::BLOCKING, mode>;
|
|
};
|
|
|
|
template <class Registers, typename CtrlFlagsA, typename CtrlFlagsB, typename CtrlFlagsC, class cfg, Mode mode>
|
|
class InterruptHardware {
|
|
public:
|
|
using data_t = typename cfg::data_t;
|
|
static constexpr auto DATA_BITS = cfg::DATA_BITS;
|
|
|
|
static void txByte(const data_t &byte) FORCE_INLINE
|
|
{
|
|
uint8_t tmpHead = (sm_txBuf.head + 1) % TX_BUFFER_SIZE;
|
|
while (tmpHead == sm_txBuf.tail)
|
|
;
|
|
|
|
sm_txBuf.buf[tmpHead] = byte;
|
|
sm_txBuf.head = tmpHead;
|
|
|
|
HardwareImpl::enableDataRegEmptyInt();
|
|
}
|
|
|
|
static bool rxByte(data_t &byte) FORCE_INLINE
|
|
{
|
|
if (sm_rxBuf.head == sm_rxBuf.tail)
|
|
return false;
|
|
|
|
uint8_t tmpTail = (sm_rxBuf.tail + 1) % RX_BUFFER_SIZE;
|
|
byte = sm_rxBuf.buf[tmpTail];
|
|
sm_rxBuf.tail = tmpTail;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool peek(data_t &byte) FORCE_INLINE
|
|
{
|
|
if (sm_rxBuf.head == sm_rxBuf.tail)
|
|
return false;
|
|
|
|
uint8_t tmpTail = (sm_rxBuf.tail + 1) % RX_BUFFER_SIZE;
|
|
byte = sm_rxBuf.buf[tmpTail];
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool peek() FORCE_INLINE
|
|
{
|
|
return (sm_rxBuf.head != sm_rxBuf.tail);
|
|
}
|
|
|
|
static void flushTx() FORCE_INLINE
|
|
{
|
|
while (sm_txBuf.head != sm_txBuf.tail)
|
|
;
|
|
while (!HardwareImpl::txEmpty())
|
|
;
|
|
while (!HardwareImpl::txComplete())
|
|
;
|
|
HardwareImpl::clearTxComplete();
|
|
}
|
|
|
|
protected:
|
|
static void rxIntHandler()
|
|
{
|
|
auto data = HardwareImpl::rxByteInterrupt();
|
|
|
|
uint8_t tmpHead = (sm_rxBuf.head + 1) % RX_BUFFER_SIZE;
|
|
|
|
if (tmpHead != sm_rxBuf.tail) {
|
|
sm_rxBuf.head = tmpHead;
|
|
sm_rxBuf.buf[tmpHead] = data;
|
|
} else {
|
|
// TODO: Handle overflow
|
|
}
|
|
}
|
|
|
|
static void dataRegEmptyIntHandler() FORCE_INLINE
|
|
{
|
|
if (sm_txBuf.head != sm_txBuf.tail) {
|
|
uint8_t tmpTail = (sm_txBuf.tail + 1) % TX_BUFFER_SIZE;
|
|
sm_txBuf.tail = tmpTail;
|
|
HardwareImpl::txByteInterrupt(sm_txBuf.buf[tmpTail]);
|
|
} else
|
|
HardwareImpl::disableDataRegEmptyInt();
|
|
}
|
|
|
|
private:
|
|
using HardwareImpl = Hardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, Driven::INTERRUPT, mode>;
|
|
|
|
static constexpr auto TX_BUFFER_SIZE = 16;
|
|
static constexpr auto RX_BUFFER_SIZE = 16;
|
|
|
|
static volatile RingBuffer<data_t, TX_BUFFER_SIZE> sm_txBuf;
|
|
static volatile RingBuffer<data_t, RX_BUFFER_SIZE> sm_rxBuf;
|
|
};
|
|
|
|
template <class Registers, typename CtrlFlagsA, typename CtrlFlagsB, typename CtrlFlagsC, class cfg, Mode mode>
|
|
volatile RingBuffer<typename InterruptHardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, mode>::data_t,
|
|
InterruptHardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, mode>::TX_BUFFER_SIZE>
|
|
InterruptHardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, mode>::sm_txBuf = {0, 0, {0}};
|
|
|
|
template <class Registers, typename CtrlFlagsA, typename CtrlFlagsB, typename CtrlFlagsC, class cfg, Mode mode>
|
|
volatile RingBuffer<typename InterruptHardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, mode>::data_t,
|
|
InterruptHardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, mode>::RX_BUFFER_SIZE>
|
|
InterruptHardware<Registers, CtrlFlagsA, CtrlFlagsB, CtrlFlagsC, cfg, mode>::sm_rxBuf = {0, 0, {0}};
|
|
|
|
} // namespace detail
|
|
|
|
} // namespace uart
|
|
|
|
#undef FORCE_INLINE
|