IIC總線
IIC協議簡要說明:
1.2條雙向串行線,一條數據線稱為SDA,一條時鍾線SCL,雙向半雙工
2.傳輸的設備之間只是簡單的主從關系,主機可以作為主機發送也可以作為主機接收,任何時候只能由一台主機發送數據
3.最多只是同時掛載128台設備(2^7),而且能夠在通信過程中改變主從身份(spi每次通信前要先設定好主機不變),可以通過仲裁和沖突檢測防止總線數據被破壞(后面講)。
連接到總線的IC數量只是受到總線的最大負載電容400pf限制。
4.支持三種速率模式
1)普通模式:100k bit/s
2)快速模式:400k bit/s
3)高速模式:3.4M bit/s
(速率模式切換可以參考datasheet:http://wenku.baidu.com/link?url=LB4zTFPmZTVyBpfBFU8lRP2eP5Fm8G9rU-fbM8ERiUTwwAw5olQhgih79msttFj1Dh89tAW74Y0aMDMXOGgCtMFDMazxhEYblyORwYh4cWC)
stm32上的硬件IIC有IIC的配置,可以設置速率,比如:
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
I2C_InitStructure.I2C_ClockSpeed = 50000;
5.IIC總線通過上拉電阻(通常取4.7k)接正電源,當總線空閑的時候兩根線都是高電平狀態,由於是線與的關系,一旦總線上的任意器件輸出低電平,總線的信號就會變成低電平,要使總線上的電平恢復高電平,那么必須要所有設備都同時恢復到高電平
6.每個接入總線的設備都有一個唯一地址(后面提)
下面結合stm32+AT24C02來說一下
原則:
IIC總線在進行數據傳輸的時候,SCL為高電平期間,SDA必須保持穩定,只有當SCL上的信號為低電平時,SDA上的高電平或低電平狀態才允許變,正常數據信號都是sda在scl低電平時准備
空閑狀態:
SDA和SCL都是在高電平,此時總線上的各個器件輸出級場效應管均處於截止狀態,釋放總線,被各自引腳上的上拉電阻(一般為4.7k)拉高。
傳輸數據時的數據有效性:在SCL為高電平的時候,SDA信號要保持穩定,SCL為低電平的時候SDA才能改變狀態,所以傳輸數據時SDA要在SCL變為高電平前做好准備
起始和停止狀態:
起始信號:SCL為高時,SDA由高到低跳變,准備跳變前高電平持續時間要大於4.7us,在SCL由高電平變低時,SDA的低電平持續時間要大於4us
停止信號:SCL為高時,SDA由低到高跳變,在SCL由低電平變高時,SDA的低電平持續時間要大於4us,跳變后高電平持續時間要大於4.7us
在起始和停止的時候都要遵守前面提到的原則,
//產生IIC起始信號
void IIC_Start(void)
{
SDA_OUT(); //sda線輸出
IIC_SDA=1;
IIC_SCL=1;
delay_us(5);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(5);
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(5);
IIC_SCL=1;
IIC_SDA=1;//發送I2C總線結束信號
delay_us(5);
}
應答狀態和非應答狀態:
主機發送完8bit數據后會等待從機的ack回復,就是在第9個位,此時從機應答需要的時鍾還是主機提供,如果從機發送ack,從機上的SDA被拉低,沒有ACK回復那么SDA就置高,主機重啟或關閉
//產生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;
}
寫狀態:
流程:
1.主機發起start
2.主機發送從機設備地址(7bit)和write(1bit)操作,等待ack
3.從機發送ack
4.主機發送寄存器地址(8bit),等待ack
5.從機發送ack
6.主機發送數據(8bit),等待ack
7.從機發送ack
8.6和7可以重復,即順序寫多個寄存器
9.主機stop
( 發送一個位)
//發送一個字節
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.主機發送從機設備地址(7bit)和write,等待ack
設備地址1010 A2A1A0 + R/W。控制字節前4位為Philip公司規定的1010,代表串行E2PROM。5-7為存儲器片選位。BIT7為1代表下一字節進行讀操作,為0代表寫
2.從機發送ack
3.主機發送寄存器地址(8bit),等待ack
4.從機發送ack
5.主機發送start
6.主機發送從機設備地址(7bit)和read操作,等待ack
7.從機發送ack
8.從機發送數據(8bit)
9.主機發送ack
10.8和9可以重復,就是順序讀取多個寄存器
11.主機stop
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;
}
AT24C02接線:
這里掛載兩個AT24C02,可以通過發送不同的地址操作不同芯片
WP接地或懸空:可讀可寫 接VCC:只可讀
AT24C02的芯片地址如下圖,1010為固定,A0,A1,A2正好與芯片的1,2,3引角對應,為當前電路中的地址選擇線,三根線可選8個芯片同時連接在電路中,當要與哪個芯片通信時傳送相應的地址即可與該芯片建立連接,設置不同的芯片地址可以將A0~A2接高低電平確定,與前面的1010形成7為編碼,即為該芯片
AT24C02的存儲容量為2Kb,內容分成32頁,每頁8B,共256B
AC24Cxx其他容量芯片:
由於24CXX的數據寬度是8位,所以當容量大於256字節時,24CXX需要分成不同的地址空間,由P2、P1、P0來決定當前訪問的地址空間。例如,AT24C04的數據空間由P0位決定,當P0為“0”時,將對AT24C04的0~255空間的數據進行操作;當P0為“1”時,將對AT24C04的256~511空間的數據進行操作。
還要注意按頁書寫的問題,AT24C01/02每頁8字節,AT24C04以上是每頁16字節,按頁寫入時,如果超過每頁字節數,超過部分會轉到該頁起始地址寫入,覆蓋原來的數據。
時鍾同步:
由於總線是線與方式,只要一個設備的SCL為低電平,總線就表現為低電平,當所有設備的SCL為高電平,總線才為高電平
仲裁:
SDA線的仲裁也是建立在總線具有線“與”邏輯功能的原理上的。節點SDA在發送1位數據后,比較總線上所呈現的數據與自己發送的是否一致。是,繼續發送;否則,退出競爭。SDA線的仲裁可以保證I2C總線系統在多個主節點同時企圖控制總線時通信正常進行並且數據不丟失。總線系統通過仲裁只允許一個主節點可以繼續占據總線
DATA1和DATA2分別是主節點向總線所發送的數據信號,SDA為總線上所呈現的數據信號,SCL是總線上所呈現的時鍾信號
過程:
當主節點1、2同時發送起始信號時,兩個主節點都發送了高電平信號。這時總線上呈現的信號為高電平,兩個主節點都檢測到總線上的信號與自己發送的信號相同,繼續發送數據。第2個時鍾周期,2個主節點都發送低電平信號,在總線上呈現的信號為低電平,仍繼續發送數據。在第3個時鍾周期,主節點1發送高電平信號,而主節點2發送低電平信號。根據總線的線“與”的邏輯功能,總線上的信號為低電平,這時主節點1檢測到總線上的數據和自己所發送的數據不一樣,就斷開數據的輸出級,轉為從機接收狀態。這樣主節點2就贏得了總線,而且數據沒有丟失,即總線的數據與主節點2所發送的數據一樣,而主節點1在轉為從節點后繼續接收數據,同樣也沒有丟掉SDA線上的數據。因此在仲裁過程中數據沒有丟失。
SDA仲裁和SCL時鍾同步處理過程沒有先后關系,而是同時進行的
24cxx.c:
#define AT24C02 255
#define EE_TYPE AT24C02
//初始化IIC接口
void AT24CXX_Init(void)
{
IIC_Init();
}
//在AT24CXX指定地址讀出一個數據
//ReadAddr:開始讀數的地址
//返回值 :讀到的數據
u8 AT24CXX_ReadOneByte(u16 ReadAddr)
{
u8 temp=0;
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //發送寫命令
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr>>8);//發送高地址
IIC_Wait_Ack();
}else IIC_Send_Byte(0XA0+((ReadAddr/256)<<1)); //發送器件地址0XA0,寫數據
IIC_Wait_Ack();
IIC_Send_Byte(ReadAddr%256); //發送低地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(0XA1); //進入接收模式
IIC_Wait_Ack();
temp=IIC_Read_Byte(0);
IIC_Stop();//產生一個停止條件
return temp;
}
//在AT24CXX指定地址寫入一個數據
//WriteAddr :寫入數據的目的地址
//DataToWrite:要寫入的數據
void AT24CXX_WriteOneByte(u16 WriteAddr,u8 DataToWrite)
{
IIC_Start();
if(EE_TYPE>AT24C16)
{
IIC_Send_Byte(0XA0); //發送寫命令
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr>>8);//發送高地址
}else
{
IIC_Send_Byte(0XA0+((WriteAddr/256)<<1)); //發送器件地址0XA0,寫數據
}
IIC_Wait_Ack();
IIC_Send_Byte(WriteAddr%256); //發送低地址
IIC_Wait_Ack();
IIC_Send_Byte(DataToWrite); //發送字節
IIC_Wait_Ack();
IIC_Stop();//產生一個停止條件
delay_ms(10);
}
//在AT24CXX里面的指定地址開始寫入長度為Len的數據
//該函數用於寫入16bit或者32bit的數據.
//WriteAddr :開始寫入的地址
//DataToWrite:數據數組首地址
//Len :要寫入數據的長度2,4
void AT24CXX_WriteLenByte(u16 WriteAddr,u32 DataToWrite,u8 Len)
{
u8 t;
for(t=0;t<Len;t++)
{
AT24CXX_WriteOneByte(WriteAddr+t,(DataToWrite>>(8*t))&0xff);
}
}
//在AT24CXX里面的指定地址開始讀出長度為Len的數據
//該函數用於讀出16bit或者32bit的數據.
//ReadAddr :開始讀出的地址
//返回值 :數據
//Len :要讀出數據的長度2,4
u32 AT24CXX_ReadLenByte(u16 ReadAddr,u8 Len)
{
u8 t;
u32 temp=0;
for(t=0;t<Len;t++)
{
temp<<=8;
temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);
}
return temp;
}
//檢查AT24CXX是否正常
//這里用了24XX的最后一個地址(255)來存儲標志字.
//如果用其他24C系列,這個地址要修改
//返回1:檢測失敗
//返回0:檢測成功
u8 AT24CXX_Check(void)
{
u8 temp;
temp=AT24CXX_ReadOneByte(255);//避免每次開機都寫AT24CXX
if(temp==0X55)return 0;
else//排除第一次初始化的情況
{
AT24CXX_WriteOneByte(255,0X55);
temp=AT24CXX_ReadOneByte(255);
if(temp==0X55)return 0;
}
return 1;
}
//在AT24CXX里面的指定地址開始讀出指定個數的數據
//ReadAddr :開始讀出的地址 對24c02為0~255
//pBuffer :數據數組首地址
//NumToRead:要讀出數據的個數
void AT24CXX_Read(u16 ReadAddr,u8 *pBuffer,u16 NumToRead)
{
while(NumToRead)
{
*pBuffer++=AT24CXX_ReadOneByte(ReadAddr++);
NumToRead--;
}
}
//在AT24CXX里面的指定地址開始寫入指定個數的數據
//WriteAddr :開始寫入的地址 對24c02為0~255
//pBuffer :數據數組首地址
//NumToWrite:要寫入數據的個數
void AT24CXX_Write(u16 WriteAddr,u8 *pBuffer,u16 NumToWrite)
{
while(NumToWrite--)
{
AT24CXX_WriteOneByte(WriteAddr,*pBuffer);
WriteAddr++;
pBuffer++;
}
}
參考:
https://www.cnblogs.com/BitArt/archive/2013/06/01/3112042.html(以c語言理解IIC,很詳細)
http://www.51hei.com/stm32/3598.html (stm32與AT24C02的I2C通信總結(模擬時序)
http://blog.csdn.net/chuckfql/article/details/19834137(I2C總線信號時序總結 )
http://blog.chinaunix.net/uid-24148050-id-120532.html (I2C接口)
http://blog.csdn.net/u010027547/article/details/47779975(
I2C總線的仲裁機制 )
http://blog.csdn.net/dndxhej/article/details/7761172(
I2C總線的7bit從機地址
)
http://blog.csdn.net/dndxhej/article/details/7757268(
I2C總線的字節格式、時鍾同步和仲裁
)