/**
 * @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
 */

/**
 ******************************************************************************
 * @file           : main.c
 * @author         : Auto-generated by STM32CubeIDE
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * <h2><center>&copy; Copyright (c) 2020 STMicroelectronics.
 * All rights reserved.</center></h2>
 *
 * This software component is licensed by ST under BSD 3-Clause license,
 * the "License"; You may not use this file except in compliance with the
 * License. You may obtain a copy of the License at:
 *                        opensource.org/licenses/BSD-3-Clause
 *
 ******************************************************************************
 */

#if !defined(__SOFT_FP__) && defined(__ARM_FP)
  #warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."
#endif

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

#include <mcalSysTick.h>
#include <mcalGPIO.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)
{
    uint32_t ledTimer = 0UL;
    uint8_t  ledPattern = 0xFF;
    uint8_t  ledPos = 0;
    uint8_t  data = 0;

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

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

    /**
     * Die beiden folgenden Zeilen sind nicht erforderlich, wenn
     * die Pull-Up-Widerstände auf der Platine vorhanden sind.
     */
//    gpioSelectPushPullType(GPIOB, PIN8, PULLUP);    // Verwendung des internen Pullup-Widerstandes
//    gpioSelectPushPullType(GPIOB, PIN9, PULLUP);    // Verwendung des internen Pullup-Widerstandes

    // Initialisierung des I2C-Controllers
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;             // I2C1: Bustakt aktivieren
    I2C1->CR1   &= ~I2C_CR1_PE;
    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 (timerTrigger == TRUE)
	    {
	        DECREMENT_TIMER(ledTimer);
	        timerTrigger = FALSE;
	    }

	    if (isSystickExpired(ledTimer))
	    {
//          i2cReadByte(I2C1, PCF8574A_ADDR, &data);
            ledPattern &= ~(1 << ledPos++);
            i2cPutByte(I2C1, PCF8574A_ADDR, ledPattern);

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

/**
 * @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;
}

