/**
 * @brief Sucht die I2C-Adressen externer I2C-Komponenten.
 *
 * Das Beispiel existiert ausschliesslich als MCAL-Version.
 *
 * @par
 * Verwendete Anschlusspins:
 *     PB8 : I2C1 SCL
 *     PB9 : I2C1 SDA
 *
 * @note
 * !!!WICHTIG!!!
 * Verwenden Sie unbedingt externe Pull-up-Widerstaende (PB8/PB9 muessen dann
 * zwingend im Open-Drain-Modus konfiguriert sein). Mit den internen Pull-up-
 * Widerstaenden funktioniert I2C beim STM32 nicht zuverlaessig!
 */

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

#include <mcalGPIO.h>
#include <mcalI2C.h>
#include <mcalRCC.h>
#include <mcalSysTick.h>
#include <mcalUsart.h>

void     i2cHandleBusyWorkaround(I2C_TypeDef *i2c, GPIO_TypeDef *port, PIN_NUM_t sdaPin, PIN_NUM_t sclPin);
uint8_t *convDecByteToHex(uint8_t byte);

bool     timerTrigger = false;
uint8_t *outString;
uint8_t *outString1 = (uint8_t *) "No I2C device at address   : 0x";
uint8_t *outString2 = (uint8_t *) "I2C device found at address: 0x";

int main(void)
{
    GPIO_TypeDef  *portA = GPIOA;
    GPIO_TypeDef  *portB = GPIOB;
    USART_TypeDef *usart = USART2;
    I2C_TypeDef   *i2c  = I2C1;
    uint8_t        scanAddr = 0x00;
    uint8_t        foundAddr = 0;
    uint32_t       i2cScanTimer;
    uint32_t       pclock;
    uint8_t       *result;

    pclock = rccGetPclk1Freq() / 1000000;

    systickInit(SYSTICK_1MS);
    systickSetTicktime(&i2cScanTimer, 10);

    // GPIOA-Bustakt aktivieren fuer USART
    gpioSelectPort(portA);
    gpioSelectPinMode(portA, PIN2, ALTFUNC);     // PA2 : Modus = Alt. Funktion
    gpioSelectAltFunc(portA, PIN2, AF7);         // PA2 : AF7 = USART2 Rx
    gpioSelectPinMode(portA, PIN3, ALTFUNC);     // PA3 : Modus = Alt. Funktion
    gpioSelectAltFunc(portA, PIN3, AF7);         // PA3 : AF7 = USART2 Tx

    // GPIOB-Bustakt aktivieren wegen der Verwendung von PB8/PB9 (I2C).
    i2cSelectI2C(i2c);                           // I2C1: Bustakt aktivieren
    i2cDisableDevice(i2c);
    gpioInitPort(portB);
    gpioSelectPinMode(portB, PIN8, ALTFUNC);
    gpioSelectAltFunc(portB, PIN8, AF4);         // PB8 : I2C1 SCL
    gpioSelectPinMode(portB, PIN9, ALTFUNC);
    gpioSelectAltFunc(portB, PIN9, AF4);         // PB9 : I2C1 SDA

    /**
     * Verwenden Sie auf keinen Fall die MCU-internen Pull-up-Widerstaende!
     * Widerstandswerte: jeweils 4k7 fuer SDA und SCL!
     */
    gpioSetOutputType(portB, PIN8, OPENDRAIN);   // Immer externe Pull-up-
    gpioSetOutputType(portB, PIN9, OPENDRAIN);   // Widerstaende verwenden!!!

    // Initialisierung des I2C-Controllers
    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

    // Initialisierung der USART-Schnittstelle
    usartSelectUsart(usart);
    usartStartUsart(usart);
    usartSetCommParams(usart, 115200, NO_PARITY, LEN_8BIT, ONE_BIT);
    usartEnableIrq(usart, USART_IRQ_TXEIE);

    NVIC_EnableIRQ(USART2_IRQn);


    /* Hauptprogramm: Endlosschleife */
    while(1)
    {
        if (true == timerTrigger)
        {
            systickUpdateTimer(&i2cScanTimer);
        }

        if (isSystickExpired(i2cScanTimer))
        {
            timerTrigger = false;

//            i2cHandleBusyWorkaround(i2c, portB, PIN9, PIN8);
            foundAddr = i2cFindSlaveAddr(i2c, scanAddr);

            result = convDecByteToHex(scanAddr);

            if (foundAddr != 0)
            {
                outString = outString2;
                usartSendString(usart, (char *)outString2);
                usartSendString(usart, (char *)result);
                usartSendByte(usart, 13);
                usartSendByte(usart, 10);
            }
            else
            {
                outString = outString1;
                usartSendString(usart, (char *)outString1);
                usartSendString(usart, (char *)result);
                usartSendByte(usart, 13);
                usartSendByte(usart, 10);
            }
            systickSetTicktime(&i2cScanTimer, 10);
            scanAddr++;
            if (scanAddr > 0xFF)
            {
                break;
            }
        }
    }
    i2cDisableDevice(i2c);
    i2cDeselectI2C(i2c);
    return 0;
}

/**
 * Die USART2-ISR verwendet mit Ausnahme der GPIO-Funktionen keine MCAL-
 * Funktionen.
 */
void USART2_IRQHandler(void)
{
    if (USART2->SR & USART_SR_TXE)
    {
        usartResetIrqFlag(USART2, USART_SR_TXE);

        if (*outString != '\0')
        {
            USART2->DR = *outString++;
        }
        else
        {
            USART2->CR1 &= ~USART_CR1_TXEIE;
        }
    }
}

void i2cHandleBusyWorkaround(I2C_TypeDef *i2c, GPIO_TypeDef *port, PIN_NUM_t sdaPin, PIN_NUM_t sclPin)
{
    // Workaround for wrong setting of STM's I2C BUSY flag
    i2cDisableDevice(i2c);
    gpioSelectPinMode(port, sclPin, OPENDRAIN);
    gpioSelectPinMode(port, sdaPin, OPENDRAIN);
    gpioSetPin(port, sclPin);
    while (!gpioGetPinState(port, sclPin))
    {
        ;
    }

    gpioSetPin(port, sdaPin);
    while (!gpioGetPinState(port, sdaPin))
    {
        ;
    }

    gpioResetPin(port, sclPin);
    while (gpioGetPinState(port, sclPin))
    {
        ;
    }

    gpioResetPin(port, sdaPin);
    while (gpioGetPinState(port, sdaPin))
    {
        ;
    }

    i2cResetDevice(i2c);
    i2c->CR1 &= ~I2C_CR1_SWRST_Msk;
    i2cEnableDevice(i2c);
}

uint8_t *convDecByteToHex(uint8_t byte)
{
    static  uint8_t hex[2] = { 0 };

    uint8_t temp;

    temp = byte % 16;
    if (temp < 10)
    {
        temp += '0';
    }
    else
    {
        temp += '7';
    }
    hex[1] = temp;

    temp = byte / 16;
    if (temp < 10)
    {
        temp += '0';
    }
    else
    {
        temp += '7';
    }
    hex[0] = temp;

    return hex;
}
