/**
  \file i2c.h
   
  \author G. Icking-Konert
  \date 2013-11-22
  \version 0.1
   
  \brief implementation of I2C functions/macros
   
  implementation of functions for I2C bus communication
  For I2C bus, see http://en.wikipedia.org/wiki/I2C
*/

/*-----------------------------------------------------------------------------
    INCLUDE FILES
-----------------------------------------------------------------------------*/
#include "stm8s.h"

/*-----------------------------------------------------------------------------
    DEFINITION OF GLOBAL MACROS/#DEFINES
-----------------------------------------------------------------------------*/
#define I2C_FAST        1
#define F_MASTER_MHZ    16UL
#define F_MASTER_HZ     16000000UL
#ifdef I2C_FAST
//400 kHz
#define F_I2C_HZ        400000UL
#else
//100 kHz
#define F_I2C_HZ        100000UL
#endif // I2C_FAST

//CCR = Fmaster/2*Fiic = 16MHz/2*100kHz = 80
#define CCR (uint16_t)(F_MASTER_HZ / (2 * F_I2C_HZ))

/* Таймаут ожидания события I2C */
extern __IO uint8_t I2C_timeout;

/*----------------------------------------------------------
    FUNCTIONS
----------------------------------------------------------*/
static uint8_t i2c_read(void);

/**
  \fn void i2c_init(void)
  \brief configure I2C bus
  configure I2C bus as master in standard mode, BR=100kHz, 7bit address, 250ns rise time
*/
void i2c_init() {
  //Частота тактирования периферии MHz
  I2C->FREQR = (uint8_t)(F_MASTER_MHZ);

  //Отключаем I2C
  I2C->CR1 &= (uint8_t)(~I2C_CR1_PE);

  //Выбираем режим
#ifdef I2C_FAST
  I2C->CCRH = I2C_CCRH_FS; // | I2C_CCRH_DUTY;
#else
  I2C->CCRH = 0;
#endif

  //Set Maximum Rise Time: 1000ns max in Standard Mode
  //= [1000ns/(1/InputClockFrequencyMHz.10e6)]+1
  //= InputClockFrequencyMHz+1
  I2C->TRISER = (uint8_t)(F_MASTER_MHZ + 1);
  I2C->CCRL = (uint8_t)CCR;
  I2C->CCRH |= (uint8_t)((uint8_t)(CCR >> 8) & I2C_CCRH_CCR);

  //Включаем I2C
  I2C->CR1 |= I2C_CR1_PE;

  //Разрешаем подтверждение в конце посылки
  I2C->CR2 |= I2C_CR2_ACK;
} // i2c_init

/**
  \fn uint8_t i2c_waitFree(void)
  \brief wait until bus is free
  \return error code (0=ok; 1=timeout)
  wait until bus is free with timeout
*/
uint8_t i2c_waitFree() {
  // wait until bus free with timeout
  I2C_timeout = 10;
  while (((I2C->SR3 & I2C_SR3_BUSY)) && I2C_timeout);
 
  // on I2C timeout return error
  if ((I2C->SR3 & I2C_SR3_BUSY)) {
    i2c_init();
    return(1);
  }
  
  // return success
  return(0);
} // i2c_waitFree

/**
  \fn uint8_t i2c_start(void)
  \brief generate I2C start condition
  \return error code (0=ok; 1=timeout)
  generate I2C start condition with timeout
*/
uint8_t i2c_start() {
  // wait until bus free with timeout
  I2C_timeout = 10;
  while (((I2C->SR3 & I2C_SR3_BUSY)) && I2C_timeout);
 
  // on I2C timeout return error
  if ((I2C->SR3 & I2C_SR3_BUSY)) {
    i2c_init();
    return(2);
  }

  // generate start condition with timeout
  I2C->CR2 |= I2C_CR2_START;
  I2C_timeout = 10;
  while (!(I2C->SR1 & I2C_SR1_SB) && I2C_timeout);
 
  // on I2C timeout return error
  if (!(I2C->SR1 & I2C_SR1_SB)) {
    i2c_init();
    return(1);
  }

  // return success
  return(0);
} // i2c_start

