I2C之知(六)--s3c2440用I2C介面訪問EEPROM
本來是想用s3c2440的SDA和SCL管腳複用為GPIO來模擬的,但在沒有示波器的情況下搞了一週,怎麼都出不來,最後還是放棄了.甚至參考了Linux下i2c-algo-bit.c和i2c-gpio.c,依然沒調出來.如果有示波器,可能很快就能找到原因,現在完全不知道問題出在哪裡.其實想用GPIO模擬I2C的目的很簡單,以一種簡單而又深刻的方式來理解I2C.
既然這條路暫時沒法走,退而求其次,用s3c2440的I2C介面來訪問EEPROM,只要按照datasheet的來做,基本上不用考慮時序咯.
從s3c2440和AT24C02A的datasheet開始:
s3c2440的介紹其實很簡單,IIC-bus介面有四種操作模式:
Master transmitter mode
Master receive mode
Slave transmitter mode
Slave receive mode
但實際上,我們只會用到M-Tx和M-Rx,因為在s3c2440和EEPROM的連線中,沒辦法將s3c2440當作slave.
然後s3c2440的datasheet從I2C的協議文件上copy了一些內容:開始終止條件\資料傳輸格式\ACK\讀寫操作\匯流排仲裁\終止條件等.這些還是看I2C的協議文件比較好.
I2C-BUS的配置:
為了控制SCL的頻率,IICCON中可以控制一個4bit的分頻器.IICADD暫存器用來儲存IIC-Bus的介面地址,這個實際也無需用,只有訪問從裝置時才需要地址.而這裡s3c2440是主裝置.
在每次IIC Tx/Rx操作前,都要做下面的操作:
如果需要的話,寫從地址到IICADD
設定IICCON暫存器(使能中斷,定義SCL的週期)
設定IICSTAT來使能序列輸出
然後就是M-Tx和M-Rx操作模式的流程圖,後面的程式碼就是嚴格按照這個圖來的.這裡就不截圖了.
暫存器的說明大概如下:
#define rIICCON
(*(volatile unsigned *)0x54000000)
/**********************
[7]:ack enable bit
[6]:Tx clock source selection 0:IICCLK = PCLK/16 1:IICCLK = PCLK/512
[5]:Tx/Rx interrupt
[4]:interrupt pending flag !!!!
[3:0]:Tx clock = IICCLK/(IICCON[3:0]+1)
**********************/
#define rIICSTAT(*(volatile unsigned *)0x54000004)
/**********
[7:6]:10:M-Rx 11:M-Tx
[5]:busy signal status/start stop conditon !!!
[4]:serial output enable/disable bit 1:enable
[3]:iic arbitration procedure status flag bit || which didn't used
[2]:address-as-slave status flag !!!
[1]:address zero status flag
[0]:last-received bit status flag 0:ack 1:nack
**********/
#define rIICADD(*(volatile unsigned *)0x54000008)
/*********
* [7:1]:slave address 只有在IICSTAT的output disable時,IICADD才可以寫.隨時可以讀.
* ************/
#define rIICDS(*(volatile unsigned *)0x5400000c)
/**************
* [7:0]:8bit data shift reg for IIC-Bus Tx/Rx operation.只有IICSTAT的output enable時,IICDS才可以寫.隨時可以讀.
* *************/
#define rIICLC(*(volatile unsigned *)0x54000010)
/**************
* 該暫存器用於多主機的情況,暫時用不到
* ************/
下面看下AT24C02A的datasheet:
AT24C02A:2K的容量,32pages,每個page8個位元組,總共256位元組.讀寫需要8bit的word address.
AT24C02A的地址是從下圖來的:
所以地址就是我們看到的0xa0,A2 A1 A0因為在原理圖上這三個管腳都接的低電平.
寫操作:
以位元組寫的圖為例:
結合s3c2440的M-Tx模式,程式碼操作如下:
- void I_Write(unsigned int slvaddr, unsigned char addr, unsigned char data)
- {
- unsigned int ack;
- init(slvaddr);
- //rIICSTAT |= 0x3<<6; //configure M Tx mode
- rIICDS = slvaddr;//0xa0; //write slave address to IICDS
- rIICCON&=~0x10; //clear pending bit
- rIICSTAT = 0xf0; //(M/T start)
- //the data of the IICDS is transmitted
- uart_SendByte('a');
- while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- rIICDS = addr;
- rIICCON&=~0x10; //clear pending bit
- //the data of the IICDS is shifted to sda
- uart_SendByte('b');
- while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- rIICDS = data;
- rIICCON&=~0x10; //clear pending bit
- //the data of the IICDS is shifted to sda
- uart_SendByte('c');
- while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- rIICSTAT = 0xD0; //write (M/T stop to IICSTAT)
- //rIICCON = 0xe0;
- rIICCON&=~0x10; //clear pending bit
- uart_SendByte('d');
- while((rIICSTAT & 1<<5) == 1);
- }
讀操作:
以隨機讀的圖為例:
隨機讀要複雜點,因為前面的DUMMY WRITE要用M-Tx模式,而後面真正的讀操作要用M-Rx模式.結合s3c2440的模式操作的流程圖,程式碼如下:
- unsigned char I_Read(unsigned int slvaddr, unsigned char addr)
- {
- unsigned char data;
- int ack;
- init(slvaddr);
- //rIICSTAT |= 0x3<<6; //configure M Tx mode
- rIICDS = slvaddr;//0xa0; //write slave address to IICDS
- rIICCON&=~0x10; //clear pending bit
- rIICSTAT = 0xf0; //(M/T start)
- //the data of the IICDS is transmitted
- while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- rIICDS = addr;
- rIICCON&=~0x10; //clear pending bit
- //the data of the IICDS is shifted to sda
- while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- init(slvaddr);
- rIICSTAT &= ~(0x1<<6);//configure M Rx mode
- rIICSTAT |= 0x1<<7;
- //rIICSTAT |= 0x2<<6; //configure M Rx mode
- rIICDS = slvaddr;
- rIICCON&=~0x10; //clear pending bit
- rIICSTAT = 0xb0; //(M/R Start)
- //the data of IICDS(slave address) is transmitted
- while((rIICCON & 1<<4) == 0);//udelay(10);//uart_SendByte('o');//ack period and then interrupt is pending::
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- data = rIICDS;
- if(data==160)
- uart_SendByte('o');
- rIICCON&=~0x10; //clear pending bit
- //sda is shifted to IICDS
- while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- data = rIICDS;
- if(data==160)
- uart_SendByte('o');
- rIICCON&=~0x10; //clear pending bit
- //sda is shifted to IICDS
- while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
- if((rIICSTAT & 0x01)==0)
- uart_SendByte('y');//ack = 0; //收到應答
- else
- uart_SendByte('n');//ack = 1; //沒有應答
- uart_SendByte('a');
- rIICSTAT = 0x90;
- uart_SendByte('b');
- rIICCON&=~0x10; //clear pending bit
- uart_SendByte('c');
- while((rIICSTAT & 1<<5) == 1)uart_SendByte('o');
- uart_SendByte('d');
- return data;
- }
這個EEPROM的其他讀寫操作依此類推.
最後,做一下總結:
1.單次的寫位元組和隨機讀之間應該加延時,驗證過程中發現,在兩次寫位元組之間延時100us的話,在第二次寫位元組的時候就收不到ACK.將延時改為1000us就正常了.
2.IICDS的讀寫操作一定要在清楚IIC interrupt pending bit之前做,也就是程式碼中出現的:
- rIICDS = slvaddr;
- rIICCON&=~0x10; //clear pending bit
if((rIICSTAT & 0x01)==0)
uart_SendByte('y');//ack = 0; //收到應答
else
uart_SendByte('n');//ack = 1; //沒有應答
只需要上面的程式碼就可以了,通過輪詢IICCON的第4bit來檢視ack period and then interrupt is pending.
當然如果用中斷系統中IIC中斷也是可以的,一個是中斷方式,一個是輪詢方式,在這裡感覺差別不大.
關於I2C裸機到此為止,但是gpio模擬I2c一直耿耿於懷啊~~
在工作中,linux下做過用I2C子系統用GPIO模擬I2C.那個只要配置好GPIO的input和output,構造資料結構,驅動就能工作了,不得不佩服這個子系統的強大.因為前面的blog對檔案系統和裝置模型都做過分析,但並沒有針對特定的子系統做過分析,過段時間就來分析linux的I2C,學習C語言也是如何實現OOP的某些特性的,體會好程式碼的設計思路.