/**
 * Projekt Kap09-SysTick-04
 * ========================
 *
 * Das Beispiel unterscheidet sich von Kap09-Systick-03 darin, dass hier die
 * Funktion systickUpdateTimerList() (bzw. in der Bare-Metal-Version
 * SYSTICKUpdateTimerList()) eingefuehrt wird.
 */

#include <stm32f4xx.h>

#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>         // Erforderlich wegen size_t

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

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

// Das Toggeln dieses Flags ersetzt die Korrektur von Timer-Variablen!
bool     timerTrigger = false;

#ifndef MCAL
/**
 * In der MCAL-Version nicht benoetigt.
 */
void     SysTick_Handler(void);
void     SYSTICKUpdateTimerList(uint32_t *timer, size_t arraySize);
#endif

#define PIN0_DELAY      (200)
#define PIN1_DELAY      (400)

#ifndef MCAL
/**
 * Das Makro ist bereits in der MCAL enthalten und wird nur fuer die Bare-
 * Metal-Version benoetigt.
 */
#define DECREMENT_TIMER( timer )   \
    ( {                            \
        if ( timer > 0 )           \
            --timer;               \
    } )
#endif

int main(void)
{
    uint32_t pin0Timer = 0UL;
    uint32_t pin1Timer = 0UL;

    // Dies ist das Array, das die Adressen aller Timer-Variablen enthaelt.
    // Auch die Groesse des Arrays wird berechnet.
    uint32_t *timerList[] = { &pin0Timer, &pin1Timer /*, weitere Timer */ };
    size_t    arraySize = sizeof(timerList)/sizeof(timerList[0]);

#ifdef MCAL     // Beginn der MCAL-Version

    // Konfiguration des SysTick-Timers
    systickInit(SYSTICK_1MS);

    // Konfiguration von GPIOA, Pin0 und Pin1
    gpioSelectPort(GPIOA);
    gpioSelectPinMode(GPIOA, PIN0, OUTPUT);
    gpioSetOutputType(GPIOA, PIN0, PUSHPULL);
    gpioSelectPushPullMode(GPIOA, PIN0, PULLUP);

    gpioSelectPinMode(GPIOA, PIN1, OUTPUT);
    gpioSetOutputType(GPIOA, PIN1, PUSHPULL);
    gpioSelectPushPullMode(GPIOA, PIN1, PULLUP);

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

    GPIO_TypeDef *port = GPIOA;

    SystemCoreClockUpdate();                    // SysTick: Konfig.
    SysTick_Config(SystemCoreClock / 1000);     // SysTick = 1 ms

    // Konfiguration von GPIOA/Pin0 und GPIOA/Pin1
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;        // GPIOA: Bustakt einschalten
    port->MODER  &= ~GPIO_MODER_MODE0_Msk;      // PA0: Reset
    port->MODER  |= GPIO_MODER_MODE0_0;         // PA0: Ouput
    port->OTYPER &= ~GPIO_OTYPER_OT0_Msk;       // PA0: Push-Pull-Mode ein
    port->PUPDR  &= ~GPIO_PUPDR_PUPD0_Msk;      // PA0: Pullup/Pulldown Reset
    port->PUPDR  |= GPIO_PUPDR_PUPD0_0;         // PA0: Int. Pullup-Wid. ein

    port->MODER  &= ~GPIO_MODER_MODE1_Msk;      // PA1: Reset
    port->MODER  |= GPIO_MODER_MODE1_0;         // PA1: Ouput
    port->OTYPER &= ~GPIO_OTYPER_OT1_Msk;       // PA1: Push-Pull-Mode ein
    port->PUPDR  &= ~GPIO_PUPDR_PUPD1_Msk;      // PA1: Pullup/Pulldown Reset
    port->PUPDR  |= GPIO_PUPDR_PUPD1_0;         // PA1: Int. Pullup-Wid. ein

#endif      // Ende: Direkte Registerprogrammierung

    while (1)
    {
#ifdef MCAL     // Beginn der MCAL-Version

        if (true == timerTrigger)
        {
            systickUpdateTimerList((uint32_t *) timerList, arraySize);
        }

        if (isSystickExpired(pin0Timer))
        {
            gpioTogglePin(GPIOA, PIN0);
            systickSetTicktime(&pin0Timer, PIN0_DELAY);
        }

        if (isSystickExpired(pin1Timer))
        {
            gpioTogglePin(GPIOA, PIN1);
            systickSetTicktime(&pin1Timer, PIN1_DELAY);
        }

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

        if (true == timerTrigger)
        {
            SYSTICKUpdateTimerList((uint32_t *) timerList, arraySize);
        }

        if (pin0Timer == 0)
        {
            if (port->IDR & GPIO_IDR_ID0)
            {
                port->BSRR = GPIO_BSRR_BR0;
            }
            else
            {
                port->BSRR = GPIO_BSRR_BS0;
            }
            pin0Timer = PIN0_DELAY;
        }

        if (pin1Timer == 0)
        {
            if (port->IDR & GPIO_IDR_ID1)
            {
                port->BSRR = GPIO_BSRR_BR1;
            }
            else
            {
                port->BSRR = GPIO_BSRR_BS1;
            }
            pin1Timer = PIN1_DELAY;
        }

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

#ifndef MCAL
/**
 * Die beiden folgenden Funktionen sind bereits in der MCAL vorhanden und
 * dürfen hier nur fuer die Bare-Metal-Version aktiv sein.
 *
 */
void SysTick_Handler(void)
{
    timerTrigger = true;
}

void SYSTICKUpdateTimerList(uint32_t *timerList, size_t arraySize)
{
    uint32_t *timer;
    size_t i;

    for (i = 0; i < arraySize; i++)
    {
        // Schreibweise vereinfacht. Moeglich waere auch
        // DECREMENT_TIMER(*((uint32_t *) timerList[i]));
        timer = (uint32_t *) timerList[i];
        DECREMENT_TIMER(*timer);
    }

    timerTrigger = false;
}
#endif
