IIC协议通信解析,内附完整代码。

一:硬件接口

1.1:功能引脚

 1.2:IIC总线通信注意事项

二:通信协议

(1)空闲状态:

(2)起始位:

 (3)有效数据位

(4)应答(非应答)

(5)停止位

IIC模拟代码 


一:硬件接口

1.1:功能引脚

IIC一般由两根线组成,一根时钟线SCL,一根数据线SDA。

SCL: 提供数据交互的时钟

SDA: 进行数据的交互

当然在进行数据交互的时候,两个芯片需要共地

 1.2:IIC总线通信注意事项

(1)对于IIC总线来说,它是可以同时接入多个从机进行通信的,但是注意只能是主机(单片机)进行唯一的数据读写操作。

(2)为了进行通信,每一个可以进行IIC通信的从机的芯片手册上会标出该自己用于IIC通信的地址。(这里我们以AT24c128为例)

可以看到,基本的前面的bit位数据是固定的,但是留出了A1和A0两个bit的控制引脚,我们在电路板中将其对应的引脚置高,那么对应的bit数据就为1,反之则为0。最后一个LSB位,0代表需要对其进行写操作,1代表需要对其进行读操作。 

(3)我们一般使用的IIC协议都是模拟的IIc协议,也就是我们通过时序图来进行对应电平切换模拟出的IIc通信代码。这里需要注意的是:

1:在不进行数据通信时,SCL和SDA引脚必须保持高电平。

2:使用的模拟IIC引脚必须接入上拉电阻,所以SDA和SCL一般配置为开漏输出。

3:SDA引脚既需要输入也需要输出,在读写操作切换时,记得修改该引脚的模式。

二:通信协议

IIC通信由:空闲状态+起始位+有效数据位+应答(非应答)+停止位 组成

(1)空闲状态:

这一时刻SDA和SCL一般都是保持高电平输出。所以我们在进行IIC模拟通信的初           始化时,SDA和SCL引脚都会被配置为高电平。

(2)起始位:

 

 这个时刻,SCL引脚保持高电平不变,SDA引脚电平从高电平跳变为低电平。 

 (3)有效数据位

这里在发送有效数据位时,有一个规范,在SCL引脚为高电平时,SDA的电平不能被修改,这是数据的采样时刻,数据交互时,会在SCL引脚高电平时进行数据的采样。只有在SCL保持低电平时,才能对SDA引脚的电平进行修改(当然也不一定要修改,比如连续发送多个bit1或者bit0) 

根据代码也能很好的看出来,每次在对SDA引脚进行电平修改时,都会将SCL引脚点哦置0,然后修改好SDA引脚电平后,再将SCL引脚电平置高。 

(4)应答(非应答)

 

这里就比较简单了,有应答信号就是SDA引脚有一个低电平,没有的话就是高电平。

(5)停止位

 

IIC模拟代码 

注意如果IIC读写输出出现问题,可以将延时适当调大一些。

// 该代码用于stm32f103c8t6测试没问题

#define I2C_SCL_PORT  GPIOB
#define I2C_SCL_Pin   GPIO_Pin_8

#define I2C_SDA_PORT  GPIOB
#define I2C_SDA_Pin   GPIO_Pin_9

#define OUTPUT 0
#define INPUT 1

static void I2C_Tail(void); //发送结束标志
static void I2C_Head(unsigned short Addrs); // 发送起始标志

static void hal_I2C_Config(void); // 模拟I2C配置函数
static void hal_I2C_SCL_SET(unsigned char i);  // 设置时钟引脚
static void hal_I2C_SDA_SET(unsigned char i);  // 设置数据引脚
static void hal_I2C_Start(void);  // 起始标志
static void hal_I2C_Stop(void);   // 结束标志
static void I2C_Delay(unsigned short t);
static void I2C_ACK(void);   // 有ACK应答
static void I2C_NOACK(void); // 无ACK应答
static void I2C_SendByte(unsigned char data);   // 写一个byte数据时序
static void I2C_WriteByte(unsigned short Addr, unsigned char data); // 发送一个byte
static unsigned char I2C_RecvByte(void); // 读一个字节时序 
static unsigned char I2C_WaitAck(void);  // 返回0,无应答数据 。返回1,有应答数据

void hal_I2C_SDA_IO_SET(unsigned char IOMode); // OUTPUT 或者 INPUT
unsigned char hal_IC_SDA_INPUT(void);
unsigned char I2C_ReadByte(unsigned short Addr); // 读一个字节数据


void hal_eeprom_Init(void)
{
  hal_I2C_Config();
}

unsigned char I2C_ReadByte(unsigned short Addr)
{
  unsigned char dat;
	hal_I2C_Start();
	I2C_SendByte(0xA0);
	I2C_WaitAck();
	
	I2C_SendByte((Addr>>8)&0xFF);
	I2C_WaitAck();
	
	I2C_SendByte(Addr&0xFF);
	I2C_WaitAck();
	
	hal_I2C_Start();
	I2C_SendByte(0xA1); // 发送 1010 0001 最低位的1代表从该芯片读取数据
	I2C_WaitAck();
	
	dat = I2C_RecvByte();
	I2C_NOACK();
	hal_I2C_Stop();
	return dat;
}

