Skip to main content

淺介I2C

I2C起源

內部整合電路(Inter-Integrated Circuit, I2C, 讀做I-square-C)是由飛利浦半導體公司開發的一種專用介面。I2C是以最少的連接線進行硬體佈線還要有靈活擴充的特性為目標而設計,最後出現了只有以序列資料線SDASerial DAta)及序列時脈線SCLSerial CLock)來進行所有通訊的I2C介面,I2C允許多主(master)多僕(slave)系統,其傳輸系統內每一個裝置都有唯一的地址可供辨識。資料的寫入和讀取都是由master主動發起,slave無法主動向master回報,除非使用中斷腳通知masterI2C傳輸速度有慢(小於100Kbps)、快(400Kbps)及高速(3.4Mbps)三種,每一種均可向下相容。

I2C電路配置

如前所述I2C為兩線式,一為時脈線SCL,另一條為資料線SDA,硬體線路如圖1,兩線皆為雙向性,且都需要透過高接電阻(pull-up, 對岸說的上拉電阻)接電。平常不使用時,SCLSDA的訊號都處於高電位。為了多裝置共線的功能,裝置的 SCL SDA 腳位要為開洩極(open-drain開集極(open-collector。一旦有一個腳位的開洩極導通接地,則整條線都為低電位,這種現象稱作wired-AND運作;如同邏輯AND運算,需要共接的腳位都是1(開洩極斷路),該條線的電位才是1。如果沒有開洩極的腳位,可以使用具內部高接電阻的腳位,當要輸出1時,則設定該腳位為高接型輸入腳;而輸出為0時,則改設定為輸出腳並輸出0的值。

1. I2C傳輸裝置接線[1]

I2C通訊協定

為使說明部分更簡潔,首先介紹幾個名詞:

位元傳輸協定

master要跟slave溝通時,會先有個起始條件(start condition)的訊號,結束時也會送出終止條件(stop condition)訊號。起始條件訊號為SDAlow接著SCLlow,做動方式如圖2,終止條件訊號則是SCLhigh接著SDAhigh,做動方式如圖3,此特殊訊號是唯二可以在SCLhigh時變換SDA值的訊號,其他位元訊號只能趁SCLlow時變換,為high時要持平如圖4
2. 起始條件之SDASCL的變化[2]

3. 終止條件之SDASCL的變化[2]

4. 位元訊號之SDASCL的變化[3]

master送出8 bits的資料內容後,masterSDA會維持高電位,等待slave回傳,若slave傳回SDA low做為ACK,表示有成功收到,且準備好繼續接收下一次的資料;若SDA依然是high,表示slave尚未準備好接收訊號,此時master應該傳送結束訊號,因此每一次的資料傳輸,master都需要發出9SCL訊號脈衝,做動方式如圖5
5. 位元訊號之SDASCL的變化[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
  • 傳送一個起始條件訊號
  • 傳送slaveI2C address以及讀/寫控制碼“0”
  • 傳送slave內要寫入的內部暫存器號碼
  • 傳送資料位元組
  • []傳送其他資料位元組
  • 等待slave傳回ACK/NACK
  • 傳送一個終止條件訊號
6. master發起寫入資料到slave的流程[3]
舉例來說,若有一個I2C位址為0xE0的超音波感測器SRF08,需對0x00指令暫存器寫入0x51來啟動搜索,其流程為:

  • 傳送一個起始條件訊號
  • 傳送0xE0slaveI2C address以及讀/寫控制碼“0”
  • 傳送0x00slave內要寫入的內部暫存器號碼)
  • 傳送0x51(資料位元組,讓感測器啟動搜索的指令)
  • 傳送一個終止條件訊號


在讀取資料方面,讀取前需要告知slave欲讀取的內部暫存器位址,所以在讀取之前要先進行寫入的動作:發動起始訊號、傳送slave位址以及寫入的位元指令、資料存放的內部暫存器位址,然後再發送一次起始訊號,傳送slave位址、使用讀取的位元指令,此時便可以進行讀取,接收完畢後,若還有下一筆資料則master需傳送ACK,若再無下一筆資料,master則傳送NACK(如圖7所示),結束後再傳送終止訊號。
7. master發起向slave讀取資料的流程[3]

舉例來說,若要讀取電子羅盤CMPS03的方位,其流程為:
  • 傳送一個起始訊號  
  • 傳送0xC0(電子羅盤CMPS03I2C位址與讀/寫控制碼”0’
  • 傳送0x01(方位暫存器的內部位址)
  • 再傳送一起始訊號(重覆起始條件, 又稱Sr
  • 傳送0xC1(電子羅盤CMPS03I2C位址與讀/寫控制碼high
  • CMPS03讀取資料
  • 傳送一終止訊號

其他事項

master要讀取slave的資料時,將資料放上SDA的是slave,然而SCL卻是由master來進行控制,master又該如何知道slave是否準備好回傳資料了呢?如果slave是正在忙碌中的微控制器,如此一來微控制器便會進入中斷,將工作儲存至暫存器,再回頭看master要讀取的暫存器位址,取出資料放到傳送暫存器,如果master很頻繁的傳送資料,這樣就會有點糟糕,所以當slave沒有準備好接收下一筆資料時,slave可以把SCLlow,此狀態又稱為時脈延展(clock stretching)讓master暫停發送,準備好時再把SCL拉回high


Sample Code

#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
}
view raw I2C_sample.c hosted with ❤ by GitHub
參考資料:
[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
[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
[4] 林錫寬. I2C串列傳輸的技巧論述.
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
[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
[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

Comments

Popular posts from this blog

【Arduino】Timers, Registers, and Fast PWM Mode

由於Arduino預設的PWM控制方法僅有500Hz(好像還有另一個),想要知道怎樣才可以調整成其他頻率,以此做記錄。 先說明Timer設定方式、PWM Mode,之後會在Arduino上實做。 如果對_BV()沒有概念,可以參考 【Arduino I/O Ports】Control under avr-libc 這篇文章。

Emart Sunny Sale: 3D Shadow QR Code

  Emart是韓國連鎖超市,近期他們發現中午12:00到下午1:00的銷售量會明顯減少,為了提高銷售量他們製作出了帶有日晷概念的3D QR code。