wtorek, 29 marca 2011

ADC - Ile da się wycisnąć? - czyli eksperymenty z ADC

Autor: drzasiek
Redakcja: dondu


Drzaśkowy pamiętnik: Spis treści


Po uruchomieniu ADC, przetestowaniu konwersji wartości zmierzonej na napięcie i odczycie napięcia na LCD przyszła pora na eksperymenty.

Oprócz rozdzielczości, dla przetwornika podstawowym parametrem jest częstotliwość próbkowania. Zacząłem się więc zastanawiać ile próbek na sekundę można wycisnąć z tego przetwornika?


Dokumentacja mówi:


kSPS - co  to za magiczna jednostka?
To szybkość przetwornika ADC wyrażona ilością pomiarów na sekundę (ang. kilo Samples Per Second), a po naszemu tysięcy próbek na sekundę. Czyli 15kSPS oznacza 15 tysięcy próbek na sekundę, czyli wykonanie 15 tysięcy pomiarów w ciągu jednej sekundy - całkiem nieźle, ale dla mnie mało!!! :-)

Czyli przy 10 bitach rozdzielczości max częstotliwość próbkowania to 15 kHz.
Zatem obliczyłem:



Ale trzeba doliczyć czas każdej konwersji (pomiaru), który:


trwa 13 cykli zegara napędzającego przetwornik ADC, stąd:


i tyle właśnie powinien wynosić okres zegara taktującego ADC

Stąd policzyłem częstotliwość zegara ADC, którą powinienem ustawić:


Można więc powiedzieć, że według dokumentacji, dla uzyskania 10 bitowej rozdzielczości mogę taktować przetwornik z częstotliwością nie większą niż 200 kHz.

Próby przeprowadziłem praktycznie na tym samym układzie co do tej pory, jedynie z dodanym potencjometrem 100R w szeregu z potencjometrem 10k tak, by mieć większą precyzję nastaw napięcia.

Procesor taktowany był kwarcem 16 MHz.



Bardzo istotny jest sposób podłączenia mikrokontrolera: ADC - Dokładność vs podłączanie
Jeżeli nie podłączysz go prawidłowo, nie będziesz miał szansy zbliżyć się do dokładności pomiarów, jaką osiągnąłem i prezentuję w dalszej części tego artykułu.

Zatem sprawdziłem w tabeli jakie mam możliwości preskalera, aby uzyskać częstotliwość taktowania ADC zbliżoną do 200 kHz:


Dla tych co nie wiedzą, co to jest preskaler: Prescaler, postscaler - Co to takiego?



Najbliższy okazał się preskaler 64:


Dla takiej częstotliwości policzyłem czas jednego cyklu zegara ADC:


oraz policzyłem czas jednego pomiaru (konwersji), który zgodnie z Table 22-1 (patrz rysunek wyżej) wynosi 13 cykli zegara ADC:


Teraz pozostało już tylko policzyć częstotliwość próbkowania jaką osiągnę tak ustawionym przetwornikiem ADC:


Otrzymałem 19,2kHz, czyli 19,2kSPS.
Niestety to przekraczało zalecenia z dokumentacji (max 15kSPS), ale postanowiłem spróbować :-)

Aby lepiej zobaczyć stabilność pomiaru wróciłem w programie do wyświetlania 10 bitowej wartości pomiaru, ale bez przeliczania na napięcie.

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

//definicja napiecia referencyjnego
#define VREF 5.0
//definicja ADCIN (wejście ADC)
#define ADCIN PC5 

volatile uint16_t adc;//zmienna do pomiaru ADC

