#pragma once #include #include #include ////////////////////////////////////////////////////////////////////////// // Preprocessor defines #if defined(__AVR_ATmega32__) || defined(__AVR_ATmega32A__) || defined(__AVR_ATmega644P__) || \ defined(__AVR_ATmega1284P__) #define GPIO_32 #endif #if defined(__AVR_ATmega8__) || defined(__AVR_ATmega8A__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega328P__) #define GPIO_23 #endif #if defined(__AVR_ATtiny13A__) || defined(__AVR_ATtiny85__) #define GPIO_6 #endif #if defined(__AVR_ATmega644P__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega168A__) || \ defined(__AVR_ATmega328P__) || defined(__AVR_ATtiny13A__) || defined(__AVR_ATtiny85__) #define HARDWARE_TOGGLE #endif #ifdef GPIO_32 #define PORT_A_AVAILABLE #endif #if defined(GPIO_32) || defined(GPIO_23) || defined(GPIO_6) #define PORT_B_AVAILABLE #endif #if defined(GPIO_32) || defined(GPIO_23) #define PORT_C_AVAILABLE #define PORT_D_AVAILABLE #define PIN_B6_AVAILABLE #define PIN_B7_AVAILABLE #endif #if defined(GPIO_32) #define PIN_C7_AVAILABLE #endif ////////////////////////////////////////////////////////////////////////// // Library implementation namespace io { enum class Dir { IN, OUT }; enum class P { #ifdef PORT_A_AVAILABLE A0 = 0x00, A1 = 0x01, A2 = 0x02, A3 = 0x03, A4 = 0x04, A5 = 0x05, A6 = 0x06, A7 = 0x07, #endif #ifdef PORT_B_AVAILABLE B0 = 0x10, B1 = 0x11, B2 = 0x12, B3 = 0x13, B4 = 0x14, B5 = 0x15, #ifdef PIN_B6_AVAILABLE B6 = 0x16, #endif #ifdef PIN_B7_AVAILABLE B7 = 0x17, #endif #endif #ifdef PORT_C_AVAILABLE C0 = 0x20, C1 = 0x21, C2 = 0x22, C3 = 0x23, C4 = 0x24, C5 = 0x25, C6 = 0x26, #ifdef PIN_C7_AVAILABLE C7 = 0x27, #endif #endif #ifdef PORT_D_AVAILABLE D0 = 0x30, D1 = 0x31, D2 = 0x32, D3 = 0x33, D4 = 0x34, D5 = 0x35, D6 = 0x36, D7 = 0x37, #endif }; enum class Bus { #ifdef PORT_A_AVAILABLE A = 0x00, #endif #ifdef PORT_B_AVAILABLE B = 0x01, #endif #ifdef PORT_C_AVAILABLE C = 0x02, #endif #ifdef PORT_D_AVAILABLE D = 0x03, #endif }; ////////////////////////////////////////////////////////////////////////// // Implementation details namespace detail { /* 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; 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 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. */ #pragma push_macro("_MMIO_BYTE") #undef _MMIO_BYTE #define _MMIO_BYTE #ifdef PORT_A_AVAILABLE static constexpr uintptr_t PORT_A_DIR_REG_ADDR = DDRA; static constexpr uintptr_t PORT_A_OUTPUT_REG_ADDR = PORTA; static constexpr uintptr_t PORT_A_INPUT_REG_ADDR = PINA; #else 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_INPUT_REG_ADDR = 0; #endif #ifdef PORT_B_AVAILABLE static constexpr uintptr_t PORT_B_DIR_REG_ADDR = DDRB; static constexpr uintptr_t PORT_B_OUTPUT_REG_ADDR = PORTB; static constexpr uintptr_t PORT_B_INPUT_REG_ADDR = PINB; #else 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_INPUT_REG_ADDR = 0; #endif #ifdef PORT_C_AVAILABLE static constexpr uintptr_t PORT_C_DIR_REG_ADDR = DDRC; static constexpr uintptr_t PORT_C_OUTPUT_REG_ADDR = PORTC; static constexpr uintptr_t PORT_C_INPUT_REG_ADDR = PINC; #else 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_INPUT_REG_ADDR = 0; #endif #ifdef PORT_D_AVAILABLE static constexpr uintptr_t PORT_D_DIR_REG_ADDR = DDRD; static constexpr uintptr_t PORT_D_OUTPUT_REG_ADDR = PORTD; static constexpr uintptr_t PORT_D_INPUT_REG_ADDR = PIND; #else 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_INPUT_REG_ADDR = 0; #endif #pragma pop_macro("_MMIO_BYTE") static constexpr auto getBus(const P pin) { // Upper 4 bits of pin encode which port this pin is on uint8_t port = static_cast(pin) >> 4 & 0x0F; return static_cast(port); } static constexpr auto getPinBit(const P pin) { // Lower 4 bits of pin encode which pin bit it is uint8_t pinBit = static_cast(pin) & 0x0F; return pinBit; } static constexpr auto getDDR(const Bus bus) { switch (static_cast(bus)) { case 0: // Bus::A return PORT_A_DIR_REG_ADDR; case 1: // Bus::B return PORT_B_DIR_REG_ADDR; case 2: // Bus::C return PORT_C_DIR_REG_ADDR; case 3: // Bus::D return PORT_D_DIR_REG_ADDR; } } static constexpr auto getPORT(const Bus bus) { switch (static_cast(bus)) { case 0: // Bus::A return PORT_A_OUTPUT_REG_ADDR; case 1: // Bus::B return PORT_B_OUTPUT_REG_ADDR; case 2: // Bus::C return PORT_C_OUTPUT_REG_ADDR; case 3: // Bus::D return PORT_D_OUTPUT_REG_ADDR; } } static constexpr auto getPIN(const Bus bus) { switch (static_cast(bus)) { case 0: // Bus::A return PORT_A_INPUT_REG_ADDR; case 1: // Bus::B return PORT_B_INPUT_REG_ADDR; case 2: // Bus::C return PORT_C_INPUT_REG_ADDR; case 3: // Bus::D return PORT_D_INPUT_REG_ADDR; } } using reg_ptr_t = volatile uint8_t *; template static inline reg_ptr_t getRegPtr() { return reinterpret_cast(Address); } template