/*
|
* i2c_bitbang.c
|
*
|
* Created on: Jan 19, 2023
|
* Author: Wenxue
|
*/
|
|
|
#include "i2c_bitbang.h"
|
|
i2c_bus_t i2c_bus[I2CBUSMAX] =
|
{
|
/* Bus ChipAddr SCL_Pin SDA_Pin */
|
{I2CBUS0, 0x00, {GPIOB, GPIO_PIN_6 }, {GPIOB, GPIO_PIN_7 } }, /* SHT20, RTC, OLED */
|
{I2CBUS1, 0x00, {GPIOB, GPIO_PIN_10}, {GPIOB, GPIO_PIN_11} }, /* MikroBUS I2C interface */
|
};
|
|
|
enum
|
{
|
LOW,
|
HIGH,
|
};
|
|
|
/*+--------------------------+
|
*| Low level GPIO API |
|
*+--------------------------+*/
|
|
static inline void SDA_IN(uint8_t bus)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
GPIO_InitStruct.Pin = i2c_bus[bus].sda.pin;
|
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
GPIO_InitStruct.Pull = GPIO_PULLUP;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
HAL_GPIO_Init(i2c_bus[bus].sda.group, &GPIO_InitStruct);
|
}
|
|
static inline void SDA_OUT(uint8_t bus)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
GPIO_InitStruct.Pin = i2c_bus[bus].sda.pin;
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
GPIO_InitStruct.Pull = GPIO_PULLUP;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
HAL_GPIO_Init(i2c_bus[bus].sda.group, &GPIO_InitStruct);
|
}
|
|
static inline void SCL_OUT(uint8_t bus)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
GPIO_InitStruct.Pin = i2c_bus[bus].scl.pin;
|
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
|
GPIO_InitStruct.Pull = GPIO_PULLUP;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
HAL_GPIO_Init(i2c_bus[bus].scl.group, &GPIO_InitStruct);
|
}
|
|
static inline void SCL_SET(uint8_t bus, int level)
|
{
|
HAL_GPIO_WritePin(i2c_bus[bus].scl.group, i2c_bus[bus].scl.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
|
}
|
|
static inline void SDA_SET(uint8_t bus, int level)
|
{
|
HAL_GPIO_WritePin(i2c_bus[bus].sda.group, i2c_bus[bus].sda.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
|
}
|
|
static inline GPIO_PinState SCL_GET(uint8_t bus)
|
{
|
return HAL_GPIO_ReadPin(i2c_bus[bus].scl.group, i2c_bus[bus].scl.pin);
|
}
|
|
static inline GPIO_PinState SDA_GET(uint8_t bus)
|
{
|
return HAL_GPIO_ReadPin(i2c_bus[bus].sda.group, i2c_bus[bus].sda.pin);
|
}
|
|
|
/*+--------------------------+
|
*| I2C Clock Sequence API |
|
*+--------------------------+*/
|
|
/* */
|
#if 1
|
#include "miscdev.h" /* delay_us() implement by TIM6 */
|
#else
|
static inline void delay_us(uint32_t us)
|
{
|
uint32_t i, j;
|
for(j=0; j<450; j++)// 450 ~500
|
for(i = 0; i < us; i++)
|
{
|
__NOP();
|
}
|
}
|
#endif
|
|
|
void I2C_StartCondition(uint8_t bus)
|
{
|
SDA_OUT(bus);
|
SCL_OUT(bus);
|
|
/* Start Condition Clock Sequence:
|
______
|
SCL: |___
|
_____
|
SDA: |_____
|
*/
|
SDA_SET(bus, HIGH);
|
SCL_SET(bus, HIGH);
|
delay_us(1);
|
|
SDA_SET(bus, LOW);
|
delay_us(1); // hold time start condition (t_HD;STA)
|
SCL_SET(bus, LOW);
|
delay_us(1);
|
}
|
|
void I2C_StopCondition(uint8_t bus)
|
{
|
SDA_OUT(bus);
|
|
/* SCL Stop Condition Clock Sequence:
|
_______
|
SCL: ___|
|
_____
|
SDA: _____|
|
*/
|
SCL_SET(bus, LOW);
|
SDA_SET(bus, LOW);
|
delay_us(1);
|
|
SCL_SET(bus, HIGH);
|
SDA_SET(bus, HIGH);
|
delay_us(1);
|
}
|
|
void I2C_Ack(uint8_t bus, uint8_t ack)
|
{
|
if(ACK_NONE == ack)
|
return ;
|
|
SCL_SET(bus, LOW);
|
SDA_OUT(bus);
|
|
if( ACK==ack )
|
SDA_SET(bus, LOW);
|
else if( NAK==ack )
|
SDA_SET(bus, HIGH);
|
|
delay_us(1);
|
|
SCL_SET(bus, HIGH);
|
delay_us(1);
|
SCL_SET(bus, LOW);
|
}
|
|
uint8_t I2C_WriteByte(uint8_t bus, uint8_t txByte)
|
{
|
uint8_t error = NO_ERROR;
|
uint8_t mask;
|
|
SDA_OUT(bus);
|
SCL_SET(bus, LOW);
|
|
/* start send 8 bits data */
|
for(mask=0x80; mask>0; mask>>=1)
|
{
|
if((mask & txByte) == 0)
|
SDA_SET(bus, LOW);
|
else
|
SDA_SET(bus, HIGH);
|
|
delay_us(1); // data set-up time (t_SU;DAT)
|
SCL_SET(bus, HIGH);
|
delay_us(1); // SCL high time (t_HIGH)
|
SCL_SET(bus, LOW);
|
delay_us(1); // data hold time(t_HD;DAT)
|
}
|
|
/* Wait for ACK/NAK */
|
SDA_IN(bus);
|
SCL_SET(bus, HIGH); // clk #9 for ack
|
delay_us(1); // data set-up time (t_SU;DAT)
|
|
/* High level means NAK */
|
if( SDA_GET(bus) )
|
error = ACK_ERROR;
|
|
SCL_SET(bus, LOW);
|
|
delay_us(1);
|
|
return error;
|
}
|
|
static inline uint8_t I2C_WaitWhileClockStreching(uint8_t bus, uint8_t timeout)
|
{
|
uint8_t error = NO_ERROR;
|
|
while( SCL_GET(bus)== 0)
|
{
|
if(timeout-- == 0)
|
return TIMEOUT_ERROR;
|
|
delay_us(10);
|
}
|
return error;
|
}
|
|
uint8_t I2C_ReadByte(uint8_t bus, uint8_t *rxByte, uint8_t ack, uint32_t timeout)
|
{
|
//==============================================================================
|
uint8_t error = NO_ERROR;
|
uint8_t mask;
|
|
*rxByte = 0x00;
|
SDA_IN(bus);
|
|
/* start read 8 bits data */
|
for(mask = 0x80; mask > 0; mask >>= 1)
|
{
|
SCL_SET(bus, HIGH); // start clock on SCL-line
|
delay_us(1); // clock set-up time (t_SU;CLK)
|
|
// wait while clock streching
|
error = I2C_WaitWhileClockStreching(bus, timeout);
|
|
delay_us(1); // SCL high time (t_HIGH)
|
|
if( SDA_GET(bus) )
|
*rxByte |= mask; // read bit
|
|
SCL_SET(bus, LOW);
|
delay_us(1); // data hold time(t_HD;DAT)
|
}
|
|
I2C_Ack(bus, ack);
|
return error;
|
}
|
|
|
uint8_t I2C_SendAddress(uint8_t bus, uint8_t dir)
|
{
|
uint8_t addr;
|
uint8_t rv;
|
|
if( I2C_WR == dir )
|
{
|
addr = W_ADDR(i2c_bus[bus].addr);
|
}
|
else
|
{
|
addr = R_ADDR(i2c_bus[bus].addr);
|
}
|
|
rv=I2C_WriteByte(bus, addr);
|
|
return rv;
|
}
|
|
int i2c_init(uint8_t bus, uint8_t addr)
|
{
|
if( bus >= I2CBUSMAX )
|
{
|
return -1;
|
}
|
|
#if 0
|
/* I2C bus used already */
|
if( 0x00 != i2c_bus[bus].addr )
|
{
|
return -2;
|
}
|
#endif
|
|
/* Set I2C slave address */
|
i2c_bus[bus].addr = addr;
|
|
return 0;
|
}
|
|
int i2c_term(uint8_t bus)
|
{
|
if( bus >= I2CBUSMAX )
|
{
|
return -1;
|
}
|
|
/* Set I2C slave address */
|
i2c_bus[bus].addr = 0x0;
|
|
return 0;
|
}
|
|
int i2c_read(uint8_t bus, uint8_t *buf, int size)
|
{
|
int i;
|
int rv = NO_ERROR;
|
uint8_t byte;
|
|
I2C_StartCondition(bus);
|
|
if( NO_ERROR != (rv=I2C_SendAddress(bus, I2C_RD) ) )
|
{
|
goto OUT;
|
}
|
|
for (i=0; i<size; i++)
|
{
|
if( !I2C_ReadByte(bus, &byte, ACK, I2C_CLK_STRETCH_TIMEOUT) )
|
buf[i] = byte;
|
else
|
goto OUT;
|
}
|
|
OUT:
|
I2C_StopCondition(bus);
|
return rv;
|
}
|
|
int i2c_write(uint8_t bus, uint8_t *data, int bytes)
|
{
|
int i;
|
int rv = NO_ERROR;
|
|
if(!data)
|
return PARM_ERROR;
|
|
I2C_StartCondition(bus);
|
|
if( NO_ERROR != (rv=I2C_SendAddress(bus, I2C_WR)) )
|
{
|
goto OUT;
|
}
|
|
for (i=0; i<bytes; i++)
|
{
|
if( NO_ERROR != (rv=I2C_WriteByte(bus, data[i])) )
|
{
|
break;
|
}
|
}
|
|
OUT:
|
I2C_StopCondition(bus);
|
return rv;
|
}
|