/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */


#ifndef INCLUDE_ECXX_PLATFORM_SPECIFIC_APPLICATION_CPP
#error "this file cannot be compiled directly"
#endif
#undef INCLUDE_ECXX_PLATFORM_SPECIFIC_APPLICATION_CPP


#include <eCxx/Platform/AVR/PinMapping.h>

#include "../AVRCommon/Internal.h"

#include "SPI_Inline.h"
#include "UART_Inline.h"

#ifdef ECXX_AVR_HAVE_HARDWARE_USB
#include "USBDriver.h"
#endif


//
// Include the AVR TIMER0 ISR routine implementation
//
#define INCLUDE_ECXX_PLATFORM_AVR_ISR_TIMER0_CPP
#include "ISR_Timer0.cpp"


namespace eCxx {


//
// According to the datasheets, ADPS[2:0] does indeed correspond to bit 2:0
//
#if ADPS2 != 2 || ADPS1 != 1 || ADPS0 != 0
#error "ADPS2, ADPS1, and ADPS0 do not corespond to bit 2, 1, and 0"
#endif


//
// Internal constants
//
static constexpr uint8_t ActivityIndicatorLED_Pin = AVR::BuiltinLED;


//
// Disable the watchdog timer early during program startup
//
void __disable_wdt_on_program_startup() __ECXX_ATTR_NAKED __ECXX_ATTR_SEC_INIT3 __ECXX_ATTR_USED;
void __disable_wdt_on_program_startup()
{
#ifdef MCUCSR
    MCUCSR = 0;
#else
    MCUSR = 0;
#endif
    wdt_disable();
}


//
// Internal utility functions
//
#ifdef __AVR_HAVE_ELPM__
    #define ECXX_PGM_READ_BYTE pgm_read_byte_far
#else
    #define ECXX_PGM_READ_BYTE pgm_read_byte_near
#endif

static inline uint8_t pinNumberToPortNumber(uint8_t pin) { return ECXX_PGM_READ_BYTE( eCxx::PGM_PTR(pin_to_pnum,    pin).p ); }
static inline uint8_t pinNumberToBitmask   (uint8_t pin) { return ECXX_PGM_READ_BYTE( eCxx::PGM_PTR(pin_to_bitmask, pin).p ); }


static inline uint8_t pinNumberToTimerNumber(uint8_t pin)
{
    // Walk through the groups to find the timer number for the given pin
    for(size_t groupCnt = 0; groupCnt < eCxx::arraySize(pwm_pins); ++groupCnt) {
        // Check against the pin number ranges
        if(pin < minPWMPinNumber[groupCnt] || pin > maxPWMPinNumber[groupCnt]) continue;
        // Get and return the timer number
        const uint8_t pinAdj = pin - minPWMPinNumber[groupCnt];
        return pwm_pins[groupCnt][pinAdj][1]; // pwm_pins[group][set][pin/timer]
    }

    // No timer found
    return 0;
}


//
// Instantiate the UART object(s)
//
#if ( defined(UBRRH) && defined(UBRRL) ) || ( defined(UBRR0H) && defined(UBRR0L) )

inline UART* uart0_instance()
{
#if defined(UBRRH) && defined(UBRRL)
    static UART _uart0( &UBRRH,  &UBRRL,  &UCSRA,  &UCSRB,  &UCSRC,  &UDR  );
#else
    static UART _uart0( &UBRR0H, &UBRR0L, &UCSR0A, &UCSR0B, &UCSR0C, &UDR0 );
#endif
    return &_uart0;
};

inline void uart0_rxComplete_isr()
{ uart0_instance()->rxComplete_isr(); }

inline void uart0_udrEmpty_isr()
{ uart0_instance()->udrEmpty_isr(); }

#endif


#if defined(UBRR1H) && defined(UBRR1L)

inline UART* uart1_instance()
{
    static UART _uart1( &UBRR1H, &UBRR1L, &UCSR1A, &UCSR1B, &UCSR1C, &UDR1 );
    return &_uart1;
};

inline void uart1_rxComplete_isr()
{ uart1_instance()->rxComplete_isr(); }

inline void uart1_udrEmpty_isr()
{ uart1_instance()->udrEmpty_isr(); }

#endif


#if defined(UBRR2H) && defined(UBRR2L)

inline UART* uart2_instance()
{
    static UART _uart2( &UBRR2H, &UBRR2L, &UCSR2A, &UCSR2B, &UCSR2C, &UDR2 );
    return &_uart2;
};

inline void uart2_rxComplete_isr()
{ uart2_instance()->rxComplete_isr(); }

inline void uart2_udrEmpty_isr()
{ uart2_instance()->udrEmpty_isr(); }

#endif


#if defined(UBRR3H) && defined(UBRR3L)

inline UART* uart3_instance()
{
    static UART _uart3( &UBRR3H, &UBRR3L, &UCSR3A, &UCSR3B, &UCSR3C, &UDR3 );
    return &_uart3;
};

inline void uart3_rxComplete_isr()
{ uart3_instance()->rxComplete_isr(); }

inline void uart3_udrEmpty_isr()
{ uart3_instance()->udrEmpty_isr(); }

#endif


//
// Instantiate the SPI object(s)
//
inline SPI* spi0_instance()
{
#if defined(ECXX_AVR_HAVE_MULTIPLE_SPI_INTERFACES) && defined(SPCR0) && defined(SPDR0) && defined(SPSR0)
    static SPI _spi0( 0, &SPCR0, &SPDR0, &SPSR0 );
#else
    static SPI _spi0;
#endif
    return &_spi0;
};


#if defined(SPCR1) && defined(SPDR1) && defined(SPSR1)

inline SPI* spi1_instance()
{
    static SPI _spi1( 1, &SPCR1, &SPDR1, &SPSR1 );
    return &_spi1;
};

#endif


//
// A standard AVR application
//
void Application::disablePWMOutput(uint8_t pin)
{
    // Get the timer number
    const uint8_t timer = pinNumberToTimerNumber(pin);

    // Disable PWM output
    switch(timer) {
        #if defined(OCR1A)
        case ECXX_TIMER1A:
            TCCR1A &= ~_BV(COM1A1);
            break;
        #endif
        #if defined(OCR1B)
        case ECXX_TIMER1B:
            TCCR1A &= ~_BV(COM1B1);
            break;
        #endif
        #if defined(OCR1C)
        case ECXX_TIMER1C:
            TCCR1A &= ~_BV(COM1C1);
            break;
        #endif
        #if defined(OCR2)
        case ECXX_TIMER2:
            TCCR2 &= ~_BV(COM21);
            break;
        #endif
        #if defined(OCR2A)
        case ECXX_TIMER2A:
            TCCR2A &= ~_BV(COM2A1);
            break;
        #endif
        #if defined(OCR2B)
        case ECXX_TIMER2B:
            TCCR2A &= ~_BV(COM2B1);
            break;
        #endif
        #if defined(OCR3A)
        case ECXX_TIMER3A:
            TCCR3A &= ~_BV(COM3A1);
            break;
        #endif
        #if defined(OCR3B)
        case ECXX_TIMER3B:
            TCCR3A &= ~_BV(COM3B1);
            break;
        #endif
        #if defined(OCR3C)
        case ECXX_TIMER3C:
            TCCR3A &= ~_BV(COM3C1);
            break;
        #endif
        #if defined(OCR4A)
        case ECXX_TIMER4A:
            TCCR4A &= ~_BV(COM4A1);
            break;
        #endif
        #if defined(OCR4B)
        case ECXX_TIMER4B:
            TCCR4A &= ~_BV(COM4B1);
            break;
        #endif
        #if defined(OCR4C) && defined(COM4C1)
        case ECXX_TIMER4C:
            TCCR4A &= ~_BV(COM4C1);
            break;
        #endif
        #if defined(OCR4D) && defined(COM4D1)
        case ECXX_TIMER4D:
            TCCR4C &= ~_BV(COM4D1);
            break;
        #endif
        #if defined(OCR5A)
        case ECXX_TIMER5A:
            TCCR5A &= ~_BV(COM5A1);
            break;
        #endif
        #if defined(OCR5B)
        case ECXX_TIMER5B:
            TCCR5A &= ~_BV(COM5B1);
            break;
        #endif
        #if defined(OCR5C)
        case ECXX_TIMER5C:
            TCCR5A &= ~_BV(COM5C1);
            break;
        #endif
    }
}


void Application::getSoftUARTBitDelayUS(uint16_t& txBit, uint16_t& rxBit, uint16_t& rxCtr, uint32_t baudrate) noexcept
{
    // Get the CPU frequency
    constexpr uint32_t cpuFreq = Application::getCPUFrequency();

    // Each loop in the __delay_loop4() function will take 4 cycles to execute
    baudrate *= 4;

    // Calculate the bit delay
    const uint16_t bitDelay = ( cpuFreq + baudrate / 2 ) / baudrate;

    // Store the bit delay
    txBit = bitDelay - 4; // 4 = approximated overhead factor
    rxBit = bitDelay - 4; // 4 = approximated overhead factor
    rxCtr = bitDelay / 8;
}


void Application::setPinMode(uint8_t pin, PinMode mode)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the port number and bitmask
    const uint8_t pn = pinNumberToPortNumber(pin);
    const uint8_t bm = pinNumberToBitmask   (pin);

