/**
 * @brief I2C-Anwendung mit dem Port-Expander PCF8574A unter Einsatz von I2C1.
 *
 * @par
 * Verwendete Anschlusspins:
 *     PB8 : I2C1 SCL
 *     PB9 : I2C1 SDA
 *
 * Hier wird der PCF8574AN verwendet
 */

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

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


#include <stm32f4xx.h>
#include <mcalSysTick.h>
#include <mcalGPIO.h>
#include <mcalI2C.h>

int8_t i2cPutByte(I2C_TypeDef *i2c, uint8_t saddr, uint8_t data);
int8_t i2cGetByte(I2C_TypeDef *i2c, uint8_t saddr, uint8_t *data);

/**
 * I2C-Adresse des PCF8574A.
 * Beachten Sie hier, dass andere Ausführungen des PCF8574 andere
 * Basisadressen haben!
 */
#define PCF8574A_ADDR       (0x70)

bool timerTrigger = false;

int main(void)
{
    GPIO_TypeDef *port = GPIOB;
    I2C_TypeDef  *i2c  = I2C1;

    uint32_t ledTimer = 0UL;
    uint8_t  ledPattern = 0xFF;
    uint8_t  ledPos = 0;

#ifdef MCAL     // Beginn der MCAL-Version

    uint8_t  pclock = 16;                       // Taktfreq. der I2C-Komponente

    systickInit(SYSTICK_1MS);
    systickSetTicktime(&ledTimer, 500);

    // GPIOB-Bustakt aktivieren wegen der Verwendung von PB8/PB9.
    gpioSelectPort(port);
    gpioSelectPinMode(port, PIN8, ALTFUNC);
    gpioSelectAltFunc(port, PIN8, AF4);        // PB8 : I2C1 SCL
    gpioSetOutputType(port, PIN8, OPENDRAIN);

    gpioSelectPinMode(port, PIN9, ALTFUNC);
    gpioSelectAltFunc(GPIOB, PIN9, AF4);        // PB9 : I2C1 SDA
    gpioSetOutputType(port, PIN9, OPENDRAIN);

    /**
     * Die beiden folgenden Zeilen muessen nur verwendet werden, wenn Sie
     *    a) die Schaltung auf einem Breadboard aufbauen und
     *    b) auf externe Pullup-Widerstände verzichten
     */
//    gpioSelectPushPullType(GPIOB, PIN8, PULLUP);
//    gpioSelectPushPullType(GPIOB, PIN9, PULLUP);

    // Initialisierung des I2C-Controllers
    i2cSelectI2C(i2c);                          // I2C1: Bustakt aktivieren
    i2cDisableDevice(i2c);
    i2cSetPeripheralClockFreq(i2c, pclock);     // I2C1: Periph. Clk in MHz
    i2cSetDutyCycle(i2c, I2C_DUTY_CYCLE_2);     // I2C1: Duty-cycle einstellen
    i2cSetRiseTime(i2c, 17);                    // I2C1: 17 ist ein bewaehrter Wert
    i2c->CCR |= 0x50;                           // I2C1: Keine MCAL-Funktion
    i2cEnableDevice(i2c);                       // I2C1: Aktivieren

    /* Hauptprogramm: Endlosschleife */
    while(1)
    {
        if (true == timerTrigger)
        {
            DECREMENT_TIMER(ledTimer);
            timerTrigger = false;
        }

        if (isSystickExpired(ledTimer))
        {
            ledPattern &= ~(1 << ledPos++);
            i2cSendByte(i2c, PCF8574A_ADDR, ledPattern);

            if (ledPos > 0x07)
            {
                ledPos = 0;
                ledPattern = 0xFF;
            }
            systickSetTicktime(&ledTimer, 500);
        }
    }

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

    SystemCoreClockUpdate();
    SysTick_Config(SystemCoreClock / 1000);

    // GPIOB-Bustakt aktivieren wegen der Verwendung von PB8/PB9.
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN;        // GPIOB: Bustakt aktivieren
    port->MODER  &= ~GPIO_MODER_MODER8_Msk;     // PB8: Reset
    port->MODER  |= GPIO_MODER_MODER8_1;        // PB8: Alt. Funktion
    port->AFR[1] &= ~GPIO_AFRH_AFSEL8_Msk;      // PB8: Reset alt. Funktion
    port->AFR[1] |= GPIO_AFRH_AFSEL8_2;         // PB8: AF4

    port->MODER  &= ~GPIO_MODER_MODER9_Msk;     // PB9: Reset
    port->MODER  |= GPIO_MODER_MODER9_1;        // PB9: Alt. Funktion
    port->AFR[1] &= ~GPIO_AFRH_AFSEL9_Msk;      // PB9: Reset alt. Funktion
    port->AFR[1] |= GPIO_AFRH_AFSEL9_2;         // PB9: AF4

    /**
     * Die folgenden Zeilen sind nicht erforderlich, wenn die Pull-Up-
     * Widerstände auf der Platine vorhanden sind.
     */
    port->OTYPER |= GPIO_OTYPER_OT8;            // PB8: Open Drain
    port->PUPDR  |= GPIO_PUPDR_PUPD8_Msk;       // PB8: Reset PUPD-Register
    port->PUPDR  |= GPIO_PUPDR_PUPD8_0;         // PB8: Pull-up aktivieren

    port->OTYPER |= GPIO_OTYPER_OT9;            // PB9: Open Drain
    port->PUPDR  |= GPIO_PUPDR_PUPD9_Msk;       // PB9: Reset PUPD-Register
    port->PUPDR  |= GPIO_PUPDR_PUPD9_0;         // PB9: Pull-up aktivieren

    // Initialisierung des I2C-Controllers
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;         // I2C1: Bustakt aktivieren
    I2C1->CR1   &= ~I2C_CR1_PE;                 // I2C1: Deaktivieren
    I2C1->CR1    = 0x0000;                      // I2C1: Defaultwert herstellen
    I2C1->CR2    = 0x0010;                      // I2C1: Peripherietakt einstellen
    I2C1->CCR    = 0x0050;                      // I2C1: Standardmode = 100 kHz
    I2C1->TRISE  = 0x0011;                      // I2C1: Max. Anstiegszeit der Flanke
    I2C1->CR1   |= I2C_CR1_PE;                  // I2C1: Peripheriekomponente aktivieren

    /* Hauptprogramm: Endlosschleife */
    while(1)
    {
        if (true == timerTrigger)
        {
            DECREMENT_TIMER(ledTimer);
            timerTrigger = false;
        }

        if (isSystickExpired(ledTimer))
        {
            ledPattern &= ~(1 << ledPos++);
            i2cSendByte(i2c, PCF8574A_ADDR, ledPattern);
            i2cPutByte(i2c, PCF8574A_ADDR, ledPattern);

            if (ledPos > 0x07)
            {
                ledPos = 0;
                ledPattern = 0xFF;
            }
            ledTimer = 500;
        }
    }

#endif      // Ende: Direkte Registerprogrammierung
}

