[技術] 用GPIO實做I2C介面(Bit-Banging)
Written on 12:00 上午 by Yu Lai
最近工作上需要去讀寫I2C介面的IC,
是顆用來量測環境溫度的IC-LM75。
I2C是由2支腳-SDA和SCL所組成的並聯介面,
所有I2C的IC都接在這2支腳上,
I2C相關資訊可以自行到Google或wiki找找。
而我們的板子的CPU本身沒有I2C的介面,
它是透過2支GPIO腳來連接LM75,
所以就必須以軟體的方式來控制GPIO來模擬I2C的Signal,
以上的實做也被稱做Bit-Banging。
照著LM75的Datasheet和網路上相關的資料,
還蠻順利的完成整個Bit-Banging的實做。
以下就是相關實做的source code。
#define SCL_PORT 6
#define SDA_PORT 23
#define ADDR_LM75 0x94 /* 1001 010 x */
#define I2C_DELAY_TIME 10 /* us */
#define ACK 1
#define NO_ACK 0
首先是會用到的#define。
SCL腳位是使用GPIO6,SDA是使用GPIO23。
而LM75使用的Address則是固定的1001配上
板子上A2,A1,A0腳位所接的電位的010當前7個bit。
另外,由於I2C算是比較慢的介面,所以要有個delay time。
void i2c_init(void) {
/* Enable SDA & SCL gpio port */
gpio_enable(SDA_PORT);
gpio_enable(SCL_PORT);
/* Set SDA & SCL to High */
gpio_set(SDA_PORT, __HIGH__);
gpio_set(SCL_PORT, __HIGH__);
}
這裡是將GPIO腳位設定啟動,並將SDA和SCL電位設成HIGH,
完成Initial的動作。
void i2c_start(void) {
/* I2C start sequence is defined as
* a High to Low Transition on the data
* line as the CLK pin is high */
gpio_set(SDA_PORT, __HIGH__); /* SDA: High */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SDA_PORT, __LOW__); /* SDA: Low */
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);
}
void i2c_stop(void) {
/* I2C stop sequence is defined as
* data pin is low, then CLK pin is high,
* finally data pin is high. */
gpio_set(SDA_PORT, __LOW__); /* SDA: Low */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
gpio_set(SDA_PORT, __HIGH__); /* SDA: High */
}
如source code裡我comment所寫的,
I2C介面溝通的start就是SDA和SCL都是由本來保持的High變成Low所開始。
而stop就是SDA和SCL由High-Low傳遞資料之間變為High持續下去。
void i2c_write(unsigned char data) {
/* An I2C output byte is bits 7-0
* (MSB to LSB). Shift one bit at a time
* to the MDO output, and then clock the
* data to the I2C Slave */
unsigned char i;
/* Write to slave */
for(i = 0; i < 8; i++) {
gpio_set(SDA_PORT, (data&0x80)?1:0); /* Send data bit */
data <<= 1; /* Shift one bit */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);
}
/* Read ACK bit from slave */
gpio_get(SDA_PORT);
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);
}
unsigned char i2c_read(unsigned char send_ack) {
unsigned char i, data;
data = 0x00;
/* Read from slave */
for(i = 0; i < 8; i++) {
data <<= 1; /* Shift one bit */
data |= gpio_get(SDA_PORT); /* Read data bit */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);
}
/* Send ACK bit to slave */
if(send_ack)
gpio_set(SDA_PORT, __LOW__); /* SDA: Low */
else
gpio_set(SDA_PORT, __HIGH__); /* SDA: High */
gpio_set(SCL_PORT, __HIGH__); /* SCL: High */
HAL_DELAY_US(I2C_DELAY_TIME);
gpio_set(SCL_PORT, __LOW__); /* SCL: Low */
HAL_DELAY_US(I2C_DELAY_TIME);
return data;
}
接著就是傳送的過程了,
一般I2C都是由高位先寫入(這個不一定,要看Datasheet)。
在寫入時,Master(指CPU)先把SDA設成寫入的bit,
再把SCL依照我們delay的時間來做clock signal的產生(low->high->low->...)。
接著重複上面的動作依序把整個byte都寫入。
寫完整個byte後要讀取一下Slave(指LM75)所回傳的ACK bit。
而在讀取時,一樣先讀入SDA的電位當成data的bit,
再把SCL產生clock signal給Slave,
Slave在收到clock signal後會在SDA上變動電位把資料依序傳出來。
所以同樣的重複上面的動作就可以把組合出整個byte的資料。
當Master讀完byte後也要有回傳ACK的動作來告知是否繼續有下一個byte要讀取。
int get_lm75_temp(void) {
unsigned char msb = 0x00, lsb = 0x00;
i2c_start();
i2c_write(ADDR_LM75); /* Ask LM75 write */
i2c_write(0x00);
i2c_start();
i2c_write(ADDR_LM75+1); /* Ask LM75 read */
msb = i2c_read(ACK);
lsb = i2c_read(NO_ACK);
i2c_stop();
return (msb << 8) | lsb;
}
最後,照著LM75的Datasheet,
Master先傳送address byte給Slave,
Slave在收到address後會先比對自己A2,A1,A0,
若符合再依照address byte的最後一個bit來回應之後的動作,
若為0則是write動作,若為1則是read動作。
所以我們要讀取現在溫度資料時,
CPU先把address byte+0給寫到LM75,告知接下來要寫入register設定,
接著把0x00寫入表示要讀取第0個register,也就是現在的溫度。
再來就是重新開始先寫入address byte+1給LM75,告知要讀取資料。
接著就把溫度資料讀出,共2個byte,所以讀了2次囉。
最後就是把資料組合起來return出去囉。 If you enjoyed this post Subscribe to our feed
您好,我最近在試著用C,透過I2C寫入程式到IC裡,參考了您的程式後,有幾個問題想請問:
(1)msb = i2c_read(ACK);lsb = i2c_read(NO_ACK);
(2)gpio_get(SDA_PORT);
想請問這兩段程式,如果要用C來執行的話,應該寫成怎麼樣子?
麻煩您了,謝謝~
宇傑你好,
關於(1)中的i2c_read()相關的C Code我也有一起貼在文章中啊.
而(2)的gpio_get(SDA_PORT);這個會這樣寫的原因是因為不同的CPU會有不同取GPIO腳位的input值的方法, 有可能是Port mapping I/O, 也有可能是memory mapping I/O. 我只好寫成function, 避免誤導大家. 至於你所選用的CPU是如何去取GPIO的值, 這可能就要去翻該CPU相關的data sheet才能得知了. 以下是我選用的CPU的實做內容, 給您參考一下吧.
int gpio_get(int port) {
unsigned int tmp;
HAL_WRITE_UINT32(PCI_ADDR, (port > 31)? GPIO_DATA2 : GPIO_DATA1);
HAL_READ_UINT32(PCI_DATA, tmp);
tmp |= PORT2BIT(port);
HAL_WRITE_UINT32(PCI_DATA, tmp);
HAL_READ_UINT32(PCI_DATA, tmp);
tmp &= PORT2BIT(port);
return (tmp)? __HIGH__:__LOW__;
}
在int get_lm75_temp(void)沒有看到void i2c_init(void) 的使用,請問這個initial function 只要在system開機後執行一次就好了嗎?
是的.
請問這個function,是否有要求須要在何種環境下執行(Kernel Space 或User Space , 還是兩者皆可呢 謝謝
基本上兩者皆可, 但還是要看你的gpio_get和gpio_set怎麼實做.