I2C兩線式串行總線通訊協(xié)議,它是由飛利浦開發(fā)的,主要用于連接微控制器及其外圍設(shè)備之間,它是由數(shù)據(jù)線SDA和信號線SCL構(gòu)成的,可發(fā)送和接收數(shù)據(jù)即在MUC和I2C設(shè)備之間,I2C和I2C之間進行全雙工信號傳輸,高速I2C總線一般可達到400kbps。一般我們也稱為TWI接口。
I2C支持多主機模式:
?
即在這個主線上可以掛載n個I2C外設(shè)。
對于I2C協(xié)議,其實也很簡單,不要想的那么復(fù)雜,其實就是電平的變換。我們可以人為的分為6個部分
整體時序圖:
?
各狀態(tài):
-
空閑狀態(tài)
?
I2C總線的SCK和SDA兩個線同時處于高電平的時候,規(guī)定為總線的空閑狀態(tài),這個就是由總線上的上拉電阻把電平拉高的。
-
起始信號
?
當(dāng)SCL為高電平期間,SDA由高電平變成低電平,即為起始信號。啟動信號是一種電平跳變時序信號,不是一個電平信號。
-
停止信號
當(dāng)SCL為高電平期間,SDA由低電平變?yōu)楦唠娖剑礊橥V剐盘枴MV剐盘栆彩且环N電平跳變時序信號,不是一個電平信號。
-
應(yīng)答信號
?
發(fā)送器每發(fā)送一個字節(jié)(8bit)數(shù)據(jù),就在時鐘脈沖(SCL)9期間釋放數(shù)據(jù)線(SDA),再由接收器來反饋一個應(yīng)答信號,應(yīng)答信號為低電平的時候,規(guī)定為有效應(yīng)答位(ACK:應(yīng)答位),表明接收器已經(jīng)成功的接收了該字節(jié),應(yīng)答信號為高電平時,規(guī)定為非應(yīng)答位(NACK:非應(yīng)答位),表示接收器沒有成功的接收該字節(jié)。
對于反饋有效應(yīng)答位(ACK):接收器在第9個時鐘脈沖之前的低電平期間將SDA拉低,并且保證在該時鐘的高電平期間,SDA為穩(wěn)定的低電平。大家主要看圖,看看是不是這樣的。
要是接收器是主控器,那么它收到最后一個字節(jié)后,發(fā)送一個NACK信號,以通知被控發(fā)送器結(jié)束數(shù)據(jù)的發(fā)送,并且釋放SDA線,以便主控接收器發(fā)送一個停止信號。
-
數(shù)據(jù)的有效性
?
時鐘信號(SCL)為高電平期間,數(shù)據(jù)線上的數(shù)據(jù)必須保持穩(wěn)定,只有在時鐘信號(SCL)為低電平期間,數(shù)據(jù)線上的高電平或者低電平才能發(fā)生變化。
數(shù)據(jù)必須在時鐘信號(SCL)的上升沿到來之前就準(zhǔn)備好,并且在數(shù)據(jù)信號的下降沿來到之前必須穩(wěn)定。
-
數(shù)據(jù)的傳輸
在SDA上的每一個位的數(shù)據(jù)的傳輸都需要一個時鐘脈沖,即在SCL串行時鐘的配合下,SDA上逐位的串行發(fā)送每一位數(shù)據(jù)。數(shù)據(jù)位的傳輸是邊沿觸發(fā)。
示例代碼講解
-
初始化IIC
?
其實就是對兩個線的初始化,我這里使用的是PA6和PA7,開始都設(shè)置為輸出,中途會改變PA7的輸入輸出屬性,在空閑狀態(tài),我們知道SCL和SDA是被拉高的,所以這個地方我們給高電平。
-
產(chǎn)生IIC起始信號
?
?
將SDA線設(shè)置為輸出,然后SDA和SCL都設(shè)置為高電平,延遲4us,然后將SDA拉低,延遲4us,最后將SCL拉低。這其實就是協(xié)議規(guī)定的動作了。
-
產(chǎn)生IIC停止信號
?
同樣的道理,和協(xié)議的時序保持一致就好了。
-
等待應(yīng)答信號到來
?
首先我們需要把SDA設(shè)置為輸入,因為接收方要給發(fā)射方返回一個應(yīng)答信號的。就是在SCL第9個高電平的時候,釋放信號線,先拉高,然后持續(xù)等待,是不是有應(yīng)答信號返回,其實就是返回一個低電平,所以我們一直在檢測READ_SDA這個電平,持續(xù)一段時間,要是沒有返回的話,我們認(rèn)為超時了,就直接停止協(xié)議了,
-
產(chǎn)生應(yīng)答信號
?
即在第9個時鐘周期內(nèi),SDA都為低電平,為應(yīng)答
-
不產(chǎn)生應(yīng)答信號
?
即在第9個時鐘周期內(nèi),SDA都為高電平,為不應(yīng)答
-
IIC發(fā)送一個字節(jié)
?
?
發(fā)送數(shù)據(jù),SDA設(shè)置為輸出。SCL拉低,SDA準(zhǔn)備。
做一個8次循環(huán),拿出1byte的數(shù)據(jù),將你發(fā)送的數(shù)據(jù)和0x80作與運算,拿出最高位,然后右移7位,將這個數(shù)據(jù)放到最低位,這個數(shù)據(jù)要是1的話,那么SDA輸出一個高電平,要是與后的結(jié)果為低電平的話,那么SDA輸出一個低電平。這都屬于準(zhǔn)備發(fā)送信號階段。
然后SCL拉高,完成數(shù)據(jù)的發(fā)送,然后SCL拉低,此時SDA也就可以拉低了,接著開始次高位的傳輸,直到傳輸完成。
-
讀取數(shù)據(jù)
?
讀取數(shù)據(jù),SDA要設(shè)置為輸入了。SCL開始為低電平,然后SCL為高電平,我們開始讀SDA上的數(shù)據(jù),然后左移數(shù)據(jù),將讀取的數(shù)據(jù)放在低位。然后檢測一下有沒有應(yīng)答。
其實基本思路就是這樣了。我把源碼附上:
i2c.h
#ifndef __MYIIC_H #define __MYIIC_H #include "sys.h" #include "stm32f10x_gpio.h" //IO方向設(shè)置 #define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;} #define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;} //IO操作函數(shù) #define IIC_SCL PBout(6) //SCL #define IIC_SDA PBout(7) //SDA #define READ_SDA PBin(7) //輸入SDA //IIC所有操作函數(shù) void IIC_Init(void); //初始化IIC的IO口 void IIC_Start(void); //發(fā)送IIC開始信號 void IIC_Stop(void); //發(fā)送IIC停止信號 void IIC_Send_Byte(u8 txd); //IIC發(fā)送一個字節(jié) u8 IIC_Read_Byte(unsigned char ack);//IIC讀取一個字節(jié) u8 IIC_Wait_Ack(void); //IIC等待ACK信號 void IIC_Ack(void); //IIC發(fā)送ACK信號 void IIC_NAck(void); //IIC不發(fā)送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 輸出高 } //產(chǎn)生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總線,準(zhǔn)備發(fā)送或接收數(shù)據(jù) } //產(chǎn)生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;//發(fā)送I2C總線結(jié)束信號 delay_us(4); } //等待應(yīng)答信號到來 //返回值:1,接收應(yīng)答失敗 // 0,接收應(yīng)答成功 u8 IIC_Wait_Ack(void) { u8 ucErrTime=0; SDA_IN(); //SDA設(shè)置為輸入 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; } //產(chǎn)生ACK應(yīng)答 void IIC_Ack(void) { IIC_SCL=0; SDA_OUT(); IIC_SDA=0; delay_us(2); IIC_SCL=1; delay_us(2); IIC_SCL=0; } //不產(chǎn)生ACK應(yīng)答 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發(fā)送一個字節(jié) //返回從機有無應(yīng)答 //1,有應(yīng)答 //0,無應(yīng)答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;//拉低時鐘開始數(shù)據(jù)傳輸 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個字節(jié),ack=1時,發(fā)送ACK,ack=0,發(fā)送nACK u8 IIC_Read_Byte(unsigned char ack) { unsigned char i,receive=0; SDA_IN();//SDA設(shè)置為輸入 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();//發(fā)送nACK else IIC_Ack(); //發(fā)送ACK return receive; }
?
評論
查看更多