I2C起源
內部整合電路(Inter-Integrated
Circuit, I2C, 讀做I-square-C)是由飛利浦半導體公司開發的一種專用介面。I2C是以最少的連接線進行硬體佈線還要有靈活擴充的特性為目標而設計,最後出現了只有以序列資料線SDA(Serial DAta)及序列時脈線SCL(Serial CLock)來進行所有通訊的I2C介面,I2C允許多主(master)多僕(slave)系統,其傳輸系統內每一個裝置都有唯一的地址可供辨識。資料的寫入和讀取都是由master主動發起,slave無法主動向master回報,除非使用中斷腳通知master。I2C傳輸速度有慢(小於100Kbps)、快(400Kbps)及高速(3.4Mbps)三種,每一種均可向下相容。
I2C電路配置
如前所述I2C為兩線式,一為時脈線SCL,另一條為資料線SDA,硬體線路如圖1,兩線皆為雙向性,且都需要透過高接電阻(pull-up, 對岸說的上拉電阻)接電。平常不使用時,SCL與SDA的訊號都處於高電位。為了多裝置共線的功能,裝置的 SCL 和 SDA 腳位要為開洩極(open-drain)或開集極(open-collector)。一旦有一個腳位的開洩極導通接地,則整條線都為低電位,這種現象稱作wired-AND運作;如同邏輯AND運算,需要共接的腳位都是1(開洩極斷路),該條線的電位才是1。如果沒有開洩極的腳位,可以使用具內部高接電阻的腳位,當要輸出1時,則設定該腳位為高接型輸入腳;而輸出為0時,則改設定為輸出腳並輸出0的值。
圖1. I2C傳輸裝置接線[1]
I2C通訊協定
為使說明部分更簡潔,首先介紹幾個名詞:
位元傳輸協定
當master要跟slave溝通時,會先有個起始條件(start condition)的訊號,結束時也會送出終止條件(stop condition)訊號。起始條件訊號為SDA拉low接著SCL拉low,做動方式如圖2,終止條件訊號則是SCL拉high接著SDA拉high,做動方式如圖3,此特殊訊號是唯二可以在SCL為high時變換SDA值的訊號,其他位元訊號只能趁SCL為low時變換,為high時要持平如圖4。
圖2. 起始條件之SDA與SCL的變化[2]
圖3. 終止條件之SDA與SCL的變化[2]
圖4. 位元訊號之SDA與SCL的變化[3]
當master送出8 bits的資料內容後,master的SDA會維持高電位,等待slave回傳,若slave傳回SDA low做為ACK,表示有成功收到,且準備好繼續接收下一次的資料;若SDA依然是high,表示slave尚未準備好接收訊號,此時master應該傳送結束訊號,因此每一次的資料傳輸,master都需要發出9個SCL訊號脈衝,做動方式如圖5。
圖5. 位元訊號之SDA與SCL的變化[1]
資料傳輸流程
I2C bus上每一個裝置都有唯一的地址(address),通常以7位元來儲存該地址,因此最多可以有128個裝置在I2C bus上。傳送資料時,除了傳送7位元地址外,會再加入第8位元做為讀/寫的控制碼,控制碼為0表示master要寫資料過去,控制碼為1表示master要進行讀取的動作,因此如要寫資料到位址0x60(1100-000)的slave,指令為0xC0(1100-0000),這樣會有點難以記憶,建議可以整個位元組一起看,直接以0xC0為該位址的裝置寫入動作指令,以0xC1為讀取指令。
當master要進行通訊的時候,會先傳送起始條件,通知I2C bus上的所有slave準備要接收訊息,接下來master就會將包含位址和控制碼的8 bit指令傳送出去,符合位址的slave便會知道要開始動作,而其他的slave會忽略此次的呼叫,等待下一次的起始訊號,接下來master跟slave開始進行通訊,結束時master再傳送結束訊號即結束此次的資料傳送。
master發起寫資料入slave的主要流程如下所述,完整流程如圖6:
- 傳送一個起始條件訊號
- 傳送slave的I2C address以及讀/寫控制碼“0”
- 傳送slave內要寫入的內部暫存器號碼
- 傳送資料位元組
- [選]傳送其他資料位元組
- 等待slave傳回ACK/NACK
- 傳送一個終止條件訊號
圖6. master發起寫入資料到slave的流程[3]
舉例來說,若有一個I2C位址為0xE0的超音波感測器SRF08,需對0x00指令暫存器寫入0x51來啟動搜索,其流程為:
- 傳送一個起始條件訊號
- 傳送0xE0(slave的I2C address以及讀/寫控制碼“0”)
- 傳送0x00(slave內要寫入的內部暫存器號碼)
- 傳送0x51(資料位元組,讓感測器啟動搜索的指令)
- 傳送一個終止條件訊號
在讀取資料方面,讀取前需要告知slave欲讀取的內部暫存器位址,所以在讀取之前要先進行寫入的動作:發動起始訊號、傳送slave位址以及寫入的位元指令、資料存放的內部暫存器位址,然後再發送一次起始訊號,傳送slave位址、使用讀取的位元指令,此時便可以進行讀取,接收完畢後,若還有下一筆資料則master需傳送ACK,若再無下一筆資料,master則傳送NACK(如圖7所示),結束後再傳送終止訊號。
圖7. master發起向slave讀取資料的流程[3]
舉例來說,若要讀取電子羅盤CMPS03的方位,其流程為:
- 傳送一個起始訊號
- 傳送0xC0(電子羅盤CMPS03的I2C位址與讀/寫控制碼”0’)
- 傳送0x01(方位暫存器的內部位址)
- 再傳送一起始訊號(重覆起始條件, 又稱Sr)
- 傳送0xC1(電子羅盤CMPS03的I2C位址與讀/寫控制碼high)
- 從CMPS03讀取資料
- 傳送一終止訊號
其他事項
當master要讀取slave的資料時,將資料放上SDA的是slave,然而SCL卻是由master來進行控制,master又該如何知道slave是否準備好回傳資料了呢?如果slave是正在忙碌中的微控制器,如此一來微控制器便會進入中斷,將工作儲存至暫存器,再回頭看master要讀取的暫存器位址,取出資料放到傳送暫存器,如果master很頻繁的傳送資料,這樣就會有點糟糕,所以當slave沒有準備好接收下一筆資料時,slave可以把SCL拉low,此狀態又稱為時脈延展(clock stretching)讓master暫停發送,準備好時再把SCL拉回high。
參考資料:Sample Code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#define SCL TRISB4 // I2C bus | |
#define SDA TRISB1 // | |
#define SCL_IN RB4 // | |
#define SDA_IN RB1 // | |
// initialize | |
SDA = SCL = 1 ; | |
SCL_IN = SDA_IN = 0 ; | |
// make master wait | |
void i2c_dly(void) { | |
// i2c is not quite fast, needs delay time | |
} | |
void i2c_start(void) { | |
/* I2C start condition is defined as | |
* a High to Low Transition on the SDA line | |
* as the SCL in a high level */ | |
SDA = 1; | |
i2c_dly(); | |
SCL = 1; // SCL High | |
i2c_dly(); | |
SDA = 0; // SDA level change | |
i2c_dly(); | |
SCL = 0; // SCL low, prepare to generate pulse | |
i2c_dly(); | |
} | |
void i2c_stop(void) { | |
/* I2C stop condition is defined as | |
* a Low to High Transition on the SDA line | |
* as the SCL in a high level */ | |
SDA = 0; | |
i2c_dly(); | |
SCL = 1; // SCL on | |
i2c_dly(); | |
SDA = 1; // SDA level change | |
i2c_dly(); // there is no need to make SCL off | |
} | |
bit i2c_tx(unsigned char data) | |
{ | |
/* An I2C output data is usually send out from bit 7 to 0 (MSB to LSB). | |
* Implement this by shift one bit at each time, set level as bit's value | |
* and them make one SCL pulse. | |
* Don't forget to add extra one pulse for slave's ACK */ | |
char x; | |
static bit b ; | |
// write data to slave | |
for(x=8 ; x; x --) { | |
if(data & 0x80 ) SDA = 1 ; // 0x80 = 1000 0000, read from MSB | |
else SDA = 0 ; | |
SCL = 1 ; // make SCL pulse | |
data <<= 1 ; // make bits shift left, keep SDA high/low | |
SCL = 0 ; // one bit send finish | |
} | |
// Read ACK from slave | |
SDA = 1; // SDA need to be high, slave will pull SDA low | |
SCL = 1; // the 9th pulse of SCL | |
i2c_dly(); // wait for slave | |
b = SDA_IN ; // possible ACK bit, if b is 0, means successful data transport | |
SCL = 0; // finish the 9th pulse | |
return b ; | |
} | |
unsigned char i2c_rx(char ack){ | |
/* An I2C input data is usually send out from bit 7 to 0 (MSB to LSB) by slave. | |
* First we need to check if we can pull high SCL, | |
* make sure not under clock stretching. | |
* Next put SDA value in the Low bit, and then shift left one bit at each time, | |
* and finish this pulse | |
* In the end, send an ACK if there is another byte need to read */ | |
char x, d =0; | |
SDA = 1; // initialize | |
// Read from slave | |
for(x=0 ; x< 8; x ++) { | |
d <<= 1 ; // move to next bit | |
do { | |
SCL = 1 ; // master's SCL needs to always pull high? | |
} | |
while(SCL_IN ==0); // wait for any SCL clock stretching | |
i2c_dly (); // slave ready, SCL_IN back to high | |
if(SDA_IN ) d |= 1 ; // get data, and put into d | |
SCL = 0 ; // finish this bit | |
} | |
// Send ACK back to slave | |
if(ack) SDA = 0 ; // ACK value is 0 | |
else SDA = 1 ; | |
SCL = 1; | |
i2c_dly(); // master need to send (N)ACK bit | |
SCL = 0; // SCL and SDA get initialize | |
SDA = 1; | |
return d ; | |
} | |
void main(){ | |
// the hole process is about how to get light sensor value, not sure yet | |
i2c_start(); // send start sequence | |
// Write message about where is light sensor's address | |
i2c_tx(0xE0 ); // SRF08 I2C address with R/W bit clear(write) | |
i2c_tx(0x01 ); // SRF08 light sensor register address | |
// Read message | |
i2c_start(); // send a restart sequence | |
i2c_tx(0xE1 ); // SRF08 I2C address with R/W bit set(read) | |
lightsensor = i2c_rx (1); // get light sensor and send acknowledge. Internal register address will increment automatically. | |
rangehigh = i2c_rx (1); // get the high byte of the range and send acknowledge. | |
rangelow = i2c_rx (0); // get low byte of the range - note we don't acknowledge the last byte. | |
i2c_stop(); // send stop sequence | |
} | |
[1] Davide bucci. Bitbanging
on I2C with a PIC micro.
Available from: http://davbucci.chez-alice.fr/index.php?argument=elettronica/pic_i2c/pic_i2c.inc
Available from: http://davbucci.chez-alice.fr/index.php?argument=elettronica/pic_i2c/pic_i2c.inc
[2] EngineersGarage. How to
Interface Serial EEPROM 24C02 with 8051 microcontroller (AT89C51). Available
from: http://www.engineersgarage.com/microcontroller/8051projects/interface-serial-eeprom-24c02-AT89C51-circuit
[3] rickey’s world. I2C Two
Wire Interface Tutorial: Introduction
Available from: http://www.8051projects.net/i2c-twi-tutorial/introduction.php
Available from: http://www.8051projects.net/i2c-twi-tutorial/introduction.php
[4] 林錫寬. I2C串列傳輸的技巧論述.
Available from: http://web.it.nctu.edu.tw/~sklin/etech/i2c.pdf
Available from: http://web.it.nctu.edu.tw/~sklin/etech/i2c.pdf
[5] Robot Electronics. Using the I2C Bus.
Available from: http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html
Available from: http://www.robot-electronics.co.uk/acatalog/I2C_Tutorial.html
[6] This is the world blog. Interface: Inter Integrated Circuit(I2C) AND Linux: I2C subsystem.
Available from: http://afrogue.blogspot.tw/2011/03/interface-inter-integrated-circuiti2c.html
Available from: http://afrogue.blogspot.tw/2011/03/interface-inter-integrated-circuiti2c.html
[7] lazf's den blog. [技術] 用GPIO實做I2C介面(Bit-Banging).Available from: http://lazyflai.blogspot.tw/2008/08/gpioi2cbit-banging.html
[8] Youtube. 艾鍗學院- MCU韌體開發實戰-I2C Spec
Available from: http://youtu.be/D29NePyvxNI
Available from: http://youtu.be/D29NePyvxNI
Comments
Post a Comment