I2C/TWI - poddaję się...

Czołem!

Wracam do tematu, który tu poruszałem na początku kwietnia, a mianowicie komunikacji pomiędzy AVRem (Mega 8, docelowo Mega 324) a wyświetlaczem OLED z kontrolerem SSD1308. O ile komunikacja po I2C zrealizowanym programowo działa prawidłowo, choć z niewiadomych przyczyn po skompilowaniu programu nowszą wersją GCC prędkość transmisji spada kilkunastokrotnie (częstotliwość zegara na SCL obniża się z 80kHz do zaledwie 5kHz), o tyle z komunikacją za pośrednictwem sprzętowego I2C jest jakiś grubszy problem. Kod wysyłający dane na sprzętowe I2C sam w sobie jest dobry. Prosty program, wysyłający dane do układu PCF8574 (ekspander portu I2C) działa aż miło, w przebiegach praktycznie nie widać różnicy czy to programowe czy sprzętowe I2C. Podmiana procedur obsługi I2C w docelowym kodzie z obsługą OLED kończy się porażką. Sam program jako całość żyje, bo diodka podpięta pod PB3 i taktowana przerwaniem timera mruga prawidłowo. Jeśli wyświetlacz jest już podłączony w momencie załączania zasilania, to efekt jest taki, że coś idzie nie tak w programie, bo nie pojawia się stan wysoki na PB0, mający za zadanie potrzymywać pracę stabilizatora zasilającego cały układ. Muszę więc cały czas trzymać wciśnięty przycisk "power on", podczas gdy normalnie wystarcza naciśnięcie go na chwilę a potem jego rolę przejmuje właśnie PB0. Jeśli natomiast spróbuję podłączyć moduł z wyświetlaczem do pracującego już procesora, to z tego co pokazuje oscyloskop, momentalnie na pysk leci sygnał na SCL.

Jakieś pomysły co z tym fantem zrobić? :(

Przebieg I2C sprzętowego dla PCF8574:

formatting link
Przebieg dla I2C programowego dla PCF8574:
formatting link
Przebieg dla I2C programowego dla OLED:
formatting link
Przebieg dla I2C sprzętowego dla OLED ale bez podłączonego wyświetlacza:
formatting link
Najśmieszniejsze w tym wszystkim jest to, że to właśnie SSD1308 oficjalnie obsługuje I2C, a Atmel tę magistralę nazywa (ze względów licencyjnych) TWI, więc prędzej możnaby się spodziewać odstępstw od wzorcowego I2C ze strony procka niż kontrolera w wyświetlaczu...

Funkcje obsługujące I2C sprzętowe: //TEST I2C SPRZĘTOWEGO

void twiinit(void) { //set SCL to 400kHz TWSR = 0x00; TWBR = 0x0C; //enable TWI TWCR = (1<<TWEN); }

void twistart(void) { TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN); while ((TWCR & (1<<TWINT)) == 0); }

void twistop(void) { TWCR = (1<<TWINT)|(1<<TWSTO)|(1<<TWEN); }

void twiwrite(uint8_t u8data) { TWDR = u8data; TWCR = (1<<TWINT)|(1<<TWEN); while ((TWCR & (1<<TWINT)) == 0); }

Funkcje obsługi I2C programowego:

// Wyliczenie czasu opóźnienia połówkowego i ćwiartkowego (cykle) #define I2C_nhalf (F_CPU/I2C_SPEED/2)

// Funkcja dłuższych opóźnień #if I2C_nhalf < 3 // Nic #elif I2C_nhalf < 8 static void i2c_xdelay(void) { NOP(); } #else #define I2C_delayloops (1+(I2C_nhalf-8)/3) #if I2C_delayloops > 255 #error Przyspiesz - bo sie nie wyrabiam ;) #endif static void i2c_xdelay(void) { asm volatile( \ "delayus8_loop%=: \n\t"\ "dec %[ticks] \n\t"\ "brne delayus8_loop%= \n\t"\ : :[ticks]"r"(I2C_delayloops) ); } #endif //I2C_nhalf >= 3

// Opóźnienia dla I2C static inline void i2c_hdelay(void) { #if I2C_nhalf < 1 return; // To jest funkcja inline, jeśli składa się tylko z "return" jest usuwana podczas optymalizacji #elif I2C_nhalf < 2 NOP(); #elif I2C_nhalf < 3 asm volatile( "rjmp exit%=\n\t" "exit%=:\n\t"::); #else i2c_xdelay(); #endif }

// Ustawienie i zerowanie wyjścia static inline void i2c_sdaset(void) { DDR(I2C_PORT) &= ~(1<<I2C_SDA); PORT(I2C_PORT) |= 1<<I2C_SDA; } static inline void i2c_sdaclear(void) { PORT(I2C_PORT) &= ~(1<<I2C_SDA); DDR(I2C_PORT) |= 1<<I2C_SDA; } // Pobieranie danej z wyprowadzenia portu // Zwraca: bajt będący odpowiednikiem fizycznego stanu portu, z wyzerowanaymi wszystkimi bitami poza sda static inline uint8_t i2c_sdaget(void) { return PIN(I2C_PORT) & (1<<I2C_SDA); }

// Zerowanie i ustawianie zegara static inline void i2c_sclset(void) { PORT(I2C_PORT) |= 1<<I2C_SCL; } static inline void i2c_sclclear(void) { PORT(I2C_PORT) &= ~(1<<I2C_SCL); }

// Warunek startu void i2c_start(void) { // Konieczne jeśli chcę użyć start bez stop i2c_sdaset(); i2c_hdelay(); i2c_sclset(); i2c_hdelay(); // Normalna sekwencja startu i2c_sdaclear(); i2c_hdelay(); i2c_sclclear(); //TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN); //while (!(TWCR & (1<<TWINT)));

}

// Warunek stop void i2c_stop(void) { i2c_sdaclear(); i2c_hdelay(); i2c_sclset(); i2c_hdelay(); i2c_sdaset(); i2c_hdelay(); //TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO); //while ((TWCR & (1<<TWSTO)));

}

// Transmisja bajtu // Zwraca: 0 jeśli było ack, wartość 1<<I2C_SDA jeśli nie było ACK uint8_t i2c_send(uint8_t data) { uint8_t n; for(n=8; n>0; --n) { if(data & 0x80) i2c_sdaset(); else i2c_sdaclear(); data <<= 1; i2c_hdelay(); i2c_sclset(); i2c_hdelay(); i2c_sclclear(); } // ack i2c_sdaset(); i2c_hdelay(); i2c_sclset(); i2c_hdelay(); n = i2c_sdaget(); i2c_sclclear(); return n; }

// Pobranie bajtu uint8_t i2c_get(uint8_t ack) { uint8_t n, temp=0; i2c_sdaset(); for(n=8; n>0; --n) { i2c_hdelay(); i2c_sclset(); i2c_hdelay(); temp<<=1; if(i2c_sdaget()) temp++; i2c_sclclear(); } // ack if(ack == I2C_ACK) i2c_sdaclear(); else i2c_sdaset(); i2c_hdelay(); i2c_sclset(); i2c_hdelay(); i2c_sclclear(); return temp; }

Reply to
badworm
Loading thread data ...

W dniu 2016-07-03 o 21:46, badworm pisze:

Nie wiem czy zauważyłeś ale sprzętowo masz 200khz a programowo 83khz, sam tek ci to na dole pokazuje zmniejsz prędkość sprzętowego twi i powinno być dobrze.

Reply to
janusz_k

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.