I2C兩線式串行總線通訊協(xié)議,它是由飛利浦開發(fā)的,主要用于連接微控制器及其外圍設(shè)備之間,它是由數(shù)據(jù)線SDA和信號(hào)線SCL構(gòu)成的,可發(fā)送和接收數(shù)據(jù)即在MUC和I2C設(shè)備之間,I2C和I2C之間進(jìn)行全雙工信號(hào)傳輸,高速I2C總線一般可達(dá)到400kbps。一般我們也稱為TWI接口。
I2C支持多主機(jī)模式:

?
即在這個(gè)主線上可以掛載n個(gè)I2C外設(shè)。
對(duì)于I2C協(xié)議,其實(shí)也很簡(jiǎn)單,不要想的那么復(fù)雜,其實(shí)就是電平的變換。我們可以人為的分為6個(gè)部分
整體時(shí)序圖:

?
各狀態(tài):
-
空閑狀態(tài)

?
I2C總線的SCK和SDA兩個(gè)線同時(shí)處于高電平的時(shí)候,規(guī)定為總線的空閑狀態(tài),這個(gè)就是由總線上的上拉電阻把電平拉高的。
-
起始信號(hào)

?
當(dāng)SCL為高電平期間,SDA由高電平變成低電平,即為起始信號(hào)。啟動(dòng)信號(hào)是一種電平跳變時(shí)序信號(hào),不是一個(gè)電平信號(hào)。
-
停止信號(hào)
當(dāng)SCL為高電平期間,SDA由低電平變?yōu)楦唠娖剑礊橥V剐盘?hào)。停止信號(hào)也是一種電平跳變時(shí)序信號(hào),不是一個(gè)電平信號(hào)。
-
應(yīng)答信號(hào)

?
發(fā)送器每發(fā)送一個(gè)字節(jié)(8bit)數(shù)據(jù),就在時(shí)鐘脈沖(SCL)9期間釋放數(shù)據(jù)線(SDA),再由接收器來反饋一個(gè)應(yīng)答信號(hào),應(yīng)答信號(hào)為低電平的時(shí)候,規(guī)定為有效應(yīng)答位(ACK:應(yīng)答位),表明接收器已經(jīng)成功的接收了該字節(jié),應(yīng)答信號(hào)為高電平時(shí),規(guī)定為非應(yīng)答位(NACK:非應(yīng)答位),表示接收器沒有成功的接收該字節(jié)。
對(duì)于反饋有效應(yīng)答位(ACK):接收器在第9個(gè)時(shí)鐘脈沖之前的低電平期間將SDA拉低,并且保證在該時(shí)鐘的高電平期間,SDA為穩(wěn)定的低電平。大家主要看圖,看看是不是這樣的。
要是接收器是主控器,那么它收到最后一個(gè)字節(jié)后,發(fā)送一個(gè)NACK信號(hào),以通知被控發(fā)送器結(jié)束數(shù)據(jù)的發(fā)送,并且釋放SDA線,以便主控接收器發(fā)送一個(gè)停止信號(hào)。
-
數(shù)據(jù)的有效性

?
時(shí)鐘信號(hào)(SCL)為高電平期間,數(shù)據(jù)線上的數(shù)據(jù)必須保持穩(wěn)定,只有在時(shí)鐘信號(hào)(SCL)為低電平期間,數(shù)據(jù)線上的高電平或者低電平才能發(fā)生變化。
數(shù)據(jù)必須在時(shí)鐘信號(hào)(SCL)的上升沿到來之前就準(zhǔn)備好,并且在數(shù)據(jù)信號(hào)的下降沿來到之前必須穩(wěn)定。
-
數(shù)據(jù)的傳輸
在SDA上的每一個(gè)位的數(shù)據(jù)的傳輸都需要一個(gè)時(shí)鐘脈沖,即在SCL串行時(shí)鐘的配合下,SDA上逐位的串行發(fā)送每一位數(shù)據(jù)。數(shù)據(jù)位的傳輸是邊沿觸發(fā)。
示例代碼講解
-
初始化IIC

