I2C兩線式串行總線通訊協議,它是由飛利浦開發的,主要用于連接微控制器及其外圍設備之間,它是由數據線SDA和信號線SCL構成的,可發送和接收數據即在MUC和I2C設備之間,I2C和I2C之間進行全雙工信號傳輸,高速I2C總線一般可達到400kbps。一般我們也稱為TWI接口。
I2C支持多主機模式:
?
即在這個主線上可以掛載n個I2C外設。
對于I2C協議,其實也很簡單,不要想的那么復雜,其實就是電平的變換。我們可以人為的分為6個部分
整體時序圖:
?
各狀態:
-
空閑狀態
?
I2C總線的SCK和SDA兩個線同時處于高電平的時候,規定為總線的空閑狀態,這個就是由總線上的上拉電阻把電平拉高的。
-
起始信號
?
當SCL為高電平期間,SDA由高電平變成低電平,即為起始信號。啟動信號是一種電平跳變時序信號,不是一個電平信號。
-
停止信號
當SCL為高電平期間,SDA由低電平變為高電平,即為停止信號。停止信號也是一種電平跳變時序信號,不是一個電平信號。
-
應答信號
?
發送器每發送一個字節(8bit)數據,就在時鐘脈沖(SCL)9期間釋放數據線(SDA),再由接收器來反饋一個應答信號,應答信號為低電平的時候,規定為有效應答位(ACK:應答位),表明接收器已經成功的接收了該字節,應答信號為高電平時,規定為非應答位(NACK:非應答位),表示接收器沒有成功的接收該字節。
對于反饋有效應答位(ACK):接收器在第9個時鐘脈沖之前的低電平期間將SDA拉低,并且保證在該時鐘的高電平期間,SDA為穩定的低電平。大家主要看圖,看看是不是這樣的。
要是接收器是主控器,那么它收到最后一個字節后,發送一個NACK信號,以通知被控發送器結束數據的發送,并且釋放SDA線,以便主控接收器發送一個停止信號。
-
數據的有效性
?
時鐘信號(SCL)為高電平期間,數據線上的數據必須保持穩定,只有在時鐘信號(SCL)為低電平期間,數據線上的高電平或者低電平才能發生變化。
數據必須在時鐘信號(SCL)的上升沿到來之前就準備好,并且在數據信號的下降沿來到之前必須穩定。
-
數據的傳輸
在SDA上的每一個位的數據的傳輸都需要一個時鐘脈沖,即在SCL串行時鐘的配合下,SDA上逐位的串行發送每一位數據。數據位的傳輸是邊沿觸發。
示例代碼講解
-
初始化IIC
?
其實就是對兩個線的初始化,我這里使用的是PA6和PA7,開始都設置為輸出,中途會改變PA7的輸入輸出屬性,在空閑狀態,我們知道SCL和SDA是被拉高的,所以這個地方我們給高電平。
-
產生IIC起始信號
?
?
將SDA線設置為輸出,然后SDA和SCL都設置為高電平,延遲4us,然后將SDA拉低,延遲4us,最后將SCL拉低。這其實就是協議規定的動作了。
-
產生IIC停止信號
?
同樣的道理,和協議的時序保持一致就好了。
-
等待應答信號到來
?
首先我們需要把SDA設置為輸入,因為接收方要給發射方返回一個應答信號的。就是在SCL第9個高電平的時候,釋放信號線,先拉高,然后持續等待,是不是有應答信號返回,其實就是返回一個低電平,所以我們一直在檢測READ_SDA這個電平,持續一段時間,要是沒有返回的話,我們認為超時了,就直接停止協議了,
-
產生應答信號
?
即在第9個時鐘周期內,SDA都為低電平,為應答
-
不產生應答信號
?
即在第9個時鐘周期內,SDA都為高電平,為不應答
-
IIC發送一個字節
?
?
發送數據,SDA設置為輸出。SCL拉低,SDA準備。
做一個8次循環,拿出1byte的數據,將你發送的數據和0x80作與運算,拿出最高位,然后右移7位,將這個數據放到最低位,這個數據要是1的話,那么SDA輸出一個高電平,要是與后的結果為低電平的話,那么SDA輸出一個低電平。這都屬于準備發送信號階段。
然后SCL拉高,完成數據的發送,然后SCL拉低,此時SDA也就可以拉低了,接著開始次高位的傳輸,直到傳輸完成。
-
讀取數據
?
讀取數據,SDA要設置為輸入了。SCL開始為低電平,然后SCL為高電平,我們開始讀SDA上的數據,然后左移數據,將讀取的數據放在低位。然后檢測一下有沒有應答。
其實基本思路就是這樣了。我把源碼附上:
i2c.h
#ifndef __MYIIC_H #define __MYIIC_H #include "sys.h" #include "stm32f10x_gpio.h" //IO方向設置 #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //IO操作函數 #define IIC_SCL PBout(6) //SCL #define IIC_SDA PBout(7) //SDA #define READ_SDA PBin(7) //輸入SDA //IIC所有操作函數 void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //發送IIC開始信號 void IIC_Stop(void); //發送IIC停止信號 void IIC_Send_Byte(u8 txd); //IIC發送一個字節 u8 IIC_Read_Byte(unsigned char ack);//IIC讀取一個字節 u8 IIC_Wait_Ack(void); //IIC等待ACK信號 void IIC_Ack(void); //IIC發送ACK信號 void IIC_NAck(void); //IIC不發送ACK信號 void IIC_Write_One_Byte(u8 daddr,u8 addr,u8 data); u8 IIC_Read_One_Byte(u8 daddr,u8 addr); #endif
i2c.c
#include "myiic.h" #include "delay.h" //初始化IIC void IIC_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );//使能GPIOB時鐘 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽輸出 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 輸出高 } //產生IIC起始信號 void IIC_Start(void) { SDA_OUT(); //sda線輸出 IIC_SDA=1; IIC_SCL=1; delay_us(4); IIC_SDA=0;//START:when CLK is high,DATA change form high to low delay_us(4); IIC_SCL=0;//鉗住I2C總線,準備發送或接收數據 } //產生IIC停止信號 void IIC_Stop(void) { SDA_OUT();//sda線輸出 IIC_SCL=0; IIC_SDA=0;//STOP:when CLK is high DATA change form low to high delay_us(4); IIC_SCL=1; IIC_SDA=1;//發送I2C總線結束信號 delay_us(4); } //等待應答信號到來 //返回值:1,接收應答失敗 // 0,接收應答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA設置為輸入 IIC_SDA=1;delay_us(1); IIC_SCL=1;delay_us(1); while(READ_SDA) { ucErrTime++; if(ucErrTime>250) { IIC_Stop(); return 1; } } IIC_SCL=0;//時鐘輸出0 return 0; } //產生ACK應答 void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //不產生ACK應答 void IIC_NAck(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //IIC發送一個字節 //返回從機有無應答 //1,有應答 //0,無應答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;//拉低時鐘開始數據傳輸 for(t=0;t<8;t++) { //IIC_SDA=(txd&0x80)>>7; if((txd&0x80)>>7) IIC_SDA=1; else IIC_SDA=0; txd<<=1; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; delay_us(2); } } //讀1個字節,ack=1時,發送ACK,ack=0,發送nACK u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//SDA設置為輸入 for(i=0;i<8;i++ ) { IIC_SCL=0; delay_us(2); IIC_SCL=1; receive<<=1; if(READ_SDA)receive++; delay_us(1); } if (!ack) IIC_NAck();//發送nACK else IIC_Ack(); //發送ACK return receive; }
?
評論
查看更多