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

[ATmega8][C] System kontroli temp na LM35

regrom 13 Gru 2009 16:17 6454 10
  • #1 7383579
    regrom
    Poziom 16  
    Witam

    Piszę w C, WinAVR, pony prog, STK 200

    Zwracam się do was z prośbą o pomoc w rozwiązaniu problemu, wziąłem się za programowanie i w ramach nauki, zapoznania się z AVR obmyśliłem popełnienie takiego czegoś:

    Docelowo:
    - pomiar temperatury - 3 czujniki lm 35
    - wysterowanie 3 wentylatorów PWM
    - sterowanie automatyczne i ręczne(przyciski)
    - wyświetlanie LED albo LCD

    Schemat ideowy: SCHEMAT
    Od strony fizycznej raczej jest ok, chociaż podłączenie nie do końca mi wyszło, dałem pod pint które zostały wolne, jak inaczej można to rozwiązać?

    W czym problem
    Po zabawie diodami, multipleksowaniem LED, udało mi się uruchomić ADC.Na chwilę obecną nie posiadam LCD więc wyświetlam na dwóch segmentach. Na początek zrobiłem termometr.

    LM35 >> 10mV/°C

    Robię konwersję w odniesieniu do VREF = 2560 mV
    Np. Dla 23 stopni dosteje 230 mV, po konwersji mamy wartość 92
    Przeliczam ją spowrotem na mV czyli 92*2.5= 230
    Teraz to wartość rozbijam na stopnie czyli, 230/100=2 , oraz 230%100=3
    jednak po kompilacji wyświetla nie to co powinno.. poprawnie jest gdy dziele przez 10 i nie mam pojęcia dlaczego, będę wdzięczny za wyjaśnienie tego zjawiska..

    Mile widziane wszelkie uwagi do kodu, dopiero się uczę, więc krytyka mile widziana.

    KOD:
    
    /* układ ATmega 1MHz */
    
    #define F_CPU 1000000L  // taktowenie wewnetrznym MHz
    #include <avr/io.h>
    #include <util/delay.h>               
    #include <avr/sfr_defs.h>
    #include <avr/interrupt.h>
    #include <inttypes.h>
    #include <stdlib.h>
    #include <math.h> 
    
    
    
    #define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit)) // makra clear bit i set bit
    #define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
    
    #define VREF 2560
    
    
    int x,y,j;  // zmienne potrzebne do wyswietlenia x segment 1(jednosci)
    			// y segment 2 (dziesiatki)
    double temp1,tempw; // do zapisu temperatury z ADC
    float k;
    
    void Inicjalizacja(void)   //funkcja inicjalizacji przetwornika ADC
    {
    
    // Wybranie wewnętrznego żródła napięcia odniesienia   	
     		sbi(ADMUX,REFS0);  //wew 2,56 z C do GND	 					//ADMUX |= _BV(REFS0);      
      		sbi(ADMUX,REFS1);						 						//ADMUX |= _BV(REFS1);      
    // Zezwolenie na konwersję   
      	    sbi(ADCSRA,ADEN);	 // wlaczenie ADC							//ADCSRA |= _BV(ADEN);      
    // Wybranie częstotliwości dla taktowania przetwornika  (1/8 częstotliwosci zegara kontrolera)
     sbi(ADCSRA,ADPS0); 													//ADCSRA |= _BV(ADPS0);   
     sbi(ADCSRA,ADPS1); // 1Mhz / 8	= 125 Khz	  							//ADCSRA |= _BV(ADPS1); 
    } 
    
    double pomiar(void)  //funkcja dokonująca pomiaru
    {   
    //wybor pinu     //ADC3
    sbi(ADMUX,MUX0);
    sbi(ADMUX,MUX1);
    //sbi(ADMUX,MUX2);
    //sbi(ADMUX,MUX2);
    //
       
    int i;
       temp1=0;
       for(i=0;i<10;i++)                  //petla pomiarowa
          {
      		sbi(ADCSRA,ADSC);  // Rozpoczęcie przetwarzania             // ADCSRA |= _BV(ADSC);        
          
          while(bit_is_set(ADCSRA,ADSC))   // Oczekiwanie na zakończenie przetwarzania
          {};   
          temp1=(double)ADCW;              // Zapisanie  wyniku konwersji do zmiennej
         //  _delay_ms(1);
          }
       temp1=temp1/10;    //wyliczenie sredniej wartosci   
       
       return temp1;
    } 
    
    
    /*
     ---A----
    |		 |
    |		 |
    F        B
    |		 |
    |		 |  
    |---G----|
    |		 |
    |		 |
    E	 	 C
    |		 |
    |        |
     ---D----
    
    */
    void led7(int i)  // funkcja do zapalania kolejnych segmentow w zaleznosci od cyfry
    {				  // dla kazdej cyfry kolejno ustawione odpowiednie bity na porcie D
    
    #define A 0  // A odpowiada 0 itd...
    #define B 1
    #define C 2
    #define D 3
    #define E 4
    #define F 5
    #define G 6
    
    if(i==0)
    {PORTD &= ~_BV(A) &~_BV(B) &~_BV(C) &~_BV(D) &~_BV(E) &~_BV(F); //0
    }
    if(i==1)
    {PORTD &= ~_BV(B) &~_BV(C); //1
    }
    if(i==2)
    {PORTD &= ~_BV(A) &~_BV(B) &~_BV(D) &~_BV(E) &~_BV(G); //2
    }
    
    if(i==3)
    {PORTD &= ~_BV(A) &~_BV(B) &~_BV(C) &~_BV(D) &~_BV(G); //3
    }
    
    if(i==4)
    {PORTD &= ~_BV(B) &~_BV(C) &~_BV(F) &~_BV(G); //4
    }
    if(i==5)
    {PORTD &= ~_BV(A) &~_BV(C) &~_BV(D) &~_BV(F) &~_BV(G); //5
    }
    if(i==6)
    {PORTD &= ~_BV(A) &~_BV(C) &~_BV(D) &~_BV(E) &~_BV(F) &~_BV(G); //6
    }
    if(i==7)
    {PORTD &= ~_BV(A) &~_BV(B) &~_BV(C); //7
    }
    if(i==8)
    {PORTD &= ~_BV(A) &~_BV(B) &~_BV(C) &~_BV(D) &~_BV(E) &~_BV(F) &~_BV(G); //8
    }
    if(i==9)
    {PORTD &= ~_BV(A) &~_BV(B) &~_BV(C) &~_BV(D) &~_BV(F) &~_BV(G); //9
    }
    
    } // koniec led7
    
    
    
    
    
    int main(void)	// główna funkcja
    {	
    //	DDRx  //  1 wyjscie ---  0 wejscie
    // DDRB  |= _BV(0)|_BV(1);  
    //	DDRC =0x00; 				// port c jako wejscie
    //	PORTB =3;
    //	PORTC =0xFF;  // likwidacja smieci na porcie
    
    DDRC =0b00010000;   // wejscie na przycisk PC5   // uPC --> switch --> GND
    //PORTC =0xFF; 
    
    DDRD =0xFF;   // wyjscia dla segmentu porty PD0 - PD6  // PD7 tranzystor zalaczajacy caly segment 1
    PORTD =0xFF; // ustawienie 1   
    
    k=2.5;
    Inicjalizacja();
    
    
    
     while (1) /**********************************/
        {
    //if(bit_is_clear(PINC,5))a=1;
    //if(bit_is_set(PINC,5))a=0;
    
    
    
    tempw=pomiar();
    //tempw=tempw*VREF;
    //tempw=tempw/1024;
    tempw=tempw*k;
    y=tempw/10;
    x=(int)tempw%10;
    
    	
    	
    
    
    			/*WYSWIETLANIE POCZATEK*/
    			for(j=0;j<500;j++)
    			{
    					// taki sposob zapisu jak ponizej po testach wykazal ze nie ma efektu
    					// poswiaty poprzedniej wartosci
    
    					_delay_ms(2);
    				PORTD =0xFF;    // wyzerowanie portu D - zgaszenie wszystkich lini
    					sbi(PORTC,4); //zgaszenie segmentu 2
    			led7(x);
    					cbi(PORTD,7); // zapalenie segment 1
    				
    					_delay_ms(2);
    				PORTD =0xFF;     	
    					sbi(PORTD,7); ///zgaszenie segmentu 1
    			led7(y);
    					
    					cbi(PORTC,4);	// zapalenie segment 2
    			}
    			/*WYSWIETLANIE KONIEC*/
    
    
    		
        }/*****************************************/
    
    
    } // koniec main
  • #3 7383922
    regrom
    Poziom 16  
    no tak, kk. ale jak dziele, 230/100=2 i przypisuje ta wartość pod y, wyświetla mi 0
    Poprawnie wyświetla mi temperaturę jeśli jest:

    y=tempw/10;   // dziesiatki
    x=(int)tempw%10;


    wtedy pod y mam 2
    a pod x mam 3
    tylko to mi się kłóci.. no bo 230/10 jest 23.. a nie 2, a 230 modulo 10 mam 3, a nie 0
  • Pomocny post
    #4 7384054
    tmf
    VIP Zasłużony dla elektroda
    Bo tam masz 23 a nie 230 - w funkcji pomiar() juz dzielisz wynik przez 10.
    Chyba zamiast:
    temp1=(double)ADCW;
    chciales zrobic:
    temp1+=(double)ADCW;

    BTW, na AVR double=float, a w tym przypadku w ogole obliczen zmiennopozycyjnych nie potrzebujesz, ale jesli masz miejsce i czas na nie to czemu nie :)
  • #5 7384168
    regrom
    Poziom 16  
    zgadza się :D , 10 próbek i z tego średnia, właśnie..

    Kolejny problem

    Wyświetlacz mruga kiedy jest dokonywany pomiar, tzn gaśnie na czas pomiaru .Jak to zlikwidować?

    Najprawdopodobniej należy tu zastosować przerwania, może mnie ktoś nakierować w jaki sposób?

    Wyświetlacz na przerwaniu z jakiegoś timera?
    ADC też w jakimś przerwaniu?

    Jeśli timer będzie użyty jako PWM, to można zrobić na nim przerwanie?
  • #6 7384521
    tmf
    VIP Zasłużony dla elektroda
    To juz zalezy od ciebie, jesli masz tylko 2 cyfry na LED to mozna wsadzic jakis zatrzask typu 573/574, ktory pamieta stan i zapomniec o wyswietlaniu multipleksowym, mozna wsadzic 16-bitowy ekstender I2C, wtedy do sterowania wykorzystujesz tylko 2 przewody, albo zrobic multipleksowo. ADC zostaw tak jak jest (chyba, ze sa inne powody, zeby przeniesc to do przerwania), a tylko procedure wyswietlania zrobic na przerwaniu timera. PWM mozna wtedy zrobic programowo, albo wykorzystac drugi timer z ktorego wyjscie podajesz na baze tranzystora podajacego zasilanie na LED.
    Mozna tez zastosowac zapomniane juz prawie 74xx4511, ktory jest latchem i dekoderem BCD na kod 7-segmentowy.
  • #7 7385844
    regrom
    Poziom 16  
    Dzięki za informację. Udało mi się zrobić multipleksowanie w ten sposób:

    Timer 1 w tryb CTC, tryb porównania
    W main dodałem
    
    TCCR1B |= (1 << WGM12); // Ustawia timer1 w tryb CTC
        OCR1A = 10000; // Ustawia wartość pożądaną na 100Hz dla preskalera 1
        TCCR1B |= (1 << CS10); // Ustawia timer z preskalerem Fcpu/1
    //	TCCR1B |= (1 << CS11); // Ustawia timer z preskalerem Fcpu/1
    //	TCCR1B |= (1 << CS12); // Ustawia timer z preskalerem Fcpu/1
        TIMSK |= (1 << OCIE1A); // Zezwolenie na przerwania dla CTC
        sei(); // Zezwolenie globalne na przerwania


    oraz przerwanie
    ISR(TIMER1_COMPA_vect)
    {
       	/*WYSWIETLANIE POCZATEK*/
    		//	for(j=0;j<500;j++)
    		//	{
    					// taki sposob zapisu jak ponizej po testach wykazal ze nie ma efektu
    					// poswiaty poprzedniej wartosci
    switch (w)
    	{
    		case 0:
    				PORTD =0xFF;    // wyzerowanie portu D - zgaszenie wszystkich lini
    					sbi(PORTC,4); //zgaszenie segmentu 2
    			led7(x);
    					cbi(PORTD,7); // zapalenie segment 1
    					w++;
    					break;
    				
    		case 1:		
    				PORTD =0xFF;     	
    					sbi(PORTD,7); ///zgaszenie segmentu 1
    			led7(y);
    					
    					cbi(PORTC,4);	// zapalenie segment 2
    					w=0;
    					break;
    		//	}
    			/*WYSWIETLANIE KONIEC*/
    	}
    }


    Znalazłem to na stronie wkretak.pl

    Tylko teraz, jak ustawie to na TIMER 0, nadal mogę wykorzystać pozostałe TIMER 1 i 2 jako PWM - w sumie 3 kanały, zgadza się?

    Jak radzicie to rozwiązać?
  • #8 7390725
    regrom
    Poziom 16  
    Witam ponownie.

    Uruchomiłem TIMER 2 jako PWM.
    W main dodałem:

    
    uint8_t pwm; 
    TCCR2 =  _BV(COM21) | _BV(COM20)| _BV(WGM21) | _BV(WGM20) |  _BV(CS20); 
    


    oraz w while(1)

    if(tempw>200&&tempw<500)
    {	
     	pwm=(uint8_t)tempw-200;
    	pwm=pwm/10;
    	pwm=pwm*8;
     	OCR2 = pwm;
     	pwm=0;  				 
    }
    if(tempw>=500)
    {	
    	pwm=255;
           OCR2 = pwm;
    }


    Ma to działać w ten sposób że w zależności od temperatury zmienia się wypełnienie. Reszta kodu jak powyżej, jednak w tym miejscu pojawia się problem, w zakresie od 200 do 450 jest w miare ok, tylko powyżej 45 stopni dioda którą mam podłączona do OC2 świruje to znaczy, raz świeci mocno raz jaśniej, podłączyłem woltomierz i napięcie skacze około tej wartości.

    Wg obliczeń jest chyba dobrze dale 200 mV mamy 0 - czyli dioda świeci maks, a dla 500 mV mamy 500-200=300, 300/10=30; 30*8=240; czyli powinna słabo świecić.

    W czym może tkwić problem?
  • Pomocny post
    #9 7405993
    zumek
    Poziom 39  
    regrom napisał:
    W czym może tkwić problem?


    Np. w tym, że 300 "nie mieści się" w uint8_t :D
  • #10 7446468
    regrom
    Poziom 16  
    No tak, masz rację, przepełnienie zmiennej :) to wyjaśnia dlaczego też przy 45 stopniach, 450-200= 250, a przy 255 już mamy przepełnienie.

    Z dalsze testy kodowe wezmę się po zlutowaniu płytki prototypowej którą już wykonałem:
    [ATmega8][C] System kontroli temp na LM35 [ATmega8][C] System kontroli temp na LM35
  • #11 7468379
    regrom
    Poziom 16  
    Witam w NOWYM ROKU!

    Układ złożony ale mam kolejny problem.
    Przerwanie od ADC

    Chcę w przerwaniu od ADC umieścić funkcję pomiar() jednak tam nie działa i nie wiem dlaczego.
    Jeśli funkcja pomiar() jest w main nie ma problemu, ale wtedy ADC nie jest właczony w trybie free running ani nie mam obsługi przerwania od ADC.

    Dla przypomnienia ATMEGA 8 + LM35

    W programie jest już jedno przerwanie od CTC timera1 do multipleksowania LED.

    NIEDZIAŁAJĄCY KOD
    volatile double temp1,tempw,tobl; // do zapisu temperatury z ADC
    
    void Inicjalizacja(void)   //funkcja inicjalizacji przetwornika ADC
    {
    // Wybranie wewnętrznego żródła napięcia odniesienia   	
     		sbi(ADMUX,REFS0);  //wew 2,56 z C do GND	 				     
      		sbi(ADMUX,REFS1);						 					
    // Zezwolenie na konwersję   
      	    sbi(ADCSRA,ADEN);	 // wlaczenie ADC						
    		sbi(ADCSRA,ADIE);     // przerwanie
    	    sbi(ADCSRA,ADFR);    //free run
    // Wybranie częstotliwości dla taktowania przetwornika  (125 kHz)
    	   sbi(ADCSRA,ADPS1); 												   
           sbi(ADCSRA,ADPS2); // 8Mhz / 64	= 125 kHz	  						
    }
    
    W MAIN OCZYWISCIE INICJALIZACJA + sei();
    
    double pomiar(void)  //funkcja dokonująca pomiaru
    {   
    //wybor pinu     //ADC3
    sbi(ADMUX,MUX0);
    sbi(ADMUX,MUX1);
    //sbi(ADMUX,MUX2);
    //sbi(ADMUX,MUX2);
    
    int i;
       temp1=0;
       for(i=0;i<10;i++)                  //petla pomiarowa
          {
      		sbi(ADCSRA,ADSC);  // Rozpoczęcie przetwarzania                 
          
             while(bit_is_set(ADCSRA,ADSC))   // Oczekiwanie na zakończenie przetwarzania
             {};   
          temp1+=(double)ADCW;          // Zapisanie  wyniku konwersji do zmiennej
          }
       temp1=temp1/10;    //wyliczenie sredniej wartosci   
       
       return temp1;
    }
    
    PRZERWANIE 
    
    ISR(ADC_vect)   //przerwanie od ADC
    {   
    tempw=pomiar();
     } 
    
    W WHILE(1) MAM przkeonwertowanie wartości na wyświetlacz
    
    tempw=tempw*k;
    tempw=tempw+17;
    y=tempw/100;  //dzesiatki
    x=(int)tempw%100/10; //jednosci
    
    


    EFEKT:
    Na segmencie jest wartość 01, i żadnej zmiany,samo konwertowania działa dobrze ale z tym przerwaniem jest coś nie tak, nie przypisuje wartości do zmiennej.
    W czym problem?
REKLAMA