#include #include #include //#define USE_INTERRUPTS // > Header: isr.h namespace Interrupt { struct ScopedInterruptDisable { ScopedInterruptDisable() { cli(); } ~ScopedInterruptDisable() { SREG = save; } private: const uint8_t save{SREG}; }; template struct volatile_atomic; template requires (sizeof(T) > 1) struct volatile_atomic { constexpr volatile_atomic() = default; volatile_atomic(const volatile_atomic&) = delete; void operator=(const volatile_atomic&) = delete; void operator=(const T v) { mValue = v; } void operator=(const T v) volatile { ScopedInterruptDisable di; mValue = v; } void operator++() volatile { ScopedInterruptDisable di; mValue = mValue + 1; // <> read-modify-write: possible lost-update -> DisableInterrupts } void operator++() { ++mValue; } void operator+=(const T v) { mValue += v; } void operator+=(const T v) volatile { ScopedInterruptDisable di; mValue = mValue + v; // <> read-modify-write: possible lost-update -> DisableInterrupt } operator T() const { return mValue; } operator T() const volatile { ScopedInterruptDisable di; return mValue; } template T modify(auto f) volatile { if constexpr(useInterrupts) { ScopedInterruptDisable di; const T prev = mValue; mValue = f(prev); return prev; } else { const T prev = mValue; mValue = f(prev); return prev; } } private: T mValue{}; }; template requires (sizeof(T) == 1) struct volatile_atomic { constexpr volatile_atomic() = default; volatile_atomic(const volatile_atomic&) = delete; void operator=(const volatile_atomic&) = delete; void operator=(const T v) { mValue = v; } void operator=(const T v) volatile { mValue = v; } void operator++() { ++mValue; } void operator+=(const T v) { mValue += v; } void operator+=(const T v) volatile { ScopedInterruptDisable di; mValue += v; } void operator++() volatile { ScopedInterruptDisable di; ++mValue; // <> read-modify-write: possible lost-update -> DisableInterrupts } operator T() const { return mValue; } operator T() const volatile { return mValue; } template T modify(auto f) volatile { if constexpr(useInterrupts) { ScopedInterruptDisable di; const T prev = mValue; mValue = f(prev); return prev; } else { const T prev = mValue; mValue = f(prev); return prev; } } void on(const T v, auto f) volatile { ScopedInterruptDisable di; if (mValue == v) { f(mValue); } } void on(const T v, auto f) { if (mValue == v) { f(mValue); } } private: T mValue{}; }; } // < // > Header: meta.h namespace Meta { template struct List; template struct first { using type = F; }; template using first_t = first::type; template struct all_same { using f = first::type; static inline constexpr bool value = (std::is_same_v && ...); }; template static inline constexpr bool all_same_v = all_same::value; template struct make_list; template struct make_list> { using type = List...>; }; template using make_list_t = make_list::type; } // < // > Header: math.h namespace Math { template inline constexpr bool isPowerof2(const T v) { return v && ((v & (v - 1)) == 0); } template inline consteval size_t log2(const T v) { for(size_t i{}; i < size_t(-1); ++i) { if (v == (1 << i)) return i; } std::unreachable(); } } // < // > Header: concepts.h namespace Concepts { template concept Pin = requires(P p) { P::init(); P::pullup(); P::read(); }; } // < // > Header: mcu.h struct A; struct B; struct C; template struct Output : std::integral_constant {}; template struct Address; template struct Gpio { using value_t = ValueType; static inline auto& port{Address>::value}; }; template<> struct Address> { static inline auto* const& value{&PORTA}; }; template<> struct Address> { static inline auto* const& value{&PORTC}; }; // < // > Header: code.h template struct Gray; template typename L, typename ValueType> requires((N < (8 * sizeof(ValueType))) && ...) struct Gray...>, ValueType> { using value_t = ValueType; static inline constexpr size_t size{sizeof...(N)}; static constexpr value_t binary(const value_t gray) { return [](std::index_sequence, const value_t g){ return (grayBitToXor(g) ^ ...); }(std::make_index_sequence{}, gray); } private: using s_value_t = std::make_signed_t; template static inline constexpr value_t bitMask{1 << B}; template static inline constexpr value_t xorMask{(1 << K) - 1}; template static constexpr s_value_t grayBitToXor(const value_t b) { return (b & bitMask) ? xorMask : value_t{0}; } static constexpr value_t spreadBits(const uint8_t c){ std::array a{(1 << N)...}; value_t r{}; for(uint8_t i{}; i < (sizeof(value_t) * 8); ++i) { if (c & (1U << i)) { r |= a[size - i - 1]; } } return r; } static_assert([]{ std::array gray; for(uint8_t i{}; auto& g : gray) { uint8_t sg = i ^ (i >> 1); g = spreadBits(sg); ++i; } for(uint8_t i{}; auto g : gray) { if (binary(g) != i++) return false; } return true; }(), "Gray [test 1]: wrong encoding to binary"); }; // < // > Header: gpio.h template> struct Pin { using gpio_t = GPIO; using output_t = Output; using value_t = GPIO::value_t; static inline constexpr uint8_t number{N}; static inline constexpr value_t mask{1U << N}; static inline auto& port{GPIO::port}; static void init() { if constexpr(Output::value) { port->DIR |= mask; } } static void pullup() { pinctl() |= PORT_PULLUPEN_bm; } static bool read() requires(!Output::value) { return port->IN & mask; } private: static auto& pinctl() { return *(&port->PIN0CTRL + N); } }; template struct PinGroup; template requires (Meta::all_same_v) && (Meta::all_same_v) struct PinGroup { using pin0_t = Meta::first_t; using gpio_t = pin0_t::gpio_t; using value_t = gpio_t::value_t; using pinNumbers_t = Meta::List...>; static inline constexpr value_t mask{((1U << Pins::number) | ...)}; static inline auto& port{gpio_t::port}; using output_t = pin0_t::output_t; static void init() { if constexpr(output_t::value) { port.DIR |= mask; } } static void pullup() { port->PINCONFIG = PORT_PULLUPEN_bm; port->PINCTRLUPD = mask; } static value_t read() requires(!output_t::value) { return port->IN & mask; } static value_t raw() requires(!output_t::value) { return port->IN; } }; template requires (!Meta::all_same_v) && (Meta::all_same_v) struct PinGroup { static_assert(Meta::all_same_v); using value_t = Meta::first_t::value_t; using s_value_t = std::make_signed_t; using pinNumbers_t = Meta::make_list_t>; using output_t = Meta::first_t::output_t; static void init() { (Pins::init(), ...); } static void pullup() { (Pins::pullup(), ...); } template static inline constexpr s_value_t xorgen(const bool b) { return b ? ((1U << (I + 1)) - 1) : 0; } static inline constexpr s_value_t binaryFromGray() requires(!output_t::value) { return [](std::index_sequence){ return (xorgen(Pins::read()) ^ ...); }(std::make_index_sequence{}); } }; // < // > Header: rotary.h template requires(Math::isPowerof2(N)) struct TransitionsPerDent : std::integral_constant { static inline constexpr uint8_t shift = Math::log2(N); }; template struct UseInterrupts : std::integral_constant {}; template struct AccumulatorType { using type = T; }; template struct ValueType { using type = T; }; template, typename UuseInts = UseInterrupts> struct RotaryEncoder; template requires (std::is_signed_v && std::is_integral_v) struct RotaryEncoder, ValueType, Trans, UseInterrupts> { static void init() { PGroup::init(); PGroup::pullup(); } class ASync { static void update() { const auto newState = []{ if constexpr(requires{PGroup::binaryFromGray();}) { return PGroup::binaryFromGray(); } else { using gray = Gray; return gray::binary(PGroup::raw()); } }(); const int8_t diff = mLastState - newState; if (diff & 0b01) { // one-step change (maybe three or more) mLastState = newState; mAccu += (diff & 0b10) - 1; // map: 0,2 -> -1,1 } } static inline Interrupt::volatile_atomic mAccu{}; public: static void isr() { update(); } static inline volatile auto& vAccu{mAccu}; }; static void periodic() requires(!useInterrupts) { ASync::isr(); } static value_t value() { if constexpr(Trans::value != 1) { mValue += ASync::vAccu.template modify([](const accu_t v){ return v & (Trans::value - 1); }) >> Trans::shift; } else { mValue += ASync::vAccu.template modify([](const accu_t v){ return 0; }); } return mValue; } private: static inline uint8_t mLastState{}; static inline value_t mValue{}; }; // < // > main.cc using pa5 = Pin<5, Gpio>; using pa6 = Pin<6, Gpio>; using pc0 = Pin<0, Gpio>; using rotaryPins = PinGroup; #ifdef USE_INTERRUPTS using rot = RotaryEncoder, ValueType, TransitionsPerDent<2>, UseInterrupts>; #else using rot = RotaryEncoder, ValueType, TransitionsPerDent<2>, UseInterrupts>; #endif #define LEDS_DDR PORTD_DIR #define LEDS PORTD_OUT int main() { #ifdef USE_INTERRUPTS TCB0.INTCTRL = TCB_CAPT_bm; TCB0.CCMP = 4000; // 1ms TCB0.CTRLA = TCB_ENABLE_bm; sei(); #endif LEDS_DDR = 0xff; rot::init(); while(true) { #ifndef USE_INTERRUPTS rot::periodic(); #endif LEDS = rot::value(); } } #ifdef USE_INTERRUPTS ISR(TCB0_INT_vect) { TCB0.INTFLAGS = TCB_CAPT_bm; rot::ASync::isr(); } #endif // <