Arduino i … pomiar napięcia na wejściach ADC.

Przedstawię trzy wersje programu odczytującego napięcie z wejścia analogowego.

Do prób posłużył klon Arduino Uno.

Program pierwszy:

int sensorValue = 0;

float voltage = 0;

long licznik = 0;

 

void setup()

{

  Serial.begin(9600);

}

 

void loop()

{

  licznik ++;

 

  sensorValue = analogRead(A0);

  voltage = sensorValue * (5.0 / 1023.0);

  if (licznik > 100000)

  {

    Serial.print(licznik);

    Serial.print(" > ");

    Serial.print(sensorValue);

    Serial.print(" > ");

    Serial.println(voltage);

  }

//  Szkic używa 3144 bajtów (9%) pamięci programu. Maksimum to 32256 bajtów.

//  Zmienne globalne używają 212 bajtów (10%) pamięci dynamicznej,

 

// czas wykonania 100000 pętli 13,86"

}

 

  

Program drugi:

 

  int sensorValue = 0;

  long licznik = 0; 

void setup()

{

  Serial.begin(9600);

}

void loop()

{

  licznik ++;

  sensorValue = analogRead(A0);

  if (licznik > 100000)

  {

    Serial.print(licznik);

    Serial.print(" > ");

    Serial.println(sensorValue);

  }

//  Szkic używa 1876 bajtów (5%) pamięci programu. Maksimum to 32256 bajtów.

//  Zmienne globalne używają 196 bajtów (9%) pamięci dynamicznej,

 

// czas wykonania 100000 pętli 12,54"

}

 

Te dwa programy w pierwszej kolejności wykonują 100000 odczytów wejścia analogowego A0.

Gdy nastąpi zwiększenie zmiennej „licznik” powyżej 100000 zostaje spełniony warunek „if”. Programy przesyłają do portu szeregowego licznik i odczytane wartości.

Czasy wykonania pętli są zbliżone, ale rozmiar programu drugiego jest znacznie mniejszy niż pierwszego.

Oznacza to, że jeśli chcemy by program załączył diodę LED gdy napięcie przekroczy 2,5V nie musimy odczytywać zmiennej „voltage”. Wystarczy porównać : „ sensorValue” i wartość 511.

Dlaczego 511? Ta wartość odpowiada napięciu 2,5V przy zasilaniu procesora napięciem 5,00V.

W dalszej części spróbuję wyjaśnić, że nie jest to w 100% prawdziwe

 

Program trzeci:

 

 

int sensorValue = 0;

long licznik = 0; 

long pom1 = 0;

long pom2 = 0;

 

void setup()

{

  Serial.begin(9600);

}

 

void loop()

