/**
 * Kap18-DS3231I2C-To-Display-MAX7219-SPI
 * ======================================
 *
 * Dieses Beispiel ist eine Kombination der Projekte Kap17-I2C-RTC-mit-DS3231
 * und Kap18-SPI-7-Segment-Display. Es verwendet die DS3231-Echtzeituhr, die
 * ueber I2C1 mit dem STM32F446 verbunden ist und zeigt die eingestellte Uhrzeit
 * dem an SPI1 angeschlossenen achtstelligen 7-Segment-Display an. Wird der an
 * PC13 angeschlossene User-Button betaetigt, so wird fuer fuenf Sekunden das
 * Datum angezeigt. Nach Ablauf dieser Zeitspanne wird wieder die Uhrzeit
 * angezeigt.
 *
 * Das Beispiel ist nicht vollstaendig! Es fehlen beispielsweise Funktionen zur
 * Einstellung von Datum und Uhrzeit: Sie werden einmalig vor dem Eintritt in
 * die while(1)-Schleife vorgegeben. Vor einem erneuten Reset des NUCLEO-Boards
 * sollten die entsprechenden Zeilen mit Kommentarzeichen versehen werden.
 */

#include <stm32f4xx.h>
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

/**
 * Kommentar einfuegen fuer SPI-16-Bit-Datenformat
 */
#define SPI_DATAFORMAT_8Bit

#include <mcalFlash.h>
#include <mcalRCC.h>
#include <mcalSysTick.h>
#include <mcalGPIO.h>
#include <mcalSPI.h>
#include <mcalI2C.h>
#include <mcalEXTI.h>
#include <max7219.h>
#include <ds3231RTC.h>

bool     timerTrigger = false;
bool     showDateBtn  = false;
uint32_t showDateTimer = 0UL;

