1. 程式人生 > >EFM8微控制器與I2C外設通訊

EFM8微控制器與I2C外設通訊

        最近幫同學做一個專案,開發板是EFM8微控制器,支援SPI和I2C協議(SMBus)。很久沒搞過微控制器了,而且條件限制,為了使微控制器和外設成功通訊,花了一個星期時間。剛開始使用SPI,發現程式碼邏輯都沒問題,就是結果不對(後來知道是因為帶中斷的程式單步除錯導致的,說多了都是淚),調了幾天發現SPI確實調不通,就換了I2C,半天時間搞定,哈哈。本文重點解釋I2C,廢話少說了。

1、簡介

        I2C(Inter-Integrated Circuit)匯流排是由PHILIPS公司開發的兩線式序列匯流排,用於連線微控制器及其外圍裝置。是微電子通訊控制領域廣泛採用的一種匯流排標準。它是同步通訊的一種特殊形式,具有介面線少,控制方式簡單,器件封裝形式小,通訊速率較高等優點。這些優點不是吹的,只需要兩個IO口就行了,比起並行傳輸節省了不知道多少成本。

2、連線圖

        2條雙向序列線,一條資料線SDA,一條時鐘線SCL。SDA傳輸資料是大端傳輸,每次傳輸8bit,即一位元組。支援多主控(multimastering),任何時間點只能有一個主控。總線上每個裝置都有自己的一個addr,共7個bit,廣播地址全0。

        本文用的是ADXL345,CS引腳拉高至VDD,ADXL345處於I2C模式,需要簡單2線式連線。ALT ADDRESS(SDO)引腳處於高電平,器件的7位I2C地址是0x1D,隨後為R/W位。這轉化為0x3A寫入,0x3B讀取。通過ALT ADDRESS引腳(引腳12)接地,可以選擇備用I2C地址0x53(隨後為R/W位)。這裡特別說明,外設和MCU不需要共GND,也不需要共VDD,我剛開始糾結了好久,查了很多資料,硬是沒查到。這轉化為0xA6寫入,0xA7讀取。連線方式如下圖:

3、讀寫流程

        I2C的時序這些就不多介紹了,網上一搜一大堆,想用IO口模擬I2C可以,大多數MCU都內建I2C模組,只要連線正確,配置和操作暫存器就能正常通訊了。不過,I2C讀寫資料的流程是必須瞭解的。

3.1、寫流程

        寫暫存器的標準流程為:

1.    Master發起START

2.    Master傳送I2C addr(7bit)和w操作0(1bit),等待ACK

3.    Slave傳送ACK

4.    Master傳送reg addr(8bit),等待ACK

5.    Slave傳送ACK

6.    Master傳送data(8bit),即要寫入暫存器中的資料,等待ACK

7.    Slave傳送ACK

8.    第6步和第7步可以重複多次,即順序寫多個暫存器

9.    Master發起STOT

3.2、讀流程

        讀流程比寫稍微麻煩一點,在讀之前要先把暫存器地址寫入,然後再開始讀:

1.    Master發起START

2.    Master傳送I2C addr(7bit)和w操作1(1bit),等待ACK

3.    Slave傳送ACK

4.    Master傳送reg addr(8bit),等待ACK

5.    Slave傳送ACK

6.    Master發起START

7.    Master傳送I2C addr(7bit)和r操作1(1bit),等待ACK

8.    Slave傳送ACK

9.    Slave傳送data(8bit),即暫存器裡的值

10.    Master傳送ACK

11.    第8步和第9步可以重複多次,即順序讀多個暫存器

4、程式原理

        程式是根據配置和操作暫存器實現I2C通訊,將I2C設為忙狀態,START標誌開始,後續所有收發資料在中斷子程式中處理。中斷子程式中,根據SMB0CN0暫存器判斷是什麼狀態,然後做出響應的處理。

        特別說明,暫存器地址和讀寫的資料複用放在陣列SMB_DATA_OUT裡。

        讀寫函式:

        void SMB_Write(uint8_t Flag)
        {
           while(SMB_BUSY);                    // Wait for SMBus to be free.
           SMB_BUSY = 1;                       // Claim SMBus (set to busy)
           SMB_RW = Flag;                         // Mark this transfer as a WRITE
           SMB0CN0_STA = 1;                    // Start transfer
           while(SMB_BUSY);
        }

        void SMB_Read(void)
        {
           while(SMB_BUSY);                    // Wait for bus to be free.
           SMB_BUSY = 1;                       // Claim SMBus (set to busy)
           SMB_RW = 1;                         // Mark this transfer as a READ

           SMB0CN0_STA = 1;                    // Start transfer

           while(SMB_BUSY);                    // Wait for transfer to complete
        }

中斷處理子程式:

      switch (SMB0CN0 & 0xF0)          // Status vector
      {
         // Master Transmitter/Receiver: START condition transmitted.
         case SMB_MTSTA:
            SMB0DAT = TARGET;          // Load address of the target slave
            SMB0DAT &= 0xFE;           // Clear the LSB of the address for the
                                       // R/W bit
            SMB0DAT |= RW_FLAG;        // Load R/W bit
            SMB0CN0_STA = 0;           // Manually clear START bit
            sent_byte_counter = 1;     // Reset the counter
            break;

         // Master Transmitter: Data byte transmitted
         case SMB_MTDB:
            if (SMB0CN0_ACK)            // Slave SMB0CN0_ACK?
            {
               if (RW_FLAG == WRITE)    // If this transfer is a WRITE,
               {
                  if (sent_byte_counter <= NUM_BYTES_WR)
                  {
                     // send data byte
                     SMB0DAT = SMB_DATA_OUT[sent_byte_counter-1];
                     sent_byte_counter++;
                  }
                  else
                  {
                     SMB0CN0_STO = 1;  // Set SMB0CN0_STO to terminate transfer
                     SMB_BUSY = 0;     // And free SMBus interface
                  }
               }
            }
            else                       // If slave NACK,
            {
               SMB0CN0_STO = 1;        // Send STOP condition, followed
               SMB0CN0_STA = 1;        // By a START
            }
            break;

         // Master Receiver: byte received
         case SMB_MRDB:
            SMB_DATA_OUT = SMB0DAT; // Store received byte
            SMB_BUSY = 0;           // Free SMBus interface
            SMB0CN0_ACK = 0;        // Send NACK to indicate last byte of this transfer

            SMB0CN0_STO = 1;        // Send STOP to terminate transfer
            break;

         default:
            FAIL = 1;                  // Indicate failed transfer
                                       // and handle at end of ISR
            break;