引言
I2C作為一種簡(jiǎn)單的數(shù)字通訊方式,僅需要兩根數(shù)據(jù)線就可以完成近距離主機(jī)(Master)與從機(jī)(Slave)之間的通訊,節(jié)省了MCU引腳以及額外的邏輯芯片,簡(jiǎn)化了PCB布板難度,因此得到了廣泛的應(yīng)用。近年來,TI也推出了越來越多支持I2C通訊功能的芯片,大大簡(jiǎn)化了芯片與MCU之間的通訊,方便了系統(tǒng)的設(shè)計(jì)。
但在實(shí)際應(yīng)用中,針對(duì)性能要求較低的應(yīng)用場(chǎng)合,通常選擇外設(shè)較為簡(jiǎn)單的低端主控MCU,可能并不具備I2C接口。對(duì)于此類應(yīng)用,可以通過MCU的IO口進(jìn)行I2C模擬,與被控器件建立通訊,達(dá)到發(fā)送控制指令、讀取內(nèi)部寄存器的目的。即使在I2C接口缺失的情況下也能夠充分發(fā)揮器件的全部功能。
本文基于C2000提供了一種利用GPIO模擬I2C控制被控芯片的解決方案,并附有完整例程。對(duì)于絕大多數(shù)采用標(biāo)準(zhǔn)I2C通信協(xié)議以及部分采用SMBus的芯片均具有參考意義。基于其它MCU的方案也可參考該例程進(jìn)行移植。
一、I2C通訊協(xié)議與GPIO模擬
I2C總線由兩條雙向信號(hào)線構(gòu)成,分別為數(shù)據(jù)線(SDA)以及時(shí)鐘線(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ù)位等。下面對(duì)各部分進(jìn)行簡(jiǎn)要的講解,并介紹如何通過C2000進(jìn)行實(shí)現(xiàn)。
圖2 I2C連接示意圖
1.1 起始及結(jié)束指令
當(dāng)某個(gè)設(shè)備在I2C總線上被配置為主機(jī)(Master),該設(shè)備可以發(fā)送起始及結(jié)束信號(hào)用來發(fā)起或結(jié)束一次I2C通信,母線電平示意圖如圖2所示。
起始信號(hào):在SCL為高電平期間,SDA由高電平轉(zhuǎn)換為低電平。
結(jié)束信號(hào):在SCL為高電平期間,SDA由低電平轉(zhuǎn)換為高電平。
圖3 I2C通訊起始及結(jié)束信號(hào)
在C2000中,可以通過以下代碼實(shí)現(xiàn)起始信號(hào)的發(fā)送。其中SCL及SDA分別代表用C2000 GPIO模擬的SDA及SCL總線,具體定義請(qǐng)參考例程部分。
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é)束信號(hào)的發(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總線時(shí)鐘信號(hào)時(shí)鐘仍由主機(jī)發(fā)出,每個(gè)SCL高電平期間對(duì)應(yīng)一位數(shù)據(jù)。在SCL高電平期間,都應(yīng)該保持SDA上的數(shù)據(jù)正確,因此在實(shí)際的應(yīng)用中,通常使得SDA的高電平脈寬寬于SCL。
地址位的發(fā)送與數(shù)據(jù)位類似,實(shí)際的操作中可以將設(shè)備的7位地址位+1位讀寫位作為一個(gè)8位字節(jié)進(jìn)行整體的發(fā)送。以BQ25703A為例,默認(rèn)設(shè)備地址為0x6B(7bit)。則在進(jìn)行讀操作時(shí),所要發(fā)送的字節(jié)為0xD7(1101011b+1b);進(jìn)行寫操作時(shí),所要發(fā)送的整體字節(jié)為0XD6 (1101011b+0b)。
數(shù)據(jù)位及地址位的發(fā)送均可參考以下發(fā)送一個(gè)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ā)生在一個(gè)byte發(fā)送結(jié)束之后,用于標(biāo)志一個(gè)byte發(fā)送的成功或失敗。特別需要注意的是,即使是在ACK時(shí)鐘周期期間,SCL總線時(shí)鐘信號(hào)也是由主機(jī)產(chǎn)生的。
ACK: 當(dāng)一次發(fā)送結(jié)束,主機(jī)釋放SDA總線。若發(fā)送成功,從機(jī)在第9個(gè)時(shí)鐘周期內(nèi)拉低SDA總線,并在整個(gè)高電平期間保持。
NACK: 當(dāng)一次發(fā)送結(jié)束,主機(jī)釋放SDA總線。若發(fā)送失敗,在第9個(gè)時(shí)鐘周期內(nèi)SDA始終處于高電平。
在通訊中作為主機(jī)的MCU通常只需要實(shí)現(xiàn)NACK的發(fā)送以及ACK信號(hào)的等待,具體可參考以下程序:
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;
}
}
}
基于以上幾個(gè)基本的I2C通訊操作,就可以發(fā)送一個(gè)完整I2C數(shù)據(jù)幀,實(shí)現(xiàn)基本的I2C通訊功能,構(gòu)建了利用GPIO口模擬I2C進(jìn)行芯片控制的基礎(chǔ)。
二、I2C模擬器件寄存器寫入與讀取
在構(gòu)建了基本的I2C通訊功能之后,就可以利用I2C通訊對(duì)Slave進(jìn)行控制或狀態(tài)的讀取,其本質(zhì)就是對(duì)Slave的內(nèi)部寄存器進(jìn)行讀寫操作。下面以一個(gè)典型的帶有I2C功能的8位寄存器芯片為例,介紹如何利用前文的基礎(chǔ)I2C模擬函數(shù)對(duì)芯片的內(nèi)部寄存器進(jìn)行寫入和讀取。
I2C 寫入:要進(jìn)行一次I2C寫入,MCU首先要發(fā)送一個(gè)起始位以及一個(gè)由7位slave地址位和讀寫位(0b)組成的8位硬件寫地址,而后釋放SDA總線。若地址正確,slave將拉低SDA發(fā)送一個(gè)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信號(hào)后發(fā)送8位的目標(biāo)寄存器地址。在Slave響應(yīng)該地址后,MCU重新發(fā)送一次起始位,以及8位Slave讀地址(7位地址+1b),ACK響應(yīng)后MCU釋放SDA總線,并繼續(xù)發(fā)送SCL時(shí)鐘信號(hào)讀取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ù),下面對(duì)例程中的主要內(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)用此代碼時(shí),只需在.h文件依照所用器件實(shí)際情況修改硬件地址及各寄存器地址,就可以很方便地調(diào)用相關(guān)函數(shù)。
2)定義I2C通訊速率
#defineI2CDelay1//DefinetoconfigureI2Crate
| I2CDelay | I2C通訊時(shí)鐘高低電平時(shí)間 |
表2 I2C通訊速率
通過改變I2CDelay可以設(shè)置I2C通訊時(shí)鐘的高低電平持續(xù)時(shí)間,進(jìn)而改變I2C的通訊速率。實(shí)際應(yīng)用中,該值可以通過實(shí)際測(cè)試進(jìn)行調(diào)整,以達(dá)到理想的通訊速率。
3)定義IO口動(dòng)作
#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對(duì)應(yīng)GPIO置1 |
| SDA_Low() | 將SDA對(duì)應(yīng)GPIO置1 |
| SDA_Input | 將SDA對(duì)應(yīng)GPIO設(shè)為輸入狀態(tài) |
| SDA_Output | 將SDA對(duì)應(yīng)GPIO設(shè)為輸出狀態(tài) |
| SDA_Data_Register | SDA對(duì)應(yīng)GPIO數(shù)據(jù)寄存器 |
| SCL_High() | 將SCL對(duì)應(yīng)GPIO置1 |
| SCL_Low() | 將SCL對(duì)應(yīng)GPIO置0 |
表3 IO口動(dòng)作宏定義
將GPIO口的動(dòng)作以宏定義的形式定義為SDA、SCL的動(dòng)作,以增強(qiáng)代碼的可讀性。在進(jìn)行程序移植時(shí),只需要根據(jù)單片機(jī)實(shí)際情況將宏定義內(nèi)的代碼更換成對(duì)應(yīng)GPIO口動(dòng)作的代碼,不需要對(duì)程序其他部分進(jìn)行改動(dòng)。其中EALLOW\EDIS語(yǔ)句是TI C2000產(chǎn)品改變GPIO口方向時(shí)需要解除相應(yīng)的保護(hù),請(qǐng)根據(jù)具體情況進(jìn)行改動(dòng)。
4)定義Delay函數(shù)
#defineDelay(A)DELAY_US(A)
Delay()函數(shù)用于進(jìn)行程序中SDA、SCL的高低電平延時(shí),在例程中實(shí)際被定義成DELAY_US()函數(shù)。在移植過程需要根據(jù)實(shí)際情況修改宏定義,更改成適用用戶MCU的延時(shí)函數(shù),不需要對(duì)后續(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通訊起始信號(hào) |
| void I2C_Finish(void) | 發(fā)送I2C通訊結(jié)束信號(hào) |
| Uint16 I2C_Wait_Ack(void) | 等待Ack應(yīng)答信號(hào),返回接收狀態(tài) |
| void I2C_NAck(void) | 發(fā)送一個(gè)NAck信號(hào),用于寄存器讀取 |
| void I2C_Send_Byte(unsigned char xtd) | 發(fā)送一個(gè)字節(jié) |
| unsigned char I2C_Read_Byte(void) | 讀取一個(gè)字節(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é)
針對(duì)由于MCU缺少I2C接口而不能直接使用I2C與外圍芯片進(jìn)行通訊的問題,本文給出了使用IO模擬I2C接口的方法。首先,從I2C協(xié)議入手對(duì)數(shù)據(jù)幀中各個(gè)位的邏輯電平進(jìn)行了詳細(xì)介紹,并給出基于C2000 GPIO的具體實(shí)現(xiàn)方法;在此基礎(chǔ)上,以常見的8位I2C通訊Slave為例介紹了內(nèi)部寄存器的讀取邏輯,并給出了實(shí)現(xiàn)方法。最后,針對(duì)附帶的參考例程內(nèi)容進(jìn)行了介紹,方便讀者參考例程,其它MCU也可以在本例程上進(jìn)行快速的移植。本文為使用IO模擬I2C需求給出了一種有效的解決方案。
審核編輯:何安
-
嵌入式處理
+關(guān)注
關(guān)注
0文章
341瀏覽量
10513
發(fā)布評(píng)論請(qǐng)先 登錄
RK平臺(tái)I2C開發(fā):從硬件原理到實(shí)戰(zhàn)排查
探索MAX7306:多功能I2C/SMBus接口GPIO與LED驅(qū)動(dòng)器
MAX7304:集成ESD保護(hù)的I2C接口16端口GPIO與LED驅(qū)動(dòng)器
ISO164x系列:增強(qiáng)EMC與GPIO功能的熱插拔雙向I2C隔離器
探索PCF8584:I2C總線控制器的卓越之選
TCA8418:I2C控制的鍵盤掃描IC深度剖析
深入剖析LM8330:I2C兼容的鍵盤控制器與多功能拓展芯片
AS32X601的I2C模塊操作EEPROM詳解
TCAL6408:8位轉(zhuǎn)換I2C總線/SMBus I/O擴(kuò)展器的深度剖析
CW32單片機(jī)I2C接口來讀寫EEPROM芯片
I2C的優(yōu)點(diǎn)介紹
深入剖析I2C協(xié)議
利用GPIO模擬I2C控制被控芯片的解決方案
評(píng)論