{

 

  licznik ++;

  sensorValue = analogRead(A0);

  if (licznik > 100000)

  {

    pom1 = sensorValue * 5 / 1023;

    pom2 = sensorValue * 5 % 1023;

    pom2 = pom2 *1000;

    pom2 = pom2 / 1023;

    Serial.print(licznik);

    Serial.print(" > ");

    Serial.print(" input= ");

    Serial.print(sensorValue);

    Serial.print(" pom1 ");

    Serial.print(pom1);

    Serial.print(",");

    if (pom2 < 10)

    {

      Serial.print("0");

    }

    if (pom2 < 100)

    {

      Serial.print("0");

    }

 

    Serial.print(pom2);

 

    Serial.println();

  }

 

 

  /*

Szkic używa 2332 bajtów (7%) pamięci programu. Maksimum to 32256 bajtów.

Zmienne globalne używają 224 bajtów (10%) pamięci dynamicznej

*/

 

// czas wykonania 100000 pętli 12,64"

 

Trzeci program odczyty wykonuje z taką samą szybkością jak program drugi, ale wartości są przeliczane tak, że wyświetlane jest napięcie z dokładnością trzech miejsc po przecinku. Jest do dokładniej niż w pierwszym programie. Jednocześnie program trzeci zajmuje 1/3 pamięci mniej niż pierwszy i trochę więcej niż drugi. Udało się to uzyskać pomimo zastosowania dodatkowych zmiennych pomocniczych  „pom1” ,  „pom2” i wyświetlaniu wartości napięcia etapami.

 

 

Jak i dlaczego przeliczamy wartość odczytaną z przetwornika ADC na napięcie

 

Na płytce Arduino Uno ( i płytkach zgodnych ) mamy do dyspozycji 6 wejść analogowych , o rozdzielczości 10- ciu bitów. Rozdzielczość ta oznacza, że wartość odczytana (sensorValue) zawiera się w przedziale 0...1023. Maksymalne napięcie mierzone bezpośrednio jest równe napięciu zasilania procesora, czyli 5V. Rozdzielczość napięciowa ( w skrócie Rn ) zależy od napięcia wzorcowego na pinie Aref , które zwykle jest równe napięciu zasilania procesora (5V).

Rn = 5V / 1023  Rn = 0,004887V (w przybliżeniu = 4,89 mV ).

Jest to wartość, której przekroczenie zmieni wartość odczytaną w programie o 1.

Oznacza to że :

                dla napięcia  w zakresie 0 ... 0,00489V odczytamy wartość 0

                dla napięcia  w zakresie 0,00490 ... 0,00979V odczytamy wartość 1

                dla napięcia  w zakresie 0,00980 ... 0,01469V odczytamy wartość 2

                …........ itd...

                aż do wartości 5V i odczytu 1023

 

Daje to teoretycznie dokładność < 0,1%.  Jest to parametr stały, na który nie mamy wpływu. Drugim parametrem jest napięcie zasilające. Nie jest ono równe dokładnie 5V, ale jest napięciem wzorcowym (Aref). Jeśli zmieni się napięcie zasilania ( np. podłączymy płytkę arduino do innego komputera przez złącze USB), a nie zmienimy wartości napięcia odniesienia w linii programu

 

voltage = sensorValue * (5.0 / 1023.0);       // 5.0 to właśnie Aref

 

to zmienią się odczyty napięcia.

Przykład:

Procesor może być zasilany napięciem w zakresie 4,5 … 5,5 V. 

Przyjmijmy, że mierzymy napięcie zewnętrzne 2,5V, przy zasilaniu 5V i dla porównania przy zasilaniu 5,2V

 

W pierwszym przypadku odczytana wartość wyniesie teoretycznie :     2,5 / 5,0 * 1023 = 0,5 * 1023 = 511,5

W rzeczywistości może przyjmować tylko wartości całkowite, czyli wyniesie:    511.

Nie następuje tu zaokrąglenie, a tylko odcięcie części ułamkowej

 

W przypadku zasilania napięciem 5,2V:  2,5 / 5,2 * 1023 = 491.82... Po odcięciu części ułamkowej 491

 

Jeżeli wcześniej obliczoną rozdzielczość Rn ( 4,89 mV czyli 0,00489 V ) pomnożymy przez otrzymany  wynik to odczytana wartość napięcia (voltage) wyniesie 491 * 0,00489V  voltage = 2,40099V Błąd wynosi prawie 0,1 V.

Błąd wyrażony w procentach : 0,1V / 2,5V * 100% = 4%

 

Co zrobić by uniknąć takiego błędu ?

Możemy wykorzystać wewnętrzne źródło odniesienia. Oficjalnie wynosi ono 1,1V Tak podaje nota katalogowa.

Aby z niego skorzystać musimy w programie zadeklarować:

 

 analogReference(INTERNAL);

 

i dokonać zmiany w linii:

 

voltage = sensorValue * (5.0 / 1023.0);  

na :

voltage = sensorValue * (1,1 / 1023.0);  

 

Wartość maksymalna odczytana przez program (1023 ) będzie odnosiła się do napięcia 1,1V, a Rn=1,1V / 1023

Rn= 0,00107 V   ( 1,07mV )

 

Jeśli zmierzymy teraz napięcie na pinie AREF dowiemy się ile wynosi napięcie odniesienia procesora. W moim egzemplarzu wynosi ono 1,080V ( różnica 20mV czyli 1,8% ) W nocie katalogowej nie znalazłem informacji o dokładności tego napięcia

Plusem jest niezależność od użytego stabilizatora. A czy jest minus ?

TAK.

Polega on na tym, że chcąc zmierzyć napięcie w zakresie 1,1 … 5V musimy zastosować dzielnik napięcia. Inaczej mówiąc zmniejszyć napięcie wejściowe z 5V do maksymalnie 1,1 V

 

Nie jest to duży problem, dlatego, że w większości przypadków będziemy mierzyć napięcia wyższe

(12V, 15V, 24V i taki pomiar będzie wymagał dzielnika napięcia. Wartości rezystorów w dzielniku zmienią się w zależności od zastosowanego napięcia Aref i maksymalnego napięcia mierzonego.

 

WAŻNE: NAPIĘCIE WIĘKSZE OD 24V MOŻE BYĆ NIEBEZPIECZNE!

POD ŻADNYM POZOREM NIE WOLNO MIERZYĆ NAPIĘCIA W GNIEŹDZIE ZASILAJĄCYM 230V

 

 

P.S.

Troszkę o bitach i bajtach:

Jako ludzie posługujemy się systemem liczenia dziesiątkowym. Oznacza to, że posługujemy się cyframi 0,1,2,3,4,5,6,7,8,9.

Dziesięć jednostek niższego rzędu tworzą jedną jednostkę wyższego rzędu. Po doliczeniu do dziewięciu następną liczbą jest dziesięć. Brakło nam (ludziom ) symbolu na przedstawienie dziesiątki

Dziesięć to liczba bo przedstawiamy ją w postaci dwóch cyfr 1 i 0 zapis :10 (dziesięć)

1- oznacza ilość  „dziesiątek” a 0- ilość „jednostek” w liczbie.

Po doliczeniu do 99 następne jest 100 czyli jedna setka zero dziesiątek i zero jedności.

 

Procesory nie znają systemu dziesiętnego (personifikuję  ale tak jest łatwiej pisać). W ich systemie nie istnieje 2,3,4,5,6,7,8,9). Rozróżniają tylko dwa stany 0 i 1.  Są to „stany logiczne” ( Prawda albo Fałsz, Jest napięcie albo Brak napięcia) W procesorach oznacza to, że na pinie  jest napięcie 0V albo  napięcie zasilające. Czy 0V będzie zerem logicznym czy jedynką logiczną to kwestia umowna. Taki stan 0 albo 1 nazywany jest BITem - podstawową jednostką informacji

Jeśli nie ma cyfry 2 to co jest w zamian ?

Procesory wykorzystują dwójkowy (binarny) system liczenia. Używał go już John Napier w XVI wieku. Gdy do 1 dodamy 1 otrzymamy wynik (10)?  - czytamy „jeden-zero” w systemie dwójkowym, NIE dziesięć.

Kolejne liczby przedstawiane są następująco:

00 = (00000000)?

01 = (00000001)?

02 = (00000010)?

03 = (00000011)?

04 = (00000100)?

05 = (00000101)?

06 = (00000110)?

07 = (00000111)?

08 = (00001000)?

09 = (00001001)?

10 = (00001010)?

11 = (00001011)?

12 = (00001100)?

13 = (00001101)?

14 = (00001110)?

15 = (00001111)?

16 = (00010000)?

…....

255 = (11111111)?

…....

…....

1023 = (1111111111)?

 

Zapisałem zera  nieznaczące dla przejrzystości.

 

Liczby z zakresu 0 … 255  w binarnym zapisie zajmuje 8 znaków (BITów).

Taki ciąg ośmiu znaków nazywamy BAJTem

Dwa BAJty to WORD czyli słowo.

WORD może pryjmować wartości 0 … 65535 czyli (0)?  … (1111111111111111)? .

Dla przejrzystości zapisu grupujemy znaki po 4 (1111 1111 1111 1111)?

 

W arduino mamy więcej typów. Typ INT ( integer ) , liczby z zakresu -32768 … 32767

Typ INT to też dwa BAJTY ale BIT oznaczony „a” określa znak 0 - liczba dodatnia 1 - liczba ujemna,  pozostałe oznaczone x określają wartość.

( axxx xxxx xxxx xxxx)?

 

Na początku programu mamy deklaracje :

int sensorValue = 0;

zmienna sensorValue jest zadeklarowana jako integer zajmuje dwa bajty (16 bitów) czyli może być z zakresu  -32768 … 32767.

Dociekliwym proponuję zadeklarować zmienną jako WORD  i dla porównania jako BYTE a następnie sprawdzić jak zmienią się odczyty zmiennej „sensorValue” w całym zakresie, od 0 do 5V na wejściu A0.

 

73 Piotr SP7A