iIC协议简析

下面我们从iic的物理层和协议层说起

物理层

I2C 通讯设备之间的常用连接方式

它的物理层有如下特点:
(1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线
中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。
(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线
(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。
(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之
间的访问。
(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空
闲,都输出高阻态时,由上拉电阻把总线拉成高电平。
(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用
总线。
(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式
下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。
(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

协议层

总线信号 : SDA :串行数据线 SCL :串行时钟

总线空闲状态 :SDA :高电平 SCL :高电平

起始位:SCL为高电平期间 SDA出现下降沿

终止位:SCL为高电平期间 SDA出现上升沿

数据传输 :SDA的数据在SCL高电平期间被写入从机。所以SDA的数据变化要发生在SCL低电平期间。

地址及数据方向

I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。 I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向,它是数据方向位(R/W),第 8 位或第 11 位。数据方向位为“ 1”时表示主机由从机读数据,该位为“0”时表示主机向从机写数据

响应

I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后,若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)” 信号,发送方接收到该信号后会产生一个停止信号,结束信号传输

.H

#ifndef __MYIIC_H
#define __MYIIC_H
#include "sys.h"
//////////////////////////////////////////////////////////////////////////////////	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK战舰STM32开发板
//IIC驱动 代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/9
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved									  
//////////////////////////////////////////////////////////////////////////////////

//IO方向设置
 
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}

//IO操作函数	 
#define IIC_SCL    PBout(6) //SCL
#define IIC_SDA    PBout(7) //SDA	 
#define READ_SDA   PBin(7)  //输入SDA 

//IIC所有操作函数
void IIC_Init(void);                //初始化IIC的IO口				 
void IIC_Start(void);				//发送IIC开始信号
void IIC_Stop(void);	  			//发送IIC停止信号
void IIC_Send_Byte(u8 txd);			//IIC发送一个字节
u8 IIC_Read_Byte(unsigned char ack);//IIC读取一个字节
u8 IIC_Wait_Ack(void); 				//IIC等待ACK信号
void IIC_Ack(void);					//IIC发送ACK信号
void IIC_NAck(void);				//IIC不发送ACK信号

void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data);
u8 IIC_Read_One_Byte(u8 daddr,u8 addr);	  
#endif

.C

#include "myiic.h"
#include "delay.h"
//////////////////////////////////////////////////////////////////////////////////	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK战舰STM32开发板
//IIC驱动 代码	   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/9/9
//版本:V1.0
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved									  
//////////////////////////////////////////////////////////////////////////////////
 
//初始化IIC
void IIC_Init(void)
{					     
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(	RCC_APB2Periph_GPIOB, ENABLE );	//使能GPIOB时钟
	   
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ;   //推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); 	//PB6,PB7 输出高
}
//产生IIC起始信号
void IIC_Start(void)
{
	SDA_OUT();     //sda线输出
	IIC_SDA=1;	  	  
	IIC_SCL=1;
	delay_us(4);
 	IIC_SDA=0;//START:when CLK is high,DATA change form high to low 
	delay_us(4);
	IIC_SCL=0;//钳住I2C总线,准备发送或接收数据 
}	  
//产生IIC停止信号
void IIC_Stop(void)
{
	SDA_OUT();//sda线输出
	IIC_SCL=0;
	IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
 	delay_us(4);
	IIC_SCL=1; 
	IIC_SDA=1;//发送I2C总线结束信号
	delay_us(4);							   	
}
//等待应答信号到来
//返回值:1,接收应答失败
//        0,接收应答成功
u8 IIC_Wait_Ack(void)
{
	u8 ucErrTime=0;
	SDA_IN();      //SDA设置为输入  
	IIC_SDA=1;delay_us(1);	   
	IIC_SCL=1;delay_us(1);	 
	while(READ_SDA)
	{
		ucErrTime++;
		if(ucErrTime>250)
		{
			IIC_Stop();
			return 1;
		}
	}
	IIC_SCL=0;//时钟输出0 	   
	return 0;  
} 
//产生ACK应答
void IIC_Ack(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=0;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}
//不产生ACK应答		    
void IIC_NAck(void)
{
	IIC_SCL=0;
	SDA_OUT();
	IIC_SDA=1;
	delay_us(2);
	IIC_SCL=1;
	delay_us(2);
	IIC_SCL=0;
}					 				     
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答			  
void IIC_Send_Byte(u8 txd)
{                        
    u8 t;   
	SDA_OUT(); 	    
    IIC_SCL=0;//拉低时钟开始数据传输
    for(t=0;t<8;t++)
    {              
        //IIC_SDA=(txd&0x80)>>7;
		if((txd&0x80)>>7)
			IIC_SDA=1;
		else
			IIC_SDA=0;
		txd<<=1; 	  
		delay_us(2);   //对TEA5767这三个延时都是必须的
		IIC_SCL=1;
		delay_us(2); 
		IIC_SCL=0;	
		delay_us(2);
    }	 
} 	    
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK   
u8 IIC_Read_Byte(unsigned char ack)
{
	unsigned char i,receive=0;
	SDA_IN();//SDA设置为输入
    for(i=0;i<8;i++ )
	{
        IIC_SCL=0; 
        delay_us(2);
		IIC_SCL=1;
        receive<<=1;
        if(READ_SDA)receive++;   
		delay_us(1); 
    }					 
    if (!ack)
        IIC_NAck();//发送nACK
    else
        IIC_Ack(); //发送ACK   
    return receive;
}

//另一种

。h

#ifndef _IIC_H
#define _IIC_H

#include "stm32l1xx_hal.h"
#include "stdint.h"
#include "main.h"

#define SCL_H         HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_SET)
#define SCL_L         HAL_GPIO_WritePin(SCL_GPIO_Port, SCL_Pin, GPIO_PIN_RESET)
    
#define SDA_H         HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_SET)
#define SDA_L         HAL_GPIO_WritePin(SDA_GPIO_Port, SDA_Pin, GPIO_PIN_RESET)
#define SCL_read     HAL_GPIO_ReadPin(SCL_GPIO_Port,SCL_Pin) 
#define SDA_read     HAL_GPIO_ReadPin(SDA_GPIO_Port,SDA_Pin) 
 

void I2C_delay(void);
bool I2C_Start(void);
void I2C_Stop(void) ;
void I2C_Ack(void);
void I2C_NoAck(void);
uint8_t I2C_WaitAck(void);
void I2C_SendByte(uint8_t SendByte);
uint8_t I2C_ReceiveByte(uint8_t ack);
bool I2C_Write(uint8_t dev,uint8_t WriteAddr,uint8_t WriteData);
bool I2C_Write2(uint8_t WriteAddr,uint8_t WriteData);
uint8_t I2C_Read(uint8_t dev,uint8_t WriteAddr);
uint8_t I2C_Read2(uint8_t WriteAddr) ;
void I2C_DevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf);
void I2C_DevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf);

#endif

。C

#include "iic.h"

void I2C_delay(void) 
{ 
   uint16_t i=1000; //Set delay time value
   while(i)  
   {  
     i--;  
   }  
} 
/**
  * @brief I2C Start    //产生IIC起始信号
  * @param None
  * @retval None
  */
bool I2C_Start(void) 
{ 
	SDA_H;
	I2C_delay();
	SCL_H;
	I2C_delay();
	if(!SDA_read)return FALSE;	//SDA Always low return FALSE
	SDA_L;
	I2C_delay();
	if(SDA_read) return FALSE;	//SDA Always high return FALSE
	SCL_L;
	I2C_delay();
	return TRUE;
} 
/**
  * @brief I2C Stop
  * @param None
  * @retval None
  */
void I2C_Stop(void) 
{ 
  SCL_L;
  I2C_delay();	
	SDA_L; 
	I2C_delay();
	SCL_H; 
	I2C_delay(); 
	SDA_H; 
	I2C_delay();
} 
/**
  * @brief I2C Ack
  * @param None
  * @retval None
  */
void I2C_Ack(void) 
{ 
  SCL_L; 
	I2C_delay(); 
	SDA_L; 
	I2C_delay(); 
	SCL_H; 
	I2C_delay(); 
	SCL_L; 
	I2C_delay(); 


} 
/**
  * @brief I2C No Ack
  * @param None
  * @retval None
  */
void I2C_NoAck(void) 
{ 
  SCL_L; 
	I2C_delay(); 
	SDA_H; 
	I2C_delay(); 
	SCL_H; 
	I2C_delay(); 
	SCL_L; 
	I2C_delay(); 
}
/**
  * @brief  Wait Ack
  * @param  None
  * @retval bool FALSE:0--->no ACK 
  *              TRUE :1--->ACK
  */
uint8_t I2C_WaitAck(void)
{ 
	uint8_t re;
	SCL_L; 
	I2C_delay();
	SDA_H; 
	I2C_delay(); 
	SCL_H; 
	I2C_delay(); 
	if(SDA_read) 
	{ 
    re=0;   
	} 
	else re=1;
	SCL_L; 
	return re; 
}
/**
  * @brief Send one Byte
  * @param uint8_t SendByte
  * @retval None
  */
void I2C_SendByte(uint8_t SendByte)
{ 
	uint8_t i=8;
	
  while(i--) 
  { 	
		SCL_L;
		I2C_delay(); 
    if(SendByte&0x80) 
		{
     SDA_H; 
		}			
    else
		{			
     SDA_L; 
		}			
    SendByte<<=1; 
    I2C_delay(); 
		SCL_H; 
    I2C_delay(); 
		
  } 
	SCL_L; 	 
}
/**
  * @brief Receive one Byte
  * @param uint8_t ack
  * @retval receive  receive one byte
  */
uint8_t I2C_ReceiveByte(uint8_t ack) 
{  
	unsigned char i=8,receive=0; 	 
			SDA_H;	
    while(i--)
    {
        receive<<=1;      
        SCL_L;
        I2C_delay();
	      SCL_H;
        I2C_delay();
        if(SDA_read)
        {
          receive|=0x01;
        }
    }
    SCL_L;
    if (!ack)
        I2C_NoAck();  //Send Nack
    else
        I2C_Ack();   //Send ack  
    return receive;
} 
/**
  * @brief Write a Byte to the device
  * @param uint8_t WriteAddr,uint8_t WriteData
  * @retval bool FALSE: 0
  *              TRUE : 1
  */
bool I2C_Write(uint8_t dev,uint8_t WriteAddr,uint8_t WriteData) 
{ 
	I2C_Start();  
	I2C_SendByte(dev); //Send write cmd   
	I2C_WaitAck();	   
	I2C_SendByte(WriteAddr); //Send addr                                                    
	I2C_WaitAck(); 	 										  		   
	I2C_SendByte(WriteData); //Send data                                             						   
	I2C_WaitAck();  		    	   
	I2C_Stop();		   	//iic stop
	return TRUE;
} 
/********************************************************************/
bool I2C_Write2(uint8_t WriteAddr,uint8_t WriteData)   
{ 
    if (!I2C_Start()) return FALSE; 
    I2C_SendByte(0x10);//设置器件地址+段地址  
    if (!I2C_WaitAck()) 
		{ 
			I2C_Stop();  
			return FALSE; 
		} 
    I2C_SendByte(WriteAddr);   //设置段内地址       
		I2C_WaitAck(); 
   
		I2C_SendByte(WriteData); 
		I2C_WaitAck(); 
		I2C_Stop(); 
 
		return TRUE; 
} 
/************************************************************************/
/**
  * @brief Read a byte from the device
  * @param uint8_t WriteAddr
  * @retval temp  Return the read byte 
  */      
uint8_t I2C_Read(uint8_t dev,uint8_t WriteAddr) 
{ 
	uint8_t temp=0;		  	    																 
	I2C_Start();  
	I2C_SendByte(dev);  //Send write cmd                                      	   
	I2C_WaitAck(); 
	I2C_SendByte(WriteAddr); //Send addr                        
	I2C_WaitAck();	

	I2C_Start();  	 	   
	I2C_SendByte(dev|1);  //Send read cmd                                               
	I2C_WaitAck();	 
	temp=I2C_ReceiveByte(0);			   
	I2C_Stop();                   	    
	return temp;
} 
/*********************************************************************************/
//读出1串数据          
uint8_t I2C_Read2(uint8_t WriteAddr) 
{ 
	uint8_t tempDat=0;	
	if (!I2C_Start()) return FALSE; 
  I2C_SendByte(0x77);//设置器件地址+段地址  

  if (!I2C_WaitAck())  
	{ 
		I2C_Stop();  
		return FALSE; 
	} 
    I2C_SendByte(WriteAddr);   //设置低起始地址       
    I2C_WaitAck(); 
    I2C_Start(); 
    I2C_SendByte(0x77 | 0x01); 
    I2C_WaitAck(); 
    tempDat = I2C_ReceiveByte(0);  
    I2C_Stop(); 

		return tempDat; 
} 
/**
  * @brief Read continuously
  * @param uint8_t devaddr  device addr
  *        uint8_t addr     Start addr 
  *        uint8_t len      read data length
  *        uint8_t *rbuf    read data buf 
  * @retval None
  */ 
void I2C_DevRead(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *rbuf)
{
	int i=0;
	I2C_Start();  
	I2C_SendByte(devaddr);  
	if(!I2C_WaitAck())
	{
	  I2C_Stop();
		return ;
	}		
	I2C_SendByte(addr);  //address ++ 
	if(!I2C_WaitAck())
	{
	  I2C_Stop();
		return ;
	}	
	I2C_Start();
	I2C_SendByte(devaddr|0x01);  	
	if(!I2C_WaitAck())
	{
	  I2C_Stop();
		return ;
	}		
	for(i=0; i<len; i++)
	{

		if(i==len-1)
		{
			rbuf[i]=I2C_ReceiveByte(0);  //The last byte does not answer
		}
		else
			rbuf[i]=I2C_ReceiveByte(1);
	}
	I2C_Stop( );	
}

/**
  * @brief Write continuously
  * @param uint8_t devaddr  device addr
  *        uint8_t addr     Start addr 
  *        uint8_t len      read data length
  *        uint8_t *rbuf    read data buf 
  * @retval None
  */ 
void I2C_DevWrite(uint8_t devaddr,uint8_t addr,uint8_t len,uint8_t *wbuf)
{
	int i=0;
	I2C_Start();  
	I2C_SendByte(devaddr);  	
	I2C_WaitAck();	
	I2C_SendByte(addr);  //address ++ 
	I2C_WaitAck();	
	for(i=0; i<len; i++)
	{
		I2C_SendByte(wbuf[i]);  
		I2C_WaitAck();		
	}
	I2C_Stop( );	
}

同步和异步通讯

首先是两者的不同:
同步通信要求接收端时钟频率和发送端时钟频率一致,发送端发送连续的比特流;异步通信时不要求接收端时钟和发送端时钟同步,发送端发送完一个字节后,可经过任意长的时间间隔再发送下一个字节。

1、同步通信效率高;异步通信效率较低。

2、同步通信较复杂,双方时钟的允许误差较小;异步通信简单,双方时钟可允许一定误差。
3、同步通信可用于点对多点;异步通信只适用于点对点。

异步通信
异步通信中的接收方并不知道数据什么时候会到达,收发双方可以有各自自己的时钟。发送方发送的时间间隔可以不均,接收方是在数据的起始位和停止位的帮助下实现信息同步的。这种传输通常是很小的分组,比如一个字符为一组,为这个组配备起始位和结束位。所以这种传输方式的效率是比较低的,毕竟额外加入了很多的辅助位作为负载,常用在低速的传输中。

以RS232协议规定为例,异步通信一个字符一个字符地传输,每个字符一位一位地传输,并且传输一个字符时,总是以“起始位”开始(低电平,逻辑值0),以“停止位”结束,字符之间没有固定的时间间隔要求。字符数据本身由5~8位数据位组成,接着字符后面是一位校验位(也可以没有校验位),最后是一位或一位半或二位停止位,停止位后面是不定长的空闲位。停止位和空闲位都规定为高电平(逻辑值1),这样就保证起始位开始处一定有一个下跳沿,

举个例子,我们的键盘按下一个按键,发出一个字符信号,异步传输机制就会为它加上前后的辅助同步信息,帮助接收方识别到我们按下了哪一个按键。因为我们敲击键盘的节奏不固定,所以异步是一种很适合的方式

同步通信
同步通信中双方使用频率一致的时钟 ,它的分组相比异步则大得多,称为一个数据帧,通过独特的bit串作为启停标识。发送方要以固定的节奏去发送数据,而接收方要时刻做好接收数据的准备,识别到前导码后马上要开始接收数据了。同步这种方式中因为分组很大,很长一段数据才会有额外的辅助位负载,所以效率更高,更加适合对速度要求高的传输,当然这种通信对时序的要求也更高。 


同步通信是一种连续串行传送数据的通信方式,一次通信只传送一帧信息,由同步字符、数据字符和校验字符(CRC)组成。

发布了14 篇原创文章 · 获赞 5 · 访问量 1021

猜你喜欢

转载自blog.csdn.net/qq_42930154/article/details/104804376