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;