引言
I2C作為一種簡單的數(shù)字通訊方式,僅需要兩根數(shù)據(jù)線就可以完成近距離主機(jī)(Master)與從機(jī)(Slave)之間的通訊,節(jié)省了MCU引腳以及額外的邏輯芯片,簡化了PCB布板難度,因此得到了廣泛的應(yīng)用。近年來,TI也推出了越來越多支持I2C通訊功能的芯片,大大簡化了芯片與MCU之間的通訊,方便了系統(tǒng)的設(shè)計(jì)。
但在實(shí)際應(yīng)用中,針對性能要求較低的應(yīng)用場合,通常選擇外設(shè)較為簡單的低端主控MCU,可能并不具備I2C接口。對于此類應(yīng)用,可以通過MCU的IO口進(jìn)行I2C模擬,與被控器件建立通訊,達(dá)到發(fā)送控制指令、讀取內(nèi)部寄存器的目的。即使在I2C接口缺失的情況下也能夠充分發(fā)揮器件的全部功能。
本文基于C2000提供了一種利用GPIO模擬I2C控制被控芯片的解決方案,并附有完整例程。對于絕大多數(shù)采用標(biāo)準(zhǔn)I2C通信協(xié)議以及部分采用SMBus的芯片均具有參考意義。基于其它MCU的方案也可參考該例程進(jìn)行移植。
一、I2C通訊協(xié)議與GPIO模擬
I2C總線由兩條雙向信號線構(gòu)成,分別為數(shù)據(jù)線(SDA)以及時鐘線(SCL),分別用電阻進(jìn)行上拉,以實(shí)現(xiàn)高低電平之間的切換,進(jìn)行設(shè)備之間的數(shù)據(jù)交交換。I2C允許的工作電壓范圍較為寬泛,典型電壓基準(zhǔn)為+3.3V或+5V。常見的I2C總線速率分為以下幾種模式:標(biāo)準(zhǔn)模式(100Kbit/s)、快速模式(400Kbit/s)以及高速模式(3.4Mbit/s)等。如圖為典型的I2C連接示意圖:
圖1 I2C連接示意圖
如圖2為典型的I2C通訊幀格式示意圖。一幀完整的數(shù)據(jù)發(fā)送主要包括起始位、地址位、讀/寫位、ACK/NCK位、數(shù)據(jù)位等。下面對各部分進(jìn)行簡要的講解,并介紹如何通過C2000進(jìn)行實(shí)現(xiàn)。
圖2 I2C連接示意圖
1.1 起始及結(jié)束指令
當(dāng)某個設(shè)備在I2C總線上被配置為主機(jī)(Master),該設(shè)備可以發(fā)送起始及結(jié)束信號用來發(fā)起或結(jié)束一次I2C通信,母線電平示意圖如圖2所示。
起始信號:在SCL為高電平期間,SDA由高電平轉(zhuǎn)換為低電平。
結(jié)束信號:在SCL為高電平期間,SDA由低電平轉(zhuǎn)換為高電平。
圖3 I2C通訊起始及結(jié)束信號
在C2000中,可以通過以下代碼實(shí)現(xiàn)起始信號的發(fā)送。其中SCL及SDA分別代表用C2000 GPIO模擬的SDA及SCL總線,具體定義請參考例程部分。
voidI2C_Start(void)
{
Delay(I2CDelay);
SCL_High();//SettheSCL
SDA_High();//SettheSDA
Delay(I2CDelay);
SDA_Low(); //CleartheSDAwhileSCLishighindicatesthestartsignal
Delay(I2CDelay);
SCL_Low(); //CleartheSCLtogetreadytotransmit
}
可以參考以下代碼實(shí)現(xiàn)結(jié)束信號的發(fā)送:
voidI2C_Finish(void)
{
SDA_Low(); //CleartheSDA
SCL_Low(); //CleartheSCL
Delay(I2CDelay);
SCL_High();//SettheSCL
Delay(I2CDelay);
SDA_High();//SettheSDAwhileSCLishighindicatesthefinishsignal
}
1.2 數(shù)據(jù)位及地址位
I2C通訊的數(shù)據(jù)位通常由1-8的數(shù)據(jù)構(gòu)成,在主機(jī)進(jìn)行數(shù)據(jù)的發(fā)送以及讀取期間,SCL總線時鐘信號時鐘仍由主機(jī)發(fā)出,每個SCL高電平期間對應(yīng)一位數(shù)據(jù)。在SCL高電平期間,都應(yīng)該保持SDA上的數(shù)據(jù)正確,因此在實(shí)際的應(yīng)用中,通常使得SDA的高電平脈寬寬于SCL。
地址位的發(fā)送與數(shù)據(jù)位類似,實(shí)際的操作中可以將設(shè)備的7位地址位+1位讀寫位作為一個8位字節(jié)進(jìn)行整體的發(fā)送。以BQ25703A為例,默認(rèn)設(shè)備地址為0x6B(7bit)。則在進(jìn)行讀操作時,所要發(fā)送的字節(jié)為0xD7(1101011b+1b);進(jìn)行寫操作時,所要發(fā)送的整體字節(jié)為0XD6 (1101011b+0b)。
數(shù)據(jù)位及地址位的發(fā)送均可參考以下發(fā)送一個8位byte的實(shí)現(xiàn)方法:
voidI2C_Send_Byte(unsignedchartxd)
{
intt;
SDA_Output(); //Config SDA GPIO as output
SCL_Low(); //CleartheSCLtogetreadytotransmit
txd&=0X00FF; // Getthe lower8bits
for(t=0;t<8;t++)??
{
SDA_Data_Register=(txd&0x80)>>7;//SendtheLSB
txd<<=1;??
Delay(I2CDelay/2);
SCL_High();//SettheSCL
Delay(I2CDelay);
SCL_Low(); //CleartheSCL
Delay(I2CDelay/2);
}
}
1.3 ACK/NACK指令
Acknowledge(ACK)以及Not Acknowledge(NACK)指令通常發(fā)生在一個byte發(fā)送結(jié)束之后,用于標(biāo)志一個byte發(fā)送的成功或失敗。特別需要注意的是,即使是在ACK時鐘周期期間,SCL總線時鐘信號也是由主機(jī)產(chǎn)生的。
ACK: 當(dāng)一次發(fā)送結(jié)束,主機(jī)釋放SDA總線。若發(fā)送成功,從機(jī)在第9個時鐘周期內(nèi)拉低SDA總線,并在整個高電平期間保持。
NACK: 當(dāng)一次發(fā)送結(jié)束,主機(jī)釋放SDA總線。若發(fā)送失敗,在第9個時鐘周期內(nèi)SDA始終處于高電平。
在通訊中作為主機(jī)的MCU通常只需要實(shí)現(xiàn)NACK的發(fā)送以及ACK信號的等待,具體可參考以下程序:
voidI2C_NAck(void)
{
SCL_Low(); //CleartheSCLtogetreadytotransmit
SDA_Low(); //CleartheSDA
Delay(I2CDelay);
SCL_High(); //SettheSCL
Delay(I2CDelay);
SCL_Low(); //CleartheSCL
Delay(I2CDelay);
}
Uint16I2C_Wait_Ack(void)
{
intErrTime=0;
intReadAck=0;
SDA_Input();//Config SDAGPIO as Input
Delay(I2CDelay);
SCL_High();//SettheSCLandwaitforACK
while(1)
{
ReadAck=SDA_Data_Register;//Readtheinput
if(ReadAck)
{
ErrTime++;
if(ErrTime>ErrLimit)
{
//Errorhandler:Seterrorflag,retryorstop.
//Definebyusers
return1;
}
}
if(ReadAck==0) //Receive a ACK
{
Delay(I2CDelay);
SCL_Low();//CleartheSCLforNextTransmit
return0;
}
}
}
基于以上幾個基本的I2C通訊操作,就可以發(fā)送一個完整I2C數(shù)據(jù)幀,實(shí)現(xiàn)基本的I2C通訊功能,構(gòu)建了利用GPIO口模擬I2C進(jìn)行芯片控制的基礎(chǔ)。
二、I2C模擬器件寄存器寫入與讀取
在構(gòu)建了基本的I2C通訊功能之后,就可以利用I2C通訊對Slave進(jìn)行控制或狀態(tài)的讀取,其本質(zhì)就是對Slave的內(nèi)部寄存器進(jìn)行讀寫操作。下面以一個典型的帶有I2C功能的8位寄存器芯片為例,介紹如何利用前文的基礎(chǔ)I2C模擬函數(shù)對芯片的內(nèi)部寄存器進(jìn)行寫入和讀取。
I2C 寫入:要進(jìn)行一次I2C寫入,MCU首先要發(fā)送一個起始位以及一個由7位slave地址位和讀寫位(0b)組成的8位硬件寫地址,而后釋放SDA總線。若地址正確,slave將拉低SDA發(fā)送一個ACK。此后,MCU發(fā)送寫入寄存器的地址,并等待slave返回的ACK。響應(yīng)后,MCU發(fā)送8位數(shù)據(jù),并在收到ACK響應(yīng)后發(fā)送停止位。
圖4 I2C寫入寄存器幀格式
具體實(shí)現(xiàn)方法可以參考以下代碼:
voidI2C_Write_Register(unsignedcharDevice,unsignedcharRegister,unsignedcharValue)
{
I2C_Start();
I2C_Send_Byte(Device);//Sendthedeviceaddress
I2C_Wait_Ack(); //Waitfortheacksignal
I2C_Send_Byte(Register);//Sendtheregisteraddress
I2C_Wait_Ack(); //Waitfortheacksignal
I2C_Send_Byte(Value); //Sendregistervalue
I2C_Wait_Ack();
I2C_Finish();
}
I2C讀取:要讀取Slave的內(nèi)部寄存器,MCU首先要與Slave進(jìn)行一次通信,告知Slave讀取的目標(biāo)寄存器,該過程與進(jìn)行寫入操作類似。MCU首先發(fā)送起始位、8位Slave寫地址,并在ACK信號后發(fā)送8位的目標(biāo)寄存器地址。在Slave響應(yīng)該地址后,MCU重新發(fā)送一次起始位,以及8位Slave讀地址(7位地址+1b),ACK響應(yīng)后MCU釋放SDA總線,并繼續(xù)發(fā)送SCL時鐘信號讀取SDA上的內(nèi)容。接收完成后,MCU 發(fā)送NACK位以及STOP位結(jié)束一次寄存器讀取操作。
圖5 I2C讀取寄存器幀格式
8位Byte的讀方法可以參考以下代碼:
unsignedcharI2C_Read_Byte(void)
{
intt,rxData;
unsignedcharreceive;
SDA_Input();
for(t=0;t<8;t++)??
{
SCL_Low();//CleartheSCL
Delay(I2CDelay);
SCL_High();//SettheSCL
receive<<=1;??
rxData=SDA_Data_Register;
if(rxData)
{
receive++;
}
Delay(I2CDelay);
}
returnreceive;
}
寄存器的讀方法可以參考以下代碼:
unsignedcharI2C_Read_Register(unsignedcharDevice_Write,unsignedcharDevice_Read,unsignedcharRegister)
{
unsignedcharReadData;
I2C_Start();
I2C_Send_Byte(Device_Write);//Sendthedeviceaddress
I2C_Wait_Ack(); //Waitfortheacksignal
I2C_Send_Byte(Register); //Sendtheregisteraddress
I2C_Wait_Ack(); //Waitfortheacksignal
I2C_Start();
I2C_Send_Byte(Device_Read); //Sendregistervalue
I2C_Wait_Ack();
SDA_High(); //SettheSDA
ReadData=I2C_Read_Byte();
I2C_NAck();
Delay(1);
I2C_Finish();
returnReadData;
}
三、參考例程
本文附帶的例程中包含了完整GPIO模擬I2C通訊的頭文件以及函數(shù),下面對例程中的主要內(nèi)容進(jìn)行介紹,以方便讀者理解。
圖6 I2C通訊程序架構(gòu)
3.1宏定義
1)定義硬件通訊通訊地址及寄存器地址:
#defineDevice_Address_Write0xC0
#defineDevice_Address_Read0xC1
#defineREG_1 0x01
#defineREG_2 0x02
#defineREG_3 0x03
#defineREG_4 0x04
Device_Address_Write | 硬件寫地址:默認(rèn)地址0x60(7bit)+0b |
Device_Address_Read | 硬件讀地址:默認(rèn)地址0x60(7bit)+0b |
REG_1 - 4 | 硬件內(nèi)部寄存器地址 |
表1 硬件讀寫地址及寄存器地址
在調(diào)用此代碼時,只需在.h文件依照所用器件實(shí)際情況修改硬件地址及各寄存器地址,就可以很方便地調(diào)用相關(guān)函數(shù)。
2)定義I2C通訊速率
#defineI2CDelay1//DefinetoconfigureI2Crate
I2CDelay | I2C通訊時鐘高低電平時間 |
表2 I2C通訊速率
通過改變I2CDelay可以設(shè)置I2C通訊時鐘的高低電平持續(xù)時間,進(jìn)而改變I2C的通訊速率。實(shí)際應(yīng)用中,該值可以通過實(shí)際測試進(jìn)行調(diào)整,以達(dá)到理想的通訊速率。
3)定義IO口動作
#defineSDA_High(){GpioDataRegs.GPASET.bit.GPIO7=1;EALLOW;GpioCtrlRegs.GPADIR.bit.GPIO7=1;EDIS;}
#defineSDA_Low(){GpioDataRegs.GPACLEAR.bit.GPIO7=1;EALLOW;GpioCtrlRegs.GPADIR.bit.GPIO7=1;EDIS;}//TocleartheSDAline.Disableprotectionforwritingregister
#defineSDA_Input(){EALLOW;GpioCtrlRegs.GPADIR.bit.GPIO7=0;EDIS;}//SDADIR=Input
#defineSDA_Output(){EALLOW;GpioCtrlRegs.GPADIR.bit.GPIO7=1;EDIS;}//SDADIR=Output
#defineSDA_Data_RegisterGpioDataRegs.GPADAT.bit.GPIO7
#defineSCL_High(){GpioDataRegs.GPASET.bit.GPIO6=1;}//SettheSCLline
#defineSCL_Low(){GpioDataRegs.GPACLEAR.bit.GPIO6=1;}//CleartheSCLline
SDA_High() | 將SDA對應(yīng)GPIO置1 |
SDA_Low() | 將SDA對應(yīng)GPIO置1 |
SDA_Input | 將SDA對應(yīng)GPIO設(shè)為輸入狀態(tài) |
SDA_Output | 將SDA對應(yīng)GPIO設(shè)為輸出狀態(tài) |
SDA_Data_Register | SDA對應(yīng)GPIO數(shù)據(jù)寄存器 |
SCL_High() | 將SCL對應(yīng)GPIO置1 |
SCL_Low() | 將SCL對應(yīng)GPIO置0 |
表3 IO口動作宏定義
將GPIO口的動作以宏定義的形式定義為SDA、SCL的動作,以增強(qiáng)代碼的可讀性。在進(jìn)行程序移植時,只需要根據(jù)單片機(jī)實(shí)際情況將宏定義內(nèi)的代碼更換成對應(yīng)GPIO口動作的代碼,不需要對程序其他部分進(jìn)行改動。其中EALLOW\EDIS語句是TI C2000產(chǎn)品改變GPIO口方向時需要解除相應(yīng)的保護(hù),請根據(jù)具體情況進(jìn)行改動。
4)定義Delay函數(shù)
#defineDelay(A)DELAY_US(A)
Delay()函數(shù)用于進(jìn)行程序中SDA、SCL的高低電平延時,在例程中實(shí)際被定義成DELAY_US()函數(shù)。在移植過程需要根據(jù)實(shí)際情況修改宏定義,更改成適用用戶MCU的延時函數(shù),不需要對后續(xù)程序進(jìn)行修改。
3.2 I2C通訊功能函數(shù)
voidI2C_Start(void);
voidI2C_Finish(void);
Uint16I2C_Wait_Ack(void);
voidI2C_NAck(void);
voidI2C_Send_Byte(unsignedcharxtd);
unsignedcharI2C_Read_Byte(void);
函數(shù)名稱 | 功能描述 |
void I2C_Start(void) | 發(fā)送I2C通訊起始信號 |
void I2C_Finish(void) | 發(fā)送I2C通訊結(jié)束信號 |
Uint16 I2C_Wait_Ack(void) | 等待Ack應(yīng)答信號,返回接收狀態(tài) |
void I2C_NAck(void) | 發(fā)送一個NAck信號,用于寄存器讀取 |
void I2C_Send_Byte(unsigned char xtd) | 發(fā)送一個字節(jié) |
unsigned char I2C_Read_Byte(void) | 讀取一個字節(jié) |
void Gpio_setup(void) | GPIO口配置 |
void I2C_Write_Register(unsigned char Device, unsigned char Register, unsigned char value) | I2C 寫寄存器函數(shù) |
void I2C_Read_Register(unsigned char Device_Write, unsigned char Device_Read, unsigned char Register) | I2C 讀寄存器函數(shù) |
表4 I2C通訊函數(shù)
四、總結(jié)
針對由于MCU缺少I2C接口而不能直接使用I2C與外圍芯片進(jìn)行通訊的問題,本文給出了使用IO模擬I2C接口的方法。首先,從I2C協(xié)議入手對數(shù)據(jù)幀中各個位的邏輯電平進(jìn)行了詳細(xì)介紹,并給出基于C2000 GPIO的具體實(shí)現(xiàn)方法;在此基礎(chǔ)上,以常見的8位I2C通訊Slave為例介紹了內(nèi)部寄存器的讀取邏輯,并給出了實(shí)現(xiàn)方法。最后,針對附帶的參考例程內(nèi)容進(jìn)行了介紹,方便讀者參考例程,其它MCU也可以在本例程上進(jìn)行快速的移植。本文為使用IO模擬I2C需求給出了一種有效的解決方案。
審核編輯:何安
-
嵌入式處理
+關(guān)注
關(guān)注
0文章
341瀏覽量
10299
發(fā)布評論請先 登錄
I2C總線與Arduino的接口示例
I2C總線的工作模式介紹
I2C總線應(yīng)用實(shí)例分析
使用GPIO模擬I2C的電路設(shè)計(jì)
tpl0102使用gpio模擬i2c通訊,通訊不通的原因?
探索GPIO/ADC/LED/I2C/SPI/USB…的完整世界

ISO1644DWEVM具有GPIO的增強(qiáng)型隔離式I2C評估模塊

物聯(lián)網(wǎng)嵌入式軟件中的I2C總線設(shè)計(jì)詳解

利用TPS6599x和TPS6599x進(jìn)行TUsB1044 I2C模式控制和調(diào)諧

面向熱插拔應(yīng)用的 I2C 解決方案

利用具備I2C通信接口的降壓轉(zhuǎn)換器獲益

I2C靜電放電防護(hù)方案

TLA2528小型8通道12位ADC,具有I2C接口GPIO數(shù)據(jù)表

評論