Compare commits

..

7 Commits

253
io.hpp
View File

@@ -1,6 +1,6 @@
#pragma once #pragma once
#include <stdint.h> #include <cstdint>
#include <avr/io.h> #include <avr/io.h>
#include <avr/sfr_defs.h> #include <avr/sfr_defs.h>
@@ -45,8 +45,6 @@
#define PIN_C7_AVAILABLE #define PIN_C7_AVAILABLE
#endif #endif
#define FORCE_INLINE __attribute__((always_inline))
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Library implementation // Library implementation
@@ -104,6 +102,8 @@ enum class P {
D6 = 0x36, D6 = 0x36,
D7 = 0x37, D7 = 0x37,
#endif #endif
NONE,
}; };
enum class Bus { enum class Bus {
@@ -119,6 +119,8 @@ enum class Bus {
#ifdef PORT_D_AVAILABLE #ifdef PORT_D_AVAILABLE
D = 0x03, D = 0x03,
#endif #endif
NONE,
}; };
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@@ -130,10 +132,10 @@ namespace detail {
The following works in avr-gcc 5.4.0, but is not legal C++, because ptr's are not legal constexpr's: The following works in avr-gcc 5.4.0, but is not legal C++, because ptr's are not legal constexpr's:
constexpr auto *foo = ptr; constexpr auto *foo = ptr;
Workaround is to store the the address of the ptr in a uintptr_t and reinterpret_cast it at call site. Workaround is to store the address of the ptr in a uintptr_t and reinterpret_cast it at call site.
The _SFR_ADDR macro in sfr_defs.h would give the address, but it does that by taking the address of the dereferenced The _SFR_ADDR macro in sfr_defs.h would give the address, but it does that by taking the address of the dereferenced
pointer and casts it to uint16_t, which is still not a legal constexpr. pointer and casts it to uint16_t, which is still not a legal constexpr.
The workaround therefore is to disable the pointer cast and dereference macro _MMIO_BYTE temporarily. The workaround therefore is to disable the pointer-cast-and-dereference macro _MMIO_BYTE temporarily.
*/ */
#pragma push_macro("_MMIO_BYTE") #pragma push_macro("_MMIO_BYTE")
@@ -186,20 +188,20 @@ static constexpr uintptr_t PORT_D_INPUT_REG_ADDR = 0;
static constexpr auto getBus(const P pin) static constexpr auto getBus(const P pin)
{ {
// Upper 4 bits of pin encode which port this pin is on // Upper 4 bits of pin encode which port this pin is on
uint8_t port = static_cast<uint8_t>(pin) >> 4 & 0x0F; const auto port = static_cast<std::uint8_t>(pin) >> 4 & 0x0F;
return static_cast<Bus>(port); return static_cast<Bus>(port);
} }
static constexpr auto getPinBit(const P pin) static constexpr auto getPinBit(const P pin)
{ {
// Lower 4 bits of pin encode which pin bit it is // Lower 4 bits of pin encode which pin bit it is
uint8_t pinBit = static_cast<uint8_t>(pin) & 0x0F; const auto pinBit = static_cast<std::uint8_t>(pin) & 0x0F;
return pinBit; return pinBit;
} }
static constexpr auto getDDR(const Bus bus) static constexpr auto getDDR(const Bus bus)
{ {
switch (static_cast<uint8_t>(bus)) { switch (static_cast<std::uint8_t>(bus)) {
case 0: // Bus::A case 0: // Bus::A
return PORT_A_DIR_REG_ADDR; return PORT_A_DIR_REG_ADDR;
case 1: // Bus::B case 1: // Bus::B
@@ -213,7 +215,7 @@ static constexpr auto getDDR(const Bus bus)
static constexpr auto getPORT(const Bus bus) static constexpr auto getPORT(const Bus bus)
{ {
switch (static_cast<uint8_t>(bus)) { switch (static_cast<std::uint8_t>(bus)) {
case 0: // Bus::A case 0: // Bus::A
return PORT_A_OUTPUT_REG_ADDR; return PORT_A_OUTPUT_REG_ADDR;
case 1: // Bus::B case 1: // Bus::B
@@ -227,7 +229,7 @@ static constexpr auto getPORT(const Bus bus)
static constexpr auto getPIN(const Bus bus) static constexpr auto getPIN(const Bus bus)
{ {
switch (static_cast<uint8_t>(bus)) { switch (static_cast<std::uint8_t>(bus)) {
case 0: // Bus::A case 0: // Bus::A
return PORT_A_INPUT_REG_ADDR; return PORT_A_INPUT_REG_ADDR;
case 1: // Bus::B case 1: // Bus::B
@@ -239,7 +241,7 @@ static constexpr auto getPIN(const Bus bus)
} }
} }
using reg_ptr_t = volatile uint8_t *; using reg_ptr_t = volatile std::uint8_t *;
template <uintptr_t Address> template <uintptr_t Address>
static inline reg_ptr_t getRegPtr() static inline reg_ptr_t getRegPtr()
@@ -247,6 +249,41 @@ static inline reg_ptr_t getRegPtr()
return reinterpret_cast<reg_ptr_t>(Address); return reinterpret_cast<reg_ptr_t>(Address);
} }
template <template <P, std::uint8_t> typename Func, P pin, P... pins>
struct CallHelper {
template <typename... Args>
[[gnu::always_inline]] static inline void call(Args... args)
{
Func<pin, sizeof...(pins)>::call(args...);
CallHelper<Func, pins...>::call(args...);
}
};
template <template <P, std::uint8_t> typename Func, P pin>
struct CallHelper<Func, pin> {
template <typename... Args>
[[gnu::always_inline]] static inline void call(Args... args)
{
Func<pin, 0>::call(args...);
}
};
template <template <P> typename Func, P pin, P... pins>
struct ReadCallHelper {
[[gnu::always_inline]] static inline std::uint8_t call()
{
return Func<pin>::call() << sizeof...(pins) | ReadCallHelper<Func, pins...>::call();
}
};
template <template <P> typename Func, P pin>
struct ReadCallHelper<Func, pin> {
[[gnu::always_inline]] static inline std::uint8_t call()
{
return Func<pin>::call();
}
};
} // namespace detail } // namespace detail
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@@ -264,79 +301,88 @@ class Pin {
// The only valid way to create a Pin object is with the default constructor // The only valid way to create a Pin object is with the default constructor
Pin() = default; Pin() = default;
static inline void dir(const Dir dir) FORCE_INLINE [[gnu::always_inline]] static inline void dir(const Dir dir)
{ {
if constexpr (pin != P::NONE) {
constexpr auto bus = detail::getBus(pin); constexpr auto bus = detail::getBus(pin);
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto pinBit = detail::getPinBit(pin);
detail::reg_ptr_t dirRegPtr = detail::getRegPtr<detail::getDDR(bus)>(); auto dirRegPtr = detail::getRegPtr<detail::getDDR(bus)>();
if (dir == Dir::IN) if (dir == Dir::IN)
*dirRegPtr &= ~(1 << pinBit); *dirRegPtr = *dirRegPtr & ~(1 << pinBit);
else if (dir == Dir::OUT) else if (dir == Dir::OUT)
*dirRegPtr |= (1 << pinBit); *dirRegPtr = *dirRegPtr | (1 << pinBit);
}
} }
static inline void pullup(const bool enable) FORCE_INLINE [[gnu::always_inline]] static inline void pullup(const bool enable)
{ {
if constexpr (pin != P::NONE) {
constexpr auto bus = detail::getBus(pin); constexpr auto bus = detail::getBus(pin);
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto pinBit = detail::getPinBit(pin);
detail::reg_ptr_t portRegPtr = detail::getRegPtr<detail::getPORT(bus)>(); auto portRegPtr = detail::getRegPtr<detail::getPORT(bus)>();
if (enable) if (enable)
*portRegPtr |= (1 << pinBit); *portRegPtr = *portRegPtr | (1 << pinBit);
else else
*portRegPtr &= ~(1 << pinBit); *portRegPtr = *portRegPtr & ~(1 << pinBit);
}
} }
static inline void write(const bool value) FORCE_INLINE [[gnu::always_inline]] static inline void write(const bool value)
{ {
if constexpr (pin != P::NONE) {
constexpr auto bus = detail::getBus(pin); constexpr auto bus = detail::getBus(pin);
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto pinBit = detail::getPinBit(pin);
detail::reg_ptr_t portRegPtr = detail::getRegPtr<detail::getPORT(bus)>(); auto portRegPtr = detail::getRegPtr<detail::getPORT(bus)>();
if (value) if (value)
*portRegPtr |= (1 << pinBit); *portRegPtr = *portRegPtr | (1 << pinBit);
else else
*portRegPtr &= ~(1 << pinBit); *portRegPtr = *portRegPtr & ~(1 << pinBit);
}
} }
static inline void toggle() FORCE_INLINE [[gnu::always_inline]] static inline void toggle()
{ {
if constexpr (pin != P::NONE) {
constexpr auto bus = detail::getBus(pin); constexpr auto bus = detail::getBus(pin);
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto pinBit = detail::getPinBit(pin);
#ifdef HARDWARE_TOGGLE #ifdef HARDWARE_TOGGLE
detail::reg_ptr_t pinRegPtr = detail::getRegPtr<detail::getPIN(bus)>(); auto pinRegPtr = detail::getRegPtr<detail::getPIN(bus)>();
*pinRegPtr |= (1 << pinBit); *pinRegPtr = *pinRegPtr | (1 << pinBit);
#else #else
detail::reg_ptr_t portRegPtr = detail::getRegPtr<detail::getPORT(bus)>(); auto portRegPtr = detail::getRegPtr<detail::getPORT(bus)>();
*portRegPtr ^= (1 << pinBit); *portRegPtr = *portRegPtr ^ (1 << pinBit);
#endif #endif
} }
}
static inline bool read() FORCE_INLINE [[gnu::always_inline]] static inline bool read()
{ {
if constexpr (pin != P::NONE) {
constexpr auto bus = detail::getBus(pin); constexpr auto bus = detail::getBus(pin);
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto pinBit = detail::getPinBit(pin);
detail::reg_ptr_t pinRegPtr = detail::getRegPtr<detail::getPIN(bus)>(); auto pinRegPtr = detail::getRegPtr<detail::getPIN(bus)>();
if (*pinRegPtr >> pinBit & 1) if (*pinRegPtr >> pinBit & 1)
return true; return true;
}
return false; return false;
} }
Pin &operator=(const bool value) FORCE_INLINE [[gnu::always_inline]] Pin &operator=(const bool value)
{ {
write(value); write(value);
return *this; return *this;
} }
operator bool() const FORCE_INLINE [[gnu::always_inline]] operator bool() const
{ {
return read(); return read();
} }
@@ -357,57 +403,156 @@ class Port {
// The only valid way to create a Port object is with the default constructor // The only valid way to create a Port object is with the default constructor
Port() = default; Port() = default;
static inline void dir(const Dir dir) FORCE_INLINE [[gnu::always_inline]] static inline void dir(const Dir dir)
{ {
detail::reg_ptr_t dirRegPtr = detail::getRegPtr<detail::getDDR(port)>(); if constexpr (port != Bus::NONE) {
auto dirRegPtr = detail::getRegPtr<detail::getDDR(port)>();
if (dir == Dir::IN) if (dir == Dir::IN)
*dirRegPtr = 0x00; *dirRegPtr = 0x00;
else if (dir == Dir::OUT) else if (dir == Dir::OUT)
*dirRegPtr = 0xFF; *dirRegPtr = 0xFF;
} }
}
static inline void pullup(const bool enable) FORCE_INLINE [[gnu::always_inline]] static inline void pullup(const bool enable)
{ {
detail::reg_ptr_t portRegPtr = detail::getRegPtr<detail::getPORT(port)>(); if constexpr (port != Bus::NONE) {
auto portRegPtr = detail::getRegPtr<detail::getPORT(port)>();
if (enable) if (enable)
*portRegPtr = 0xFF; *portRegPtr = 0xFF;
else else
*portRegPtr = 0x00; *portRegPtr = 0x00;
} }
static inline void write(const uint8_t value) FORCE_INLINE
{
detail::reg_ptr_t portRegPtr = detail::getRegPtr<detail::getPORT(port)>();
*portRegPtr = value;
} }
static inline void invert() FORCE_INLINE [[gnu::always_inline]] static inline void write([[maybe_unused]] const std::uint8_t value)
{ {
if constexpr (port != Bus::NONE) {
auto portRegPtr = detail::getRegPtr<detail::getPORT(port)>();
*portRegPtr = value;
}
}
[[gnu::always_inline]] static inline void invert()
{
if constexpr (port != Bus::NONE) {
#ifdef HARDWARE_TOGGLE #ifdef HARDWARE_TOGGLE
detail::reg_ptr_t pinRegPtr = detail::getRegPtr<detail::getPIN(port)>(); auto pinRegPtr = detail::getRegPtr<detail::getPIN(port)>();
*pinRegPtr = 0xFF; *pinRegPtr = 0xFF;
#else #else
detail::reg_ptr_t portRegPtr = detail::getRegPtr<detail::getPORT(port)>(); auto portRegPtr = detail::getRegPtr<detail::getPORT(port)>();
*portRegPtr = ~(*portRegPtr); *portRegPtr = ~(*portRegPtr);
#endif #endif
} }
static inline uint8_t read() FORCE_INLINE
{
detail::reg_ptr_t pinRegPtr = detail::getRegPtr<detail::getPIN(port)>();
return *pinRegPtr;
} }
inline Port &operator=(const uint8_t value) FORCE_INLINE [[gnu::always_inline]] static inline std::uint8_t read()
{
if constexpr (port != Bus::NONE) {
auto pinRegPtr = detail::getRegPtr<detail::getPIN(port)>();
return *pinRegPtr;
}
return 0x00;
}
[[gnu::always_inline]] inline Port &operator=(const std::uint8_t value)
{ {
write(value); write(value);
return *this; return *this;
} }
inline operator uint8_t() const FORCE_INLINE [[gnu::always_inline]] inline operator std::uint8_t() const
{
return read();
}
};
//////////////////////////////////////////////////////////////////////////
// Zero overhead Virtual Port object for pretty code without losing performance
namespace detail {
template <P pin, std::uint8_t offset>
struct Callers {
[[gnu::always_inline]] static void call(const Dir dir)
{
Pin<pin>::dir(dir);
};
[[gnu::always_inline]] static void call(const bool enable)
{
Pin<pin>::pullup(enable);
};
[[gnu::always_inline]] static void call(const std::uint8_t value)
{
Pin<pin>::write(value >> offset & 1);
};
[[gnu::always_inline]] static void call()
{
Pin<pin>::toggle();
};
};
template <P pin>
struct readCaller {
[[gnu::always_inline]] static bool call()
{
return Pin<pin>::read();
};
};
} // namespace detail
template <P... pins>
class VirtPort {
public:
static_assert(sizeof...(pins) <= 8, "A virtual port cannot have more than 8 pins");
// VirtPort objects cannot be moved or copied
VirtPort(const VirtPort &) = delete;
VirtPort(VirtPort &&) = delete;
VirtPort &operator=(const VirtPort &) = delete;
VirtPort &operator=(VirtPort &&) = delete;
// The only valid way to create a VirtPort object is with the default constructor
VirtPort() = default;
[[gnu::always_inline]] static inline void dir(const Dir dir)
{
detail::CallHelper<detail::Callers, pins...>::call(dir);
}
[[gnu::always_inline]] static inline void pullup(const bool enable)
{
detail::CallHelper<detail::Callers, pins...>::call(enable);
}
[[gnu::always_inline]] static inline void write(const std::uint8_t value)
{
detail::CallHelper<detail::Callers, pins...>::call(value);
}
[[gnu::always_inline]] static inline void invert()
{
detail::CallHelper<detail::Callers, pins...>::call();
}
[[gnu::always_inline]] static inline std::uint8_t read()
{
return detail::ReadCallHelper<detail::readCaller, pins...>::call();
}
[[gnu::always_inline]] inline VirtPort &operator=(const std::uint8_t value)
{
write(value);
return *this;
}
[[gnu::always_inline]] inline operator std::uint8_t() const
{ {
return read(); return read();
} }
@@ -429,5 +574,3 @@ class Port {
#undef PIN_B6_AVAILABLE #undef PIN_B6_AVAILABLE
#undef PIN_B7_AVAILABLE #undef PIN_B7_AVAILABLE
#undef PIN_C7_AVAILABLE #undef PIN_C7_AVAILABLE
#undef FORCE_INLINE