I2C總線是由NXP(原PHILIPS)公司設計,有十分簡潔的物理層定義,其特性如下:
只要求兩條總線線路:一條串行數據線SDA,一條串行時鐘線SCL;
每個連接到總線的器件都可以通過唯一的地址和一直存在的簡單的主機/從機關系軟件設定地址,主機可以作為主機發(fā)送器或主機接收器;
它是一個真正的多主機總線,如果兩個或更多主機同時初始化,數據傳輸可以通過沖突檢測和仲裁防止數據被破壞;
串行的8 位雙向數據傳輸位速率在標準模式下可達100kbit/s,快速模式下可達400kbit/s,高速模式下可達3.4Mbit/s;
連接到相同總線的IC 數量只受到總線的最大電容400pF 限制。
其典型的接口連線如下:
I2C的協議很簡單:
數據的有效性
在傳輸數據的時候,SDA線必須在時鐘的高電平周期保持穩(wěn)定,SDA的高或低電平狀態(tài)只有在SCL 線的時鐘信號是低電平時才能改變 。
起始和停止條件
SCL 線是高電平時,SDA 線從高電平向低電平切換,這個情況表示起始條件;
SCL 線是高電平時,SDA 線由低電平向高電平切換,這個情況表示停止條件。
字節(jié)格式
發(fā)送到SDA 線上的每個字節(jié)必須為8 位,每次傳輸可以發(fā)送的字節(jié)數量不受限制。每個字節(jié)后必須處理一個響應位。
應答響應
數據傳輸必須帶響應,相關的響應時鐘脈沖由主機產生。在響應的時鐘脈沖期間發(fā)送器釋放SDA 線(高)。
在響應的時鐘脈沖期間,接收器必須將SDA 線拉低,使它在這個時鐘脈沖的高電平期間保持穩(wěn)定的低電平。
也就是說主器件發(fā)送完一字節(jié)數據后要接收一個應答位(低電平),從器件接收完一個字節(jié)后要發(fā)送一個低電平。
尋址方式(7位地址方式)
第一個字節(jié)的頭7 位組成了從機地址,最低位(LSB)是第8 位,它決定了傳輸的 普通的和帶重復開始條件的7位地址格式方向。第一個字節(jié)的最低位是
“0”,表示主機會寫信息到被選中的從機;
“1”表示主機會向從機讀信息。
當發(fā)送了一個地址后,系統(tǒng)中的每個器件都在起始條件后將頭7 位與它自己的地址比較,如果一樣,器件會判定它被主機尋址,至于是從機接收器還是從機發(fā)送器,都由R/W 位決定。
仲裁
I2C是所主機總線,每個設備都可以成為主機,但任一時刻只能有一個主機。
stm32至少有一個I2C接口,提供多主機功能,可以實現所有I2C總線的時序、協議、仲裁和定時功能,支持標準和快速傳輸兩種模式,同時與SMBus 2.0兼容。
本實驗直接操作寄存器實現對I2C總線結構的EEPROM AT24c02的寫入和讀取。AT24c02相關操作詳見單片機讀取EEPROM(AT24C02)。
庫函數實現使用stm32的兩個I2C模擬I2C設備間的數據收發(fā),并通過串口查看數據交換情況。
直接操作寄存器
首先需要配置I2C接口的時鐘,相關寄存器如下:
I2C_CR2寄存器低五位:
FREQ[5:0]:I2C模塊時鐘頻率 ,必須設置正確的輸入時鐘頻率以產生正確的時序,允許的范圍在2~36MHz之間:
000000:禁用 000001:禁用 000010:2MHz ... 100100:36MHz 大于100100:禁用。
用于設置I2C設備的輸入時鐘,本例使用的是PLCK1總線上的時鐘所以為36Mhz;
時鐘控制寄存器(I2C_CCR)低12位:
CCR[11:0]:快速/標準模式下的時鐘控制分頻系數(主模式),該分頻系數用于設置主模式下的SCL時鐘。
在I2C標準模式或SMBus模式下:
Thigh = CCR ×TPCLK1
Tlow = CCR ×TPCLK1
時鐘周期為 T = Thigh + Tlow;
例如:在標準模式下,FREQR = 36 即36Mhz,產生200kHz的SCL的頻率
時鐘控制分頻系數 = Freqr /2/f f 為想得到的頻率
配置好時鐘,還需要配置本機地址,I2C支持7位地址和10位地址,這里用的是7位地址:
自身地址寄存器1(I2C_OAR1)[7:1]:接口地址,地址的7~1位。
其他相關操作參見代碼,有詳細注釋:(system.h 和stm32f10x_it.h等相關代碼參照stm32 直接操作寄存器開發(fā)環(huán)境配置)
User/main.c
#include#include"system.h"#include"usart.h"#include"i2c.h"#defineLED1PAout(4)#defineLED2PAout(5)#defineLED3PAout(6)#defineLED4PAout(7)voidGpio_Init(void);intmain(void){Rcc_Init(9);//系統(tǒng)時鐘設置Usart1_Init(72,9600);Nvic_Init(1,0,I2C1_EV_IRQChannel,4);//設置搶占優(yōu)先級為1,響應優(yōu)先級為0,中斷分組為4Nvic_Init(0,0,I2C1_ER_IRQChannel,4);//設置I2C錯誤中斷搶占優(yōu)先級為0Gpio_Init();I2c_Init(0x30);//設置I2C1地址為0x30I2c_Start();while(1);}voidGpio_Init(void){RCC->APB2ENR|=1<<2;//使能PORTA時鐘RCC->APB2ENR|=1<<3;//使能PORTB時鐘;GPIOA->CRL&=0x0000FFFF;//PA0~3設置為浮空輸入,PA4~7設置為推挽輸出GPIOA->CRL|=0x33334444;GPIOB->CRL&=0x00FFFFFF;//PB6I2C1_SCL,PB7I2C1_SDLGPIOB->CRL|=0xFF000000;//復用開漏輸出//USART1串口I/O設置GPIOA->CRH&=0xFFFFF00F;//設置USART1的Tx(PA.9)為第二功能推挽,50MHz;Rx(PA.10)為浮空輸入GPIOA->CRH|=0x000008B0;}
User/stm32f10x_it.c
#include"stm32f10x_it.h"#include"system.h"#include"stdio.h"#include"i2c.h"#defineLED1PAout(4)#defineLED2PAout(5)#defineLED3PAout(6)#defineLED4PAout(7)#defineADDRS_R0xA1//讀操作地址#defineADDRS_W0xA0//寫操作地址u8go=0;//操作步驟標記voidI2C1_EV_IRQHandler(void)//I2C1EventInterrupt{u16clear=0;if(I2C1->SR1&1<<0)//已發(fā)送起始條件,寫數據寄存器的操作將清除該位{printf("rnI2C1Start..rn");switch(go){case0:{I2c_Write(ADDRS_W);//寫入從機地址,寫指令操作地址break;}case1:{I2c_Write(ADDRS_W);//寫入從機地址,寫指令操作地址break;}case2:{I2c_Write(ADDRS_R);//寫入從機地址,讀數據操作地址break;}}}if(I2C1->SR1&1<<1)//從機地址已發(fā)送{printf("rnI2C1hassendaddress..rn");clear=I2C1->SR2;//讀取SR2可以清除該位中斷switch(go){case0:{I2c_Write(0x01);//寫入待寫入的EEPROM單元地址break;}case1:{I2c_Write(0x01);//寫入待寫入的EEPROM單元地址break;}case2:{delay(100000);printf("rnRead0x%XfromAt24c02,Address0x01..rn",I2c_Read());I2c_Stop();break;}}}if(I2C1->SR1&1<<2)//字節(jié)發(fā)送結束發(fā)送地址字節(jié)時,不觸發(fā)此中斷{//printf("rnI2C1sendbytesuccess..rn");switch(go){case0:{I2c_Write(0x86);//寫入數據printf("rnWrite0x%XtoAt24c02,Address0x01..rn",0x86);//I2c_Stop();delay(10000);go=1;I2c_Start();break;}case1:{delay(10000);go=2;I2c_Start();break;}case2:{break;}}}delay(100000);LED3=1;//I2C1->CR2&=~(1<<9);//事件中斷關閉}voidI2C1_ER_IRQHandler(void)//I2C1ErrorInterrupt{delay(100000);LED4=1;if(I2C1->SR1&1<<10)//應答失敗{printf("rnACKERROR..rn");I2C1->SR1&=~(1<<10);//清除中斷}if(I2C1->SR1&1<<14)//超時{printf("rnTimeout..rn");I2C1->SR1&=~(1<<14);//清除中斷}if(I2C1->SR1&1<<11)//過載/欠載{printf("rnOverrun/Underrun..rn");I2C1->SR1&=~(1<<11);//清除中斷}if(I2C1->SR1&1<<9)//仲裁丟失{printf("rnArbitrationlost..rn");I2C1->SR1&=~(1<<9);//清除中斷}if(I2C1->SR1&1<<8)//總線出錯{printf("rnBuserror..rn");I2C1->SR1&=~(1<<8);//清除中斷}}