IIC總線是由數據線SDA和時鐘SCL構成的串行總線,可發送和接收數據。它在傳輸數據過程中有三種特殊類型信號,分別是:開始信號、結束信號和應答信號。IIC通信:IIC通信無論發送還是接收,都需要結合時鐘SCL來進行數據SDA的傳輸,因此一次要使用兩條線。傳輸數據與時鐘有關,因此稱爲同步串行通信
總線空閒狀態:
I2C總線總線的SDA和SCL兩條信號線同時處於高電平時,規定爲總線的空閒狀態。此時各個器件的輸出級場效應管均處在截止狀態,即釋放總線,由兩條信號線各自的上拉電阻把電平拉高。
IIC通信的起始信號和停止信號:
時鐘線SCL爲高電平期間,數據線SDA由高電平向低電平變化——起始信號
時鐘線SCL爲高電平期間,數據線SDA由低電平向高電平變化——停止信號
IIC通信的數據傳輸:
時鐘線SCL爲高電平期間,數據線SDA上的數據必須保持穩定。只有時鐘線SCL信號爲低電平期間,數據線SDA狀態才允許變化。
應答ACK:
每一個完整的傳輸都必須保證是一個字節的傳輸(8bit),在傳輸過程中是由MSB到LSB進行傳輸的,每一個字節傳輸完成後都必須跟隨一位應答信號,即每一個完成的傳輸是9bit。
如果需要檢測應答信號“0”,得先置高數據線SDA,如果數據線後來檢測到應答信號“0”,則是應答信號存在的最好說明!如果需要檢測非應答信號“1”,得先拉低數據線SDA,如果數據線後來檢測到非應答信號“1”,也是非應答信號存在的最好說明!
注意:並非每一次字節傳輸完成後都會有ACK信號,有以下三種情況例外:
1、當從機無法響應主機發送給從機的地址時(例如從機正忙,這個地址錯誤等)在第9個SCL週期內SDA沒有被拉低,即沒有ACK信號,這是主機會發送一個STOP標誌來終止信號的傳輸或者重新發出一個RESTART信號請求新的傳輸。
2、如果從機接收器在傳輸過程中不能接收更多的數據時,它也不會發出ACK標誌,這樣主機在接收不到ACK標誌的時候就會發出一個STOP信號來終止傳輸或者重新發出一個RESTART信號來請求新的傳輸。
3、主機接收器在接收到最後一個字節後,也不會發出ACK信號,於是從機釋放SDA線,以允許主機發送STOP信號來結束傳輸。
總結:對主機來說,它會不斷地檢測在SCL處於第9週期時SDA是否爲低,只要發現爲低,那麼主機在接下來就會發出STOP信號。
IIC相關結構體:
typedef struct
{
uint32_t I2C_ClockSpeed; /* 設置SCL線上的時鐘頻率,此值不可高於400000 */
uint16_t I2C_Mode; /* 指定工作模式,I2C模式或者SMBUS模式可選 */
uint16_t I2C_DutyCycle; /* 時鐘佔空比,即高電平時間/低電平時間 */
uint16_t I2C_OwnAddress1; /* 指定I2C自身設備的地址 */
uint16_t I2C_Ack; /* 應答使能或關閉,一般爲使能 */
uint16_t I2C_AcknowledgedAddress;/* 指定地址長度,7位/10位可選 */
}I2C_InitTypeDef;
寫通訊過程:
1. 主控在檢測到總線空閒的狀況下,首先發送一個START信號掌管總線;
2. 發送一個地址字節(包括7位地址碼和一位R/W);
3. 當被控器件檢測到主控發送的地址與自己的地址相同時發送一個應答信號(ACK);
4. 主控收到ACK後開始發送第一個數據字節;
5. 被控器收到數據字節後發送一個ACK表示繼續傳送數據,發送NACK表示傳送數據結束;
6. 主控發送完全部數據後,發送一個停止位STOP,結束整個通訊並且釋放總線;
讀通訊過程:
1. 主控在檢測到總線空閒的狀況下,首先發送一個START信號掌管總線;
2. 發送一個地址字節(包括7位地址碼和一位R/W);
3. 當被控器件檢測到主控發送的地址與自己的地址相同時發送一個應答信號(ACK);
4. 主控收到ACK後釋放數據總線,開始接收第一個數據字節;
5. 主控收到數據後發送ACK表示繼續傳送數據,發送NACK表示傳送數據結束;
6. 主控發送完全部數據後,發送一個停止位STOP,結束整個通訊並且釋放總線;
IIC 相關操作:
//使用IIC1 掛載PB6,PB7
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)8<<28;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=(u32)3<<28;}
#define IIC_SCL PBout(6) //SCL
#define IIC_SDA PBout(7) //SDA
#define READ_SDA PBin(7) //輸入SDA
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE );
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);
IIC_SCL=1;
IIC_SDA=1;
}
void IIC_Start(void) //產生IIC起始信號
{
SDA_OUT(); //sda線輸出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;// 時鐘線SCL爲高電平期間,數據線SDA由高電平向低電平變化
delay_us(4);
IIC_SCL=0;//鉗住I2C總線,準備發送或接收數據
}
void IIC_Stop(void) //產生IIC停止信號
{
SDA_OUT();//sda線輸出
IIC_SCL=0;
IIC_SDA=0;// 時鐘線SCL爲高電平期間,數據線SDA由低電平向高電平變化
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;
}
void IIC_Ack(void) //產生ACK應答
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
void IIC_NAck(void) //不產生ACK應答
{
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;
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;
}
void I2C_WriteByte(uint16_t addr,uint8_t data,uint8_t device_addr)
{
IIC_Start();
if(device_addr==0xA0) //地址大於1字節
IIC_Send_Byte(0xA0 + ((addr/256)<<1));//發送高地址
else
IIC_Send_Byte(device_addr); //發器件地址
IIC_Wait_Ack();
IIC_Send_Byte(addr&0xFF); //發送低地址
IIC_Wait_Ack();
IIC_Send_Byte(data); //發送字節
IIC_Wait_Ack();
IIC_Stop();//產生一個停止條件
if(device_addr==0xA0) //
delay_ms(10);
else
delay_us(2);
}
//讀寄存器或讀數據
uint16_t I2C_ReadByte(uint16_t addr,uint8_t device_addr,uint8_t ByteNumToRead)
{
uint16_t data;
IIC_Start();
if(device_addr==0xA0)
IIC_Send_Byte(0xA0 + ((addr/256)<<1));
else
IIC_Send_Byte(device_addr);
IIC_Wait_Ack();
IIC_Send_Byte(addr&0xFF); //發送低地址
IIC_Wait_Ack();
IIC_Start();
IIC_Send_Byte(device_addr+1); //發器件地址
IIC_Wait_Ack();
if(ByteNumToRead == 1)//LM75溫度數據爲11bit
{
data=IIC_Read_Byte(0);
}
else
{
data=IIC_Read_Byte(1);
data=(data<<8)+IIC_Read_Byte(0);
}
IIC_Stop();//產生一個停止條件
return data;
}