    // Set pin mode
    switch(mode) {
        case PinMode::Output      : *pnum_to_DDR[pn] |=  bm;                           break;
        case PinMode::InputPullUp : *pnum_to_DDR[pn] &= ~bm; *pnum_to_PORT[pn] |=  bm; break;
        default                   : *pnum_to_DDR[pn] &= ~bm; *pnum_to_PORT[pn] &= ~bm; break;
    }

    // Disable PWM output on the pin as needed
    if(_pwmEnabled) disablePWMOutput(pin);
}


void Application::digitalWrite(uint8_t pin, bool bit)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the port number and bitmask
    const uint8_t pn = pinNumberToPortNumber(pin);
    const uint8_t bm = pinNumberToBitmask   (pin);

    // Write bit
    if(bit) *pnum_to_PORT[pn] |=  bm;
    else    *pnum_to_PORT[pn] &= ~bm;
}


bool Application::digitalRead(uint8_t pin)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return false;

    // Get the port number and bitmask
    const uint8_t pn = pinNumberToPortNumber(pin);
    const uint8_t bm = pinNumberToBitmask   (pin);

    // Read bit
    return *pnum_to_PIN[pn] & bm;
}


void Application::analogWrite(uint8_t pin, uint8_t val)
{
    // Check if PWM is not enabled
    if(!_pwmEnabled) return;

    // Get the timer number
    const uint8_t timer = pinNumberToTimerNumber(pin);

    // Enable PWM output and set its duty cycle (process according to the timer number)
    switch(timer) {
        #if defined(OCR1A)
        case ECXX_TIMER1A:
            TCCR1A |= _BV(COM1A1);
            OCR1A   = val;
            break;
        #endif
        #if defined(OCR1B)
        case ECXX_TIMER1B:
            TCCR1A |= _BV(COM1B1);
            OCR1B   = val;
            break;
        #endif
        #if defined(OCR1C)
        case ECXX_TIMER1C:
            TCCR1A |= _BV(COM1C1);
            OCR1C   = val;
            break;
        #endif
        #if defined(OCR2)
        case ECXX_TIMER2:
            TCCR2 |= _BV(COM21);
            OCR2   = val;
            break;
        #endif
        #if defined(OCR2A)
        case ECXX_TIMER2A:
            TCCR2A |= _BV(COM2A1);
            OCR2A   = val;
            break;
        #endif
        #if defined(OCR2B)
        case ECXX_TIMER2B:
            TCCR2A |= _BV(COM2B1);
            OCR2B   = val;
            break;
        #endif
        #if defined(OCR3A)
        case ECXX_TIMER3A:
            TCCR3A |= _BV(COM3A1);
            OCR3A   = val;
            break;
        #endif
        #if defined(OCR3B)
        case ECXX_TIMER3B:
            TCCR3A |= _BV(COM3B1);
            OCR3B   = val;
            break;
        #endif
        #if defined(OCR3C)
        case ECXX_TIMER3C:
            TCCR3A |= _BV(COM3C1);
            OCR3C   = val;
            break;
        #endif
        #if defined(OCR4A)
        case ECXX_TIMER4A:
            TCCR4A |= _BV(COM4A1);
            OCR4A   = val;
            break;
        #endif
        #if defined(OCR4B)
        case ECXX_TIMER4B:
            TCCR4A |= _BV(COM4B1);
            OCR4B   = val;
            break;
        #endif
        #if defined(OCR4C) && defined(COM4C1)
        case ECXX_TIMER4C:
            TCCR4A |= _BV(COM4C1);
            OCR4C   = val;
            break;
        #endif
        #if defined(OCR4D) && defined(COM4D1)
        case ECXX_TIMER4D:
            TCCR4C |=  _BV(COM4D1);
            TCCR4C &= ~_BV(COM4D0); // Ensure the inverting output is not activated
            OCR4D   = val;
            break;
        #endif
        #if defined(OCR5A)
        case ECXX_TIMER5A:
            TCCR5A |= _BV(COM5A1);
            OCR5A   = val;
            break;
        #endif
        #if defined(OCR5B)
        case ECXX_TIMER5B:
            TCCR5A |= _BV(COM5B1);
            OCR5B   = val;
            break;
        #endif
        #if defined(OCR5C)
        case ECXX_TIMER5C:
            TCCR5A |= _BV(COM5C1);
            OCR5C   = val;
            break;
        #endif
    }
}


