IIC示例(硬件IIC和软件IIC)

本文仅供参考

        IIC是一个串行,半双工,同步的常用的总线协议,IIC总线有两根双向的信号线一根数据线SDA用于收发数据,一根时钟线SCL用于通信双方时钟的同步。
        IIC总线是一种多主机总线,连接在IIC总线上的器件分为主机和从机,主机有权发起和结束一次通信,从机只能被主机呼叫。当总线上有多个主机同时启用总线时,IIC也具备冲突检测和仲裁的功能来防止错误的产生;每个连接到IIC总线上的器件都有一个唯一的地址(7bit),且IIC上的所有设备都可以做主机,但同一时刻只能有一个主机,总线上的器件增加和删除不影响其他期间的正常使用。
硬件IIC和软件IIC有什么区别?
        硬件IIC是由开发板内部的硬件的内部模块来实现的,使用CPU的时钟信号来控制数据传输和时序。硬件IIC对于软件来说相对简单一点,不用编写复杂的代码,硬件IIC对应芯片上的IIC外设,有相应的IIC驱动电路,其所使用的IIC管脚也是专用的。
        软件IIC是由CPU的GPIO模拟实现的,通过CPU的软件来控制时序和数据传输。可以实现多路IIC通信,硬件IIC一般只能实现单路通信,可以在普通GPIO上实现IIC通信,相对灵活,可以实现任意时序,更加灵活。
        下面是软件IIC的代码实现示例:

#include "IIC.h"

#define IIC_SDA_0         GPIOB->ODR &= ((uint8_t)(~0x01))
#define IIC_SDA_1         GPIOB->ODR |= ((uint8_t)0x01)
#define IIC_SDA          (GPIOB->IDR & (uint8_t)0x01)
#define IIC_SCL_0         GPIOB->ODR &= ((uint8_t)(~0x02))
#define IIC_SCL_1         GPIOB->ODR |= ((uint8_t)0x02)

#define  Peripheral_Wr_Addr1	0x**
#define  Peripheral_Rd_Addr1	0x**
extern Delay(u8 n);//精确延时到微秒


//模拟IIC引脚方向配置
void IIC_Pin_Init(void)
{
  GPIOB->DDR |= ((uint8_t)0x01);//PC0 IIC_SDA  输出
  GPIOB->CR1 |= ((uint8_t)0x01);//PC0 IIC_SDA  推挽

  GPIOB->DDR |= ((uint8_t)0x02);//PC1 IIC_SCL  输出
  GPIOB->CR1 |= ((uint8_t)0x02);//PC1 IIC_SCL  推挽
  
  IIC_SCL_1;  //拉高时钟线
  IIC_SDA_1;  //拉高数据线
}

//模拟IIC_SDA引脚方向配置	输入
void SDA_IN(void)
{
	GPIOB->DDR &= ((uint8_t)~0x01);//PC0 IIC_SDA  输入
    GPIOB->CR1 &= ((uint8_t)~0x01);//PC0 IIC_SDA  浮空
}
//模拟IIC_SDA引脚方向配置	输出
void SDA_OUT(void)
{
	GPIOB->DDR |= ((uint8_t)0x01);//PC0 IIC_SDA  输出
    GPIOB->CR1 |= ((uint8_t)0x01);//PC0 IIC_SDA  推挽
}

