/********************************************************************** * Copyright: (C)2024 LingYun IoT System Studio * Author: GuoWenxue * * 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