uint16_t Application::analogRead(uint8_t pin)
{
    // Check if the pin number is within range
    const bool specialChannel = (pin == AVR::A_TempSenseK || pin == AVR::A_Bandgap);

    if(!specialChannel) {
        if(pin < minADCPinNumber || pin > maxADCPinNumber) return 0;
        pin -= minADCPinNumber;
    }

#ifdef MUX5
    // Clear MUX5 first
    ADCSRB &= ~_BV(MUX5);

    // Determine whether to read from channels 0 to 7 or 8 to 15
    if(!specialChannel && pin > 7) {
        if(adcPortHigh == ECXX_NOT_ADC) return 0;
        ADCSRB |= _BV(MUX5);
        pin    &= 7;
    }
#endif

    // Temperature measurement
    if(pin == AVR::A_TempSenseK) {
#if defined(__AVR_ATmega48__ ) || defined(__AVR_ATmega48A__ ) || defined(__AVR_ATmega48P__ ) || defined(__AVR_ATmega48PA__ ) || defined(__AVR_ATmega48PB__ ) || \
    defined(__AVR_ATmega88__ ) || defined(__AVR_ATmega88A__ ) || defined(__AVR_ATmega88P__ ) || defined(__AVR_ATmega88PA__ ) || defined(__AVR_ATmega88PB__ ) || \
    defined(__AVR_ATmega168__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega168PB__) || \
    defined(__AVR_ATmega328__) ||                                defined(__AVR_ATmega328P__)                                 || defined(__AVR_ATmega328PB__)
        // Select the internal 1.1 V reference voltage, right-adjusted result, and the temperature sensor channel
        ADCSRA &= ~_BV(ADEN);
        ADMUX   =   0b11000000 | 0b00001000;
        ADCSRA |=  _BV(ADEN);
#elif defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)
        // Select the internal 2.56 V reference voltage, right-adjusted result, and the temperature sensor channel
        ADCSRA &= ~_BV(ADEN);
        ADCSRB |=  _BV(MUX5);
        ADMUX   =   0b11000000 | 0b00000111;
        ADCSRA |=  _BV(ADEN);
#else
        // Temperature measurement is not supported by the MCU
        return 0;
#endif
        // Perform a dummy conversion to ensure the ADC is properly initialized
        delayMS(1);
        ADCSRA |= _BV(ADSC);
        while( ADCSRA & _BV(ADSC) ) Application::yield();
    }

    // Bandgap voltage measurement
    else if(pin == AVR::A_Bandgap) {
#ifdef MUX4
        // Select the user-selected reference voltage, right-adjusted result, and the Vbandgap channel
        ADCSRA &= ~_BV(ADEN);
    #ifdef MUX5
        ADCSRB &= ~_BV(MUX5);
    #endif
        ADMUX   =  _adcReferenceVoltage | 0b00011110;
        ADCSRA |=  _BV(ADEN);
#else
        // Select the user-selected reference voltage, right-adjusted result, and the Vbandgap channel
        ADCSRA &= ~_BV(ADEN);
        ADMUX   =  _adcReferenceVoltage | 0b00001110;
        ADCSRA |=  _BV(ADEN);
#endif
        // Perform a dummy conversion to ensure the ADC is properly initialized
        delayMS(1);
        ADCSRA |= _BV(ADSC);
        while( ADCSRA & _BV(ADSC) ) Application::yield();
    }

    // Pin voltage measurement
    else {
        // Select the user-selected reference voltage, right-adjusted result, and the user-selected channel
        //
        // REFS1      REFS0      ADLAR      XXX      XXX      MUX2      MUX1      MUX0
        // _adcReferenceVoltage  0          0        0        pin2      pin1      pin0
        //
        // NOTE : _adcReferenceVoltage is already << 6 by Application::setADCReferenceVoltage()
        //
        ADMUX = _adcReferenceVoltage | pin;
    }

    // Ensure all the setting above are settled before continuing
    delayMS(1);

    // Start the conversion
    ADCSRA |= _BV(ADSC);

    // Wait until the conversion is completed
    while( ADCSRA & _BV(ADSC) ) Application::yield();

    // Get the result
    const uint8_t low  = ADCL;
    const uint8_t high = ADCH;

    // Calculate the ADC value
    const uint16_t adcValue = ( static_cast<uint16_t>(high) << 8 ) | low;

    // Return the result - temperature sensor
    if(pin == AVR::A_TempSenseK) {
#if defined(__AVR_ATmega48__ ) || defined(__AVR_ATmega48A__ ) || defined(__AVR_ATmega48P__ ) || defined(__AVR_ATmega48PA__ ) || defined(__AVR_ATmega48PB__ ) || \
    defined(__AVR_ATmega88__ ) || defined(__AVR_ATmega88A__ ) || defined(__AVR_ATmega88P__ ) || defined(__AVR_ATmega88PA__ ) || defined(__AVR_ATmega88PB__ ) || \
    defined(__AVR_ATmega168__) || defined(__AVR_ATmega168A__) || defined(__AVR_ATmega168P__) || defined(__AVR_ATmega168PA__) || defined(__AVR_ATmega168PB__) || \
    defined(__AVR_ATmega328__) ||                                defined(__AVR_ATmega328P__)                                 || defined(__AVR_ATmega328PB__)
        return static_cast<uint32_t>(adcValue * 100) - 4500 + 27315;
#elif defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)
        return adcValue * 100;
#else
        // Temperature measurement is not supported by the MCU
        return 0;
#endif
    }

    // Return the result - others
    return adcValue;
}


