/**
 * Projekt Kap07-EXTI-02
 * =====================
 *
 * Einfaches EXTI-Projekt mit zwei Tastern und einer LED.
 */
#include <stdint.h>
#include <stdbool.h>

#include <stm32f4xx.h>
#include <system_stm32f4xx.h>

/**
 * Kommentar in Zeile 14 entfernen, wenn Sie die MCAL testen möchten.
 */
#define MCAL

#ifdef MCAL
#include <mcalGPIO.h>
#include <mcalEXTI.h>
#include <mcalSysTick.h>
#endif


/**
 * Makro zum Dekrementieren von Timer-Variablen. Das Makro ist vollstaendig
 * in mcalSysTick.h enthalten und darf daher hier nur in der Bare-Metal-
 * Version aktiviert sein. Anderenfalls meldet der Compiler einen 'multiple
 * definition'-Fehler.
 */
#ifndef MCAL
#define DECREMENT_TIMER(timer)  \
    ( {                         \
        if (timer > 0)          \
        {                       \
            --timer;            \
        }                       \
    })
#endif

/**
 * Funktionsprototypen
 */
void UPDATETimers(void);

/**
 * Globale Variablen
 */
bool     timerTrigger = false;
uint32_t timerPA4     = 200UL;
uint32_t currentDelay = 200UL;


int main(void)
{
#ifdef MCAL // Beginn der MCAL-Version

    // SysTick-Timer konfigurieren
    systickInit(SYSTICK_1MS);

    // GPIOA: Bustakt aktivieren
    gpioInitPort(GPIOA);

    // SYSCFG: Bustakt aktivieren (noch keine MCAL-Funktion vorhanden)
    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN; /* SYSCFG: Clock an */

    /**
     * Konfiguration von GPIOA PA0 und PA4
     */
    gpioSelectPinMode(GPIOA, PIN0, INPUT);
    gpioSelectPinMode(GPIOA, PIN1, INPUT);
    gpioSelectPinMode(GPIOA, PIN4, OUTPUT);
    gpioSetOutputType(GPIOA, PIN4, PUSHPULL);
    gpioSelectPushPullMode(GPIOA, PIN4, NO_PULLUP_PULLDOWN);

    extiInit();
    extiConfigIrq(GPIOA, PIN0);
    extiEnableIrq(EXTI_PIN0);
    extiSetTriggerEdge(EXTI_PIN0, FALLING_EDGE);

    extiConfigIrq(GPIOA, PIN1);
    extiEnableIrq(EXTI_PIN1);
    extiSetTriggerEdge(EXTI_PIN1, FALLING_EDGE);

#else       // Ende der MCAL-Version, Beginn: Direkte Registerprogrammierung

    /**
     * SysTick-Timer: Funktionen wurden von Arm und ST entwickelt.
     */
    SysTick_Config(SystemCoreClock / 1000);
    SystemCoreClockUpdate();

    __disable_irq();

    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;    /* GPIOA aktivieren */
    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;   /* SYSCFG: Clock an */

    /**
     * Konfiguration von GPIOA PA0, PA1 und PA4
     */
    GPIOA->MODER &= ~(GPIO_MODER_MODE0_Msk | GPIO_MODER_MODE1_Msk | GPIO_MODER_MODE4_Msk);
    GPIOA->MODER |= GPIO_MODER_MODE4_0;

    GPIOA->OTYPER &= ~(GPIO_OTYPER_OT0 | GPIO_OTYPER_OT1 | GPIO_OTYPER_OT4);
    GPIOA->PUPDR  &= ~(GPIO_PUPDR_PUPD0 | GPIO_PUPDR_PUPD1 | GPIO_PUPDR_PUPD4);

    SYSCFG->EXTICR[0] &= ~0x000F;           /* EXTI0 an */
    EXTI->IMR  |= EXTI_IMR_IM0;             /* EXTI0: PA0 Enable IRQ */
    EXTI->FTSR |= EXTI_FTSR_TR0;            /* EXTI0: PA0 Fallende Flanke */

    SYSCFG->EXTICR[0] &= ~0x00F0;           /* EXTI1 an */
    EXTI->IMR  |= EXTI_IMR_IM1;             /* EXTI1: PA1 Enable IRQ */
    EXTI->FTSR |= EXTI_FTSR_TR1;            /* EXTI1: PA1 Fallende Flanke */

    __enable_irq();

#endif      // Ende: Direkte Registerprogrammierung

    // Generelle Aktivierung des Interrupts
    NVIC_EnableIRQ(EXTI0_IRQn);             /* EXTI0: Aktivieren    */
    NVIC_EnableIRQ(EXTI1_IRQn);             /* EXTI1: Aktivieren */

    while (1)
    {
        if (true == timerTrigger)
        {
            UPDATETimers();
        }

#ifdef MCAL
        if (isSystickExpired(timerPA4))
        {
            gpioTogglePin(GPIOA, PIN4);
            systickSetTicktime(&timerPA4, currentDelay);
            timerTrigger = false;
        }
#else
        if (timerPA4 == 0)
        {
            if (GPIOA->IDR & GPIO_IDR_ID4)
            {
                GPIOA->BSRR = GPIO_BSRR_BR4;    // PA4 --> Low
            }
            else
            {
                GPIOA->BSRR = GPIO_BSRR_BS4;    // PA4 --> High
            }
            timerPA4 = currentDelay;            // timerpA4 neu setzen
            timerTrigger = false;
        }
#endif
    }
}

/**
 * EXTI0: Interrupt-Service-Routine
 */
void EXTI0_IRQHandler(void)
{
#ifdef MCAL // Beginn der MCAL-Version

    systickSetTicktime(&timerPA4, 200);     /* Neue MCAL-Funktion */
    currentDelay = 200;
    EXTI->PR |= EXTI_PR_PR0;                /* Reset IRQ-Pending-Flag */

#else       // Ende der MCAL-Version + Beginn der Bare-Metal-Version

    timerPA4 = 200UL;
    currentDelay = timerPA4;
    EXTI->PR |= EXTI_PR_PR0;                /* Reset IRQ-Pending-Flag */

#endif      // Ende der Bare-Metal-Version
}

/**
 * EXTI1: Interrupt-Service-Routine
 */
void EXTI1_IRQHandler(void)
{
#ifdef MCAL

    systickSetTicktime(&timerPA4, 1000);    /* Neue MCAL-Funktion */
    currentDelay = 1000;
    EXTI->PR |= EXTI_PR_PR1;                /* Reset IRQ-Pending-Flag */

#else
    timerPA4 = 1000UL;
    currentDelay = timerPA4;
    EXTI->PR |= EXTI_PR_PR1;
#endif
}

/**
 * SysTick-Timer: Interrupt-Service-Routine (ISR)
 *
 * Die ISR ist bereits in der MCAL enthalten und darf daher hier nicht
 * aktiv sein, da sonst beim Kompilieren ein 'multiple definition'-Fehler
 * auftritt.
 */
#ifndef MCAL
void SysTick_Handler(void)
{
    timerTrigger = true;
}
#endif

/**
 * Aktualisierung der Timer-Variablen
 */
void UPDATETimers(void)
{
    DECREMENT_TIMER(timerPA4);
    timerTrigger = false;
}
