1.基本理解
? ? ? ? iic通信協議:雙線制串行通信協議,由時鐘線SCL和數據線SDA構成.
? ? ? ? 通信方式:主從模式,主設備發起通信,從設備響應通信
2.通信的基本步驟
?? ?a.主設備發送一個開始信號,表示開始通信,即啟動I2C ?
????????條件:SCL=1,SDA出現下降沿?
?? ??? ?IIC啟動之后,SCL=1時,SDA的電平不允許有變化
? ? ? ? ? ? ? ? ???? ??? ??? ?SCL=0時,數據發送方才能在SDA上改變發送電平
?? ?b.主設備發送一個從設備的地址和(讀寫位) 一般地址的長度是7bit,最后一個bit是讀寫指令,
?? ??? ?1是讀(主機接收從機數據),0是寫(主機發送數據到從機)
?? ?C.仲裁機制和應答,從機只有收到自己的地址信息才會被喚醒,這個過程是以一個低電平的SDA脈沖應答,
?? ??? ?主機檢測這個應答位,0是 1不是
?? ?d.數據傳輸 確認目標后,主設備發送或者接收數據,數據傳輸在每個時鐘周期的上升沿或者下降沿進行
?? ?e.停止:SCL = 1,SDA出現上升沿
?? ??? ?注意:I2C外部需根據傳輸速率匹配上拉電阻,速率越高,上拉電阻越小,否則會影響時序;
? ? ? ? ? ? ? ? ? ? I2C引腳作為輸出時需是開漏輸出,作為輸入時需是浮空輸入,不能匹配內部上拉或下拉電阻;
?? ??? ??? ? ?
? ? 3.軟件模擬
?? ?1.結構體定義總線的SDA和SCL兩個引腳:用于控制兩個引腳的輸入輸出
typedef struct{uint32_t SDA_RCC_APB2Periph;//SDA腳的時鐘GPIO_TypeDef* SDA_Port;//SDA腳Portuint16_t SDA_Pin;//SDA腳Pinuint32_t SCL_RCC_APB2Periph;//SCL腳的時鐘GPIO_TypeDef* SCL_Port;//SCL腳Portuint16_t SCL_Pin;//SCL腳Pin}sw_i2c_gpio_t;
? ? ?2.宏定義
?? ? ?
#define I2C_USE_7BIT_ADDR //如果使用的從機地址是7Bit模式,則打開這個宏,否則注釋掉這個宏#define I2C_DELAY ? ? ? ? ? ? ? ?50 // I2C每個Bit之間的延時時間,延時越小I2C的速率越高#define SW_I2C_SCL_LOW ? ? ? ? ?GPIO_ResetBits(gpio->SCL_Port,gpio->SCL_Pin) // I2C SCL腳輸出0 地電平#define SW_I2C_SCL_HIGH ? ? ? ? GPIO_SetBits(gpio->SCL_Port,gpio->SCL_Pin) // I2C SCL腳輸出1 高電平#define SW_I2C_SDA_LOW ? ? ? ? ?GPIO_ResetBits(gpio->SDA_Port,gpio->SDA_Pin) // I2C SDA腳輸出0 低電平#define SW_I2C_SDA_HIGH ? ? ? ? GPIO_SetBits(gpio->SDA_Port,gpio->SDA_Pin) // I2C SDA腳輸出1 高電平#define SW_I2C_SDA_INPUT ? ? ? ?sw_i2c_set_sda_input(gpio) // 將SDA腳方向設置為輸入?#define SW_I2C_SDA_OUTPUT ? ? ? ?sw_i2c_set_sda_output(gpio) // 將SDA腳方向設置為輸出#define SW_I2C_SDA_STATUS ? ? ? ?sw_i2c_sda_status(gpio) // 獲取SDA腳輸入電平狀態 ?#define i2c_delay_us(a) ? ? ? ? ? ?SystemDelayUs(a) // 延時
? ?3,其他準備函數
??
?SDA輸入輸出及狀態/**************************************************************************
*** ? ? ? ? ? ? ? ? ? ? ? ? ?讀取SDA腳的狀態 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ***
***************************************************************************/
static uint8_t sw_i2c_sda_status(sw_i2c_gpio_t *gpio)
{uint8_t sda_status;sda_status = GPIO_ReadInputDataBit(gpio->SDA_Port,gpio->SDA_Pin); ? ?return sda_status?1:0;
}
/**************************************************************************
*** ? ? ? ? ? ? ? ? ? ? ? ? ?設置SDA腳為輸入 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ***
***************************************************************************/
static void sw_i2c_set_sda_input(sw_i2c_gpio_t *gpio)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = gpio->SDA_Pin; ? ?GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空輸入模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init (gpio->SDA_Port, & GPIO_InitStructure );
}
/**************************************************************************
*** ? ? ? ? ? ? ? ? ? ? ? ? ?設置SDA腳為輸出 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ***
***************************************************************************/
static void sw_i2c_set_sda_output(sw_i2c_gpio_t *gpio)
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_InitStructure.GPIO_Pin = gpio->SDA_Pin; ? ?GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; ? //開漏輸出模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init (gpio->SDA_Port, & GPIO_InitStructure );
}I2C啟動static void sw_i2c_start(sw_i2c_gpio_t *gpio)
{// I2C 開始時序:SCL=1時,SDA由1變成0.SW_I2C_SDA_HIGH; ? ? ? ??i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH; ? ? ? ? ??i2c_delay_us(I2C_DELAY);SW_I2C_SDA_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}
I2C停止
static void sw_i2c_stop(sw_i2c_gpio_t *gpio)
{// I2C 開始時序:SCL=1時,SDA由0變成1.SW_I2C_SDA_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SDA_HIGH;
}等待數據接收方反饋
static uint8_t sw_i2c_wait_ack(sw_i2c_gpio_t *gpio)
{uint8_t sda_status;uint8_t wait_time=0;uint8_t ack_nack = 1;//先設置SDA腳為輸入SW_I2C_SDA_INPUT;//等待SDA腳被從機拉低while(SW_I2C_SDA_STATUS){wait_time++;//如果等待時間過長,則退出等待if (wait_time>=200){ack_nack = 0;break;}}// SCL由0變為1,讀入ACK狀態// 如果此時SDA=0,則是ACK// 如果此時SDA=1,則是NACKi2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);//再次將SCL=0,并且將SDA腳設置為輸出SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SDA_OUTPUT;i2c_delay_us(I2C_DELAY);return ack_nack;
}發送ACK給數據發送方 ?應答信號
static void sw_i2c_send_ack(sw_i2c_gpio_t *gpio)
{// 發送ACK就是在SDA=0時,SCL由0變成1SW_I2C_SDA_LOW;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}發送NACK給數據發生方 非應答信號,表示接受沒有成功
static void sw_i2c_send_nack(sw_i2c_gpio_t *gpio)
{// 發送NACK就是在SDA=1時,SCL由0變成1SW_I2C_SDA_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}
4.主設備接受和發送字節?
static void sw_i2c_write_byte(sw_i2c_gpio_t *gpio,uint8_t aByte)
{uint8_t i;for (i=0;i<8;i++){//先將SCL拉低;SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);//然后在SDA輸出數據if(aByte&0x80){SW_I2C_SDA_HIGH;}else{SW_I2C_SDA_LOW;}i2c_delay_us(I2C_DELAY);//最后將SCL拉高,在SCL上升沿寫入數據SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);aByte = aByte<<1;//數據位移}//寫完一個字節只后要將SCL拉低SW_I2C_SCL_LOW;i2c_delay_us(I2C_DELAY);
}主設備從從設備讀取字節
static uint8_t sw_i2c_read_byte(sw_i2c_gpio_t *gpio)
{uint8_t i,aByte;//先將SDA腳設置為輸入SW_I2C_SDA_INPUT;for (i=0;i<8;i++){//數據位移aByte = aByte << 1;//延時等待SDA數據穩定i2c_delay_us(I2C_DELAY);//SCL=1,鎖定SDA數據SW_I2C_SCL_HIGH;i2c_delay_us(I2C_DELAY);//讀取SDA狀態if(SW_I2C_SDA_STATUS){aByte |= 0x01;}//SCL=0,解除鎖定SW_I2C_SCL_LOW;}//讀完一個字節,將SDA重新設置為輸出SW_I2C_SDA_OUTPUT;return aByte;
}