static unsigned char I2C_RecvByte(void)
{
   unsigned char i = 0;
	 unsigned char RecvByte = 0;
	
   hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
	 hal_I2C_SDA_SET(1);
	 hal_I2C_SDA_IO_SET(INPUT);
	 
	 for(; i < 8; i++)
	{
     	RecvByte<<=1;
		  I2C_Delay(1);
		  hal_I2C_SCL_SET(1);
		  I2C_Delay(1);
		  if(hal_IC_SDA_INPUT()) {
			  RecvByte |= 0x01;
			}
			hal_I2C_SCL_SET(0);
	}
	hal_I2C_SDA_IO_SET(OUTPUT);
	I2C_Delay(1);
	return RecvByte;
}

void hal_I2C_Read( unsigned short Addrs, unsigned char *pBuf, unsigned short Len)
{
  unsigned short i = 0;
	
	I2C_Head(Addrs);
	
	hal_I2C_Start();
	I2C_SendByte(0xA1); // 发送 1010 0001 最低位的1代表从该芯片读取数据
	I2C_WaitAck();
	
	for( i = 0; i < Len; i++)
	{
	  pBuf[i] = I2C_RecvByte();
		if(i == Len - 1) I2C_NOACK();
		else I2C_ACK();
	}
	
	hal_I2C_Stop();
}

static void I2C_WriteByte(unsigned short Addr, unsigned char data)
{
  hal_I2C_Start(); // 发送有效数据起始标志
	
	// 该芯片最低位的一个bit位代表读/写:0 代表写入数据 || 1 代表读出数据
	I2C_SendByte(0xA0); // 发送需要通信的硬件地址(对应的硬件手册上有!!!!!)
	I2C_WaitAck();
	
	I2C_SendByte((Addr>>8)&0xFF); // 发送写入的地址(高字节地址)
	I2C_WaitAck();
	
	I2C_SendByte(Addr&0xFF);    // 发送写入的地址(低字节地址)
	I2C_WaitAck();
	
	I2C_SendByte(data);
	I2C_WaitAck();
	
	hal_I2C_Stop();  // 发送有效数据结束标志
	I2C_Delay(20000);
}


static unsigned char I2C_WaitAck(void)
{
  hal_I2C_SDA_SET(1);
	hal_I2C_SDA_IO_SET(INPUT);  // 设置SDA引脚为输入引脚
	hal_I2C_SCL_SET(1);
	I2C_Delay(1);
	if(hal_IC_SDA_INPUT()) { // 该引脚为高电平,代表无应答
		hal_I2C_SDA_IO_SET(OUTPUT);  // 设置SDA引脚为输出引脚
	  return 0;
	}
	
	hal_I2C_SCL_SET(0);
	hal_I2C_SDA_IO_SET(OUTPUT); 
	I2C_Delay(1);
	return 1;
}

static void I2C_Head(unsigned short Addrs)
{
    hal_I2C_Start(); // 发送有效数据起始标志
	
	  // 该芯片最低位的一个bit位代表读/写:0 代表写入数据 || 1 代表读出数据
	  I2C_SendByte(0xA0); // 发送需要通信的硬件地址(对应的硬件手册上有!!!!!)
	  I2C_WaitAck();
	
	  I2C_SendByte((Addrs>>8)&0xFF); // 发送写入的地址(高字节地址)
	  I2C_WaitAck();
	
	  I2C_SendByte(Addrs&0xFF);    // 发送写入的地址(低字节地址)
	  I2C_WaitAck();
}

static void I2C_Tail(void)
{
  hal_I2C_Stop();  // 发送有效数据结束标志
	I2C_Delay(20000);
}

void hal_I2C_Write(unsigned short Addrs, unsigned char* pBuf, unsigned short Len)
{
	unsigned short tmp = 0;
	unsigned short i = 0, j = 0;
	unsigned short page = 0;
	unsigned short size;
	
	if(Addrs%EEPROM_PAGE_SIZE) 
	{
	  tmp = EEPROM_PAGE_SIZE-(Addrs%EEPROM_PAGE_SIZE); // 拿到当前页还有多少字节可写入
		if(tmp > Len)
		{
	     tmp = Len;	// 后续写入操作,需要tmp来做循环因子
		}
	}
	// 先将数据写入当前页
	if(tmp)
	{
		I2C_Head(Addrs);
		// 正式写入数据
		for(i = 0; i < tmp; i++)
		{
		  I2C_SendByte(pBuf[i]);
			I2C_WaitAck();
		}
		I2C_Tail();
	}
	
	Len -= tmp;   // 将已经发送过的数据个数减掉
	Addrs += tmp; // 将写入的地址更新
	
	page = Len/EEPROM_PAGE_SIZE; // int整形除法,只会得到整数
	size = Len%EEPROM_PAGE_SIZE; // 不足一页还有几个字节
	
	for(i = 0; i < page; i++)
	{
	  I2C_Head(Addrs);
		
		for(j = 0; j < EEPROM_PAGE_SIZE; j++)
		{
		  I2C_SendByte(pBuf[tmp + j]);
			I2C_WaitAck();
		}
		I2C_Tail();
		Addrs += EEPROM_PAGE_SIZE;
		tmp += EEPROM_PAGE_SIZE;
	}
	
	if(size)
	{
	  I2C_Head(Addrs);
		
		for(i = 0; i < size; i++)
		{
		  I2C_SendByte(pBuf[tmp + i]);
			I2C_WaitAck();
		}
		I2C_Tail();
	}
	
}