uint32_t Application::micros() noexcept
{
    uint32_t m;
    uint8_t  t;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        m = t0OVFCount;

#if defined(TCNT0)
        t = TCNT0;
#elif defined(TCNT0L)
        t = TCNT0L;
#else
    #error "unsupported Timer0 'Counter' register name"
#endif

#if defined(TIFR)
        if( ( TIFR  & _BV(TOV0) ) && (t < 255) ) ++m; // In case interrupt occurring during the function call
#elif defined(TIFR0)
        if( ( TIFR0 & _BV(TOV0) ) && (t < 255) ) ++m; // In case interrupt occurring during the function call
#else
    #error "unsupported Timer0 'Interrupt Flag' register name"
#endif
    }

    return ( (m << 8) + t ) * Application::_microsMultFactor;
}


uint32_t Application::millis() noexcept
{
    uint32_t m;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        m = t0Millis;
    }

    return m;
}


TWIMaster& Application::twiMaster()
{ return TWIMaster::instance(); }


TWISlave& Application::twiSlave()
{ return TWISlave::instance(); }


Application::OWIMaster& Application::owiMaster()
{
    static OWIMaster _owimaster;
    return _owimaster;
}


bool Application::attachInterruptArg(uint8_t pin, VoidFuncPtrArg userFunc, void* arg, InterruptMode mode)
{ return AVR::attachInterruptArg(pin, userFunc, arg, mode); }


