Compare commits

..

10 Commits

2 changed files with 263 additions and 96 deletions

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 BlackMark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

338
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,
}; };
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@@ -127,20 +129,23 @@ enum class Bus {
namespace detail { 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.
For this to work we need to temporarily disable the _SFR_IO8 macro so that the register macro just gives the address 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.
The workaround therefore is to disable the pointer-cast-and-dereference macro _MMIO_BYTE temporarily.
*/ */
#undef _SFR_IO8 #pragma push_macro("_MMIO_BYTE")
#define _SFR_IO8 #undef _MMIO_BYTE
#define _MMIO_BYTE
#ifdef PORT_A_AVAILABLE #ifdef PORT_A_AVAILABLE
static constexpr uintptr_t PORT_A_DIR_REG_ADDR = DDRA + __SFR_OFFSET; static constexpr uintptr_t PORT_A_DIR_REG_ADDR = DDRA;
static constexpr uintptr_t PORT_A_OUTPUT_REG_ADDR = PORTA + __SFR_OFFSET; static constexpr uintptr_t PORT_A_OUTPUT_REG_ADDR = PORTA;
static constexpr uintptr_t PORT_A_INPUT_REG_ADDR = PINA + __SFR_OFFSET; static constexpr uintptr_t PORT_A_INPUT_REG_ADDR = PINA;
#else #else
static constexpr uintptr_t PORT_A_DIR_REG_ADDR = 0; static constexpr uintptr_t PORT_A_DIR_REG_ADDR = 0;
static constexpr uintptr_t PORT_A_OUTPUT_REG_ADDR = 0; static constexpr uintptr_t PORT_A_OUTPUT_REG_ADDR = 0;
@@ -148,9 +153,10 @@ static constexpr uintptr_t PORT_A_INPUT_REG_ADDR = 0;
#endif #endif
#ifdef PORT_B_AVAILABLE #ifdef PORT_B_AVAILABLE
static constexpr uintptr_t PORT_B_DIR_REG_ADDR = DDRB + __SFR_OFFSET;
static constexpr uintptr_t PORT_B_OUTPUT_REG_ADDR = PORTB + __SFR_OFFSET; static constexpr uintptr_t PORT_B_DIR_REG_ADDR = DDRB;
static constexpr uintptr_t PORT_B_INPUT_REG_ADDR = PINB + __SFR_OFFSET; static constexpr uintptr_t PORT_B_OUTPUT_REG_ADDR = PORTB;
static constexpr uintptr_t PORT_B_INPUT_REG_ADDR = PINB;
#else #else
static constexpr uintptr_t PORT_B_DIR_REG_ADDR = 0; static constexpr uintptr_t PORT_B_DIR_REG_ADDR = 0;
static constexpr uintptr_t PORT_B_OUTPUT_REG_ADDR = 0; static constexpr uintptr_t PORT_B_OUTPUT_REG_ADDR = 0;
@@ -158,9 +164,9 @@ static constexpr uintptr_t PORT_B_INPUT_REG_ADDR = 0;
#endif #endif
#ifdef PORT_C_AVAILABLE #ifdef PORT_C_AVAILABLE
static constexpr uintptr_t PORT_C_DIR_REG_ADDR = DDRC + __SFR_OFFSET; static constexpr uintptr_t PORT_C_DIR_REG_ADDR = DDRC;
static constexpr uintptr_t PORT_C_OUTPUT_REG_ADDR = PORTC + __SFR_OFFSET; static constexpr uintptr_t PORT_C_OUTPUT_REG_ADDR = PORTC;
static constexpr uintptr_t PORT_C_INPUT_REG_ADDR = PINC + __SFR_OFFSET; static constexpr uintptr_t PORT_C_INPUT_REG_ADDR = PINC;
#else #else
static constexpr uintptr_t PORT_C_DIR_REG_ADDR = 0; static constexpr uintptr_t PORT_C_DIR_REG_ADDR = 0;
static constexpr uintptr_t PORT_C_OUTPUT_REG_ADDR = 0; static constexpr uintptr_t PORT_C_OUTPUT_REG_ADDR = 0;
@@ -168,35 +174,34 @@ static constexpr uintptr_t PORT_C_INPUT_REG_ADDR = 0;
#endif #endif
#ifdef PORT_D_AVAILABLE #ifdef PORT_D_AVAILABLE
static constexpr uintptr_t PORT_D_DIR_REG_ADDR = DDRD + __SFR_OFFSET; static constexpr uintptr_t PORT_D_DIR_REG_ADDR = DDRD;
static constexpr uintptr_t PORT_D_OUTPUT_REG_ADDR = PORTD + __SFR_OFFSET; static constexpr uintptr_t PORT_D_OUTPUT_REG_ADDR = PORTD;
static constexpr uintptr_t PORT_D_INPUT_REG_ADDR = PIND + __SFR_OFFSET; static constexpr uintptr_t PORT_D_INPUT_REG_ADDR = PIND;
#else #else
static constexpr uintptr_t PORT_D_DIR_REG_ADDR = 0; static constexpr uintptr_t PORT_D_DIR_REG_ADDR = 0;
static constexpr uintptr_t PORT_D_OUTPUT_REG_ADDR = 0; static constexpr uintptr_t PORT_D_OUTPUT_REG_ADDR = 0;
static constexpr uintptr_t PORT_D_INPUT_REG_ADDR = 0; static constexpr uintptr_t PORT_D_INPUT_REG_ADDR = 0;
#endif #endif
#undef _SFR_IO8 #pragma pop_macro("_MMIO_BYTE")
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
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
@@ -210,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
@@ -224,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
@@ -236,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()
@@ -244,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
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@@ -261,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)
{ {
constexpr auto bus = detail::getBus(pin); if constexpr (pin != P::NONE) {
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto bus = detail::getBus(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)
{ {
constexpr auto bus = detail::getBus(pin); if constexpr (pin != P::NONE) {
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto bus = detail::getBus(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)
{ {
constexpr auto bus = detail::getBus(pin); if constexpr (pin != P::NONE) {
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto bus = detail::getBus(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()
{ {
constexpr auto bus = detail::getBus(pin); if constexpr (pin != P::NONE) {
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto bus = detail::getBus(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()
{ {
constexpr auto bus = detail::getBus(pin); if constexpr (pin != P::NONE) {
constexpr auto pinBit = detail::getPinBit(pin); constexpr auto bus = detail::getBus(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)
return true;
if (*pinRegPtr >> pinBit & 1)
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();
} }
@@ -354,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 [[gnu::always_inline]] static inline void write([[maybe_unused]] const std::uint8_t value)
{ {
detail::reg_ptr_t portRegPtr = detail::getRegPtr<detail::getPORT(port)>(); if constexpr (port != Bus::NONE) {
*portRegPtr = value; auto portRegPtr = detail::getRegPtr<detail::getPORT(port)>();
*portRegPtr = value;
}
} }
static inline void invert() FORCE_INLINE [[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 [[gnu::always_inline]] static inline std::uint8_t read()
{ {
detail::reg_ptr_t pinRegPtr = detail::getRegPtr<detail::getPIN(port)>(); if constexpr (port != Bus::NONE) {
auto pinRegPtr = detail::getRegPtr<detail::getPIN(port)>();
return *pinRegPtr; return *pinRegPtr;
}
return 0x00;
} }
inline Port &operator=(const uint8_t value) FORCE_INLINE [[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();
} }
@@ -426,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