?
其實(shí)就是對(duì)兩個(gè)線的初始化,我這里使用的是PA6和PA7,開始都設(shè)置為輸出,中途會(huì)改變PA7的輸入輸出屬性,在空閑狀態(tài),我們知道SCL和SDA是被拉高的,所以這個(gè)地方我們給高電平。
-
產(chǎn)生IIC起始信號(hào)

?

?
將SDA線設(shè)置為輸出,然后SDA和SCL都設(shè)置為高電平,延遲4us,然后將SDA拉低,延遲4us,最后將SCL拉低。這其實(shí)就是協(xié)議規(guī)定的動(dòng)作了。
-
產(chǎn)生IIC停止信號(hào)

?
同樣的道理,和協(xié)議的時(shí)序保持一致就好了。
-
等待應(yīng)答信號(hào)到來

?
首先我們需要把SDA設(shè)置為輸入,因?yàn)榻邮辗揭o發(fā)射方返回一個(gè)應(yīng)答信號(hào)的。就是在SCL第9個(gè)高電平的時(shí)候,釋放信號(hào)線,先拉高,然后持續(xù)等待,是不是有應(yīng)答信號(hào)返回,其實(shí)就是返回一個(gè)低電平,所以我們一直在檢測(cè)READ_SDA這個(gè)電平,持續(xù)一段時(shí)間,要是沒有返回的話,我們認(rèn)為超時(shí)了,就直接停止協(xié)議了,
-
產(chǎn)生應(yīng)答信號(hào)

?
即在第9個(gè)時(shí)鐘周期內(nèi),SDA都為低電平,為應(yīng)答
-
不產(chǎn)生應(yīng)答信號(hào)

?
即在第9個(gè)時(shí)鐘周期內(nèi),SDA都為高電平,為不應(yīng)答
-
IIC發(fā)送一個(gè)字節(jié)

?

?
發(fā)送數(shù)據(jù),SDA設(shè)置為輸出。SCL拉低,SDA準(zhǔn)備。
做一個(gè)8次循環(huán),拿出1byte的數(shù)據(jù),將你發(fā)送的數(shù)據(jù)和0x80作與運(yùn)算,拿出最高位,然后右移7位,將這個(gè)數(shù)據(jù)放到最低位,這個(gè)數(shù)據(jù)要是1的話,那么SDA輸出一個(gè)高電平,要是與后的結(jié)果為低電平的話,那么SDA輸出一個(gè)低電平。這都屬于準(zhǔn)備發(fā)送信號(hào)階段。
然后SCL拉高,完成數(shù)據(jù)的發(fā)送,然后SCL拉低,此時(shí)SDA也就可以拉低了,接著開始次高位的傳輸,直到傳輸完成。
-
讀取數(shù)據(jù)

?
讀取數(shù)據(jù),SDA要設(shè)置為輸入了。SCL開始為低電平,然后SCL為高電平,我們開始讀SDA上的數(shù)據(jù),然后左移數(shù)據(jù),將讀取的數(shù)據(jù)放在低位。然后檢測(cè)一下有沒有應(yīng)答。
其實(shí)基本思路就是這樣了。我把源碼附上:
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開始信號(hào) void IIC_Stop(void); //發(fā)送IIC停止信號(hào) void IIC_Send_Byte(u8 txd); //IIC發(fā)送一個(gè)字節(jié) u8 IIC_Read_Byte(unsigned char ack);//IIC讀取一個(gè)字節(jié) u8 IIC_Wait_Ack(void); //IIC等待ACK信號(hào) void IIC_Ack(void); //IIC發(fā)送ACK信號(hào) void IIC_NAck(void); //IIC不發(fā)送ACK信號(hào) 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時(shí)鐘 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起始信號(hào) 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停止信號(hào) 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é)束信號(hào) delay_us(4); } //等待應(yīng)答信號(hào)到來 //返回值: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;//時(shí)鐘輸出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ā)送一個(gè)字節(jié) //返回從機(jī)有無(wú)應(yīng)答 //1,有應(yīng)答 //0,無(wú)應(yīng)答 void IIC_Send_Byte(u8 txd) { u8 t; SDA_OUT(); IIC_SCL=0;//拉低時(shí)鐘開始數(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個(gè)字節(jié),ack=1時(shí),發(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; }
?
評(píng)論