bool Application::detachInterrupt(uint8_t pin)
{ return AVR::detachInterrupt(pin); }


UART* Application::uart0()
{
#if ( defined(UBRRH) && defined(UBRRL) ) || ( defined(UBRR0H) && defined(UBRR0L) )
    return uart0_instance();
#else
    return nullptr;
#endif
}


UART* Application::uart1()
{
#if defined(UBRR1H) && defined(UBRR1L)
    return uart1_instance();
#else
    return nullptr;
#endif
}


UART* Application::uart2()
{
#if defined(UBRR2H) && defined(UBRR2L)
    return uart2_instance();
#else
    return nullptr;
#endif
}


UART* Application::uart3()
{
#if defined(UBRR3H) && defined(UBRR3L)
    return uart3_instance();
#else
    return nullptr;
#endif
}


SPI* Application::spi0()
{ return spi0_instance(); }


SPI* Application::spi1()
{
#if defined(SPCR1) && defined(SPDR1) && defined(SPSR1)
    return spi1_instance();
#else
    return nullptr;
#endif
}


#ifdef ECXX_AVR_HAVE_HARDWARE_USB

VirtualUART* Application::vuart0()
{
    static VirtualUART vuart0(0);
    return &vuart0;
}


VirtualUART* Application::vuart1()
{
    static VirtualUART vuart1(1);
    return &vuart1;
}


void Application::vuart0MagicBaudrateCallback(VoidCallback cb)
{ setVUART0MagicBaudrateCallback(cb); }


void Application::vuart0DtrLowCallback(VoidCallback cb)
{ setVUART0DtrLowCallback(cb); }


void Application::vuart0ChangeSettingsCallback(VoidCallback cb)
{ setVUARTChangeSettingsCallback(0, cb); }


void Application::vuart1ChangeSettingsCallback(VoidCallback cb)
{ setVUARTChangeSettingsCallback(1, cb); }


void Application::vuart0GetSettings(uint32_t& baudrate, uint8_t& charSize, uint8_t& parity, uint8_t& stopBits)
{ getVUARTSerialSettings(0, baudrate, charSize, parity, stopBits); }


void Application::vuart1GetSettings(uint32_t& baudrate, uint8_t& charSize, uint8_t& parity, uint8_t& stopBits)
{ getVUARTSerialSettings(1, baudrate, charSize, parity, stopBits); }


uint32_t Application::vuart0GetBaudrate()
{ return getVUARTBaudrate(0); }


uint32_t Application::vuart1GetBaudrate()
{ return getVUARTBaudrate(1); }


void Application::vuart0GetRtsDtrState(bool& rts, bool& dtr)
{
    const uint8_t rts_dtr = getVUARTRtsDtrState(0);

    rts = rts_dtr & 0b00000010;
    dtr = rts_dtr & 0b00000001;
}


