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

[ATMEGA16][GCC + AVR Studio] 1Wire, DS18B20, komunikacja

gdL 03 Sie 2010 18:25 6028 14
  • #1 8361333
    gdL
    Poziom 27  
    I2C mam za sobą i próbowałem dziś napisać sterownik 1-Wire. Na płytce ewaluacyjnej mam MEGĘ16 (16Mhz), DS18B20, który na pewno działa. Problemem u mnie jest wątpliwa inicjalizacja i brak komunikacji. Co może być źle ?


    Napisałem "biblioteczkę", są komentarze, powinno się wygodnie czytać.

    
    #include <avr/io.h>
    #include <util/delay.h>
    
    ////////////////////////////////
    // makrodefinicje///////////////
    #define N1Wire 0
    #define PORT1WireIn PINA
    #define PORT1WireOut PORTA
    #define SET1WireOut DDRA|=(1<<N1Wire)       // DDRX jako wyjscie
    #define SET1WireIn DDRA&=~(1<<N1Wire) 	    // DDRX jako wejscie
    ////////////////////////////////
    unsigned char reset1Wire(void) {
    unsigned char counter=10;
    ////
    SET1WireOut;							// DDRX jako wyjscie
    PORT1WireOut&=~(1<<N1Wire); 			// w stanie niskim
    _delay_us(500); 						// przynajmniej na 480us, nastepnie
    SET1WireIn;								// DDRX jako wejscie
    PORT1WireOut|=(1<<N1Wire); 				// w stanie wysokim
    _delay_us(50);							// relaksacja pinow
    	while((PORT1WireIn&(1<<N1Wire)) && (counter>0)) {	// sprawdzam, czy pomimo pullup'a  magistrala 0
    	counter--; 							// i licznik >0
    	_delay_us(30);						
    	}
    	_delay_us(500);
    	if (counter<=0) {					// nie zgloszenie sie w czasie jest rownoznaczne
    	return 0;							// z brakiem aktywnego czujnika na magistrali
    	} else { 
    	return 1;
    	}
    }
    ////////////////////////////////
    void sendByte1Wire(unsigned char data) {
    
    	for(int i=0;i<8;i++) {				// dla kolejnych bitow
    	SET1WireIn;							//
    	_delay_us(1);						// przerwa >= 1us
    	SET1WireOut;						// ustaw port jako wyjscie
    	PORT1WireOut&=~(1<<N1Wire); 		// w stanie niskim
    	_delay_us(5);						// poczekaj
    
    		if (data&(1<<i)) {				// WYSYLAM logiczne 1
    		SET1WireIn;						// zwalniam magistrale
    		PORT1WireOut|=(1<<N1Wire); 		// w stanie wysokim
    		_delay_us(50);
    		} else {						// WYSYLAM logiczne 0
    		_delay_us(50);					// >60us
    		SET1WireIn;						// zwalniam magistrale
    		PORT1WireOut|=(1<<N1Wire); 		// w stanie wysokim
    		}
    	}
    }
    ////////////////////////////////
    unsigned char readByte1Wire() {
    unsigned char bitTmp,data=0;
    unsigned char counter=15;
    	for(int i=0;i<8;i++) {				// dla kolejnych bitow
    	bitTmp=1;							// zalozenie, ze bit tymczasowy wynosi 1 (x)
    	////
    	SET1WireIn;
    	_delay_us(1);	
    	SET1WireOut;						// port 1 Wire jako wyjscie
    	PORT1WireOut&=~(1<<N1Wire); 		// w stanie niskim
    	_delay_us(1);
    	SET1WireIn;							// jako wejscie... 
    	PORT1WireOut|=(1<<N1Wire); 	// w stanie wysokim
    	_delay_us(10);
    	//////////////////////////////////////
        while((PORT1WireIn&(1<<N1Wire)) && (counter>0)) {
    	_delay_us(1);					// poczekaj ->
    	counter--;
    	}
    	if (counter<=0) {
    	bitTmp = 1;
    	} else {
    	bitTmp = 0;	
    	}
    	data|=(bitTmp<<i); // ustawienie odebranego bitu
    	//*/
    	//data|=((PORT1WireIn&(1<<N1Wire))<<i); // ustawienie odebranego bitu
    	_delay_us(60);	   // czekanie na koniec okresu odczytywania
    	}
    return data;
    }
    ////////////////////////////////
    


    Biblioteczki używam :

    
    unsigned char returned, returned1[8];
    /////
    returned=reset1Wire(); //-> dostaje sygnał presence
    sendByte1Wire(0x33);   // proszę o zameldowanie swojego numeru ROM
    
    for(int x=0;x<8;x++) {
    returned1[x]=readByte1Wire(); // -> zapisuje kolejno
    }


    Wyniki readByte1Wire(); są zupełnie bzdurne. W większości 255'tki.
  • Pomocny post
    #2 8361854
    gaskoin
    Poziom 38  
    ustawianie stanów logicznych na pinie odbywa się w przypadku 1-wire nieco inaczej niż pokazałeś. Ty ustawiasz kierunek i wymuszasz stan, a to troche nie tak.

    Ustalać powinieneś jedynie kierunek transmisji. Domyślnie w rejestrze PORTx są same zera, więc nie musisz się nim w ogóle przejmować. Dla np resetu powinno być tak: ustawiasz port jako wyjście, wówczas atmega wymusza stan niski na linii. Następnie zwalniasz port (ustawiasz go na wejście) i wymuszenie wysokiego stanu nie powinno być wynikiem Twojej działalności, tylko rezystora podciągającego jaki zapewne na Twojej płytce też jest (lub powinien być). Po czasie stan wysoki jest przez dsa zmieniany na niski - ten wykryty stan niski to impuls z urządzenia niosący informację - HALO JESTEM TUTAJ :D

    Nie przyglądałem się zbytnio kodowi, ale to mi się rzuciło na pierwsze spojrzenie. Spróbuj wywalić linijki ustawiające porty, jeżeli wszystko inne jest ok to powinno zadziałać
  • Pomocny post
    #3 8366100
    flapo213
    Poziom 21  
    Witaj,

    Jeżeli otrzymujesz odpowiedź z układu typu 255 czyli 0xFF to oznacza że masz błędnie napisane procedury niskopoziomowe prawdopodobnie czasówki. Na początek sprawdź czy przypadkiem nie masz ustawionego zegara zbyt niskiego 1[MHz] wewnętrznego. Aby funkcje opóźniające biblioteczne chodziły prawidłowo potrzebne jest z 4[MHz] a dobrze jest 8 [MHz].

    Zrób symulację w Avrstudio softwarową tylko wyłącz wszystkie przerwania oraz dobrze ustaw kwarc w tym programie. Będziesz widział co i jak.

    pozdrawiam
  • #4 8366522
    gdL
    Poziom 27  
    Udało mi się doprowadzić 'biblioteczkę' do działania. Nie sprawdzałem co prawda innych komend poza 0x33, jednak otrzymuję dane rozpoczynające się
    0x28....0x01,0x00,0x00
    , które wydają się być poprawne.

    Działająca biblioteka wygląda tak :

    #include <avr/io.h>
    #include <util/delay.h>
    
    //////////////////////////////////////////
    // makrodefinicje/////////////////////////
    #define N1Wire 0
    #define PORT1WireIn PINA
    #define PORT1WireOut PORTA
    #define SET1WireOut DDRA|=(1<<N1Wire)   // DDRX jako wyjscie
    #define SET1WireIn DDRA&=~(1<<N1Wire) 	// DDRX jako wejscie
    //RESET 1 Wire////////////////////////////
    unsigned char reset1Wire(void) {
    unsigned char counter=12,tmp;
    ////
    SET1WireOut;							// DDRX jako wyjscie
    PORT1WireOut&=~(1<<N1Wire); 			// w stanie niskim
    _delay_us(500); 						// przynajmniej na 480us, nastepnie
    SET1WireIn;								// DDRX jako wejscie (ext pullup)
    //PORT1WireOut|=(1<<N1Wire);				// wewnetrzny pullup 20KOhm wlaczony (zeby przeladowac pin)
    _delay_us(60);							// czekaj 15 - 60 us
    	while(((PORT1WireIn>>N1Wire)&1) && (counter!=0)) {	// sprawdzam, czy pomimo pullup'a  magistrala 0
    	counter--; 							// i licznik >0
    	_delay_us(20);						// masz 240 us					
    	}
    tmp = (PORT1WireIn>>N1Wire)&1;
    _delay_us(500);
    return !tmp;
    }//Wysylanie Bajtu "data"//////////////////
    void sendByte1Wire(unsigned char data) {
    	for(int i=0;i<8;i++) {				// dla kolejnych bitow
    	SET1WireIn;							// ustaw jako wejscie (ext pullup)
    	_delay_us(2);						// przerwa >= 1us
    	SET1WireOut;						// ustaw port jako wyjscie
    	PORT1WireOut&=~(1<<N1Wire); 		// w stanie niskim
    	_delay_us(10);						// poczekaj <15us
    		if (data&(1<<i)) {				// WYSYLAM logiczne 1
    		SET1WireIn;						// zwalniam magistrale (ext pullup)
    		_delay_us(50);					// odczekaj do konca okna transmisji
    		} else {						// WYSYLAM logiczne 0
    		_delay_us(50);					// odczekaj do konca okna transmisji
    		SET1WireIn;						// zwalniam magistrale (ext pullup)
    		}
    	}
    }
    //Odczytanie bajtu "data"/////////////////
    unsigned char readByte1Wire() {
    unsigned char counter,data=0;
    	for(int i=0;i<8;i++) {				// dla kolejnych bitow
    	counter=10;							// potrzebne do osiagniecia max czasu czekania
    	SET1WireIn;
    	_delay_us(2);	
    	SET1WireOut;						// port 1 Wire jako wyjscie
    	PORT1WireOut&=~(1<<N1Wire); 		// w stanie niskim
    	_delay_us(2);
    	SET1WireIn;							// jako wejscie... 
    	_delay_us(5);						// czas na pullup
        	while(((PORT1WireIn>>N1Wire)&1) && (counter!=0)) {
    		_delay_us(1);					// czas na pullup
    		counter--;						// pamietajmy, ze "0" jest stanem aktywnym
    		}
    	data|=(((PORT1WireIn>>N1Wire)&1)<<i);	// ustawienie odebranego bitu
    	_delay_us(50);	      				// czekanie na koniec okresu odczytywania 
    	}
    return data;
    }
    //////////////////////////////////////////
    


    Zapraszam do korzystania i testowania, jeśli komuś jeszcze coś nie działa.
  • #5 8366536
    flapo213
    Poziom 21  
    Rozdziel sobie odczyt bajtu z one wire na 1wire_read_bit i 1wire_read_byte na przykład jak poniżej

    unsigned char OW_Read_Bit(void)
    {
    	unsigned char res = 0;
    
    	OWOUT;OWL; // Drives DQ Low
    
    	OWIN; // Release the bus
    
    	// Shall be necessary delay ~15[us] according to specification
    	uDelay(2);
    
    	res = OWR;
    
    	if(res)
    		res = 1;
    
    	// Must be about 100[us] wait for dallas rest time
    	uDelay(20);
    
    	return res;
    
    }
    
    
    
    
    
    
    
    unsigned char OW_Read_Byte(void)
    {
    	unsigned char res = 0;
    	unsigned char i = 0;
    
    	cli();
    
    	for(i=0; i<8; i++)
    	{
    
    		res >>= 1;
    
    		if(OW_Read_Bit())
    			res |= 0x80;
    
    	}
    	sei();
    	return res;
    }
  • #6 8367027
    gdL
    Poziom 27  
    OK, powiedzice mi tylko jedno. Mój DS18b20 jest podpięty pullupem 10KOhm, po włączeniu tej funkcji :

    //RESET 1 Wire////////////////////////////
    unsigned char reset1Wire(void) {
    unsigned char counter=12,tmp;
    ////
    SET1WireOut;							// DDRX jako wyjscie
    PORT1WireOut&=~(1<<N1Wire); 			// w stanie niskim
    _delay_us(500); 						// przynajmniej na 480us, nastepnie
    SET1WireIn;								// DDRX jako wejscie (ext pullup)
    //PORT1WireOut|=(1<<N1Wire);				// wewnetrzny pullup 20KOhm wlaczony (zeby przeladowac pin)
    _delay_us(60);							// czekaj 15 - 60 us
    	while(((PORT1WireIn>>N1Wire)&1) && (counter!=0)) {	// sprawdzam, czy pomimo pullup'a  magistrala 0
    	counter--; 							// i licznik >0
    	_delay_us(20);						// masz 240 us					
    	}
    tmp = (PORT1WireIn>>N1Wire)&1;
    _delay_us(500);
    return !tmp;
    }


    i usunięciu z niej wewnętrznego pullupa "PORT1WireOut|=(1<<N1Wire);", po 60us port jest ciągle uważany za ustawiony w logicznym zerze. Wynikiem funkcji jest więc zawsze 1, obojętnie, czy DS jest podłączony czy nie.
    Z "PORT1WireOut|=(1<<N1Wire);" działa dobrze.

    Gdzieś się mylę ?
  • #7 8367717
    flapo213
    Poziom 21  
    Witaj,

    moja procedura resetu wygląda tak

    unsigned char Reset_One_Wire(void)
    {
    	volatile unsigned int delay = 0;
    	unsigned char slave_present = 0;
    
    	unsigned char sp_next = 0;
    
    
    	OWOUT;OWL; // Drives DQ Low
    
    	// Wait 480us
    	delay = 0xEA;
    
    	while(delay--);
    
    	// Set pin DO to INPUT
    	OWIN;
    
    	// Wait 70us + 240us waiting for presence pulse
    	delay = 0x7A;
    
    	while(delay--)
    	{
    		if((OWR) != OWMASK)
    			slave_present = 1;
    	}
    
    	if(slave_present == 1)
    	{
    		// Wait 120 us
    		delay = 0x16;
    
    		// Check shortcut to GND
    		while(delay--)
    		{
    			if((OWR) == OWMASK)
    				sp_next = 1;
    			else
    				sp_next = 0;
    		}
    	}
    
    
    	return sp_next;
    
    }



    gdzie maskami są

    
    
    // set pin mask
    #define OWMASK     0x04
    // set 1-wire pin for output
    #define OWOUT      DDRC |= OWMASK
    // set 1-wire pin for input (bus released)
    #define OWIN       DDRC &= ~OWMASK
    // set 1-wire bus high
    #define OWH        PORTC |= OWMASK
    // set 1-wire bus low
    #define OWL        PORTC &= ~OWMASK
    // get 1-wire bus input (bus released)
    #define OWR        PINC & OWMASK
    
    


    Ja to starałem się zrobić zgodnie z tym co jest napisane w dokumentacji one-wire.

    Stąd też w mojej procedurze resetu sprawdzam stan układu po wygenerowaniu obecności czy przypadkiem nie jest znowu stanem niskim co wskazywałoby na błędną interpretację podłączonego urządzenia. Zrób sobie test z tzw. śrubokrętem tzn. odłącz DS-a i zwieraj na krótko do masy kilka raz linię DQ, zobaczysz wtedy że za którymś razem wygenerujesz presence pulse. Naturalnie w mojej procedurze też istnieje taka możliwość ale dużo rzadziej i ciężej ją złapać.

    Pozdrawiam
  • #8 8368768
    tmf
    VIP Zasłużony dla elektroda
    flapo213 -twoje procedury są do niczego, z kilku powodów. Po pierwsze opóźnienia nie są zależne od zegara, czyli trzeba mieć wyjątkowe szczęście, żeby procesor akurat był tak samo taktowany. Druga wada jest poważniejsza - coś takiego:
       delay = 0xEA;
    
       while(delay--); 


    przy sensownych opcjach optymalizacji powinno być przez kompilator usunięte. Ty się ratujesz stosując atrybut volatile dla tej zmiennej. Ale to na każdej wersji gcc generuje inne opóźnienie. Z tego powodu ktoś kto używa np. noweszej wersji gcc może się zdziwić po skompilowaniu twojego kodu.
    BTW, masz jakiś rozsądny powód dla którego nie używasz funkcji z delay.h?
  • #9 8368843
    gdL
    Poziom 27  
    Niby bardzo fajne połączony zapis i odczyt bitu, bo rzeczywiście początek każdego "time slotu" i czas trwania jest z grubsza taki sam. Tylko, czy taki Twój kod ? Ja to chyba już gdzieś widziałem.

    To jednak nie odpowiada na moje pytanie dotyczące wewnętrznego pullup'a.
  • #10 8369050
    flapo213
    Poziom 21  
    Witam,

    do użytkownika TMF, u mnie taka procedura typu (wartość jest przystosowana do moich potrzeb)

    volatile unsigned int delay = 0xFE;

    while(delay--);

    działa poprawnie zgodnie z zamierzeniami

    myślę że gdybym nie dał volatile to może to by działało jak ty myślisz.

    Definicja volatile:

    Cytat:
    Indicates that a variable can be changed by a background routine.

    Keyword volatile is an extreme opposite of const. It indicates that a variable may be changed in a way which is absolutely unpredictable by analysing the normal program flow (for example, a variable which may be changed by an interrupt handler). This keyword uses the following syntax:
    volatile data-definition;

    Every reference to the variable will reload the contents from memory rather than take advantage of situations where a copy can be in a register.



    a pozatym nic nie stoi na przeszkodzie zrobić tak

    while(delay--)
    {
    asm volatile "nop";
    }


    Zaproponuj swoje rozwiązanie chętnie oglądnę.

    PS.

    Ja nie sugeruję użytkownikom wklejania na ślepo co zamieszczam tylko pokazuję sposób realizacji jeden z wielu.

    A co idzie do stosowania delay.h to chciałem zaznaczyć co wielu początkujących gubi jak wygenerować 1us przy zegarze 1MHz ?

    Pozdrawiam
  • #11 8369500
    tmf
    VIP Zasłużony dla elektroda
    Rozwiązanie jest proste - stosować _delay_us z <util\delay.h>. Przy 1MHz co prawda można minimalnie mieć 3us opóźnienie, ale w tym przypadku jest to bez znaczenia. A BTW, dziwię się po co tak komplikować prostą sprawę jaką jest obsługa 1-w, skoro nawet w notach aplikacyjnych Atmela pokazane jest jak to zrobić na USART, bez jakichkolwiek opóźnień, praktycznie całkowicie sprzętowo.
    Co do volatile - twoje opóźnienie nic nie gwarantuje. Jeśli kompilator wpakuje twoją zmienną do pamięci to za każdym razem będzie ją musiał odczytać i zapisać, jeśli stwierdzi, że ma wolny rejestr i tam ją umieści to pętla się skróci o parę taktów. Tak więc wynik może się zmieniać nawet przy tej samej wersji kompilatora. Zresztą sensowny optymalizator i tak zignoruje w tym wypadku volatile, bo definiujesz zmienną lokalną, która w dodatku nie jest wskaźnikiem, nie ma więc możliwości, żeby ulegała zmianie poza normalnym ciągiem wykonywania instrukcji. W przerwaniach się nie zmieni, bo jest poza zasięgiem, nie jest też wskaźnikiem na jakikolwiek zasób sprzętowy, więc tu też się zmienić nie może. Jeśli kompilator to ciągle traktuje jako volatile to należałoby zgłosić misoptimization bug do developerów.

    Dodano po 2 [minuty]:

    Poza tym twoje makro OWR już w definicji umieściłbym w nawiasach, inaczej jak ktoś je pominie przy instrukcji warunkowej to ciekawe jaja mogą się zrobić. Poza tym, że w ogóle bym zapomniał o makrach i wrzucił normalne funkcje inline. Jest wiele dobrych powodów, żeby tak zrobić.
  • #12 8369940
    flapo213
    Poziom 21  
    Witam,

    ok wycofuje wstawkę z

    
    
    while(delay--);
    
    


    oraz zmiana makra

    z

    
    // get 1-wire bus input (bus released) 
    #define OWR        PINC & OWMASK 
    


    na

    
    // get 1-wire bus input (bus released) 
    #define OWR        (PINC & OWMASK) 
    


    Po przeanalizowaniu przyznaję rację użytkownikowi TMF. Przepraszam za wprowadzenie w błąd.

    Proponuję zastąpić tylko i wyłącznie to wystąpienie jakimś delayem z biblioteki zgodnie z czasem napisanym w komentarzu

    Co do makr to ja ich nie pisałem niskopoziomową konfigurację portów zaciągnałem z jakiejś noty aplikacyjnej.

    Myślę że w takiej formie chyba może to zostać.

    Odnośnie dostoswania czasówki w procedurze resetu chyba warto wykorzystać symulator softwarowy w avrstudio bądź oscyloskop aby dobrać je zgodnie ze specyfikacją

    Odnośnie Uarta to fakt jest prościej i szybciej i nawet z przerwania śmiga ale chyba zasialania w trybie parasite power sie zrobić nie da, ponieważ po nadaniu ostatniego bitu komendy convert jest tylko 10us na przełączenie lini DQ na strong.

    Pozdrawiam
  • #13 8370939
    flapo213
    Poziom 21  
    Do GDL

    Nie jest wymagany żadny weak pullup od strony portu atmegi zewnętrzny rezystor 4.7k lub mniejszy odrobinę jest wystarczający.

    Jedyne co musisz w procedurze resetu zrobić to przywrócić po wymuszeniu na porcie 0 logicznego po 480us podciąganie właśnie przez ten rezystor zewnętrzny port tylko na input ustawiasz.

    Pozdrawiam
  • #14 8370981
    gdL
    Poziom 27  
    Pozostaje pytanie, dlaczego w mojej procedurze resetu, po 60us bez wewnętrznego pullupa stan pinu pozostaje "0". DS jest w trybie zasilania zewnętrznego (3 kable), podłączony pullupem 10KOhm.

    10KOhm to trochę dużo, jednak nie sądzę, że to problem. Co w takim razie jest problemem ?
  • #15 8371057
    flapo213
    Poziom 21  
    Ustaw w atmedze input with HIz. Pamiętaj również że układ DS18x20 może przetrzymać stan logicznego zera przez 240us zgodnie ze specyfikacją.

    Pozdrawiam
REKLAMA