[技術] 用GPIO實做I2C介面(Bit-Banging)

Written on 12:00 上午 by Yu Lai

最近工作上需要去讀寫I2C介面的IC,
是顆用來量測環境溫度的IC-LM75。
I2C是由2支腳-SDA和SCL所組成的並聯介面,
所有I2C的IC都接在這2支腳上,
I2C相關資訊可以自行到Googlewiki找找。

而我們的板子的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出去囉。

[技術] 執行檔的檔案格式轉換

Written on 6:04 下午 by Yu Lai

最近為了產生flash的image raw file,
需要將compiler編好的srec格式檔或elf檔式檔進行轉換。
上網找了老半天,原來我們的gnu binutils就有提供這些功能了 ^^。

使用方法如下:
objcopy -I -O

例1: 將srec轉成binary raw file
objcopy -I srec -O binary image.srec image.bin

例2: 將elf32-bigmips轉成binary raw file
objcopy -I elf32-bigmips -O binary image.srec image.bin

[技術] Busybox使用心得

Written on 11:58 上午 by Yu Lai

最近為了產品的新功能以及業務反應的功能,
我重新配置了busybox來達到以上需求。
之前的busybox是由嘉義那邊的同事安裝好打包過來的,
也沒把source code一起帶過來,加上版本也有點舊了,
所以我就安裝新版的當做順便更新囉。

以下是安裝過程中遇到的問題與心得的筆記,
就當做分享與記錄囉。

首先,busybox可以到[http://busybox.net/]下載回來。
然後解開後,如果有需要透過cross-compile來編譯的話,
可以在Makefile裡找到ARCH與CROSS_COMPILE配置它,
另外也可以在Makefile.flags裡直接配置CC、AR和LD等變數。

設定好後直接執行make menuconfig來進行busybox的applet設定囉。
這裡有幾個比較要注意的地方,在Build Options中,我是建議把
Busybox設成Build BusyBox as a static binary (no shared libs)。
雖然比較佔空間,但省下來搞library的時間就夠值得了。
而在設定Login/Password Management Utilities的時候,
為了免去配置glibc的麻煩,最好設置使用busybox自己的password
和shadow文件的功能(Use internal password and group functions
rather than system functions)。
同時要把(login)和(Support for login scripts)打勾,
這樣login才會正常運作。

接著直接使用make和make install就可以把busybox編譯出來啦。
編譯好busybox後並把buxybox copy到rootfs中,
接著要配置好rootfs以配合busybox的運作。
這裡要配置的有etc/inittab、etc/init.d/rcS
以及etc/passwd和etc/shadow(有勾選的話)。

最後就把rootfs包起來燒到target裡就可以啦。

(PS: 以上要配置的檔案可以在網路上找到許多範例,改天再補進來吧)

[技術] Linux的SysRq機制

Written on 1:28 下午 by Yu Lai

最早SysRq是IBM在PC/AT上用來執行低階OS指令的function key。

後來在Linux則是被用來設定kernel提供系統除錯或回復crash的機制。
也就是所謂的"Magic SysRq key"。

以下是透過Linux SysRq機制的操作方法。
首先enable或disable:
# echo 0 > /proc/sys/kernel/sysrq
# echo 1 > /proc/sys/kernel/sysrq

執行magic commands,可以透過echo指令來達成:
# echo b > /proc/sysrq-trigger
以上指令等價於組合鍵Alt + SysRq + B,也就是重新開機。

同理,強制關機可以使用:
# echo o > /proc/sysrq-trigger