//##############################################################################
void main(void)
{
  char wynik[]="    ";//bufor tekstowy, wyczyszczenie bufora
   
  LCD_Initalize();   //inicjalizacja LCD
  LCD_GoTo(1, 0);      //Ustawienie kursora w pozycji (0,0)
  LCD_WriteText("19 kHz 10 bit");

  //Inicjalizacja ADC
  ADCSRA = (1<<ADEN)   //Bit 7 - ADEN: ADC Enable (uruchomienie przetwornika)
            |(1<<ADFR)      //tryb Free run
            |(1<<ADIE)      //uruchomienie zgłaszania przerwań
            |(1<<ADSC)      //rozpoczęcie konwersji
            |(1<<ADPS1)
            |(1<<ADPS2);   //ADPS2:1: (ustawienie preskalera) preskaler= 64

  ADMUX  =  (1<<REFS0)             //VCC jako napięcie referencyjne
           |(1<<MUX2) | (1<<MUX0); //Input Channel Selections (ADC5 - Pin 5 )

  DDRC &=~ (1<<ADCIN);             //Ustawienie Wejścia ADC

  sei();//Globalne uruchomienie przerwań 

  for(;;)//główna pętla programu
  {
       LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
       LCD_WriteText("    ");  //Czyszczenie poprzednij wartości
       itoa(adc,wynik,10);     //konwersja wyniku do tablicy char
       LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
       LCD_WriteText(wynik);   //Wyświetlenie wyniku
       _delay_ms(100);         //opóźnienie
  }
}


ISR(ADC_vect)//obsługa przerwania po zakończeniu konwersji ADC
{
   adc=ADC;   //odczytaj pomiar
}
Pobierz program: prog1.rar

Przy częstotliwości próbkowania ok.19 kHz rozdzielczość 10 bitowa nadal była zachowana.
Pomiar był stabilny, a przy bardzo powolnych zmianach napięcia wynik zmniejszał się lub zwiększał o 1, a więc nadal 10 bitowa rozdzielczość !!!



Próbowałem więc dalej. Kolejnym preskalerem jest 32. Wykonałem po kolei te same obliczenia:


i naniosłem poprawki w programie:

//Pomiar napięcia przetwornikiem A/C, prezentacja wyniku na LCD 2x16 HD44780
//Drzasiek 2011
//Atmega8 zegar 16MHz

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

//definicja napiecia referencyjnego
#define VREF 5.0
//definicja ADCIN (wejście ADC)
#define ADCIN PC5 

volatile uint16_t adc;//zmienna do pomiaru ADC


void main(void)
{
  char wynik[]="    ";//bufor tekstowy, wyczyszczenie bufora
   
  LCD_Initalize();   //inicjalizacja LCD
  LCD_GoTo(1, 0);      //Ustawienie kursora w pozycji (0,0)
  LCD_WriteText("38 kHz 10 bit");

  //Inicjalizacja ADC
  ADCSRA = (1<<ADEN)      // ADC Enable (uruchomienie przetwornika)
            |(1<<ADFR)      //tryb Free run
            |(1<<ADIE)      //uruchomienie zgłaszania przerwań
            |(1<<ADSC)      //rozpoczęcie konwersji
            |(1<<ADPS0)
            |(1<<ADPS2);   //ADPS2, ADPS0: (ustawienie preskalera) preskaler= 32

  ADMUX  =  (1<<REFS0)            //VCC jako napięcie referencyjne
           |(1<<MUX2) | (1<<MUX0);   //Wybór wejścia (ADC5 - Pin 5 )

  DDRC &=~ (1<<ADCIN);               //Ustawienie pinu wejściowego ADC

  sei();//Globalne uruchomienie przerwań 

  for(;;)//główna pętla programu
  {
      LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
      LCD_WriteText("    ");  //Czyszczenie poprzednij wartości
      itoa(adc,wynik,10);     //konwersja wyniku do tablicy char
      LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
      LCD_WriteText(wynik);   //Wyświetlenie wyniku
      _delay_ms(100);         //opóźnienie
  }
}

ISR(ADC_vect)//obsługa przerwania po zakończeniu konwersji ADC
{
   adc=ADC;   //odczytaj pomiar
}
Pobierz program: prog2.rar


Pomiar nadal był stabilny, wynik przy powolnych zmianach napięcia zmniejszał się lub zwiększał o 1, a więc nadal rozdzielczość 10 bitowa byłą zachowana pomimo, że ponad dwukrotnie przekraczałem dopuszczalne parametry pracy ADC!!!