void Application::vuart1GetRtsDtrState(bool& rts, bool& dtr)
{
    const uint8_t rts_dtr = getVUARTRtsDtrState(1);

    rts = rts_dtr & 0b00000010;
    dtr = rts_dtr & 0b00000001;
}

#endif


AnalogComparatorLite& Application::analogComparatorLite()
{
    static AnalogComparatorLite _acl;
    return _acl;
}


//
// A standard AVR application - fast-digital-read/write
//
void Application::_preparePinFDR(_PinFDR& pinFDR, uint8_t pin) noexcept
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the port number and bitmask
    const uint8_t pn = pinNumberToPortNumber(pin);
    const uint8_t bm = pinNumberToBitmask   (pin);

    // Store the configurations
    pinFDR._sfr = pnum_to_PIN[pn];
    pinFDR._bm  = bm;
}


void Application::_preparePinFDW(_PinFDW& pinFDW, uint8_t pin) noexcept
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the port number and bitmask
    const uint8_t pn = pinNumberToPortNumber(pin);
    const uint8_t bm = pinNumberToBitmask   (pin);

    // Store the configurations
    pinFDW._sfr = pnum_to_PORT[pn];
    pinFDW._bm  = bm;
}


void Application::_preparePinFDX(_PinFDX& pinFDX, uint8_t pin) noexcept
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return;

    // Get the port number and bitmask
    const uint8_t pn = pinNumberToPortNumber(pin);
    const uint8_t bm = pinNumberToBitmask   (pin);

    // Store the configurations
    pinFDX._ddr       = pnum_to_DDR [pn];
    pinFDX._sfrr      = pnum_to_PIN [pn];
    pinFDX._sfrw      = pnum_to_PORT[pn];
    pinFDX._bm        = bm;
    pinFDX._usePullUp = true;

    // Initialize the pin as input with pull-up
    pinFDX.setReadMode();
}


//
// A standard AVR application - low level OWI functions
//
// # Using only AVR's internal pull-up resistor (without adding external pull-up resistor),
//   would result in an unstable and even unusable OWI bus.
// # However, if the internal pull-up is not enabled, then the system would hung if we try
//   to search the bus for slave device(s) while nothing is actually connected to it.
#define ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP

/*
 * This code is written based on the information from:
 *     [AN126] 1-Wire Communication Through Software
 *     https://pdfserv.maximintegrated.com/en/an/AN126.pdf
 */
bool Application::owiBegin(OWIData& owiData, uint8_t pin)
{
    // Check if the pin number is too large
    if(pin > maxDigitalPinNumber) return false;

    // Get the port number and bitmask
    owiData.portNumber = pinNumberToPortNumber(pin);
    owiData.bitmask    = pinNumberToBitmask   (pin);

#ifdef ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP
    // Set pin mode to input with pull-up
    *pnum_to_DDR [owiData.portNumber] &= ~owiData.bitmask;
    *pnum_to_PORT[owiData.portNumber] |=  owiData.bitmask;
#else
    // Set pin mode to input
    *pnum_to_DDR [owiData.portNumber] &= ~owiData.bitmask;
    *pnum_to_PORT[owiData.portNumber] &= ~owiData.bitmask;
#endif

    // Disable PWM output on the pin as needed
    if(_pwmEnabled) disablePWMOutput(pin);

    // Reset and check for the presence of at least one slave (reset twice, just in case)
        owiReset(owiData);
    if( owiReset(owiData) ) return true;

    // No slave was found
    owiEnd(owiData);
    return false;
}


void Application::owiEnd(OWIData& owiData)
{
    // Set pin mode to input
    *pnum_to_DDR [owiData.portNumber] &= ~owiData.bitmask;
    *pnum_to_PORT[owiData.portNumber] &= ~owiData.bitmask;

    // Clear the OWI data
    owiData.portNumber = 0;
    owiData.bitmask    = 0;
}


bool Application::owiReset(const OWIData& owiData)
{
    // Reset bus and check for the presence of any slave
    bool bit = true;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Delay
        delayUS(OWIMaster::Delay_G);

        // Drive bus low and delay
        *pnum_to_PORT[owiData.portNumber] &= ~owiData.bitmask;
        *pnum_to_DDR [owiData.portNumber] |=  owiData.bitmask;
        delayUS(OWIMaster::Delay_H);

        // Release bus and delay
        *pnum_to_DDR [owiData.portNumber] &= ~owiData.bitmask;
#ifdef ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP
        *pnum_to_PORT[owiData.portNumber] |=  owiData.bitmask;
#endif
        delayUS(OWIMaster::Delay_I);

        // Sample bus and delay
        bit = *pnum_to_PIN[owiData.portNumber] & owiData.bitmask;
        delayUS(OWIMaster::Delay_J);
    }

    // Return true if there is at least one slave present
    return !bit;
}


