51微控制器模擬I2C協議
什麼是I2C
首先需要知道什麼是I2C協議。I2C匯流排是由Philips公司開發的一種簡單、雙向二線制同步序列匯流排。它只需要兩根線即可在連線於總線上的器件之間傳送資訊(摘自百度百科)。I2C主要有兩條線,一條SDA資料線,一條SCL時鐘線。由於I2C支援主機與多從機之間的通訊,所以在傳輸資料之前先進行定址操作,才能與對應的從機建立通訊。多從機的存在可能出現多個從機同時需要佔用匯流排的情況,這時候就要通過匯流排仲裁來選擇與某個從機進行通訊了。每次通訊傳送或接收的資料為8位一個位元組。
I2C時序圖詳解及通訊協議程式
空閒時的SCL和SDA兩條線都為高電平,每次通訊結束後都需要釋放匯流排,將SCL和SDA拉高。
起始訊號和結束訊號
起始訊號和結束訊號時序圖如下:
可以看到起始訊號是這樣產生的:先將SDA資料匯流排拉低,再將SCL時鐘線拉低;而結束訊號與其實訊號相反,先把SCL時鐘線拉高後再將SDA資料匯流排拉高。
程式程式碼如下:
//起始訊號
void I2C_start()
{
I2C_SCL_1;
I2C_SDA_1;
I2C_Delay();
I2C_SDA_0;
I2C_Delay();
I2C_SCL_0;
}
//結束訊號
void I2C_stop()
{
I2C_SDA_0;
I2C_SCL_1;
I2C_Delay();
I2C_SDA_1 ;
}
資料傳輸
I2C要求在SCL高電平期間資料線保持穩定,在低電平期間SDA可以產生電平跳變和資料變化。
傳送資料程式碼如下:
void I2C_sendbyte(uint8_t send_data)
{
uint8_t i;
for(i=0;i<8;i++)
{
if(send_data&(0X80>>i))
I2C_SDA_1;
else
I2C_SDA_0;
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0 ;
}
}
接收資料如下:
uint8_t I2C_readbyte(uint8_t ack)
{
uint8_t read_data=0;
uint8_t i;
uint8_t readbit;
for(i=0;i<8;i++)
{
if(I2C_SDA_0)
readbit=0;
else
readbit=1;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0;
read_data= (read_data|readbit)<<1;
}
I2C_Delay();
//應答訊號
if(ack==0)
I2C_NACK();
else
I2C_ACK();
return Data;
}
應答訊號
有應答訊號(ACK)和非應答訊號(NACK)兩種,程式碼如下:
void I2C_ACK(void) // 應答訊號
{
I2C_SDA_0;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0;
I2C_SDA_1;
}
void I2C_NACK(void) //非應答訊號
{
I2C_SDA_1;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
I2C_SCL_0;
I2C_Delay();
I2C_SDA_0;
}
還有一個等待從機發送應答訊號的函式:
uint8_t waitACK()
{
uint8_t receive;
I2C_SDA_1;
I2C_Delay();
I2C_SCL_1;
I2C_Delay();
if(I2C_SDA_1)
receive=1;
else
receive=0;
I2C_SCL_0;
I2C_Delay();
return receive;
}
通訊協議
在對某個晶片的暫存器進行讀寫操作的時候,需要嚴格按照時序圖的操作來執行:
I2C的通訊協議大概分為幾個步驟:
1.主機發出起始訊號
2.主機發出定址訊號並確認本次操作是讀還是寫(由定址訊號的最後一位是0還是1決定,0為寫,1為讀)
3.從機發送應答訊號,主機接收
4.主機發送資料訊號,從機接收
5.從機發送應答訊號,主機接收
6.主機發送停止訊號,從機接收,然後結束一次通訊。
寫入暫存器時的程式碼如下:
void I2C_write_reg(uint8_t slaveaddr,uint8_t regaddr,uint8_t*writebuffer,uint8_t Wlen)
{
uint8_t writeflag=0;
uint8_t i;
uint8_t error;
I2C_start(); //起始訊號
I2C_sendbyte(slaveaddr|0x00); //定址訊號:從機地址+寫操作
if(waitACK) //等待從機應答訊號
error|=0x01;
I2C_sendbyte(regaddr); //暫存器地址
if(waitACK)
error|=0x02;
for(i=0;i<Wlen;i++) //寫入資料
{
I2C_sendbyte(writebuffer[i]);
if(waitACK)
error|=0x04;
}
I2C_stop(); //停止訊號
}
讀取從機資料的程式碼如下。需要注意每讀取完一個位元組(8位)資料後,都需要傳送一次應答訊號,當最後一個位元組資料讀取完畢之後傳送非應答訊號和結束訊號以結束本次通訊:
void I2C_read_reg(uint8_t slaveaddr,uint8_t regaddr,uint8_t*readbuffer,uint8_t Rlen)
{
uint8_t error=0;
uint8_t i=0;
I2C_start();
I2C_sendbyte(slaveaddr|0x00);
if(waitACK)
error|=0x01;
I2C_sendbyte(regaddr);
if(waitACK)
error|=0x02;
I2C_sendbyte(slaveaddr|0x01);
if(waitACK)
error|=0x04;
for(i=0;i<=(Rlen-1);i++)
{
*readbuffer=I2C_readbyte(1); //每讀取完一個位元組,都需要給從機一個應答訊號
readbuffer++;
}
*readbuffer=I2C_readbyte(0); //最後一個位元組讀取完傳送非應答訊號
I2C_stop();
}