Kolejny preskaler to 16. I znowu takie same obliczenia:


To "kosmicznie" niezgodne z parametrami ADC tego mikrokontrolera (przekroczenie ponad 4 krotne!!!), ale chciałem zobaczyć jaki będzie efekt końcowy. Ponownie więc próba przy 10 bitach rozdzielczości:

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

//definicja napiecia referencyjnego
#define VREF 5.0
//definicja ADCIN (wejście ADC)
#define ADCIN PC5 

volatile uint16_t adc;//zmienna do pomiaru ADC


void main(void)
{
  char wynik[]="    ";//bufor tekstowy, wyczyszczenie bufora
   
  LCD_Initalize();   //inicjalizacja LCD
  LCD_GoTo(0, 0);      //Ustawienie kursora w pozycji (0,0)
  LCD_WriteText("76,9 kHz 10 bit");

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

  ADMUX  =  (1<<REFS0)            //VCC jako napięcie referencyjne
           |(1<<MUX2) | (1<<MUX0);   //Wybór wejścia (ADC5 - Pin 5 )

  DDRC &=~ (1<<ADCIN);               //Ustawienie pinu wejściowego ADC

  sei();//Globalne uruchomienie przerwań 

  for(;;)//główna pętla programu
  {
      LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
      LCD_WriteText("    ");  //Czyszczenie poprzednij wartości
      itoa(adc,wynik,10);     //konwersja wyniku do tablicy char
      LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
      LCD_WriteText(wynik);   //Wyświetlenie wyniku
      _delay_ms(100);         //opóźnienie
  }
}


ISR(ADC_vect)//obsługa przerwania po zakończeniu konwersji ADC
{
    adc=ADC;   //odczytaj pomiar
}
Pobierz program: prog3.rar


Przegiąłem ostro :-)
Teraz przy zmianie napięcia było już widać nieliniowe skoki zmiany wartości.

Pora więc zrezygnować z 10 bitów rozdzielczości i zadowolić się 8 bitami. Zrobiłem to rezygnując z dwóch najmniej znaczących bitów. W dokumentacji znalazłem podpowiedź:



Jak widać wynik pomiaru przechowywany jest w 16 bitowym rejestrze ADC, który składa się z dwóch 8 bitowych rejestrów ADCH (starsza część) oraz ADCL (młodsza część).

Tabela 22.8.3.2 podpowiada, że gdy bit ADLAR = 1 to wynik w rejestrze ADC będzie wyrównany do lewej strony a więc w rejestrze ADCH znajdzie się 8 najstarszych bitów pomiaru ADC, a więc to, co mnie akurat interesuje.

Pora więc na program:

//kod4

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

//definicja napiecia referencyjnego
#define VREF 5.0
//definicja ADCIN (wejście ADC)
#define ADCIN PC5 

volatile uint8_t adc;//zmienna do pomiaru ADC