void Application::owiWriteBit0(const OWIData& owiData)
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Drive bus low and delay
        *pnum_to_PORT[owiData.portNumber] &= ~owiData.bitmask;
        *pnum_to_DDR [owiData.portNumber] |=  owiData.bitmask;
        delayUS(OWIMaster::Delay_C);

        // Release bus and delay
        *pnum_to_DDR [owiData.portNumber] &= ~owiData.bitmask;
#ifdef ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP
        *pnum_to_PORT[owiData.portNumber] |=  owiData.bitmask;
#endif
        delayUS(OWIMaster::Delay_D);
    }
}


void Application::owiWriteBit1(const OWIData& owiData)
{
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Drive bus low and delay
        *pnum_to_PORT[owiData.portNumber] &= ~owiData.bitmask;
        *pnum_to_DDR [owiData.portNumber] |=  owiData.bitmask;
        delayUS(OWIMaster::Delay_A);

        // Release bus and delay
        *pnum_to_DDR [owiData.portNumber] &= ~owiData.bitmask;
#ifdef ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP
        *pnum_to_PORT[owiData.portNumber] |=  owiData.bitmask;
#endif
        delayUS(OWIMaster::Delay_B);
    }
}


bool Application::owiReadBit(const OWIData& owiData)
{
    // Read bit
    bool bit = false;

    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
        // Drive bus low and delay
        *pnum_to_PORT[owiData.portNumber] &= ~owiData.bitmask;
        *pnum_to_DDR [owiData.portNumber] |=  owiData.bitmask;
        delayUS(OWIMaster::Delay_A);

        // Release bus and delay
        *pnum_to_DDR [owiData.portNumber] &= ~owiData.bitmask;
#ifdef ECXX_AVR_OWIMASTER_USE_INTERNAL_PULLUP
        *pnum_to_PORT[owiData.portNumber] |=  owiData.bitmask;
#endif
        delayUS(OWIMaster::Delay_E);

        // Sample bus and delay
        bit = *pnum_to_PIN[owiData.portNumber] & owiData.bitmask;
        delayUS(OWIMaster::Delay_F);
    }

    // Return the result
    return bit;
}