static void I2C_SendByte(unsigned char data)
{
  unsigned char i = 0;
	for(; i < 8; i++)
	{
		hal_I2C_SCL_SET(0);
		I2C_Delay(1);
	  if(data&0x80){
		  hal_I2C_SDA_SET(1);
		} else {
		  hal_I2C_SDA_SET(0);
		}
		I2C_Delay(1);
		hal_I2C_SCL_SET(1);
		I2C_Delay(1);
		data<<=1;
	}
	
	hal_I2C_SCL_SET(0);
  I2C_Delay(1);
	hal_I2C_SDA_SET(1);
  I2C_Delay(1);
}


static void I2C_ACK(void)
{
   hal_I2C_SCL_SET(0);
	 I2C_Delay(1); 
	 hal_I2C_SDA_SET(0); // SDA数据线置低,进行数据位应答。
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(1);
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
}


static void I2C_NOACK(void)
{
   hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
	 hal_I2C_SDA_SET(1); // SDA数据线置高,不进行数据应答。
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(1);
	 I2C_Delay(1);
	 hal_I2C_SCL_SET(0);
	 I2C_Delay(1);
}

static void hal_I2C_Start(void)
{
  hal_I2C_SDA_SET(1);
	I2C_Delay(1);
	hal_I2C_SCL_SET(1);
	I2C_Delay(1);
	hal_I2C_SDA_SET(0);
	I2C_Delay(1);
}

static void hal_I2C_Stop(void)
{
  hal_I2C_SDA_SET(0);
	I2C_Delay(1);
	hal_I2C_SCL_SET(1);
	I2C_Delay(1);
	hal_I2C_SDA_SET(1);
	I2C_Delay(1);
}


static void hal_I2C_Config(void)
{
   GPIO_InitTypeDef GPIO_Struct;
	 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	 
	 GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_OD;
	 GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
	 GPIO_Struct.GPIO_Pin = I2C_SCL_Pin | I2C_SDA_Pin;
	 GPIO_Init(GPIOB, &GPIO_Struct);
	
	 hal_I2C_SDA_SET(1);
	 hal_I2C_SCL_SET(1);
	
}


void hal_I2C_SDA_IO_SET(unsigned char IOMode)
{
  if(0 == IOMode) {
	 GPIO_InitTypeDef GPIO_Struct;
	 GPIO_Struct.GPIO_Mode = GPIO_Mode_Out_OD;
	 GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
	 GPIO_Struct.GPIO_Pin =  I2C_SDA_Pin;
	 GPIO_Init(GPIOB, &GPIO_Struct);
	} 
	if(1 == IOMode) {
	 GPIO_InitTypeDef GPIO_Struct;
	 GPIO_Struct.GPIO_Mode = GPIO_Mode_IPU;
	 GPIO_Struct.GPIO_Speed = GPIO_Speed_50MHz;
	 GPIO_Struct.GPIO_Pin =  I2C_SDA_Pin;
	 GPIO_Init(GPIOB, &GPIO_Struct);
	}
}


static void hal_I2C_SDA_SET(unsigned char i)
{
  if(i) {
	   GPIO_SetBits(I2C_SDA_PORT,I2C_SDA_Pin);
	 } else {
	   GPIO_ResetBits(I2C_SDA_PORT,I2C_SDA_Pin);
	 }
}


static void hal_I2C_SCL_SET(unsigned char i)
{
   if(i) {
	   GPIO_SetBits(I2C_SCL_PORT,I2C_SCL_Pin);
	 } else {
	   GPIO_ResetBits(I2C_SCL_PORT,I2C_SCL_Pin);
	 }
}

static void I2C_Delay(unsigned short t)
{
  unsigned short i=50,j,c; 
   c = t;
   for(j=0; j<c; j++)
   {
	   while(i) 
	   { 
				i--; 
	   } 
	}
}

unsigned char hal_IC_SDA_INPUT(void)
{
  return GPIO_ReadInputDataBit(I2C_SDA_PORT, I2C_SDA_Pin);
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值