1. 程式人生 > >I2C之知(六)--s3c2440用I2C介面訪問EEPROM

I2C之知(六)--s3c2440用I2C介面訪問EEPROM

在前面閱讀理解了I2C的官方協議文件後,就拿s3c2440和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模式,程式碼操作如下:

  1. void I_Write(unsigned int slvaddr, unsigned char addr, unsigned char data)  
  2. {  
  3.     unsigned int ack;  
  4.     init(slvaddr);  
  5.     //rIICSTAT |= 0x3<<6;  //configure M Tx mode
  6.     rIICDS = slvaddr;//0xa0;  //write slave address to IICDS
  7.     rIICCON&=~0x10; //clear pending bit
  8.     rIICSTAT = 0xf0;  //(M/T start)
  9.     //the data of the IICDS is transmitted
  10.     uart_SendByte('a');  
  11.     while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
  12.     if((rIICSTAT & 0x01)==0)  
  13.         uart_SendByte('y');//ack = 0;    //收到應答
  14.     else
  15.         uart_SendByte('n');//ack = 1;    //沒有應答
  16.     rIICDS = addr;  
  17.     rIICCON&=~0x10; //clear pending bit
  18.     //the data of the IICDS is shifted to sda
  19.     uart_SendByte('b');  
  20.     while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
  21.     if((rIICSTAT & 0x01)==0)  
  22.         uart_SendByte('y');//ack = 0;    //收到應答
  23.     else
  24.         uart_SendByte('n');//ack = 1;    //沒有應答
  25.     rIICDS = data;  
  26.     rIICCON&=~0x10; //clear pending bit
  27.     //the data of the IICDS is shifted to sda
  28.     uart_SendByte('c');  
  29.     while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
  30.     if((rIICSTAT & 0x01)==0)  
  31.         uart_SendByte('y');//ack = 0;    //收到應答
  32.     else
  33.         uart_SendByte('n');//ack = 1;    //沒有應答
  34.     rIICSTAT = 0xD0; //write (M/T stop to IICSTAT)
  35.     //rIICCON = 0xe0;
  36.     rIICCON&=~0x10; //clear pending bit
  37.     uart_SendByte('d');  
  38.     while((rIICSTAT & 1<<5) == 1);  
  39. }  
        

        讀操作:

        以隨機讀的圖為例:



        隨機讀要複雜點,因為前面的DUMMY WRITE要用M-Tx模式,而後面真正的讀操作要用M-Rx模式.結合s3c2440的模式操作的流程圖,程式碼如下:

  1. unsigned char I_Read(unsigned int slvaddr, unsigned char addr)  
  2. {  
  3.     unsigned char data;  
  4.     int ack;  
  5.     init(slvaddr);  
  6.     //rIICSTAT |= 0x3<<6;  //configure M Tx mode
  7.     rIICDS = slvaddr;//0xa0;  //write slave address to IICDS
  8.     rIICCON&=~0x10; //clear pending bit
  9.     rIICSTAT = 0xf0;  //(M/T start)
  10.     //the data of the IICDS is transmitted
  11.     while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
  12.     if((rIICSTAT & 0x01)==0)  
  13.         uart_SendByte('y');//ack = 0;    //收到應答
  14.     else
  15.         uart_SendByte('n');//ack = 1;    //沒有應答
  16.     rIICDS = addr;  
  17.     rIICCON&=~0x10; //clear pending bit
  18.     //the data of the IICDS is shifted to sda
  19.     while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
  20.     if((rIICSTAT & 0x01)==0)  
  21.         uart_SendByte('y');//ack = 0;    //收到應答
  22.     else
  23.         uart_SendByte('n');//ack = 1;    //沒有應答
  24.     init(slvaddr);  
  25.     rIICSTAT &= ~(0x1<<6);//configure M Rx mode
  26.     rIICSTAT |= 0x1<<7;  
  27.     //rIICSTAT |= 0x2<<6;  //configure M Rx mode
  28.     rIICDS = slvaddr;  
  29.     rIICCON&=~0x10; //clear pending bit
  30.     rIICSTAT = 0xb0;  //(M/R Start)
  31.     //the data of IICDS(slave address) is transmitted
  32.     while((rIICCON & 1<<4) == 0);//udelay(10);//uart_SendByte('o');//ack period and then interrupt is pending::
  33.     if((rIICSTAT & 0x01)==0)  
  34.         uart_SendByte('y');//ack = 0;    //收到應答
  35.     else
  36.         uart_SendByte('n');//ack = 1;    //沒有應答
  37.     data = rIICDS;  
  38.     if(data==160)  
  39.         uart_SendByte('o');  
  40.     rIICCON&=~0x10; //clear pending bit
  41.     //sda is shifted to IICDS
  42.     while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
  43.     if((rIICSTAT & 0x01)==0)  
  44.         uart_SendByte('y');//ack = 0;    //收到應答
  45.     else
  46.         uart_SendByte('n');//ack = 1;    //沒有應答
  47.     data = rIICDS;  
  48.     if(data==160)  
  49.         uart_SendByte('o');  
  50.     rIICCON&=~0x10; //clear pending bit
  51.     //sda is shifted to IICDS
  52.     while((rIICCON & 1<<4) == 0);//udelay(10);//ack period and then interrupt is pending
  53.     if((rIICSTAT & 0x01)==0)  
  54.         uart_SendByte('y');//ack = 0;    //收到應答
  55.     else
  56.         uart_SendByte('n');//ack = 1;    //沒有應答
  57.     uart_SendByte('a');  
  58.     rIICSTAT = 0x90;  
  59.     uart_SendByte('b');  
  60.     rIICCON&=~0x10; //clear pending bit
  61.     uart_SendByte('c');  
  62.     while((rIICSTAT & 1<<5) == 1)uart_SendByte('o');  
  63.     uart_SendByte('d');  
  64.     return data;  
  65. }  

        這個EEPROM的其他讀寫操作依此類推.

        最後,做一下總結:

        1.單次的寫位元組和隨機讀之間應該加延時,驗證過程中發現,在兩次寫位元組之間延時100us的話,在第二次寫位元組的時候就收不到ACK.將延時改為1000us就正常了.

        2.IICDS的讀寫操作一定要在清楚IIC interrupt pending bit之前做,也就是程式碼中出現的:

  1. rIICDS = slvaddr;  
  2. rIICCON&=~0x10; //clear pending bit
        3.讀資料的時候可能會讀到160,也就是0xa0,沒有關係,再多讀一次位元組就是資料了.         4.IICCON暫存器中第5bit是Tx/Rx interrupt enable/disable bit.而第4bit是interrupt pending flag.         值得注意的是該暫存器介紹後的notes中的第二點和第五點:         IIC-Bus中斷髮生的條件:a.一byte的資料收發完成;b.通用廣播或者從地址匹配;c.匯流排仲裁失敗         如果IICCON[5]=0,IICCON[4]不會正常操作.所以即使不用IIC中斷,建議將IICCON[5]=1.         到重點了,其實沒必要用IIC的中斷,我的意思是說在s3c2440中斷系統中關於IIC的操作不用變,但是IICCON[5]=1.         那怎麼知道收發完成呢? 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;    //沒有應答

       只需要上面的程式碼就可以了,通過輪詢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的某些特性的,體會好程式碼的設計思路.