/**
 * 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 Uhrzeit und Datum auf
 * dem an SPI1 angeschlossenen achtstelligen 7-Segment-Display an. Es verwendet
 * zudem den User-Button des NUCLEO-Boards. Standardmaessig wird die aktuelle
 * Uhrzeit angezeigt. Wird der User-Button betaetigt, so wird fuer fuenf Sekunden
 * das Datum angzeigt. Nach Ablauf der fuenf Sekunden 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 in Zeile 27 entfernen, wenn Sie die MCAL testen möchten.
 */
#define MCAL

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


#include <mcalSystem.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;


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;
//    uint32_t      showDateTimer = 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;
//
//    bool          showDate = false;

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

#ifdef MCAL     // Start der MCAL-Version

    // Initialisiert den Systick-Timer
    systickInit(SYSTICK_1MS);
    systickSetTicktime(&ds3231Timer, 500);
    systickSetTicktime(&max7219Timer, 500);
//    systickSetTicktime(&showDateTimer, 5000);

    // Konfiguration des User-Buttons
//    gpioSelectPort(btnPort);                        // Bustakt aktivieren
//    gpioSelectPinMode(btnPort, PIN13, INPUT);
//    extiConfigIrq(btnPort, PIN13);

    // SPI-Interface: Konfiguration der GPIO-Pins
    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);

    // I2C-Interface:: Konfiguration der GPIO-Pins
    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

    // SPI-Interface: Konfiguration
    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

    // I2C-Interface: Konfiguration
    pclk1Freq = sysGetPclk1Freq();
    i2cSelectI2C(i2c);
    i2cInitI2C(i2c, pclk1Freq, I2C_DUTY_CYCLE_2, 17, I2C_CLOCK_100);


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

    // Konfiguration des SysTick-Timers
    SystemCoreClockUpdate();
    SysTick_Config(SystemCoreClock / 1000);

    // Konfiguration von GPIOA
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN;            // GPIOA: Bustakt aktivieren
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;            // GPIOB: Bustakt aktivieren

    // SPI-Interface: GPIO-Port konfigurieren
    spiPort->MODER  &= ~GPIO_MODER_MODE4_Msk;       // PA4  : Reset
    spiPort->MODER  |= GPIO_MODER_MODE4_0;          // PA4  : Output

    spiPort->MODER  &= ~GPIO_MODER_MODE5_Msk;       // PA5  : Reset der Betriebsart
    spiPort->MODER  |= GPIO_MODER_MODE5_1;          // PA5  : AltFunc-Modus ein (SPI CLK)
    spiPort->AFR[0] |= (GPIO_AFRL_AFRL5_2 |         // PA5  : AF5 waehlen
                     GPIO_AFRL_AFRL5_0);

    spiPort->MODER  &= ~GPIO_MODER_MODE7_Msk;       // PA7  : Reset der Betriebsart
    spiPort->MODER  |= GPIO_MODER_MODE7_1;          // PA7  : AltFunc-Modus ein (SPI MOSI)
    spiPort->AFR[0] |= (GPIO_AFRL_AFRL7_2 |         // PA7  : AF5 waehlen
                     GPIO_AFRL_AFRL7_0);
    // I2C-Interface: GPIA-Port konfigurieren
    i2cPort->MODER  &= ~GPIO_MODER_MODE8_Msk;       // PB8  : Reset der Betriebsart
    i2cPort->MODER  |= GPIO_MODER_MODE8_1;          // PB8  : AltFunc-Modus ein (I2C SCL)
    i2cPort->AFR[1] |= GPIO_AFRH_AFSEL8_2;          // PB8  : AF4 waehlen

    i2cPort->MODER  &= ~GPIO_MODER_MODE9_Msk;       // PB9  : Reset der Betriebsart
    i2cPort->MODER  |= GPIO_MODER_MODE9_1;          // PB9  : AltFunc-Modus ein (I2C SDA)
    i2cPort->AFR[1] |= GPIO_AFRH_AFSEL9_2;          // PB9  : AF4 waehlen

    // I2C-Interface aktivieren und konfigurieren
    RCC->APB1ENR    |= RCC_APB1ENR_I2C1EN;          // I2C : Bustakt aktivieren
    i2c->CR1   &= ~I2C_CR1_PE;                      // I2C: Deaktivieren
    i2c->CR1    = 0x0000;                           // I2C: Defaultwert herstellen
    i2c->CR2    = 0x0010;                           // I2C: Peripherietakt 16 MHz
    i2c->CCR    = 0x0050;                           // I2C: Standardmode = 100 kHz
    i2c->TRISE  = 0x0011;                           // I2C: Max. Anstiegszeit der Flanke
    i2c->CR1   |= I2C_CR1_PE;                       // I2C: Peripheriekomponente aktivieren


    // Konfiguration von SPI1: CLKDIV = 16, DATA_FORMAT = 8 Bit
    // Die &= ~xxx-Verknuepfungen sind nicht zwingend erforderlich, da alle Bits von CR1
    // zunaechst zurueckgesetzt werden. Es ist aber sauberer, dies dennoch zu tun, um ein
    // besseres Verstaendnis von SPI zu erlangen.
    RCC->APB2ENR |= RCC_APB2ENR_SPI1EN;             // SPI : Bustakt aktivieren
    spi->CR1     =  0;                              // SPI : Reset CR1
    spi->CR1     |= SPI_CR1_BR_1 | SPI_CR1_BR_0;    // SPI : CLKDIV = 16

#ifdef SPI_DATAFORMAT_8BIT
    spi->CR1     &= ~SPI_CR1_DFF_Msk;               // SPI : Datenformat = 8 Bit
#else
    spi->CR1     |= SPI_CR1_DFF;                    // SPI : Datenformat = 16 Bit
#endif
    spi->CR1     |= SPI_CR1_SSM;                    // SPI : SSM aktivieren
    spi->CR1     |= SPI_CR1_SSI;                    // SPI : SSI Level High
    spi->CR1     |= SPI_CR1_MSTR;                   // SPI : Master Mode aktivieren
    spi->CR1     &= ~SPI_CR1_CPHA_Msk;              // SPI : Clockphase 1. Flanke
    spi->CR1     &= ~SPI_CR1_CPOL_Msk;              // SPI : Idle = Low setzen
    spi->CR1     |= SPI_CR1_SPE;                    // SPI : Enable
    spi->CR2     = 0;

#endif      // Ende: Direkte Registerprogrammierung

    // Init MAX7219
    max7219Init(spi);
    max7219SetDecodeMode(spi, DECODE_ALL);
    max7219ResetDigits(spi, 0);

    ds3231SetTime(i2c, DS3231_ADDR, 15, 6, 0);
    ds3231SetDate(i2c, DS3231_ADDR, 02, 9, 21);

//    NVIC_EnableIRQ(EXTI15_10_IRQn);                 // User-Button

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

        if (isSystickExpired(ds3231Timer))
        {
            ds3231GetTime(i2c, DS3231_ADDR, &hours, &minutes, &seconds);
            systickSetTicktime(&ds3231Timer, 500);
//            systickSetTicktime(&showDateTimer, 5000);
        }

        if (isSystickExpired(max7219Timer))
        {
            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(SPI1, MAX7219_CS_PORT, MAX7219_CS_PIN, REG_DIG2, 0x0A);            // Bindestrich

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

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

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

            systickSetTicktime(&max7219Timer, 500);
        }
    }

    return 0;
}

//void EXTI15_10_IRQHandler(void)
//{
//    uint8_t dummy;
//
//    dummy = 0;
//}
