wtorek, 29 marca 2011

ADC - Przełączanie kanałów

Autor: drzasiek
Redakcja: dondu

Drzaśkowy pamiętnik: Spis treści


Mikrokontrolery wyposażone w przetwornik ADC niezależnie od producenta z reguły posiadają co najmniej kilka wejść pomiarowych, zwanych kanałami.

Tak własnie jest w przypadku Atmega8, który posiada jeden przetwornik ADC i może mierzyć napięcie na kilku kanałach dzięki multiplekserowi (przełącznikowi), który znajduje się na wejściu ADC. Jego zadaniem jest przełączanie wejścia pomiarowego przetwornika ADC pomiędzy kanałami, które podłączone są do pinów mikrokontrolera i nie tylko (patrz rysunek poniżej).


Rzućmy okiem na schemat multipleksera ADC.




Spróbowałem więc wykonać pomiar z 4 wejść.


Pobierz schemat: schemat1.rar

Do tego program, który wykonuje na przemian pomiary 4 kanałów wyświetlając je na LCD:

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>   
#include <stdlib.h>  
#include "HD44780.h"


//definicje wejść ADC
#define wej_1 PC2 
#define wej_2 PC3 
#define wej_3 PC4 
#define wej_4 PC5 

volatile uint8_t adc1;//zmienna do pomiaru ADC wej_1
volatile uint8_t adc2;//zmienna do pomiaru ADC wej_2
volatile uint8_t adc3;//zmienna do pomiaru ADC wej_3
volatile uint8_t adc4;//zmienna do pomiaru ADC wej_4

volatile uint8_t wejscie=2;//zmienna do zmiany wejścia, początkowo pc2
 
//##############################################################################
void main(void)
{
   char wynik[]="    ";//bufor tekstowy, wyczyszczenie bufora
    
   LCD_Initalize();   //inicjalizacja LCD

   //numerowanie pomiarów
   LCD_GoTo(0, 0);      
   LCD_WriteText("1:");
   LCD_GoTo(7, 0);      
   LCD_WriteText("2:");
   LCD_GoTo(0, 1);      
   LCD_WriteText("3:");
   LCD_GoTo(7, 1);      
   LCD_WriteText("4:");

   //Inicjalizacja ADC
   ADCSRA = (1<<ADEN)      // ADC Enable (uruchomienie przetwornika)
             |(1<<ADFR)      //tryb Free run
             |(1<<ADSC)      //rozpoczęcie konwersji
             |(1<<ADPS2);   //ADPS2: (ustawienie preskalera) preskaler= 16

   ADMUX  =   (1<<ADLAR)     //Wyrównanie wyniku do lewej
             |(1<<REFS0)            //VCC jako napięcie referencyjne
             |wejscie;   //Wybór wejścia początkowego czyli PC2 

   //Inicjalizacja Timera
   TIMSK |= (1<<TOIE0); //Przerwanie overflow przepełnienie timera
   TCCR0 |= (1<<CS01); // źródłem CLK, preskaler 8 (2000000 Hz)
   TCNT0 = 155; //Początkowa wartość licznika 

   DDRC &=~ (1<<wej_1);        //Ustawienie pinów wejściowych ADC
   DDRC &=~ (1<<wej_2);        //Ustawienie pinów wejściowych ADC
   DDRC &=~ (1<<wej_3);        //Ustawienie pinów wejściowych ADC
   DDRC &=~ (1<<wej_4);        //Ustawienie pinów wejściowych ADC

   sei();//Globalne uruchomienie przerwań 


   for(;;)//główna pętla programu
   {
      LCD_GoTo(3, 0);         //Ustawienie kursora w pozycji (3,0)
      LCD_WriteText("   ");  //Czyszczenie poprzednij wartości
      itoa(adc1,wynik,10);     //konwersja wyniku do tablicy char
      LCD_GoTo(3, 0);         //Ustawienie kursora w pozycji (3,0)
      LCD_WriteText(wynik);   //Wyświetlenie wyniku

      LCD_GoTo(10, 0);         //Ustawienie kursora w pozycji (10,0)
      LCD_WriteText("   ");  //Czyszczenie poprzednij wartości
      itoa(adc2,wynik,10);     //konwersja wyniku do tablicy char
      LCD_GoTo(10, 0);         //Ustawienie kursora w pozycji (10,0)
      LCD_WriteText(wynik);   //Wyświetlenie wyniku

      LCD_GoTo(3, 1);         //Ustawienie kursora w pozycji (3,1)
      LCD_WriteText("   ");  //Czyszczenie poprzednij wartości
      itoa(adc3,wynik,10);     //konwersja wyniku do tablicy char
      LCD_GoTo(3, 1);         //Ustawienie kursora w pozycji (3,1)
      LCD_WriteText(wynik);   //Wyświetlenie wyniku

      LCD_GoTo(10, 1);         //Ustawienie kursora w pozycji (10,1)
      LCD_WriteText("   ");  //Czyszczenie poprzednij wartości
      itoa(adc4,wynik,10);     //konwersja wyniku do tablicy char
      LCD_GoTo(10, 1);         //Ustawienie kursora w pozycji (10,1)
      LCD_WriteText(wynik);   //Wyświetlenie wyniku

      _delay_ms(100);         //opóźnienie
 }
}


