2017-06-28 66 views
0

我一直試圖讓我的ATTINY85處於bit-bang I2C(讀/寫)狀態。我有以下配置:讀取I2C時奇怪的延遲問題

PB0 = SDA 
PB1 = LED 
PB2 = SCL 

我能夠毫無問題地寫,但只有在閱讀作品,如果我有我的「延遲()」讀循環中的功能,到目前爲止好:

char i2c_read(void) 
{ 
    uint8_t B = 0; 
    DDRB &= 0b11111110; // switch PB0 to input 

    for (int bit = 0; bit < 0x08; bit++) 
    {   
     delay(); // <--!!!!!!!!! the root of all evil 

     SIGNAL_HIGH(PORT_SCL); 

     B <<= 1; 

     if(PINB & (1 << PB0)) 
     { 
      B |= 1;    
     } 
     else 
     { 
      B |= 0;    
     } 

     SIGNAL_LOW(PORT_SCL); 
    } 

    DDRB |= 0b00000001; // switch PB0 as output 

    i2c_nack(); 

    return B; 
} 

如果我刪除延遲(),I2C不再工作,我無法從設備讀取(設備不響應​​)。看起來合乎邏輯,但我想刪除delay()的原因是因爲它實際上並不是一個「真正」延遲,它只是打開和關閉不同引腳(PB1)上的LED,I2C線路位於PB0和PB2上。

_delay_ms太慢了,所以我只是打開和關閉PB1引腳,以便做出很小的延遲,這是它工作的唯一方式。這裏是我的延時功能的內容,如果我離開它像這樣一切都很正常:

void delay() 
{ 
    LED_ON(); 
    LED_OFF(); 
} 

void LED_ON(void) 
{ 
    PORTB |= 0b00000010; // PB1 
} 

void LED_OFF(void) 
{ 
    PORTB &= 0b11111101; // PB1 
} 

我懷疑,我可能「釘」的創建由其他設備預計到相應的信號長度完美的延遲,所以我嘗試使用for循環和示波器,使相同的延遲:

void delay() 
{ 
    for(int i=0; i<20; i++){ } 
} 

沒有運氣,I2C讀停止工作..

然後,我決定了LED切換到另一個PIN,完全不理會PB1看看它是延遲相關還是pin /電路相關:

void delay() 
{ 
    LED_ON(); 
    LED_OFF(); 
} 

void LED_ON(void) 
{ 
    PORTB |= 0b00001000; // PB3 
} 


void LED_OFF(void) 
{ 
    PORTB &= 0b11110111; // PB3 
} 

奇怪的是I2C再次停止工作!它只有在PB1高/低時纔有效。我仍然無法理解,如果我碰巧指出了所需的完美延遲,而恰恰是PB1的打開比PB3打開所花費的時間更少,或者它與電路本身有關,而且LED做了某種上拉操作,在I2C上提供上拉/下拉功能(原諒我的無知,我是初學者),但PB1根本沒有連接到I2C線。

任何人都可以請說明爲什麼它只在我打開/關閉PB1時才工作,而不是真正的延遲?謝謝!

完整的源:

#define PORT_SDA PB0 
#define PORT_SCL PB2 

#define SIGNAL_HIGH(PORT) PORTB |= (1 << PORT) 
#define SIGNAL_LOW(PORT) PORTB &= ~(1 << PORT) 

void delay(); 
void LED_ON(void); 
void LED_OFF(void); 

void i2c_init(void); 
void i2c_start(void); 
char i2c_read(void); 
void i2c_stop(void); 
void i2c_nack(void); 
void i2c_ack(void); 
void i2c_ack_slave(void); 
void i2c_write(uint8_t byte); 

void i2c_init() 
{ 
    DDRB = 0b00000010; // TODO: should be removed once the weird delay issue is solved 
    DDRB |= (1 << PORT_SDA); 
    DDRB |= (1 << PORT_SCL); 
} 

