Elektroda.pl
Elektroda.pl
X
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

termomert atmega16 problem C

Pissak 08 Jan 2011 18:34 1443 11
  • #1
    Pissak
    Level 10  
    Witam wszystkich!
    Mam problem z moim projektem, do atmegi 16 podłączyłem bezpośrednio LM35 i dwa wyświetlacze 7 segmentowe. Napisałem program który odczytuje wartość podaną z LM35 poprzez ADC ale na wyświetlaczu cały czas mi się pokazuje 90... Poniżej kod programu w C. Proszę o pomoc.

    #include <avr/io.h>
    #include <avr/interrupt.h>
    
    
    
    
    void ADC_Init(void)   //funkcja konfiguracji ADC
    	{
    	ADMUX |=(1<<REFS0);  //AREF pobierane z zewnątrz 5V
    	ADMUX&=~(1<<REFS1);
    	ADMUX |=(1<<ADLAR);	// wyrównanie wyniku poomiaru do lewej
    	ADCSRA|=(1<<ADEN);  //ADC włączone
    	ADCSRA|=(1<<ADIE);	//przerywa przerwaniem po zakonczeniu pomiaru
    	SFIOR&=~(1<<ADTS0); //free runing
    	SFIOR&=~(1<<ADTS1);
    	SFIOR&=~(1<<ADTS2);
    	ADCSRA|=(1<<ADPS0);	//prescaler
    	ADCSRA|=(1<<ADPS1);
    	ADCSRA&=~(1<<ADPS2);
    	ADMUX=0b10000000;	//kanał 7 ADC
    	ADCSRA|=(1<<ADSC); //start konwersji
    	}
    
    	int pomiar,temp,x;
    
    SIGNAL(SIG_ADC)
    	{
    		pomiar =ADCH; //wpisujemy wynik 8 bitowy do pomiar
    
    		temp=pomiar*100; //zamiana napięcia ADC na stopnie 10mV/1st
    		
    		ADCSRA|=(1<<ADSC); //start konwersji
    	}
    void main (void)
    	{
    	DDRA=0b01111111; // kanał 7 we ADC, reszta wy DISP Jednosci
    	DDRB=0xff;	// wyjscia DISP dziesiatek
    	ADC_Init();
    	int d1,d2;
    			
    			
    		
    
    	sei();
    	while(1)
    		{	d1=temp/10;
    			d2=temp%10;
    
    		if(d1==0){PORTB=00000010;} //0
    		if(d1==1){PORTB=10011110;} //1
    		if(d1==2){PORTB=00100100;} //2
    		if(d1==3){PORTB=00001100;} //3
    		if(d1==4){PORTB=10011000;} //4
    		if(d1==5){PORTB=01001000;} //5
    		if(d1==6){PORTB=01000000;} //6
    		if(d1==7){PORTB=00011110;} //7
    		if(d1==8){PORTB=00000000;} //8
    		if(d1==9){PORTB=00001000;} //9
    
    		if(d2==0){PORTA=00000001;} //0
    		if(d2==1){PORTA=01001111;} //1
    		if(d2==2){PORTA=00010010;} //2
    		if(d2==3){PORTA=00000110;} //3
    		if(d2==4){PORTA=01001101;} //4
    		if(d2==5){PORTA=00100100;} //5
    		if(d2==6){PORTA=00100000;} //6
    		if(d2==7){PORTA=00001111;} //7
    		if(d2==8){PORTA=00000000;} //8
    		if(d2==9){PORTA=00001100;} //9
    		}
    	}
    
  • Helpful post
    #2
    vonar
    Level 28  
    ADMUX=0b10000000;   //kanał 7 ADC

    Zamiast 7 ustawiasz zerowy kanał ADC, a ten pin wykorzystujesz jako wyjście na wyświetlacz przecież... nic dziwnego, że bzdury wychodzą.
    O co chodzi z tym mnożeniem przez 100 (i dlaczego w przerwaniu, do tego jeszcze na zmiennej globalnej)? Jeśli już to powinno być przez 2 (wtedy wystarczą liczby 8-bitowe) albo (na 16-bitowych) mnożenie przez np. 500 i dzielenie przez 256.
    Poza tym przy odniesieniu 5V i odczycie tylko starszych 8 bitów masz rozdzielczość ok. 20mV - dwa stopnie Celsjusza, słabo! Można by wyrównać do prawej i odczytywać tylko młodsze bity - o ile przetwornik nie będzie oszukiwał będzie lepiej (choć przydało by się wtedy jakieś uśrednianie).

    Ponadto:
    - zmienna przekazująca z przerwania wynik konwersji powinna być volatile
    - zmienne niepotrzebnie ze znakiem
    - SIGNAL() jest przestarzałe (zaleca się ISR())
    - d1, d2 niepotrzebnie 16-bitowe (właściwie to wystarczy jedna zmienna)
  • #3
    tmf
    Moderator of Microcontroller designs
    Poza tym w tym PORTA= powinno być 0b na początku, bo to co wpisujesz z pewnością nie jest liczbą binarną. Zamiast tyle if'ów zrób tablicę wartości indeksowaną d1 i d2. Skoro korzystasz z free running, to w przerwaniu ADC nie ustawiaj bitu ADSC, kolejna konwersja zacznie się automatycznie (właściwie w chwili wejścia w przerwanie już się zaczęła). I wywal te wszystkie |, & itd. Po prostu ustaw wartość rejestru tak jak chcesz. Weż pod uwagę, że rejestry IO są volatile i zapis rejestr|=costam generuje co najmniej 3 instrukcje assemblera.
  • #4
    vonar
    Level 28  
    tmf wrote:
    ...zapis rejestr|=costam generuje co najmniej 3 instrukcje assemblera.

    Niekoniecznie. GCC rozpoznaje ustawienie/kasowanie pojedynczego bitu i używa w takim wypadku instrukcji sbi/cbi.

    A tak właściwie to w tym programie można by zrezygnować z przerwania ADC i tylko bezpośrednio odczytywać (w pętli głównej) wynik konwersji przed wyświetleniem.
  • #5
    tmf
    Moderator of Microcontroller designs
    Jeśli rejestr jest w przestrzeni dostępnej dla sbi/cbi. A swoją drogą nawet w tym przypadku wygeneruje tyle instrukcji ile bitów się ustawia zamiast dwóch instrukcji ustawiających cały rejestr.
  • #6
    Pissak
    Level 10  
    Dzięki za odpowiedzi


    Wprowadziłem kilka zmian i poprawiłem błędy, jednak dalej robię coś nie tak...

    W funkcji konfiguracji ADC pozostawiałem tylko gdzie wpisuje "1" wygląda dobrze

     void ADC_Init(void)   //funkcja konfiguracji ADC
    	{
    			ADCSRA|=(1<<ADEN); //adc enable
    			ADCSRA|=(1<<ADIE); //przerywaj przerwaniem gdy zakończy się pomiar ADC
    			ADCSRA|=(1<<ADATE); //free runing
    			ADCSRA|=(1<<ADPS0); //ustawiamy prescaler 128
    			ADCSRA|=(1<<ADPS1);
    			ADCSRA|=(1<<ADPS2);
    			ADMUX = 0b0000111; //wybieramy kanał 7
    			ADCSRA|=(1<<ADSC); //start konwersji
    	} 


    W przerwaniu od ADC odczytuję wartość zmierzoną i tu chyba robię coś nie tak...

    ISR(SIG_ADC)
    	{	
    		pomiar=(ADCH&512)|ADCL;//wpisujemy wynik do pomiar		
    	}
    


    Resztę programu mam podobnie jak miałem:
    
    
    
    		DDRA=0b01111111; // kanał 7 we ADC, reszta wy DISP Jednosci
    		DDRB=0xff;	// wyjscia DISP dziesiatek
                    char d1,d2; // zmienne dla wyświetlacza
    	
    		ADC_Init();
    		sei();
    		while(1)
    			{	temp=pomiar*500/256;
    
    				d1=temp/10;
    				d2=temp%10
                      if ... i dalej te if'y
    
    


    a problem teraz polega na tym że wyświetla mi się 85 cały czas po zresetowaniu układu czasami sie zmieni na 86, 87 ale w ogóle nie reaguje na zmianę temperatury, poratujecie coś jeszcze?
  • Helpful post
    #7
    tmf
    Moderator of Microcontroller designs
    Kanał 1 zdecydowanie nie tak się wybiera:
    ADMUX = 0b0000111;
    To ci wybiera kanał 7, w dodatku wyłącza wewn. Vref i musisz podać zewnętrzne na końcówkę Vref.
    Kolejny błąd: pomiar=(ADCH&512)|ADCL;
    ADCH & 512 da ci zawsze zero, bo ADCH jest 8-bitowy. Powinno być pomiar=ADC;
    Kolejne problemy to zmienne d1 i d2. Są 15-bitowe (bo ze znakiem), jak je przemnożysz razy 500 to w zależności od Vref i temperatury być może wyjdziesz poza zakres int i ci wynik obetnie. rzeba to przeliczyć, albo dać uint32_t (ulong).
  • #8
    Pissak
    Level 10  
    co do ustawiania kanału ADC to mam pod 7 podpięty czujnik, i mam zewnętrzne 5V źródło odniesienia także myślę, że z tym ok?
    zmieniłem tak jak poleciłeś z tym pomiar=ADC oraz zmienne d1 i d2 na 32 bity zaczęło się coś dziać, pokazuje i reaguje na temperaturę ale przeskakuje w ułamku sekundy np. pomiędzy 19 a 20 jest jakiś sposób żeby to się płynnie zmieniało? np jakieś opóźnienie czy uśrednienie?
  • Helpful post
    #9
    tmf
    Moderator of Microcontroller designs
    Tak, mój błąd, te 7 w komentarzu wydawało mi się, że to jeden :)
    Skakać będzie, bo to kwestia szumu, chociaż o cały stopnień nie powinno. Generalnie spróbuj zastosować noise canceller, mierzyć w power down, zrobić oversampling i uśrednianie iluśtam pomiarów. Powinieneś bez problemu uzyskać stabilne odczyty z rozdzielczością do 0,1 stopnia. Zakładając oczywiście poprawne połączenia elektryczne, projekt płytki itd. Te 5V źródło odniesienia jak realizujesz?
  • #10
    Pissak
    Level 10  
    zależy mi głównie na rozdzielczości do 1st bo to i tak będzie pokazywało dwie cyfry. Schemacik ręcznie narysowany wrzucam jak mam popodłączane.

    termomert atmega16 problem C
  • #11
    tmf
    Moderator of Microcontroller designs
    Wywal połączenie Vref z Vcc, zamiast tego wykorzystaj wewnętrzne źródło referencyjne. Na AVcc daj osobny kondensator odsprzęgający, najlepiej daj też dławik (ale nie jest konieczny). Z portu na któym jest LM nie steruj już niczy, a z pewnością nie przełączaj stanu pinów tego portu podczas pomiaru.
  • #12
    Pissak
    Level 10  
    ok dzięki bardzo za pomoc

    ///////////////////////////////////////////////

    Zrobiłem wszystko wg. zaleceń tmf'a i termometr działa tak jak chciałem.
    Mam teraz problem z "pętlą histerezy", ponieważ chcę aby teraz jak termometr wyryje temperaturę 40 i powyżej załączył się wentylator i chłodził tak długo aż temperatura spadnie do 30 i sie wyłączył, z grzałką podobnie tyle że 15 st włącza i 25 st wyłącza.

    Napisałem kod w przerwaniu ADC jednak nie działa to do końca dobrze, gdyż włącza się ok przy 40 wentylator ale wyłącza się przy 38-37 i grzalka podobnie. Ma ktoś pomysł jak zrobić żeby działało... ?

    
    ISR(SIG_ADC)
    	{		
    		pomiar = ADC;    // pobranie wartosci pomiaru z rejestru ADC
    	
    		if (pomiar<=43)   //włącz grzałkę ( 15 st )
    		{ PORTC =0b00000010; }
    		
                     if (pomiar>=51) 	//wyłącz grzalkę ( 25 st )
    		{PORTC=0x00;}
    		
                     if (pomiar<=67) //wyłącz wentylator ( 30 st )
    		{PORTC=0x00;}
    
    		if (pomiar>=80) //wlacz wentylator ( 40 st )
    		{PORTC =0b00000001;}
    	}