//产生IIC起始信号
void IIC_Start(void)
{
  SDA_OUT();//IIC_SDA线输出
  IIC_SDA_1;	 //拉高数据线 	  
  IIC_SCL_1;	 //拉高时钟线	
  Delay(10);
  IIC_SDA_0;   //拉低数据线
  Delay(10);
  IIC_SCL_0;   //拉低时钟线  发送IIC总线开始信号
}	  
//产生IIC停止信号
void IIC_Stop(void)
{
  SDA_OUT();//IIC_SDA线输出
  IIC_SCL_0;   //拉低时钟线 
  IIC_SDA_0;   //拉低数据线
  Delay(10);
  IIC_SCL_1;   //拉高时钟线	  
  Delay(10);
  IIC_SDA_1;   //拉高数据线  发送IIC总线停止信号	
  Delay(10);							   	
}
//产生ACK应答
void IIC_Ack(void)
{
  IIC_SCL_0;   //拉低时钟线 
  SDA_OUT();//IIC_SDA线输出
  IIC_SDA_0;   //拉低数据线
  Delay(10);
  IIC_SCL_1;   //拉高时钟线
  Delay(10);
  IIC_SCL_0;   //拉低时钟线 
}
//不产生ACK应答		    
void IIC_NotAck(void)
{
  IIC_SCL_0;   //拉低时钟线 
  SDA_OUT();//IIC_SDA线输出
  IIC_SDA_1;   //拉高数据线
  Delay(10);
  IIC_SCL_1;   //拉高时钟线
  Delay(10);
  IIC_SCL_0;   //拉低时钟线 
}
//等待应答信号到来
//返回值:1		接收应答失败
//       0	        接收应答成功
u8 IIC_Wait_Ack(void)
{
  u8 Wait_TOut_Cnt = 0;//设置等待应答信号超时计数
  SDA_IN(); 		 //IIC_SDA线输入
  IIC_SDA_1;		     //拉高数据线
  Delay(10);	   
  IIC_SCL_1;         //拉高时钟线  等待应答信号	
  Delay(10);	 
  while(IIC_SDA)
  {
    Wait_TOut_Cnt++;
    if(Wait_TOut_Cnt > 250)
    {
      IIC_Stop();			 //等待应答信号超时  发送IIC总线停止信号	
      return 1;
    }
  }
  IIC_SCL_0;				 //拉低时钟线  结束应答信号	
  return 0;  
} 					 				     
//IIC发送一个字节		  
void IIC_Write_Byte(u8 WByte)
{                        
  u8 Wb_Cnt = 0; //写数据位计数    
  SDA_OUT();//IIC_SDA线输出 	    
  IIC_SCL_0;   //拉低时钟线    开始数据传输
  for(Wb_Cnt=0; Wb_Cnt<8; Wb_Cnt++)
  {
    if(WByte&0x80)
    {
      IIC_SDA_1;    
    }
    else
    {
      IIC_SDA_0;
    }
    WByte <<= 1; //数据移位
    Delay(10);
    IIC_SCL_1; //拉高时钟线
    Delay(10); 
    IIC_SCL_0; //拉低时钟线   准备开始传送数据位	
    Delay(10);
  }	 
} 	    
//IIC读取一个字节
//参数值:1		发送Ack
//       0	        不发送Ack
u8 IIC_Read_Byte(u8 SF_Ack)
{
  u8 Rb_Cnt = 0; 		//读数据位计数 
  u8 RByte  = 0; 		//读字节
  SDA_IN();				//SDA设置为输入
  for(Rb_Cnt=0; Rb_Cnt<8; Rb_Cnt++)
  {
    IIC_SCL_0; 			//拉低时钟线   准备开始传送数据位 
    Delay(10);
    IIC_SCL_1; 			//拉高时钟线
    RByte <<= 1; 		//数据移位
    if(IIC_SDA)
    {
      RByte++;
    }			
    Delay(10); 
  }					 
  if(!SF_Ack)   		 //0	不发送Ack
  {
    IIC_NotAck(); 		 //发送NAck
  }
  else           		//1		发送Ack
  {
    IIC_Ack();   		//发送Ack
  }		
  return RByte;
}
 // 写一个字节
void Peripheral_Write_Byte(u8 i2c_addr, u8 *Wreg_data)
{
  IIC_Start();								 		//发送IIC起始信号
  IIC_Write_Byte(Peripheral_Wr_Addr1);//发送IIC写地址
  IIC_Wait_Ack();							 		//等待IIC应答信号	
  IIC_Write_Byte(i2c_addr);   		 		//发送IIC寄存器地址
  IIC_Wait_Ack(); 	 					 		//等待IIC应答信号					  		   
  IIC_Write_Byte(*Wreg_data);      		//发送写入寄存器的数据					   
  IIC_Wait_Ack();  		    	   		//等待IIC应答信号			 
  IIC_Stop();									 		//发送IIC停止信号	
}

