下面我們從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)組成。