//
// A standard AVR application - initialization
//
void Application::initialize(const Application::Config& config)
{
    // Initialize variables
    _pwmEnabled       = config.enablePWM;
    setADCReferenceVoltage();

    t0MillisInc       = config.timer0MillisInc;
    t0FractFInc       = config.timer0FractFInc;
    _microsMultFactor = config.microsMultiplierFactor;

    // Configure the activity indicator LED and turn it off
    setPinMode(ActivityIndicatorLED_Pin, PinMode::Output);
    setActivityIndicatorLED(false);

    // Set Timer0 prescaler to 64
#if defined(__AVR_ATmega64__) || defined(__AVR_ATmega128__)
    TCCR0 |= _BV(CS02);
#elif defined(TCCR0)
    TCCR0 |= _BV(CS01);
    TCCR0 |= _BV(CS00);
#elif defined(TCCR0B)
    TCCR0B |= _BV(CS01);
    TCCR0B |= _BV(CS00);
#else
    #error "unsupported Timer0 prescaler initialization"
#endif

    // Enable Timer0 overflow interrupt
#if defined(TIMSK)
    TIMSK |= _BV(TOIE0);
#elif defined(TIMSK0)
    TIMSK0 |= _BV(TOIE0);
#else
    #error "unsupported Timer0 interrupt initialization"
#endif

    // Configure PWM so that for each 1 MHZ CPU clock, it will produce ~245 Hz PWM frequency
    //     # mode      : 8-bit phase correct
    //     # prescaler : 8
    if(_pwmEnabled) {
        // Walk through the groups
        for(size_t groupCnt = 0; groupCnt < eCxx::arraySize(pwm_pins); ++groupCnt) { // pwm_pins[group][set][pin/timer]
            // Walk through the sets of pin and timer numbers
            for(size_t setCnt = 0; setCnt < eCxx::arraySize(pwm_pins[groupCnt]); ++setCnt) {
                // Process according to the timer number
                switch(pwm_pins[groupCnt][setCnt][1]) {
                    #if defined(OCR1A) || defined(OCR1B) || defined(OCR1C)
                    case ECXX_TIMER1A: /* FALLTHROUGH */
                    case ECXX_TIMER1B: /* FALLTHROUGH */
                    case ECXX_TIMER1C:
                        TCCR1A |= _BV(WGM10);
                        TCCR1B |= _BV(CS11 );
                        break;
                    #endif
                    #if defined(OCR2)
                    case ECXX_TIMER2:
                        TCCR2 |= _BV(WGM20);
                        TCCR2 |= _BV(CS21 );
                        break;
                    #endif
                    #if defined(OCR2A) || defined(OCR2B)
                    case ECXX_TIMER2A: /* FALLTHROUGH */
                    case ECXX_TIMER2B:
                        TCCR2A |= _BV(WGM20);
                        TCCR2B |= _BV(CS21 );
                        break;
                    #endif
                    #if defined(OCR3A) || defined(OCR3B) || defined(OCR3C)
                    case ECXX_TIMER3A: /* FALLTHROUGH */
                    case ECXX_TIMER3B: /* FALLTHROUGH */
                    case ECXX_TIMER3C:
                        TCCR3A |= _BV(WGM30);
                        TCCR3B |= _BV(CS31 );
                        break;
                    #endif
                    #if defined(OCR4A) || defined(OCR4B) || defined(OCR4C)
                    case ECXX_TIMER4A: /* FALLTHROUGH */
                    case ECXX_TIMER4B: /* FALLTHROUGH */
                    case ECXX_TIMER4C:
                        TCCR4A |= _BV(WGM40);
                        TCCR4B |= _BV(CS41 );
                        break;
                    #endif
                    #if defined(OCR5A) || defined(OCR5B) || defined(OCR5C)
                    case ECXX_TIMER5A: /* FALLTHROUGH */
                    case ECXX_TIMER5B: /* FALLTHROUGH */
                    case ECXX_TIMER5C:
                        TCCR5A |= _BV(WGM50);
                        TCCR5B |= _BV(CS51 );
                        break;
                    #endif
                } // switch pwm_pins
            } // for setCnt
        } // for groupCnt
    } // if config.enablePWM

    // Calculate the ADC prescaler so that the ADC will be clocked at a frequency
    // that will produce results with acceptable accuracy
    uint16_t adcPrescaler = (_cpuFrequency / 125000UL);

    --adcPrescaler;
    adcPrescaler |= adcPrescaler >> 1;
    adcPrescaler |= adcPrescaler >> 2;
    adcPrescaler |= adcPrescaler >> 4;
    adcPrescaler |= adcPrescaler >> 8;
    ++adcPrescaler;

         if(adcPrescaler > 128) adcPrescaler = 128;
    else if(adcPrescaler <   2) adcPrescaler =   2;

    uint8_t adps210 = 0;
    while(adcPrescaler >>= 1) ++adps210;

    // Set the ADC prescaler and enable the ADC
    ADCSRA |= adps210;
    ADCSRA |= _BV(ADEN);

    // Configure watchdog timer
    if(config.watchdogTimeout != Application::Config::WDTO::Disabled) {
        const uint8_t tmax = static_cast<uint8_t>(Application::Config::WDTO::_2S);
        const uint8_t wdto = static_cast<uint8_t>(config.watchdogTimeout);
        wdt_enable( (wdto < tmax) ? wdto : tmax );
        wdt_reset();
    }

    // Enable global interrupt
    sei();

#ifdef ECXX_AVR_HAVE_HARDWARE_USB

    // Delay for a while blinking the activity indicator LED
    for(uint8_t i = 0; i < 5; ++i) {
        setActivityIndicatorLED(true ); delayMS(100);
        setActivityIndicatorLED(false); delayMS(100);
    }

    // Start the USB device module
    usbStart();
    wdt_reset();

    // Delay for a while blinking the activity indicator LED
    for(uint8_t i = 0; i < 5; ++i) {
        setActivityIndicatorLED(true ); delayMS(50);
        setActivityIndicatorLED(false); delayMS(50);
    }

#endif
}



} // namespace eCxx


//
// Include the common implementation file
//
#define INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_APPLICATION_CPP
#include "../AVRCommon/Application.cpp"


//
// Include the UART wrapper implementation and the ISR routines implementation
//
#define INCLUDE_ECXX_PLATFORM_AVR_UART_CPP
#include "UART.cpp"

#define INCLUDE_ECXX_PLATFORM_AVR_ISR_UART_CPP
#include "ISR_UART.cpp"

#ifdef ECXX_AVR_HAVE_HARDWARE_USB
    #define INCLUDE_ECXX_PLATFORM_AVR_VIRTUAL_UART_CPP
    #include "VirtualUART.cpp"
#endif


//
// Include the SPI wrapper implementation
//
#define INCLUDE_ECXX_PLATFORM_AVR_SPI_CPP
#include "SPI.cpp"


//
// Include the TWI-master and TWI-slave wrapper implementation
//
#define INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_TWIMASTER_CPP
#include "TWIMaster.cpp"

#define INCLUDE_ECXX_PLATFORM_SPECIFIC_AVR_COMMON_TWISLAVE_CPP
#include "TWISlave.cpp"