void main(void)
{
  char wynik[]="    ";//bufor tekstowy, wyczyszczenie bufora
   
  LCD_Initalize();   //inicjalizacja LCD
  LCD_GoTo(1, 0);      //Ustawienie kursora w pozycji (0,0)
  LCD_WriteText("76,9 kHz 8 bit");

  //Inicjalizacja ADC
  ADCSRA = (1<<ADEN)      // ADC Enable (uruchomienie przetwornika)
           |(1<<ADFR)      //tryb Free run
           |(1<<ADIE)      //uruchomienie zgłaszania przerwań
           |(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
           |(1<<MUX2) | (1<<MUX0);   //Wybór wejścia (ADC5 - Pin 5 )

  DDRC &=~ (1<<ADCIN);               //Ustawienie pinu wejściowego ADC

  sei();//Globalne uruchomienie przerwań 

  for(;;)//główna pętla programu
  {
      LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
      LCD_WriteText("    ");  //Czyszczenie poprzednij wartości
      itoa(adc,wynik,10);     //konwersja wyniku do tablicy char
      LCD_GoTo(5, 1);         //Ustawienie kursora w pozycji (5,1)
      LCD_WriteText(wynik);   //Wyświetlenie wyniku
      _delay_ms(100);         //opóźnienie
  }
}


ISR(ADC_vect)//obsługa przerwania po zakończeniu konwersji ADC
{
     adc=ADCH;   //odczytaj tylko starszy bajt pomiaru
}
Pobierz program: prog4.rar


Pomiar z rozdzielczością 8 bitową przy częstotliwości ok. 76,9kHz (kSPS) wyglądał całkiem prawidłowo.




Oczywiście taki pomiar nie daje zbyt dobrego obrazu jak szybko można tym przetwornikiem próbkować przy bardzo dobrej jakości i precyzji.


Podsumowanie

Najlepszym sposobem sprawdzenia jakości pomiaru przy większych częstotliwościach próbkowania jest podanie na wejście przetwornika sygnały zmiennego (najlepiej czysty sinus), spróbkowanie przynajmniej jednego okresu przebiegu do bufora i następnie narysowanie wykresu z tych próbek. Jeśli wyjdzie ładny sinus tzn.,  że z daną częstotliwością można próbkować, ale jeśli przebieg wyjdzie nieproporcjonalny, poszarpany itd. tzn., że dla tego przetwornika taka częstotliwość to już za szybko.

Druga sprawa, to maksymalna częstotliwość próbkowania niezależnie od uzyskanej rozdzielczości i jakości pomiaru. To, że w dokumentacji najmniejszym preskalerem jest 2 to nie znaczy, że przy częstotliwości taktowania procesora 16 MHz jeśli ustawimy preskaler ADC na 2 to zgodnie z wcześniejszymi obliczeniami uzyskamy częstotliwość próbkowania ponad 600 kHz.

Małe wartości preskalera są dla niższych częstotliwości taktowania procesora.

Przeczytaj także:

5 komentarzy:

  1. Kiedyś próbowałem podkręcać ATmegę128 wymieniając kwarce na szybsze. Przy 20MHz nie stwierdziłem żadnych problemów. Przy 24MHz też wszystko działało :) Kiedyś w EP podkręcali ATmega32 i doszli chyba nawet do 42MHz, ale sygnał zegarowy nie był zadawany kwarcem ale z generatora.

    OdpowiedzUsuń
  2. Nie wiem czy dobrze to rozumiem? Jak chcemy mieć rozdzielczość 8 bitów to bierzemy tylko starszy bajt ADC. A jak bym chciał tylko 6 bitów to co zrobić? Czy wszystko to oznacza, że rozdzielczość (liczba bitów) nie ma wpływu na czas konwersji i zawsze trwa ona 13 cykli zegara taktującego ADC?

    OdpowiedzUsuń
  3. Jeżeli chcesz tylko 6 bitów to po prostu olewasz dwa najmniej znaczące. Tak, konwersja zawsze trwa tyle samo, zmniejszając rozdzielczość (poprzez ignorowanie najmniej znaczących bitów) możesz po prostu taktować szybciej ADC. Przy rozdzielczości 1 bita można uzyskać nawet 10 Msps :) Oczywiście wtedy nie korzysta się z ADC, lecz pinu IO ;P

    OdpowiedzUsuń
  4. Czyli jeżeli przetwornik powinien być taktowany zegarem o częstotliwości 50-200kHz to stopień doboru podziału prescalera dobieramy tak aby zawsze uzyskać jak najwyższą częstotliwość? (np dla 8Mhz mamy prescaler 64 i 128 mieszczący się w zakresie więc wybieramy 64?)

    OdpowiedzUsuń
  5. To zależy. W trybie pojedynczej konwersji zazwyczaj dobiera się wysoki sample rate, chyba, że impedancja wyjściowa mierzonego źródła to ogranicza (AVR dysponuje wejściem SH i musi się zdążyć naładować kondensator wejściowy). W trybie free running, dobierasz częstotliwość próbkowania tak, aby odpowiadała wymaganej w zależności od próbkowanego przebiegu.

    OdpowiedzUsuń