#ifndef MCAL
/**
 * @brief Sendet ein Datenbyte an die I2C-Komponente.
 *
 * @param[in]  *i2c   : Pointer auf das I2C-Interface
 * @param[in]   saddr : I2C-Adresse des Slave
 * @param[in]   data  : Daten, die gesendet werden sollen.
 */
int8_t i2cPutByte(I2C_TypeDef *i2c, uint8_t saddr, uint8_t data)
{
    static   uint32_t dummy = 0UL;

    /* Prueft, ob die gewaehlte Komponente 'busy' ist. */
    while(i2c->SR2 & I2C_SR2_BUSY)
    {
        ;
    }

    // Generieren und senden des Start-Signals. Warten, bis
    // das Busy-Bit auf "1" wechselt.
    i2c->CR1 |= I2C_CR1_START;
    while(!(i2c->SR1 & I2C_SR1_SB))
    {
        ;
    }

    // Senden der Slave-Adresse. VORSICHT: Die Slave-Adresse
    // kann bereits um eine Stelle nach links geschoben sein.
    // Dann reicht es aus, i2c->DR = saddr; zu senden.
    //    i2c->DR = saddr << 1;
    i2c->DR = saddr;

    // Prueft, ob die Adresse erkannt wurde (ob sie gueltig ist).
    while(!(i2c->SR1 & I2C_SR1_ADDR))
    {
        ++dummy;
    }

    // Lesen von SR1 und SR2: Die Werte werden nicht weiter
    // benoetigt.
    i2c->SR1;
    i2c->SR2;

    // Warten, bis das Transmit-Register leer ist.
    while(!(i2c->SR1 & I2C_SR1_TXE))
    {
        ;
    }

    // Datenbyte senden
    i2c->DR = data;

    // Warten, bis der Bit-Transfer abgeschlossen ist
    while(!(i2c->SR1 & I2C_SR1_BTF))
    {
        ;
    }

    // Stopp-Signal generieren und senden
    i2c->CR1 |= I2C_CR1_STOP;

    return 0;
}

/**
 * @brief Liest ein Datenbyte von der I2C-Komponente.
 *
 * @param[in]  *i2c   : Pointer auf das I2C-Interface
 * @param[in]   saddr : I2C-Adresse des Slave
 * @param[in]  *data  : Pointer auf die Variable, in der Daten gespeichert werden
 */
int8_t i2cGetByte(I2C_TypeDef *i2c, uint8_t saddr, uint8_t *data)
{
//    static   uint32_t dummy = 0UL;

    /* Prueft, ob die gewaehlte Komponente 'busy' ist. */
    while(i2c->SR2 & I2C_SR2_BUSY)
    {
        ;
    }

    /* Generiere Start-Sequenz */
    i2c->CR1 |= I2C_CR1_START;
    while(!(i2c->SR1 & I2C_SR1_SB))
    {
        ;
    }

    /* Sende Slave-Adresse und warte, bis das Adressflag gesetzt ist */
    i2c->DR = saddr | 1;
    while (!(i2c->SR1 & I2C_SR1_ADDR))
    {
        ;
    }

    /* Deaktiviere ACK */
    i2c->CR1 &= ~I2C_CR1_ACK_Msk;
    i2c->SR2;       // Nur Auslesen von SR2, Wert wird nicht verwendet

    i2c->CR1 |= I2C_CR1_STOP;

    /* Warte, bis das RXNE-Bit gesetzt ist */
    while (!(i2c->SR1 & I2C_SR1_RXNE))
    {
        ;
    }

    /* Speichere Datenbyte in data */
    *data = i2c->DR;

    return 0;
}
#endif

