I2C總線我已經用很久了,也用了很多次,但每到下一次使用時,都會或多或少的發現一些小問題,比如讀寫單個字節時沒有問題,在連續讀寫大量數據時卻出現讀寫不正確的現象,下面來總結一下模擬I2C驅動代碼的實現關鍵
1、起始信號start:這個一般不會出錯,在SCL=1時,讓SDA出現一個下降沿,即SDA=1 --->SDA=0;
2、停止信號stop:這個一般也不會出錯,在SCL=1時,讓SDA出現一個上升沿,即SDA=0 --->SDA=1;
3、主機(如單片機)檢測應答信號:I2C要求,接收方在接收到一個字節後,在第9個時鐘回一個應答信號,主機檢測這個應答信號應這樣操作,SCL=0時,釋放SDA(即SDA=1),然後讓SCL=1,接收方在檢測到SCL=1後會讓SDA=0作爲應答信號,隨後發送方就可檢測SDA的狀態;
4、主機回覆應答信號:主機在接收從機發來的一個字節後,要回一個應答信號,主機應這樣操作,在SCL=0時,使SDA=0,然後讓SCL=1,隨後從機會檢測到SDA的狀態;
5、主機回覆非應答信號:主機在接收數據後,在stop信號前,回一個非應答信號,主機應該這樣操作,在SCL=0時,使SDA=1,然後讓SCL=1,隨後從機會檢測到SDA的狀態;
以上5點用過幾次I2C後一般都會掌握。
以下幾點最爲容易忽視,導致I2C時序出錯,連續讀寫大量數據時容易丟包
1、特別注意SCL=1時,SDA的任何變化都會使時序出錯,從機將視爲起始信號或停止信號,所以要特別注意start 、write/read、ack/no_ack、stop銜接的前後操作,不要出現有SCL=1改變SDA狀態的情況;爲此,建議在以上幾個函數的開始處後退出前,使SLC=0,這樣在函數銜接時不會出現SCL跳變的情況,也不會在SCL=1時誤操作SDA.
2、start 、write/read、ack/no_ack、stop函數前後銜接時,要保持SCL的狀態一致性,否則讀寫過程中可能會導致時鐘個數錯誤,這個問題在讀寫單個字節時可能不會出現,但在多次讀寫中就會出現。
3、部分用戶在連續讀時沒發現出錯,覺得自己的時序是正確的,但連續寫時就會出錯,感覺莫名其妙,這是因爲大多數器件的數據讀出要比寫入的速度要快,在連續寫時,應在中間要延時一段時間;
4、 由於硬件上要求SCL和SDA都接上拉電阻,stop信號時在讀寫時最後的一個操作,所以,stop退出前,建議使SDA=1,SCL=1,從而不讓上拉電阻兩端有高低電位差,以降低功耗,當然,也要保證SLC=1時不要改變SDA,否則設備會認爲是一個新的起始或停止信號。
下面時實用例程C代碼
void I2C_delay(void)
{
u16 i;
/*以STM32 72MHz爲例
循環10次,SCL頻率爲20K
循環7次,SCL頻率爲340K
循環5次,SCL頻率爲420K
*/
for(i=0;i<5;i++);
}
void E2PROM_I2C_start(void)
{
I2C_SetOut_Mode();
E2PROM_I2C_SCL=0;
E2PROM_I2C_SDA_WR=1;
E2PROM_I2C_SCL=1;
I2C_delay();
E2PROM_I2C_SDA_WR=0;
E2PROM_I2C_SCL=0;
I2C_delay();
}
void E2PROM_I2C_stop(void)
{
I2C_SetOut_Mode();
E2PROM_I2C_SCL=0;
E2PROM_I2C_SDA_WR=0;
E2PROM_I2C_SCL=1;
I2C_delay();
E2PROM_I2C_SDA_WR=1;
E2PROM_I2C_SCL=0;
I2C_delay();
E2PROM_I2C_SDA_WR=1;//拉高數據和時鐘線,以降低功耗
E2PROM_I2C_SCL=1;
}
u8 E2PROM_I2C_check_ack(void)
{
u8 ack_status=1;
I2C_SetIn_Mode();
E2PROM_I2C_SCL=0;
E2PROM_I2C_SDA_WR=1;
E2PROM_I2C_SCL=1;
I2C_delay();
ack_status=0x01&E2PROM_I2C_SDA_RE;
E2PROM_I2C_SCL=0;
I2C_delay();
return ack_status;
}
void E2PROM_I2C_ack(void)
{
I2C_SetOut_Mode();
E2PROM_I2C_SCL=0;
E2PROM_I2C_SDA_WR=0;
E2PROM_I2C_SCL=1;
I2C_delay();
E2PROM_I2C_SCL=0;
I2C_delay();
E2PROM_I2C_SDA_WR=1;
}
void E2PROM_I2C_NoAck(void)
{
I2C_SetOut_Mode();
E2PROM_I2C_SCL=0;
E2PROM_I2C_SDA_WR=1;
I2C_delay();
E2PROM_I2C_SCL=1;
I2C_delay();
E2PROM_I2C_SCL=0;
I2C_delay();
}
void E2PROM_I2C_write_char(u8 dat)
{
u8 i=0;
E2PROM_I2C_SCL=0;
I2C_SetOut_Mode();
for(i=0;i<8;i++)
{
if(dat&0x80) E2PROM_I2C_SDA_WR=1;
else E2PROM_I2C_SDA_WR=0;
I2C_delay();
E2PROM_I2C_SCL=1;
I2C_delay();
E2PROM_I2C_SCL=0;
dat<<=1;
}
}
u8 E2PROM_I2C_read_char(void)
{
u8 i=0,dat=0;
E2PROM_I2C_SCL=0;
I2C_SetIn_Mode();
for(i=0;i<8;i++)
{
dat<<=1;
E2PROM_I2C_SCL=1;
I2C_delay();
if(E2PROM_I2C_SDA_RE) dat|=0x01;
E2PROM_I2C_SCL=0;
I2C_delay();
}
return dat;
}
void AT24CXX_Write(u8 DeviceAddr,u8 ByteAddr,u8 dat)
{
E2PROM_I2C_start();
E2PROM_I2C_write_char(DeviceAddr);
E2PROM_I2C_check_ack();
E2PROM_I2C_write_char(ByteAddr);
E2PROM_I2C_check_ack();
E2PROM_I2C_write_char(dat);
E2PROM_I2C_check_ack();
E2PROM_I2C_stop();
}
unsigned char AT24CXX_Read(u8 DeviceAddr,u8 ByteAdrr)
{
unsigned char dat=0;
E2PROM_I2C_start();
E2PROM_I2C_write_char(DeviceAddr);
E2PROM_I2C_check_ack();
E2PROM_I2C_write_char(ByteAdrr);
E2PROM_I2C_check_ack();
E2PROM_I2C_start();
E2PROM_I2C_write_char(DeviceAddr|0x01);
E2PROM_I2C_check_ack();
dat=E2PROM_I2C_read_char();
E2PROM_I2C_NoAck();
E2PROM_I2C_stop();
return dat;
}
u8 E2PROM_buffer[256];
int main(void)
{
u16 i,j,k;
for(i=0;i<256;i++)
{
AT24CXX_Write(I2C_24C08_write_cmd,i,i);
Delay_ms(2);
}
k=0;
printf("\r\n");
for(i=0;i<16;i++)
{
for(j=0;j<16;j++)
{
E2PROM_buffer[k]=AT24CXX_Read(I2C_24C08_write_cmd,k);
printf("%d ",E2PROM_buffer[k]);
k++;
}
printf("\r\n");
}
printf("\r\n");
for(i=0;i<255;i++)
{
if(E2PROM_buffer[i+1]-E2PROM_buffer[i] != 1)
{
printf(" \r\n E2PROM TEST error !!! \r\n" );
break;
}
}
}