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)組成。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章