/**
 * Projekt Kap07-EXTI-03
 * =====================
 *
 * Beispiel zum Einsatz von EXTIs, die einen gemeinsamen IRQ-Handler verwenden.
 * Hier muss das EXTI->PR-Register ausgewertet werden.
 *
 * GPIOA: PA0 --> Ansteuerung der LED
 * GPIOB: PB8/PB9 --> Taster
 */

/*********************************************************************
 * !!!!! A C H T U N G !!!!!                                         *
 * =========================                                         *
 *                                                                   *
 * In der CMSIS (V1.26.x) ist GPIO_MODER_MODE8_Msk falsch definiert! *
 * Statt GPIO_MODER_MODER8_Msk steht hier GPIO_MODER_MODER2_Msk.     *
 ********************************************************************/
#include <stdint.h>
#include <stdbool.h>

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

/**
 * Kommentar in Zeile 28 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 timerPA0     = 200UL;
uint32_t currentDelay = 200UL;


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

    // SysTick-Timer konfigurieren
    systickInit(SYSTICK_1MS);

    // GPIOA + GPIOB: Bustakt aktivieren
    gpioSelectPort(GPIOA);
    gpioSelectPort(GPIOB);

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

    /**
     * Konfiguration von PA0/PB8/PB9
     */
    gpioSelectPinMode(GPIOA, PIN0, OUTPUT);
    gpioSelectPinMode(GPIOB, PIN8, INPUT);
    gpioSelectPinMode(GPIOB, PIN9, INPUT);
    gpioSetOutputType(GPIOA, PIN0, PUSHPULL);
    gpioSelectPushPullMode(GPIOA, PIN0, NO_PULLUP_PULLDOWN);

    extiInit();
    extiConfigIrq(GPIOB, PIN8);
    extiEnableIrq(EXTI_PIN8);
    extiSetTriggerEdge(EXTI_PIN8, FALLING_EDGE);

    extiConfigIrq(GPIOB, PIN9);
    extiEnableIrq(EXTI_PIN9);
    extiSetTriggerEdge(EXTI_PIN9, FALLING_EDGE);

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

    GPIO_TypeDef *port1 = GPIOA;
    GPIO_TypeDef *port2 = GPIOB;

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

    __disable_irq();

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

    /* Konfiguration von PA0 */
    port1->MODER &= ~(GPIO_MODER_MODE0_Msk);
    port1->MODER |= GPIO_MODER_MODE0_0;     /* PA0: Ausgang f. LED */
    port1->OTYPER &= ~(GPIO_OTYPER_OT0);
    port1->PUPDR  &= ~(GPIO_PUPDR_PUPD0);

    /* Konfiguration von PB8 und PB9: Input im PUSHPULL-Modus */
    port2->MODER &= ~(GPIO_MODER_MODE8_Msk | GPIO_MODER_MODE9_Msk);
    port2->OTYPER &= ~(GPIO_OTYPER_OT8_Msk | GPIO_OTYPER_OT9_Msk);

    // SYSCFG->EXTICR: Die vier Bit  pro Port werden zunaechst zurueckgesetzt,
    // damit nicht versehentlich ein voellig anderer Port fuer EXTI verwendet
    // wird. Erst im Anschluss wird fuer die Pins 8 und 9 GPIOB aktiviert!
    SYSCFG->EXTICR[2] &= (SYSCFG_EXTICR3_EXTI8_PA | SYSCFG_EXTICR3_EXTI9_PA);   // PA = 0x0000
    SYSCFG->EXTICR[2] |= (SYSCFG_EXTICR3_EXTI8_PB | SYSCFG_EXTICR3_EXTI9_PB);   // PB = 0x0001
    EXTI->IMR  |= EXTI_IMR_IM8;             /* EXTI8: PB8 Enable IRQ */
    EXTI->FTSR |= EXTI_FTSR_TR8;            /* EXTI8: PB8 Fallende Flanke */
    EXTI->IMR  |= EXTI_IMR_IM9;             /* EXTI9: PB9 Enable IRQ */
    EXTI->FTSR |= EXTI_FTSR_TR9;            /* EXTI9: PB9 Fallende Flanke */

    __enable_irq();

#endif  // Ende: Direkte Registerprogrammierung

    // Generelle Aktivierung des Interrupts
    NVIC_EnableIRQ(EXTI9_5_IRQn);           /* EXTI9_5: Aktivieren    */

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

#ifdef MCAL
        if (isSystickExpired(timerPA0))
        {
            gpioTogglePin(GPIOA, PIN0);
            systickSetTicktime(&timerPA0, currentDelay);
            timerTrigger = false;
        }
#else
        if (timerPA0 == 0)
        {
            if (port1->IDR & GPIO_IDR_ID0)
            {
                port1->BSRR = GPIO_BSRR_BR0;    // PA0 --> Low
            }
            else
            {
                port1->BSRR = GPIO_BSRR_BS0;    // PA0 --> High
            }
            timerPA0 = currentDelay;            // timerPA0 neu setzen
            timerTrigger = false;
        }
#endif
    }
}

/**
 * EXTI9_5: Interrupt-Service-Routine
 */
void EXTI9_5_IRQHandler(void)
{
    uint16_t pending = EXTI->PR;      /* MCAL-Version noch nicht vorhanden */

    switch(pending)
    {
        case EXTI_PR_PR8:
        {
            currentDelay = 200UL;
            EXTI->PR |= EXTI_PR_PR8;  // Reset des Pending-Flags von PB8
            break;
        }

        case EXTI_PR_PR9:
        {
            currentDelay = 1000UL;
            EXTI->PR |= EXTI_PR_PR9;  // Reset des Pending-Flags von PB9
            break;
        }
    }
}

/**
 * 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(timerPA0);
    timerTrigger = false;
}
