#include "stm32f0xx.h"
#include "i2c.h"

void i2c_init(void)
{
	GPIOB->MODER |= (2 << GPIO_MODER_MODER6_Pos)|(2 << GPIO_MODER_MODER7_Pos); // PB6 and PB7 alternate function
	GPIOB->OTYPER |= GPIO_OTYPER_OT_6 | GPIO_OTYPER_OT_7; // open drain
	GPIOB->OSPEEDR |= (3 << GPIO_OSPEEDR_OSPEEDR6_Pos)|(3 << GPIO_OSPEEDR_OSPEEDR7_Pos); // high speed
	GPIOB->PUPDR |= (1 << GPIO_PUPDR_PUPDR6_Pos)|(1 << GPIO_PUPDR_PUPDR7_Pos); // pull-up
	GPIOB->AFR[0] |= (1 << GPIO_AFRL_AFRL6_Pos)|(1 << GPIO_AFRL_AFRL7_Pos); // used by I2C1

	// calculated by cube mx, 400 kHz fast mode, 60 ns rise and fall time @ 32 MHz
	I2C1->TIMINGR = 0x00500F34;
	I2C1->CR1 |= (1 << I2C_CR1_PE_Pos); // i2c enable
}

void i2c_write(uint8_t dev_address, uint8_t reg_address, uint8_t *data, uint8_t length)
{
	uint16_t time_out = 1000;

	while(I2C1->ISR & (1 << I2C_ISR_BUSY_Pos) && time_out--) // wait if the bus is busy
		;

	I2C1->CR2 = (I2C1->CR2 & ~I2C_CR2_SADD_Msk)|(dev_address << I2C_CR2_SADD_Pos); // set slave address
	I2C1->CR2 = (I2C1->CR2 & ~I2C_CR2_NBYTES_Msk)|((length + 1) << I2C_CR2_NBYTES_Pos); // transmitted bytes = 1 byte (reg_address) + data length
	I2C1->CR2 &= ~(1 << I2C_CR2_RD_WRN_Pos); // write data to slave
	I2C1->CR2 |= (1 << I2C_CR2_AUTOEND_Pos); // after transmission is done send stop
	I2C1->CR2 |= (1 << I2C_CR2_START_Pos); // send start

	I2C1->TXDR = reg_address;

	while(length--)
	{
		time_out = 1000;
		while(!(I2C1->ISR & (1 << I2C_ISR_TXE_Pos)) && time_out--) // doesn't work with TXIS flag as described in datasheet
			;
  	I2C1->TXDR = *data++;
	}
}

void i2c_read(uint8_t dev_address, uint8_t reg_address, uint8_t *data, uint8_t length)
{
	uint16_t time_out = 1000;

	while(I2C1->ISR & (1 << I2C_ISR_BUSY_Pos) && time_out--) // wait if the bus is busy
		;

	I2C1->CR2 = (I2C1->CR2 & ~I2C_CR2_SADD_Msk)|(dev_address << I2C_CR2_SADD_Pos); // set slave address
	I2C1->CR2 = (I2C1->CR2 & ~I2C_CR2_NBYTES_Msk)|(1 << I2C_CR2_NBYTES_Pos); // transmitted bytes = 1 byte (reg_address)
	I2C1->CR2 &= ~((1 << I2C_CR2_AUTOEND_Pos)|(1 << I2C_CR2_RD_WRN_Pos)); // no automatic stop after transmission is done, write data to slave
	I2C1->CR2 |= (1 << I2C_CR2_START_Pos); // send start

	I2C1->TXDR = reg_address;
	time_out = 1000;
	while(!(I2C1->ISR & (1 << I2C_ISR_TC_Pos)) && time_out--) // wait for the transmission to be complete
		;

	I2C1->CR2 = (I2C1->CR2 & ~I2C_CR2_NBYTES_Msk)|(length << I2C_CR2_NBYTES_Pos); // received bytes = data length
	I2C1->CR2 |= (1 << I2C_CR2_AUTOEND_Pos)|(1 << I2C_CR2_RD_WRN_Pos); // after transmission is done send stop, read data from slave
	I2C1->CR2 |= (1 << I2C_CR2_START_Pos); // repeated start

  while(length--)
  {
  	time_out = 1000;
    while(!(I2C1->ISR & (1 << I2C_ISR_RXNE_Pos)) && time_out--)
    	;
    *data++ = I2C1->RXDR;
  }
}