void i2c_start(void) 
{ 
    SIGNAL_LOW( PORT_SCL); 
    SIGNAL_HIGH(PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW( PORT_SDA); 
    SIGNAL_LOW( PORT_SCL); 
} 

void i2c_stop(void) 
{ 
    SIGNAL_LOW( PORT_SCL); 
    SIGNAL_LOW( PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_HIGH(PORT_SDA); 
} 

void i2c_ack(void) 
{ 
    SIGNAL_LOW( PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW( PORT_SCL); 
    SIGNAL_HIGH(PORT_SDA); 
} 

void i2c_nack(void) 
{ 
    SIGNAL_HIGH(PORT_SDA); 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW( PORT_SCL); 
} 

void i2c_ack_slave(void) 
{ 
    SIGNAL_HIGH(PORT_SCL); 
    SIGNAL_LOW(PORT_SCL); 
} 

void i2c_write(uint8_t byte) 
{ 
    uint8_t bit; 

    for (bit = 0; bit < 0x08; bit++) 
    { 
     if((byte << bit) & 0x80) 
      SIGNAL_HIGH(PORT_SDA); 
     else 
      SIGNAL_LOW(PORT_SDA); 

     SIGNAL_HIGH(PORT_SCL); 
     SIGNAL_LOW(PORT_SCL); 
    } 

    // Clear both lines (needed?) 
    SIGNAL_LOW(PORT_SCL); 
    SIGNAL_LOW(PORT_SDA); 

    i2c_ack(); 
} 

char i2c_read(void) 
{ 
    uint8_t B = 0; 
    DDRB &= 0b11111110; // switch PB0 to input 

    for (int bit = 0; bit < 0x08; bit++) 
    {   
     delay(); // <-- the root of all evil 

     SIGNAL_HIGH(PORT_SCL); 

     B <<= 1; 

     if(PINB & (1 << PB0)) 
     { 
      B |= 1;    
     } 
     else 
     { 
      B |= 0;    
     } 

     SIGNAL_LOW(PORT_SCL); 
    } 

    DDRB |= 0b00000001; // switch PB0 as output 

    i2c_nack(); 

    return B; 
} 


void delay() 
{ 
    LED_ON(); 
    LED_OFF(); 
} 


void LED_ON(void) 
{ 
    PORTB |= 0b00000010; 
} 


void LED_OFF(void) 
{ 
    PORTB &= 0b11111101; 
} 
+0

您實質上是在SCL線的上升沿讀取SDA線;也許你的設備還沒有準備好呢?您是否嘗試過讀取SDA以接近下降沿呢?也就是說,'SIGNAL_HIGH(PORT_SCL); _delay_us(1); B =(B << 1)| !!(PINB&(1 << PB0)); SIGNAL_LOW(PORT_SCL); _delay_us(1);'(重複八次)? –

回答

1

的I2c限定用於信號的數目最小定時 - 重要這裏是SCL的高電平和低電平時間 - SCL到下一過渡之前應該是穩定的時間量相反的狀態是允許的。 這些時序典型值爲〜5μs,應從數據表中獲取準確數字。

循環在read循環的最後需要2到3條指令,具體取決於編譯器的功能。 AVR指令取決於你的時鐘頻率,大約需要200ns,所以(沒有延遲)SCL低至600ns左右,給出或取出 - 這太短了,至少顯然是針對你的特定「其他終端設備」。

當您在被調用的函數中插入了一個函數調用和一個端口訪問時,您插入了足夠長的指令以使SCL保持低電平足夠長時間才能正常工作。

在你的代碼中,HIGH時間並不是問題,因爲你讓AVR在SCL高時執行更多指令 - 顯然,足夠長的時間讓SCL保持高電平足夠長。

您在延遲功能中切換端口引腳的事實與此處無關 - 唯一相關的是您需要在SCL較低時花費一些時間。顯然,你現在做的是浪費端口引腳,只花一些時間等待 - 使用delay_us,試驗延遲而不是那個。但請檢查「另一端」的數據表以瞭解所需的確切時間,4-5μs應該沒問題。

爲什麼你的延遲循環不起作用?它很可能被編譯器優化了,它認識到你在那個空循環中沒有做相關的事情。

理想情況下,您應該在SCL的HIGH階段的中間嘗試讀取SDA - 使用8位的未滾動循環和一些delay_us傳播應該完美。