一、Bit Bang
关于 Bit Bang 的解释:Use software to control serial communication at general-purpose I/O pins,简单来讲就是使用软件通过 IO 脚去实现 I2C 的时序从而使用 I2C 协议进行通信。
这样做的好处是可以突破硬件上的限制,例如芯片不具有硬件 I2C 模块,或者硬件 I2C 模块损坏,又或者使用硬件 I2C 模块时布线非常麻烦。坏处是需要写代码模拟时序,根据不同的硬件平台和不同的时钟频率,代码中的部分参数是不一样的。
二、代码分析
以下代码基于 STM32 系列 MCU
使用软件模拟 I2C 的步骤如下:
- 1、设置 GPIO 管脚
设置两个管脚作为 SCL 和 SDA,例如 GPIOA1 和 GPIOA2
1 2 3 4 5 6 7 8 9 10 11 12
| #define SCL_PORT GPIOA #define SCL_PIN GPIO_Pin_1 #define SCL_HIGH GPIOA->BSRR=(uint32_t)GPIO_Pin_1 #define SCL_LOW GPIOA->BRR=(uint32_t)GPIO_Pin_1
#define SDA_PORT GPIOA #define SDA_PIN GPIO_Pin_2 #define SDA_HIGH GPIOA->BSRR=(uint32_t)GPIO_Pin_2 #define SDA_LOW GPIOA->BRR=(uint32_t)GPIO_Pin_2 #define SDA_READ (uint16_t)(GPIOA->IDR&GPIO_Pin_2) #define SDA_OUT GPIOA->MODER|=(((uint32_t)GPIO_Mode_OUT) << (2 * 2)) #define SDA_IN GPIOA->MODER&=~(GPIO_MODER_MODER0<<(2 * 2))
|
1 2 3 4 5
| void I2C_Delay(void) { uint8_t i = 200; while(i--); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| #define SCL_OUTH() SCL_HIGH #define SCL_OUTL() SCL_LOW #define SDA_OUTH() SDA_HIGH #define SDA_OUTL() SDA_LOW #define SDA_SETIN() SDA_IN #define SDA_READ() SDA_READ
void SDA_SETOUT(void) { SDA_IN; SDA_OUT; }
|
- 4、I2C 启动

1 2 3 4 5 6 7 8 9 10
| void I2C_Start(void) { SCL_OUTH(); SDA_OUTH(); I2C_Delay(); SDA_OUTL(); I2C_Delay(); SCL_OUTL(); I2C_Delay(); }
|
- 5、I2C停止

1 2 3 4 5 6 7 8 9 10
| void I2C_Stop(void) { SCL_OUTL(); SDA_OUTL(); I2C_Delay(); SCL_OUTH(); I2C_Delay(); SDA_OUTH(); Delay(Delay5ms); }
|
- 6、发送 8 位数据,返回值为从响应 ACK 标志
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| uint8_t I2C_WriteByte(uint8_t Data) { uint8_t i,bAck=0;
for(i=0;i<8;i++) { SCL_OUTL(); if (Data & 0x80) SDA_OUTH(); else SDA_OUTL(); I2C_Delay(); SCL_OUTH(); I2C_Delay(); Data <<= 1; }
SCL_OUTL(); I2C_Delay(); SCL_OUTH(); I2C_Delay(); SDA_SETIN(); if(SDA_READ()) bAck = 1; else bAck = 0;
SCL_OUTL(); SDA_SETOUT(); SDA_OUTH(); I2C_Delay(); return ((uint8_t)(!bAck)); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| uint8_t I2C_ReadByte(uint8_t bLSByte) { uint8_t i,Data=0; SDA_SETIN(); for(i=8; i!=0; i--) { SCL_OUTL(); Data = Data << 1; I2C_Delay(); SCL_OUTH(); I2C_Delay(); if(SDA_READ()) Data |= 0x01; else Data &= 0xfe; }
SCL_OUTL(); SDA_SETOUT(); if(bLSByte) SDA_OUTH(); else SDA_OUTL(); I2C_Delay(); SCL_OUTH(); I2C_Delay();
SCL_OUTL(); I2C_Delay(); return(Data); }
|
三、操作实例
以下代码为通过调用上面的基本代码来实现 I2C 通信
三个参数分比为从机地址,寄存器地址,8 位数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| uint8_t DAC_Write_1byte(uint8_t Slave,uint8_t Regis_Addr,uint8_t Data) { uint8_t succ,time=0;
I2C_Start(); succ=I2C_WriteByte(Slave); while((succ!=1)&&(time<3)) { I2C_Stop(); I2C_Start(); succ=I2C_WriteByte(Slave); time++; } succ=I2C_WriteByte(Regis_Addr); succ=I2C_WriteByte(Data); I2C_Stop(); return succ; }
|
两个参数分别为从机地址,寄存器地址,返回数据为 16 位。这是由于某些器件的硬件设计,采用 7 位表示寄存器地址,而每个寄存器包含 9 位数据。更常见的方式为 8 位寄存器地址,一个寄存器 8 位数据,这种方式的代码仅返回 8 位数据,见代码 2。
代码 1,返回 16 位数据,不常见
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| uint16_t DAC_Read_1byte(uint8_t Slave,uint8_t Regis_Addr) { uint8_t Data[2]; uint8_t succ,time=0; uint16_t retData=0;
I2C_Start(); succ=I2C_WriteByte(Slave); while((succ!=1)&&(time<3)) { I2C_Stop(); I2C_Start(); succ=I2C_WriteByte(Slave); time++; } succ=I2C_WriteByte(Regis_Addr); I2C_Start(); succ=I2C_WriteByte((Slave|0x01)); Data[0] = I2C_ReadByte(0); Data[1] = I2C_ReadByte(1); I2C_Stop(); retData = (uint16_t)Data[0]<<8; retData += (uint16_t)Data[1]; return retData; }
|
代码 2,返回 8 位数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| uint8_t DAC_Read_1byte(uint8_t Slave,uint8_t Regis_Addr) { uint8_t succ,time=0; uint8_t dat;
I2C_Start(); succ=I2C_WriteByte(Slave+1); while((succ!=1)&&(time<3)) { I2C_Stop(); I2C_Start(); succ=I2C_WriteByte(Slave+1); time++; } succ=I2C_WriteByte(Regis_Addr); dat=I2C_ReadByte(0); I2C_Stop(); return dat; }
|
原文链接:本人CSDN博客