/**********************************************************************
|
* Copyright: (C)2024 LingYun IoT System Studio
|
* Author: GuoWenxue<guowenxue@gmail.com>
|
*
|
* Description: The purpose of this code is to provide a simple C library,
|
* which providing software bit-bang of the I2C protocol on
|
* any GPIO pins for ISKBoard.
|
*
|
* ChangeLog:
|
* Version Date Author Description
|
* V1.0.0 2024.08.29 GuoWenxue Release initial version
|
*
|
***********************************************************************/
|
|
#include "i2c_bitbang.h"
|
#include "miscdev.h" /* udelay() implement by TIM6 */
|
|
/*
|
*+---------------------------------+
|
*| I2C GPIO port Level functions |
|
*+---------------------------------+
|
*/
|
|
#define mdelay(x) HAL_Delay(x)
|
|
enum
|
{
|
LOW,
|
HIGH,
|
};
|
|
i2c_bus_t i2c_bus =
|
{
|
/* Addr SCL_Pin SDA_Pin */
|
0x00, {GPIOB, GPIO_PIN_6 }, {GPIOB, GPIO_PIN_7 },
|
};
|
|
static inline void SDA_IN(void)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
GPIO_InitStruct.Pin = i2c_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.sda.group, &GPIO_InitStruct);
|
}
|
|
static inline void SDA_OUT(void)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
GPIO_InitStruct.Pin = i2c_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.sda.group, &GPIO_InitStruct);
|
}
|
|
static inline void SCL_OUT(void)
|
{
|
GPIO_InitTypeDef GPIO_InitStruct = {0};
|
|
GPIO_InitStruct.Pin = i2c_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.scl.group, &GPIO_InitStruct);
|
}
|
|
static inline void SCL_SET(int level)
|
{
|
HAL_GPIO_WritePin(i2c_bus.scl.group, i2c_bus.scl.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
|
}
|
|
static inline void SDA_SET(int level)
|
{
|
HAL_GPIO_WritePin(i2c_bus.sda.group, i2c_bus.sda.pin, level?GPIO_PIN_SET:GPIO_PIN_RESET);
|
}
|
|
static inline GPIO_PinState SCL_GET(void)
|
{
|
return HAL_GPIO_ReadPin(i2c_bus.scl.group, i2c_bus.scl.pin);
|
}
|
|
static inline GPIO_PinState SDA_GET(void)
|
{
|
return HAL_GPIO_ReadPin(i2c_bus.sda.group, i2c_bus.sda.pin);
|
}
|
|
/*
|
*+--------------------------------+
|
*| I2C Low Level API functions |
|
*+--------------------------------+
|
*/
|
|
void I2C_StartCondition(void)
|
{
|
SDA_OUT();
|
SCL_OUT();
|
|
/* Start Condition Clock Sequence:
|
______
|
SCL: |___
|
_____
|
SDA: |_____
|
*/
|
SDA_SET(HIGH);
|
SCL_SET( HIGH);
|
udelay(1);
|
|
SDA_SET(LOW);
|
udelay(1); // hold time start condition (t_HD;STA)
|
SCL_SET(LOW);
|
udelay(1);
|
}
|
|
void I2C_StopCondition(void)
|
{
|
SDA_OUT();
|
|
/* SCL Stop Condition Clock Sequence:
|
_______
|
SCL: ___|
|
_____
|
SDA: _____|
|
*/
|
SCL_SET(LOW);
|
SDA_SET(LOW);
|
udelay(1);
|
|
SCL_SET(HIGH);
|
SDA_SET(HIGH);
|
udelay(1);
|
}
|
|
int I2C_SendAddress(uint8_t dir)
|
{
|
uint8_t addr;
|
uint8_t rv;
|
|
if( I2C_WR == dir )
|
{
|
addr = W_ADDR(i2c_bus.addr);
|
}
|
else
|
{
|
addr = R_ADDR(i2c_bus.addr);
|
}
|
|
rv=I2C_WriteByte(addr);
|
|
return rv;
|
}
|
|
void I2C_Ack(uint8_t ack)
|
{
|
if(ACK_NONE == ack)
|
return ;
|
|
SCL_SET(LOW);
|
SDA_OUT();
|
|
if( ACK==ack )
|
SDA_SET(LOW);
|
else if( NAK==ack )
|
SDA_SET(HIGH);
|
|
udelay(1);
|
|
SCL_SET( HIGH);
|
udelay(1);
|
SCL_SET(LOW);
|
}
|
|
int I2C_WriteByte(uint8_t txByte)
|
{
|
int error = 0;
|
uint8_t mask;
|
|
SDA_OUT();
|
SCL_SET(LOW);
|
|
/* start send 8 bits data, MSB */
|
for(mask=0x80; mask>0; mask>>=1)
|
{
|
if((mask & txByte) == 0)
|
SDA_SET(LOW);
|
else
|
SDA_SET(HIGH);
|
|
udelay(1); // data set-up time (t_SU;DAT)
|
SCL_SET(HIGH);
|
udelay(1); // SCL high time (t_HIGH)
|
SCL_SET(LOW);
|
udelay(1); // data hold time(t_HD;DAT)
|
}
|
|
/* Wait for ACK/NAK */
|
SDA_IN();
|
SCL_SET(HIGH); // clk #9 for ack
|
udelay(1); // data set-up time (t_SU;DAT)
|
|
/* High level means NAK */
|
if( SDA_GET() )
|
error = -ERROR_ACK;
|
|
SCL_SET(LOW);
|
|
udelay(1);
|
|
return error;
|
}
|
|
static inline int I2C_WaitWhileClockStreching(uint8_t timeout)
|
{
|
while( SCL_GET()== 0)
|
{
|
if(timeout-- == 0)
|
return -ERROR_TIMEOUT;
|
|
udelay(10);
|
}
|
return ERROR_NONE;
|
}
|
|
int I2C_ReadByte(uint8_t *rxByte, uint8_t ack, uint32_t timeout)
|
{
|
uint8_t error = 0;
|
uint8_t mask;
|
|
*rxByte = 0x00;
|
SDA_IN();
|
|
/* start read 8 bits data, MSB */
|
for(mask = 0x80; mask > 0; mask >>= 1)
|
{
|
SCL_SET(HIGH); // start clock on SCL-line
|
udelay(1); // clock set-up time (t_SU;CLK)
|
|
// wait while clock streching
|
error = I2C_WaitWhileClockStreching(timeout);
|
|
udelay(1); // SCL high time (t_HIGH)
|
|
if( SDA_GET() )
|
*rxByte |= mask; // read bit
|
|
SCL_SET(LOW);
|
udelay(1); // data hold time(t_HD;DAT)
|
}
|
|
I2C_Ack(ack);
|
|
return error;
|
}
|
|
/*
|
*+--------------------------------+
|
*| I2C High Level API functions |
|
*+--------------------------------+
|
*/
|
|
int i2c_lock(uint8_t addr)
|
{
|
/* I2C bus already be used */
|
while( i2c_bus.addr && i2c_bus.addr!=addr )
|
{
|
mdelay(10);
|
}
|
|
i2c_bus.addr = addr;
|
|
return 0;
|
}
|
|
void i2c_free(void)
|
{
|
i2c_bus.addr = 0x0;
|
}
|
|
int i2c_write(uint8_t *data, int bytes)
|
{
|
int i;
|
int rv = 0;
|
|
if( !data )
|
{
|
return -ERROR_PARM;
|
}
|
|
/* send start condition */
|
I2C_StartCondition();
|
|
/* send device address for write */
|
if( (rv=I2C_SendAddress(I2C_WR)) < 0 )
|
goto OUT;
|
|
/* send data to slave device */
|
for (i=0; i<bytes; i++)
|
{
|
if( (rv=I2C_WriteByte(data[i])) < 0 )
|
{
|
break;
|
}
|
}
|
|
OUT:
|
/* send stop condition */
|
I2C_StopCondition();
|
|
return rv;
|
}
|
|
int i2c_read(uint8_t *buf, int size)
|
{
|
int i;
|
int rv = ERROR_NONE;
|
uint8_t byte;
|
|
/* send start condition */
|
I2C_StartCondition();
|
|
/* send device address for read */
|
if( (rv=I2C_SendAddress(I2C_RD)) < 0 )
|
goto OUT;
|
|
/* read data from slave device */
|
for (i=0; i<size; i++)
|
{
|
if( (rv=I2C_ReadByte(&byte, ACK, I2C_CLK_STRETCH_TIMEOUT)) < 0 )
|
goto OUT;
|
|
buf[i] = byte;
|
}
|
|
OUT:
|
/* send stop condition */
|
I2C_StopCondition();
|
return rv;
|
}
|