logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[atmega324p][C][I2C] I2C działa tylko raz potem się zawiesza

Sinistra 19 Wrz 2010 20:01 3282 6
REKLAMA
  • #1 8528709
    Sinistra
    Poziom 14  
    Witam,

    Tak więc mam hardware jak w temacie, i na dodatek podłączony eeprom at24c08 razem z rtc ds1388 na twi. Atmega ma kryształ 18.432MHz, rezystory na linii SDA i SCL to 10k każdy. Na dodatek, jest jeszcze procek stp16cp05, czyli kontroler LED z seryjnym podaniem danych (główna część pętli while()) i LEDy podłączone pod PORTD.7 i 4.

    Oto część kodu:

    
    #include <avr/io.h>
    #include <util/twi.h>
    
    #define RTC_ADDR_R_0 0b11010001 //czytanie zmiennych
    #define RTC_ADDR_W_0 0b11010000 //zmiana zmiennych
    
    #define set_clk PORTB   |= 0b10000000
    #define clr_clk PORTB  &= ~0b10000000
    #define set_data PORTB  |= 0b00100000
    #define clr_data PORTB &= ~0b00100000
    #define set_le PORTB    |= 0b00001000
    #define clr_le PORTB   &= ~0b00001000
    #define set_oe PORTB    |= 0b00010000
    #define clr_oe PORTB   &= ~0b00010000
    
    int main()
    {
    DDRA=0b11100000;
    PORTA=0b00000000;
    DDRB=0b10111000;
    PORTB=0b00000000;
    DDRC=0b00000000; //pc1 i pc0 to sda i scl
    DDRD=0b10010000;
    PORTD=0b10010000;
    
    TWBR = 4;
    TWSR = 0;
    
    
    int i;
    
    while(1)
    {
            PORTD=0;
    	if(!read_time()) {linia=0xf0;}
    	for(i=0;i<=7;++i)
    	{
    		clr_clk;
    		if(linia&(1<<i)) {set_data;} else {clr_data;}
    		set_clk;
    	}
    
    	set_le;
    	clr_le;
    	for(i=0;i<=1000;++i)
    	{}
    }
    }


    a to jest funkcja read_time():

    char read_time (void) //zapisywanie czasu z rtc do zmiennych zwraca 1 jesli OK, 0 jesli blad
    {
    TWCR = 	(1<<TWSTA) | (1<<TWEN) | (1<<TWINT);
    while		(!(TWCR & (1<<TWINT))) 
    			{} //Tutaj się zawiesza ********************
    if 		((TWSR & 0xF8) != TW_START) 
    			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    		
    TWDR = 	RTC_ADDR_W_0;
    TWCR =	(1<<TWEN) | (1<<TWINT);
    while		(!(TWCR & (1<<TWINT))) 
    			{}
    if 		((TWSR & 0xF8) != TW_MT_SLA_ACK)
    			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    
    TWDR =	0x01;
    TWCR =	(1<<TWEN) | (1<<TWINT);
    while		(!(TWCR & (1<<TWINT))) 
    			{}
    if 		((TWSR & 0xF8) != TW_MT_DATA_ACK)
    			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}			
    
    			
    TWCR =	(1<<TWSTA) | (1<<TWEN) | (1<<TWINT);
    while		(!(TWCR & (1<<TWINT))) 
    			{}
    if 		((TWSR & 0xF8) != TW_REP_START)
    			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    
    TWDR =	RTC_ADDR_R_0;
    TWCR =	(1<<TWEN) | (1<<TWINT);
    while		(!(TWCR & (1<<TWINT))) 
    			{}
    if 		((TWSR & 0xF8) != TW_MR_SLA_ACK)
    			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    			
    //tutaj jest jeszcze odebrany adres slave, a chyba powinien juz byc bajt z adresu 1, nie? nie wiem dlaczego tak jest...
    TWCR =	(1<<TWEA) | (1<<TWEN) | (1<<TWINT);
    while		(!(TWCR & (1<<TWINT))) 
    			{}
    if 		((TWSR & 0xF8) != TW_MR_DATA_ACK)
    			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}	
    
    linia =	TWDR;			
    TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT);
    return 1;
    }


    linia to unsigned char.

    Problemy są dwa:
    Jeden, w funkcji read_time(), podczas odbioru pierwszego bajtu ze slave'a, zwraca adres który wcześniej zapisałem do TWDR. Adres sprawdziłem i jest dobry. Dlaczego tak jest?

    Dwa, gdy procek dochodzi do pętli while(), wykonuje ją całą, (wiem to, ponieważ diody zaświecają się z jakimiś danymi, które nie są 0xf0). I dalej nic nie idzie, ani SCL się nie zmienia, ani dane do driverów led się nie zmieniają, wszystko stoi.

    Gdy ręcznie resetnę atmegę, to na diody wchodzą kolejne dane, przesuwając stare na bok, i wtedy znowu się zatrzymuje. Te nowe dane są już inne niż stare, ponieważ adres 0x01 w rtc to sekundy, które się zmieniły.

    Tak więc zmieniłem read_time(), aby przeczytać dane z eepromu (który miał już kiedyś coś w sobie zapisanego). Ta sama sytuacja, czyta jeden bajt, potem się zatrzymuje i nic.

    Jako że ja piszę w notepad++, kompiluje w avrgcc, i programuje w avrdude, to nie mam szansy na debugowanie. Tak więc w pewnych miejscach powklejałem
    kod

    
    PORTD=0; PORTD=0b10010000;
    

    który miga diodami.

    Wynik był zaskakujący, otóż wyszło na to, że program się "zacina" w pętli while, w zaznaczonym miejscu w kodzie, w funkcji read_time().

    Pytanie jest, dlaczego akurat przy drugim podejściu wysyłania START, a nie przy pierwszym czy jakimś innym? I dlaczego w ogóle się zacina...?

    Proszę pomóżcie, nic mi nie przychodzi do głowy...

    PS. Z I2C mam trochę doświadczenia, bo już się bawiłem na pcf8563, ale nigdy nie miałem takiego problemu.
  • REKLAMA
  • Pomocny post
    #2 8529131
    Andrzej__S
    Poziom 28  
    Sinistra napisał:

    
    TWDR =   RTC_ADDR_R_0; 
    TWCR =   (1<<TWEN) | (1<<TWINT); 
    while      (!(TWCR & (1<<TWINT))) 
             {} 
    if       ((TWSR & 0xF8) != TW_MR_SLA_ACK) 
             {TWCR =   (1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;} 
    
    TWCR =   (1<<TWEA) | (1<<TWEN) | (1<<TWINT); // !!! TWEA = 0 (NOT ACKNOWLEDGE)
    while      (!(TWCR & (1<<TWINT))) 
             {} 
    if       ((TWSR & 0xF8) != TW_MR_DATA_ACK) 
             {TWCR =   (1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}    
    
    linia =   TWDR;          
    TWCR =   (1<<TWSTO) | (1<<TWEN) | (1<<TWINT);
    


    Wygląda na to, że odbierasz tylko jeden bajt danych, więc nie powinieneś chyba ustawiać bitu TWEA, bo slave będzie myślał, że powinien nadal nadawać dane. Z tego mogą wyniknąć problemy.
  • REKLAMA
  • #3 8529409
    Sinistra
    Poziom 14  
    no właśnie, w tym problem, że jak tam nie dam ACK, to odbiorę tylko ten "pierwszy bajt" (którego odczytania z TWDR nawet tam nie dałem), którym jest z jakiegoś powodu adres slave'a.

    A potem, czyli gdy chcę odebrać "drugi bajt", czyli adres 0x01 nie dostałbym go.

    "Drugi bajt" bo tak naprawdę, to master chyba po prostu nie wyczyścił rejestru TWDR z adresem slave'a, ale jak na mój gust, to po tym czasie, tam powinien być już bajt z 0x01. Ale nie ma.


    Tak czy inaczej spróbuję...

    Dodano po 21 [minuty]:

    Ok, tak więc spróbowałem,
    i.... zadziałało.


    Ale nie rozumiem dlaczego.

    moje rozumowanie jest takie (takie działało z pcf8563):

    master wysyła START, czeka na wysłanie start,
    potem wysyła adres slave'a+read bit, czeka na ACK,
    Tutaj jest dziwnie, bo po sprawdzeniu statusu, w TWDR nadal jest adres slave'a. I w tym miejscu (niepoprawnie dawałem ACK) ignorowałem wartość TWDR, wysyłałem ack itd, potem dopiero odbierałem wartość bajtu z TWDR, wysłałem NACK i STOP.

    Moje pytanie: dlaczego po wysłaniu slave address, sprawdzeniu statusu, że został wysłany, po otrzymaniu ACK na tren adresze slave'a, ten adres jest wciąż w TWDR???
  • REKLAMA
  • #4 8529603
    Andrzej__S
    Poziom 28  
    Sinistra napisał:

    Moje pytanie: dlaczego po wysłaniu slave address, sprawdzeniu statusu, że został wysłany, po otrzymaniu ACK na tren adresze slave'a, ten adres jest wciąż w TWDR???


    ATmega324P datasheet napisał:

    TWDR always contains the last byte present on the bus, except after a wake up from
    a sleep mode by the TWI interrupt. In this case, the contents of TWDR is undefined.
  • #5 8529705
    Sinistra
    Poziom 14  
    Ok, to wiem, ale do czego jest potrzebna ta zaznaczona sekcja?:

    TWDR =	RTC_ADDR_R_0;
    TWCR =	(1<<TWEN) | (1<<TWINT);
    while		(!(TWCR & (1<<TWINT))) 
    			{}
    if 		((TWSR & 0xF8) != TW_MR_SLA_ACK)
    			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    
    /**//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /**/TWCR =	(1<<TWEN) | (1<<TWINT);									
    /**/while		(!(TWCR & (1<<TWINT))) 								
    /**/			{}											
    /**/if 		((TWSR & 0xF8) != TW_MR_DATA_NACK)						
    /**/			{TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}		
    /**//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    
    linia =	TWDR;			
    TWCR =	(1<<TWSTO) | (1<<TWEN) | (1<<TWINT);
    return 1;


    Wysłałem adres, dostałem ack, to teraz od razu powinien być bajt z pamięci, nie?
    a zamiast tego muszę NACK'ować i sprawdzać status jeszcze raz.
  • REKLAMA
  • Pomocny post
    #6 8530241
    Andrzej__S
    Poziom 28  
    napisał:

    Wysłałem adres, dostałem ack, to teraz od razu powinien być bajt z pamięci, nie?

    A w jaki sposób slave ma Tobie przysłać odpowiedź w tym samym czasie, gdy Ty wysyłasz do niego zapytanie?

    Odbywa się to tak:
    
    ...
    // wysyłasz adres układu z bitem kierunku ustawionym na READ
       TWDR =   RTC_ADDR_R_0;
    
    // włączasz magistralę
       TWCR =   (1<<TWEN) | (1<<TWINT);
    
    // oczekujesz na wysłanie zawartości TWDR na magistralę
       while (!(TWCR & (1<<TWINT)));
    
    // sprawdzasz poprawność transmisji
    // slave musi potwierdzić gotowość bitem ACK
       if ((TWSR & 0xF8) != TW_MR_SLA_ACK)
          {TWCR =   (1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    
    // ********************************************************
    // włączasz magistralę ustawiając bit TWEA
       TWCR =   (1<<TWEA) | (1<<TWEN) | (1<<TWINT);
    
    // oczekujesz na odebranie bajtu danych z układu slave
    // po odebraniu Twój mikrokontroler wyśle na magistralę ACK
    // (ponieważ TWEA = 1) informując układ slave,
    // że będzie musiał nadal wysyłać dane
       while      (!(TWCR & (1<<TWINT)));
    
    // sprawdzasz poprawność transmisji
       if ((TWSR & 0xF8) != TW_MR_DATA_ACK)
          {TWCR =   (1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    
    // odczytujesz odebrany bajt
       jakis_bufor[0] =   TWDR;
    
    // ********************************************************
    
    // powtarzasz zaznaczony fragment jeden raz mniej,
    // niż ilość odbieranych bajtów
    
    // ostatni bajt odbierasz tak:
    
    // włączasz magistralę - TWEA = 0
       TWCR =   (1<<TWEN) | (1<<TWINT);
    
    // oczekujesz na odebranie bajtu danych z układu slave
    // po odebraniu Twój mikrokontroler wyśle na magistralę NACK
    // (ponieważ TWEA = 0) informując układ slave,
    // że to jest koniec transmisji
       while (!(TWCR & (1<<TWINT)));
    
    // sprawdzasz poprawność transmisji
       if ((TWSR & 0xF8) != TW_MR_DATA_NACK)
          {TWCR =   (1<<TWSTO) | (1<<TWEN) | (1<<TWINT); return 0;}
    
    // odczytujesz odebrany bajt
       jakis_bufor[i] =   TWDR;
    
    // generujesz sygnał STOP na magistrali
       TWCR =   (1<<TWSTO) | (1<<TWEN) | (1<<TWINT);
    
    // koniec transmisji
    ...
    

    W ten sposób można odebrać dowolną ilość danych, zależną oczywiście od układu slave, jaki obsługujesz. Oczywiście to tylko kod poglądowy. Do odbioru większej ilości danych najlepiej użyć pętli for().

    Poza tym magistrala I2C jest stosunkowo wolna, więc najlepiej obsługiwać ją przy pomocy przerwań (chyba że nie musimy oszczędzać czasu procesora).
  • #7 8531655
    Sinistra
    Poziom 14  
    Ahaaa...., czyli tak to wygląda,

    Teraz rozumiem,
    dlatego mi to w pcf'ie działało, bo zawsze przynajmniej 3 bajty odbierałem.



    Dzięki wielkie jestem bardzo wdzięczny.
REKLAMA