int main(void)
{
    GPIO_TypeDef *spiPort = GPIOA;
    GPIO_TypeDef *i2cPort = GPIOB;
    GPIO_TypeDef *btnPort = GPIOC;

    SPI_TypeDef  *spi = SPI1;
    I2C_TypeDef  *i2c = I2C1;

    uint32_t      pclk1Freq    = 0UL;
    uint32_t      max7219Timer = 0UL;
    uint32_t      ds3231Timer  = 0UL;

    uint8_t       seconds = 0U;
    uint8_t       minutes = 0U;
    uint8_t       hours   = 0U;
    uint8_t       day     = 20;
    uint8_t       month   = 8;
    uint8_t       year    = 21;

    uint32_t *timerList[] = { &max7219Timer, &ds3231Timer, &showDateTimer };
    size_t    arraySize   = sizeof(timerList) / sizeof(timerList[0]);

    /**
     * Initialisierung des SysTick-Timers
     */
    systickInit(SYSTICK_1MS);
    systickSetTicktime(&ds3231Timer, 500);
    systickSetTicktime(&max7219Timer, 500);
    systickSetTicktime(&showDateTimer, 5000);

    /**
     * Vorab-Konfiguration des an GPIOC/Pin 13 angeschlossenen User-Buttons
     */
    gpioSelectPort(btnPort);                        // Bustakt aktivieren
    gpioSelectPinMode(btnPort, PIN13, INPUT);

    /**
     * EXTI-Konfiguration des User-Buttons
     */
    RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;           // SYSCFG: Bustakt aktivieren
    extiConfigIrq(btnPort, PIN13);                  // PIN13 = EXTI_PIN13
    extiSetTriggerEdge(PIN13, FALLING_EDGE);
    extiEnableIrq(PIN13);

    /**
     * Konfiguration von GPIOA: Das SPI-Interface nutzt die Pins PA4/PA5/PA7
     */
    gpioSelectPort(spiPort);
    gpioSelectPinMode(spiPort, PIN4, OUTPUT);       // PA4 = ~CS
    gpioSelectPushPullMode(spiPort, PIN4, PULLUP);
    gpioSelectPinMode(spiPort, PIN5, ALTFUNC);      // PA5 = SPI1 Clock
    gpioSelectAltFunc(spiPort, PIN5, AF5);
    gpioSelectPinMode(spiPort, PIN7, ALTFUNC);      // PA7 = SPI1 MOSI
    gpioSelectAltFunc(spiPort, PIN7, AF5);

    /**
     * Konfiguration von GPIOB: Das I2C-Interface nutzt die Pins PB8/PB9
     */
    gpioSelectPort(i2cPort);
    gpioSelectPinMode(i2cPort, PIN8, ALTFUNC);      // PB8 : AF-Modus
    gpioSelectAltFunc(i2cPort, PIN8, AF4);          // PB8 : I2C1 SCL
    gpioSetOutputType(i2cPort, PIN8, OPENDRAIN);
    gpioSelectPushPullMode(i2cPort, PIN8, PULLUP);  // Interner Pullup

    gpioSelectPinMode(i2cPort, PIN9, ALTFUNC);      // PB9 : AF-Modus
    gpioSelectAltFunc(i2cPort, PIN9, AF4);          // PB9 : I2C1 SDA
    gpioSetOutputType(i2cPort, PIN9, OPENDRAIN);
    gpioSelectPushPullMode(i2cPort, PIN9, PULLUP);  // Interner Pullup

    /**
     * Initialisierung und Konfiguration der SPI-Schnittstelle
     */
    spiSelectSPI(spi);

#ifdef SPI_DATAFORMAT_8Bit
    spiInitSPI(spi, CLK_DIV_16, DATA_FORMAT_8, SSM_ON, SSI_LVL_HIGH, MASTER, SPI_PHASE_EDGE_1, SPI_IDLE_LOW);
#else
    spiInitSPI(spi, CLK_DIV_16, DATA_FORMAT_16, SSM_ON, SSI_LVL_HIGH, MASTER, SPI_PHASE_EDGE_1, SPI_IDLE_LOW);
#endif

    /**
     * Initialisierung und Konfiguration der I2C-Schnittstelle
     */
    pclk1Freq = rccGetPclk1Freq();
    i2cSelectI2C(i2c);
    i2cInitI2C(i2c, pclk1Freq, I2C_DUTY_CYCLE_2, 17, I2C_CLOCK_100);

    /**
     * Initialisierung des MAX7219
     */
    max7219Init(spi);
    max7219SetDecodeMode(spi, DECODE_ALL);
    max7219ResetDigits(spi, 0);

    /**
     * Einmalige Einstellung von Uhrzeit und Datum.
     */
//    ds3231SetTime(i2c, DS3231_ADDR, 11, 37, 00);
//    ds3231SetDate(i2c, DS3231_ADDR, 26, 12, 21);

    /**
     * Aktivierung des EXTI fuer den User-Button (PIN13)
     */
    NVIC_EnableIRQ(EXTI15_10_IRQn);

    while (1)
    {
        // Wird im Takt des SysTick-Timers regelmaessig aufgerufen
        if (timerTrigger == true)
        {
            systickUpdateTimerList((uint32_t *) timerList, arraySize);
        }

        // Prueft, ob die angegebenen Timer(-Variablen) abgelaufen sind
        if (isSystickExpired(ds3231Timer))
        {
            ds3231GetTime(i2c, DS3231_ADDR, &hours, &minutes, &seconds);
            ds3231GetDate(i2c, DS3231_ADDR, &day, &month, &year);
            systickSetTicktime(&ds3231Timer, 500);
        }

        // Anzeige der Uhrzeit: User-Button war/ist nicht aktiv
        if (isSystickExpired(max7219Timer) && (false == showDateBtn))
        {
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG0, seconds % 10);
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG1, seconds / 10);

            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG2, 0x0A);            // Bindestrich

            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG3, minutes % 10);
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG4, minutes / 10);

            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG5, 0x0A);            // Bindestrich

            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG6, hours % 10);
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG7, hours / 10);

            systickSetTicktime(&max7219Timer, 500);
        }

        // Prueft, ob der User-Button betaetigt wurde. Falls dies der Fall war, wird
        // zusaetzlich geprueft, ob der showDateTimer abgelaufen ist. In diesem Fall
        // wird das Button-Flag zurueckgesetzt.
        if (showDateBtn == true && isSystickExpired(showDateTimer))
        {
            showDateBtn = false;
        }

        // Anzeige des Datums. Die Oder-Verknuepfung mit 0x80 steuert den Dezimalpunkt
        // der entsprechenden Stelle an.
        if (showDateBtn == true)
        {
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG7, 0x0F);          // Blank
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG6, 0x0F);          // Blank

            spiSendData (spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG5, day / 10);
            spiSendData (spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG4, (day % 10) | 0x80);

            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG3, month / 10);
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG2, (month % 10) | 0x80);

            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG1, year / 10);
            spiSendData(spi, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG0, year % 10);
        }
    }

    return 0;
}

/**
 * ISR fuer den User-Button. Das Pending-Flag MUSS hier zurueckgesetzt werden! Zudem
 * wird hier die Timer-Variable fuer die Anzeige des Datums auf fuenf Sekunden gesetzt.
 * Hier wird auch das showDateBtn-Flag gesetzt, das anzeigt, dass der User-Button
 * betaetigt wurde.
 */
void EXTI15_10_IRQHandler(void)
{
    extiResetPendingIRQ(EXTI_PIN13);
    systickSetTicktime(&showDateTimer, 5000);
    showDateBtn = true;
}