ISR(TIMER0_OVF_vect)
{
   //Przerwanie przepełnienia Timer0

   switch(wejscie)
   {
      case 2://gdy PC2
        adc1=ADCH;     //odczytaj tylko starszy bajt pomiaru
        break;

      case 3://gdy PC3
        adc2=ADCH;     //odczytaj tylko starszy bajt pomiaru
        break;

      case 4://gdy PC4
        adc3=ADCH;     //odczytaj tylko starszy bajt pomiaru
        break;

      case 5://gdy PC5
        adc4=ADCH;     //odczytaj tylko starszy bajt pomiaru
        break;
   }

   if(wejscie<5)
        wejscie++;
   else
        wejscie=2;

   ADMUX = 0;  //kasowanie rejestru

   ADMUX  =   (1<<ADLAR) |(1<<REFS0)| wejscie; //Ustawianie nowych wartości

   TCNT0 = 155;  //Początkowa wartość licznika
}
Pobierz program: 07_ADC_LCD_Prog6.zip (kopia)

A tak wygląda wynik pracy programu:



Przeczytaj także:

16 komentarzy:

  1. Czy rzeczywiście ten timer jest tu potrzebny? Przecież ADC może działać w trybie free running mode i może zgłaszać przerwanie po zakończeniu pomiaru - to wtedy można zapisać wynik do zmiennej globalnej i przełączyć kanał na inny.

    OdpowiedzUsuń
  2. Moim zdanie rzeczywiście bez timera można się tutaj obyć, ale proszę zwrócić uwagę, że jest to rozbudowany kod z wcześniejszego artykułu. Prawdopodobnie pan Drzasiek zaoszczędził po prostu trochę czasu, po co implementować przetwornik drugi raz skoro tematem artykułu jest przełączanie kanałów.

    Btw dzięki za następne części pamiętnika :)

    OdpowiedzUsuń
  3. Te trzy tematy z ADC które ukazały się dzisiaj, były jednym wielkim artykułem, który ja jako redaktor postanowiłem podzielić, na trzy osobne, by były bardziej strawne (możecie zauważyć, że programy do pobrania mają kolejne numery).

    Dlatego też ostatni temat dot. kanałów jest faktycznie dalszym ciągiem tego samego kodu, co zauważył kol. MrLol.

    Tryb Free Running Drzasiek omówił we wcześniejszym artykule: ADC - Prezentacja wyniku na LCD

    OdpowiedzUsuń
  4. Ale czasami zależy nam aby pomiary odbywały się co ściśle określony czas a nie wtedy gdy przetwornik zakończył przetwarzanie.
    Po prostu próbkować.
    Np. gdy chcemy zrobić oscyloskop.

    OdpowiedzUsuń
  5. Linia: "TIMSK |= (1<<TOIE0) | (1<<TOIE1);" włącza przerwania zarówno dla Timer0 (8bit) i Timer1(16bit). Wystarczy "TIMSK |= (1<<TOIE0);" w w/w przykładzie.
    Pozdrawiam, Grzesiek.

    OdpowiedzUsuń
  6. Mimo wszystko warto w przerwaniu sprawdzić flagę zajętości ADC, w takiej formie jak jest obsłużony przetwornik, w niektórych przerwaniach będzie czytał "śmieci" z przetwornika...

    OdpowiedzUsuń
    Odpowiedzi
    1. Nie jest konieczne sprawdzanie flagi zajętości ADC - jeden warunek - odstęp pomiędzy przerwaniami timera musi być dłuższy niż czas przetwarzania. W przeciwnym przypadku w ogóle należałoby zrezygnować z timera i puścić ADC w tryb free running ze zgłaszaniem przerwań po każdej konwersji.

      Usuń
  7. Może ktoś mi wytłumaczyć co dzieje się w tym przerwaniu ? Co robi tam funkcja switch i case. Jak to odczytuje tam wartości z kolejnych pinow

    OdpowiedzUsuń
    Odpowiedzi
    1. Wszystko jest opisane w komentarzach. Czego konkretnie nie rozumiesz?

      Usuń
  8. W jaki sposób prosto możemy przeliczyć te wartości na Volty?
    Próbuje w funkcji sprintf niestety nie daje to rezultatu.
    Ogólnie połączyłem ten program z programem o USART, dane wyświetlają się na terminalu przez BT. Tylko mam problem z konwersją oraz czyszczeniem ekranu w ten sposób, żeby pomiary wyskakiwały cały czas w tym samym miejscu jeden pod drugim.
    Proszę o pomoc. Pozdrawiam.

    OdpowiedzUsuń
    Odpowiedzi
    1. Przykład przeliczania pokazany jest w innej części Kursu AVR: ADC - Prezentacja wyniku na LCD

      Jaki terminal i jak to robisz.

      Usuń
    2. Od razu muszę przyznać, że jestem początkującym w dziedzinie programowania, a muszę zrealizować projekt, który będzie odczytywał wartości napięcia z kilku kanałów ADC i wysyłał te dane do aplikacji na androida.Dodatkowo z telefonu muszę odbierać znaki do avr, żeby móc sterować ustawieniami digipota po SPI, ale to na później. Chwilowo dane zamiast na telefonie odbieram sobie przez moduł HC-05 na terminalu na komputerze. Proszę o jakąkolwiek pomoc. (Zakupiłem książke Tomasza Francuza, ale niestety nie zdążę się w tym momencie z nią zapoznać. Swoja drogą Dondu też musisz chyba coś skrobnąć, bo na stronie nieźle Ci to idzie ;)). Program, który mam tak jak wspomniałem to zlepek http://mikrokontrolery.blogspot.com/2011/03/rs-232-atmega8-komputer-terminal.html i http://mikrokontrolery.blogspot.com/2011/03/adc-multipleksowanie.html
      Pozdrawiam.

      Usuń
    3. Najlepiej by było, gdybyś opisał swój problem na forum, a tutaj wstawił link do tematu, byśmy mogli tam trafić.

      Usuń
    4. Proszę link do tematu:
      http://www.elektroda.pl/rtvforum/topic3149820.html
      Wkleiłem również cały kod, który w tym momencie posiadam.
      Dzięki za zainteresowanie ;)

      Usuń
  9. Witam, mam dwa poważne problemy z powyższym kodem:
    Po pierwsze zmiana wartości na jednym kanale powoduje zmianę wartości na pozostałych wejściach
    Po drugie przetwornik nawet bez podpięcia przewodów do wejścia szczytuje jakieś śmieci. Ale ten problem może być spowodowany podłączeniem układu na płytce stykowej.

    OdpowiedzUsuń
  10. Witam
    Nie wiem czy dobrze zauważyłem ale w części odpowiedzialnej za inicjalizację ADC przed znakiem "=" brakuje "|". U mnie efekt był bardzo podobny do tego który opisuje "brylaas". wszystko działa poprawnie gdy dodałem |.
    Testuje na EVB4.3 z Atmega16.
    Pozdrawiam Autora (Bardzo pomocny materiał)
    Karol

    OdpowiedzUsuń