/**
  \fn uint8_t i2c_stop(void)
  \brief generate I2C stop condition
  \return error code (0=ok; 1=timeout)
  generate I2C stop condition with timeout 
*/
uint8_t i2c_stop() {
  // generate stop condition with timeout
  I2C->CR2 |= I2C_CR2_STOP;
  I2C_timeout = 10;
  while ((I2C->SR3 & I2C_SR3_MSL) && I2C_timeout);
 
  // on I2C timeout set error flag
  if (I2C->SR3 & I2C_SR3_MSL) {
    i2c_init();
    return(1);
  }
  
  // return success
  return(0);
} // i2c_stop

/**
  \fn uint8_t i2c_send(uint8_t addr, uint8_t numTx, uint8_t *bufTx)
  \brief write data via I2C
  \param[in]  addr        7b address [6:0] of I2C slave
  \param[in]  numTx       number of bytes to send
  \param[in]  bufTx       send buffer
  \return error code (0=ok; 1=timeout)
  write data to I2C slave with frame timeout. Note that no start or 
  stop condition is generated.
*/
uint8_t i2c_send(uint8_t addr, uint8_t numTx, uint8_t *bufTx) {
  uint8_t   i;

  // send 7b slave adress [7:1] + write flag (LSB=0)
  I2C->DR = (uint8_t) ((addr << 1) & ~0x01);       // shift left and set LSB (write=0, read=1)
  I2C_timeout = 10;
  while ((!(I2C->SR1 & I2C_SR1_ADDR)) && I2C_timeout);     // wait until address sent or timeout
  I2C_timeout = 10;
  while ((!(I2C->SR3 & I2C_SR3_BUSY)) && I2C_timeout);     // seems to be required...???
  
  // no I2C timeout -> send data
  if (I2C_timeout) {
    for (i=0; i<numTx; i++) {
      I2C->DR = bufTx[i];
      I2C_timeout = 2;
      while ((!(I2C->SR1 & I2C_SR1_TXE)) && I2C_timeout);  // wait until DR buffer empty or timeout
    }
  }
  // on I2C timeout return error
  else {
    i2c_init();
    return(1);
  }

  // return success
  return(0);
} // i2c_send

static uint8_t i2c_read(void) {
    I2C->CR2 &= ~I2C_CR2_ACK;
    i2c_stop();
    while (!(I2C->SR1 & I2C_SR1_RXNE));
    return I2C->DR;
}

/**
  \fn uint8_t i2c_request(uint8_t slaveAddr, uint8_t numRx, uint8_t *bufRx)
  \brief request data via I2C as master with i2c_stop()
  \param[in]  slaveAddr   7b address [6:0] of I2C slave
  \param[in]  numRx       number of bytes to receive
  \param[out] bufRx       receive buffer
  \return error code (0=ok; 1=timeout)
  request data from I2C slave with frame timeout. Note that no start or 
  stop condition is generated.
*/
uint8_t i2c_request(const uint8_t slaveAddr, const uint8_t numRx, uint8_t *bufRx) {
  uint8_t i = numRx;
  
  // send 7b slave adress [7:1] + read flag (LSB=1)
  I2C->DR = (uint8_t) ((slaveAddr << 1) | 0x01);   // shift left and set LSB (write=0, read=1)
  I2C_timeout = 10;
  while ((!(I2C->SR1 & I2C_SR1_ADDR)) && I2C_timeout);   // wait until adress sent or timeout
  I2C_timeout = 2;
  while ((!(I2C->SR3 & I2C_SR3_BUSY)) && I2C_timeout);   // seems to be required...???

  while (i-- > 1) {
    I2C_timeout = 2;
    while ((!(I2C->SR1 & I2C_SR1_RXNE)) && I2C_timeout); // wait until DR buffer not empty or timeout
    *(bufRx++) = I2C->DR;
  }
    *bufRx = i2c_read();
 
  // on I2C timeout return error
  if (I2C_timeout == 0) {
    i2c_init();
    return(1);
  }

  // return success
  return(0);
} // i2c_request

/*-----------------------------------------------------------------------------
    END OF MODULE
-----------------------------------------------------------------------------*/