// 写N个字节
void Peripheral_Write_NByte(u8 i2c_addr, u8 *Wreg_data, u8 WLen)
{
  u8 WB_Cnt = 0;
  IIC_Start();								 		//发送IIC起始信号
  IIC_Write_Byte(Peripheral_Wr_Addr1);//发送IIC写地址
  IIC_Wait_Ack();							 		//等待IIC应答信号	
  IIC_Write_Byte(i2c_addr);   		 		//发送IIC寄存器地址
  IIC_Wait_Ack(); 	 					 		//等待IIC应答信号					  		   
  for(WB_Cnt=0; WB_Cnt<WLen; WB_Cnt++)
  {
    IIC_Write_Byte(Wreg_data[WB_Cnt]);      //连续写入寄存器的数据		等待应答信号	  
    IIC_Wait_Ack();  		    	   		//等待IIC应答信号			 
  }  
  IIC_Stop();									 		//发送IIC停止信号	
}

// 读一个字节
void Peripheral_Read_Byte(u8 i2c_addr, u8 *reg_dataR)
{
  IIC_Start();								 		//发送IIC起始信号
  IIC_Write_Byte(Peripheral_Wr_Addr1);//发送IIC写地址
  IIC_Wait_Ack();  						 		//等待IIC应答信号
  IIC_Write_Byte(i2c_addr);   		 		//发送IIC寄存器地址
  IIC_Wait_Ack();	    				 		//等待IIC应答信号	
  IIC_Start();  	 	   				 		//发送IIC起始信号
  IIC_Write_Byte(Peripheral_Rd_Addr1);//发送IIC读地址	   
  IIC_Wait_Ack();	 						 		//等待IIC应答信号		
  *reg_dataR = IIC_Read_Byte(0);	 		//读取寄存器的数据	   
  IIC_Stop();									 		//发送IIC停止信号	
}

// 读N个字节
void Peripheral_Read_NByte(u8 i2c_addr, u8 *reg_dataR, u8 RLen)
{
  u8 RB_Cnt = 0;							 		   //读字节计数
  IIC_Start();								 		   //发送IIC起始信号
  IIC_Write_Byte(Peripheral_Wr_Addr1);                     //发送IIC写地址
  IIC_Wait_Ack();							 		   //等待IIC应答信号	
  IIC_Write_Byte(i2c_addr);			 			       //发送IIC寄存器地址
  IIC_Wait_Ack();	    				 			   //等待IIC应答信号		
  for(RB_Cnt=0; RB_Cnt<(RLen-1); RB_Cnt++)
  {
    reg_dataR[RB_Cnt] = IIC_Read_Byte(1);                  //连续读取寄存器的数据		等待应答信号	  
  }
  reg_dataR[RB_Cnt] = IIC_Read_Byte(0);                    //读取寄存器最后一个数据		不等待应答信号
  IIC_Stop();										   //发送IIC停止信号	
}

 

实现软件IIC的代码需要通过GPIO模拟IIC时序,具体步骤如下:
配置GPIO用于IIC通信,将SCL和SDA引脚分别配置为推挽输出模式;
实现IIC起始信号、停止信号、产生ACK应答、不产生ACK应答、发送数据、接收数据等操作;
编写具体的IIC外设读写函数:1.主机发送起始信号启用总线
                           2.主机发送一个字节数据指明从机地址和后续字节的传送方向
                           3.被寻址的从机发送应答信号回应主机
                           4.发送器发送一个字节数据
                           5.接收器发送应答信号回应发送器
                           ... ...(循环步骤4、5)
                           n.通信完成后主机发送停止信号释放总线

时序上
空闲状态:iic通信协议规定SDA、SCL两根线同时处于高电平;
起始信号:通信的起始点;    起始信号要求,SCL处于高电平时,将SDA从高拉低(跳变);
停止信号:通信的结束点;    停止信号要求在SCL处于高电平时,将SDA由低拉高(跳变);
应答信号:通信过程中接收器对发送器的反馈;    
            IIC总线通信时每个字节为8位长度,数据传送时,先传送最高位,后传送低位,发送器发送完一个字节数据后
            接收器必须发送1位应答位来回应发送器即一帧共有9位,和串口的先发低位不同,IIC是先发高位,然后从高到低。
            9位=7位从机地址+1位传送方向标志位+1位对齐从机的应答位
            9位=8位数据+1位对齐从机的应答位
数据有效性:数据传输过程中,当SCL=1高电平时,数据线SDA必须保持稳定,要么保持高电平,要么保持低电平,这期间不能有电平跳变,只有在时钟线上的信号为低电平时,数据线上的高电平或低电平状态才允许变化。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值