Pomiar napięcia i przetwornik ADC
Prawie wszystkie mikrokontrolery z rodziny AVR zawierają przetwornik ADC. Dzięki temu modułowi możemy zmierzyć napięcie. Pomiar napięcia otwiera nam bardzo wiele możliwości, na przykład wiele czujników jest opartych o rezystancję, np fotorezystor. Mając możliwość pomiaru napięcia możemy określić stopień rozładowania baterii a także kontrolować wiele przycisków za pomocą jednego pinu. Można na przykład podpiąć kilka przycisków micro switch do jednego pinu mikrokontrolera i różnych rezystorów, w ten sposób poprzez pomiar napięcia będzie można określić, który przycisk został wciśnięty.
Wiele urządzeń działa analogowo. Rozwiń swoją wyobraźnię, pomyśl chwilę co jeszcze możemy zrealizować i ruszamy z tematem bez dalszego owijania w bawełnę.
Jak zmierzyć napięcie za pomocą mikrokontrolera Atmega88PA?
Do tego ćwiczenia będziemy potrzebować jakiegoś wyświetlacza LCD (ja wykorzystam mój z poprzednich części 4×20 i bibliotekę z artykułu Piszemy własną bibliotekę dla wyświetlacza LCD(HD44780) na AVR atmega88PA) i potencjometru (ewentualnie kilku różnych rezystorów). Po prostu podłączymy zasilanie przez potencjometr/rezystor, wyświetlimy wynik na wyświetlaczu i zmienimy wartość rezystancji i będziemy obserwować zmiany napięcia.
Zacznijmy od implementacji wyświetlacza. Niestety będę musiał zmienić podpięcie ponieważ będę potrzebował co najmniej jednego pinu ADC. W mikrokontrolerze Atmega88PA piny portu PC0 – PC5 będą odpowiednie dla pomiaru. Dzisiaj wykorzystam do pomiarów PIN PC0 PORTC. Wyświetlacz podpinam następująco:
#define LCD_PIN_RS (1<<PB6)
#define LCD_PIN_RW (1<<PB7)
#define LCD_PIN_E (1<<PB0)
#define LCD_PIN_4 (1<<PC5)
#define LCD_PIN_5 (1<<PC4)
#define LCD_PIN_6 (1<<PC3)
#define LCD_PIN_7 (1<<PC2)
Pamiętaj żeby odpowiednio dostosować kod w twoim pliku nagłówkowym lcd.h, Inaczej wyświetlacz nie będzie działał.
Do tego całego eksperymentu potrzebuję jeszcze jakiegoś źródła zasilania, w którym chciałbym zmierzyć napięcie. Mam akurat pod ręką koszyczek na baterie paluszki, załadowany widocznie jeszcze nie rozładowanymi bateriami co daje mi napięcie lekko ponad 6V. Na wszelki wypadek zmierzyłem moim miernikiem aby mieć pewność czy wynik będzie dobry.
Plus podpinam do mojego pinu mikrokontrolera PC0 a minus do GND. Teraz można przejść do napisania funkcji. (za chwilę dodamy do tego jeszcze rezystory).
Jak działa pomiar napięcia w mikrokontrolerze Atmega88PA?
W mikrokontrolerach z rodziny AVR pomiar napięcia na wejściach ADC odbywa się na zasadzie porównań. Dlatego też potrzebujemy źródła zasilania, z którym będziemy porównywać. I tutaj do dyspozycji mamy kilka możliwości. Zazwyczaj porównujemy z wewnętrznym źródłem, często w mikrokontrolerach AVR wynosi ono 1,1V lub 2,56V. Informację tę można znaleźć w nocie katalogowej i tak np dla mojego mikrokontrolera Atmega88PA będzie 1,1V. Dzisiaj szukamy rozdziału Analog-to-Digital Converter (czyli w skrócie ADC), już na początku znalazłem informację od dostępnym napięciu do porównania.
Możemy też wykorzystać źródło, którym zasilamy mikrokontroler ale nie jest to najlepsze rozwiązanie. Chociażby jak zasilamy mikrokontroler baterią to napięcie spada.
Wybraliśmy źródło wewnętrzne 1,1V. Teraz trzeba będzie napisać program, który porówna nasze napięcie z tym podłączonym do pinu, który badamy. W zależności od tego ile bitów oferuje mikrokontroler otrzymamy wynik z przedziału 0 – 1023 (wersja 10-bit) lub 0 – 154 (wersja 8-bit). Górną wartością będzie maksymalna liczba jaką można zapisać w dostępnej ilości bitów minus jeden. Moja wersja jest 10-bitowa jak większość w mikrokontrolerach AVR. Tę informację także pobieram z noty katalogowej.
Liczba, którą otrzymamy w wyniku pomiaru będzie odzwierciedlać stosunek wartości na wejściu do wartości napięcia na naszym źródle, z którym porównujemy. Na przykład gdybyśmy otrzymali wynik pomiaru 500 mając dostępnych 10 bitów i porównanie do 1,1V oznaczałoby to że na wejściu mamy ok 0,54V.
Obliczenia wyniku pomiaru ADC w mikrokontrolerze AVR
Wynik pomiaru / 1023 * 1,1V = Napięcie na wejściu
Czyli dla naszego przykładu wyżej to będzie (500/1023)*1,1 = 0,537..
Niestety nie jest to tak do końca proste ponieważ problem pojawia się gdy wartość na wejściu będzie większa niż ta porównywana. W momencie przekroczenia wartości napięcia wynik będzie zawsze wartością maksymalną czyli w moim przypadku powinno to być zawsze 1023. (tak poza tym nie za dobrze jest podłączać do mikrokontrolera więcej niż 5V, więc nie rób tego w domu, zamiast tego weź np baterię 1,5V paluszek 😛 ).
Oczywiście jest na to rozwiązanie w postaci dzielnika napięcia, który za chwilę sobie z grubsza omówimy. Można stworzyć dzielnik napięcia w taki sposób żeby można było mierzyć napięcie w dość dużej rozpiętości. Jednak czym większa rozpiętość tym mniejsza dokładność, ale w końcu nie budujemy dokładnego miernika bo do tego trzeba by było trochę więcej teorii no i elementów. Spróbujemy na razie napisać kod i zobaczyć czy to co napisane wyżej rzeczywiście będzie działać w taki sposób jak opisano.
Jak zaprogramować odczyt pomiaru napięcia?
Zacznijmy może od czegoś prostego na początek czyli pomiar z baterii paluszka z porównaniem z zasilaniem mikrokontrolera. W tym momencie mam podpięte zasilanie z USB około 5V. Mam pod ręką trochę rozładowaną baterię (zmieści się w pomiarze ze źródłem wewnętrznym 1,1V).
Na początek nasz program będzie wyglądał dość standardowo. Trzeba dołączyć bibliotekę do obsługi wyświetlacza LCD i zadeklarować funkcję do pomiaru. Nazwę ją checkVoltage(uint8_f changel). Funkcja będzie pobierała numer kanału mikrokontrolera.
#include <avr/io.h>
#include "lcd.c"
//deklaracja funkcji do pomiaru
uint16_t checkVoltage(uint8_t chanel);
Dalej przechodzimy do funkcji głównej naszego programu. Na początku inicjujemy wyświetlacz i ustawiamy wybór napięcia oraz preskeler. W pętli programu zmierzymy napięcie co sekundę za pomocą naszej funkcji i wyświetlimy na LCD.
int main(void)
{
lcdInit();
//wybór napięcia na źródło zewnętrzne 5V
ADMUX |= (1 << REFS0);
//włączam moduł ADC i ustawiam preskeler na 8
ADCSRA |= (1 << ADEN) | (1 << ADPS1) | (1 << ADPS0);
while (1)
{
lcdClear();
lcdStr("Wynik: ");
lcdInt(checkVoltage(0));
_delay_ms(1000);
}
}
No i teraz najważniejsza funkcja do odczytu pomiaru gdzie w rejestrze ADMUX ustawiam kanał czyli w praktyce wybór pinu mikrokontrolera, z którego odczytam napięcie. Dalej odpalam pomiar i w pętli czekam aż skończy się mierzyć. Na koniec zwracam wynik, czyli zapisaną wartość do rejestru ADCW, gdzie powinna się znaleźć 10 bitowa wartość z przedziału 0 – 1023.
uint16_t checkVoltage(uint8_t chanel) {
//ustawiam kanał
ADMUX |= (ADMUX & 0xF8) | chanel;
//start pomiaru
ADCSRA |= (1 << ADSC);
//czekam na koniec pomiaru
while(ADCSRA & (1 << ADSC));
//zwracam wynik
return ADCW;
}
Ja widać na powyższym obrazku otrzymaliśmy 211. Gdybyśmy spróbowali podstawić wynik pod wzór to:
(211 / 1023) * 5 = 1,031V
Czyli wynik bardzo nieznacznie odbiega od tego co udało się uzyskać zaawansowanym miernikiem. Legendy głoszą że te napięcie, które wykorzystujemy do zasilania niekoniecznie jest równe dokładnie 5V. Dodatkowo mówi się też że taki pomiar z mikrokontrolera nie jest zbyt dokładny ale do podstawowych zastosowań jak kontrola rozładowania akumulatora w zupełności wystarczy.
Spróbujmy teraz omówić trochę rejestry i wykorzystać wewnętrzne źródło zasilania 1,1V.
Analiza dokumentacji
Aby lepiej zrozumieć co się dzieje w kodzie warto zajrzeć do noty katalogowej i obejrzeć rejestry oraz co jak możemy ustawić. Na początek patrz na schemat mikrokontrolera, zaznaczone są tam wejścia ADC i każde ma jakiś numer, te numery to właśnie kanały, do których będzie się odnosić w kodzie.
Przechodzimy dalej w dokumentacji i szukamy rejestru ADMUX. Da się znaleźć tabelkę, z której możemy wyczytać co tam się da ustawić.
Powyżej wrzuciłem screen wyżej wymienionej tabelki. REFS1 i REFS0 służą do wyboru źródła do porównania. Jest tutaj też tabelka, które bity trzeba ustawić aby wybrać żródło zewnętrzne a kiedy wewnętrzne.
Z powyższego już powinieneś wiedzieć że aby przełączyć się ze źródła zasilania ok 5V na źródło wewnętrzne 1,1V należy dodatkowo ustawić bit REFS1 na jedynkę czyli:
//wybór napięcia na źródło zewnętrzne 1,1V
ADMUX |= (1 << REFS0) | (1 << REFS1);
Dalej w tym rejestrze mamy 4 bity MUX0..3, dzięki nim wybieramy kanał. Domyślnie wszystkie są na zero więc nie trzeba wybierać przy wejściu ADC0. Ale my to rozwiązaliśmy znacznie lepiej pisząc funkcję i przekazując dowolny kanał za pomocą linijki
//ustawiam kanał
ADMUX |= (ADMUX & 0xF8) | chanel;
Oczywiście w dokumentacji jest też tabelka z opisem ustawiania kanałów.
Dalej mamy rejestr ADCSRA, który wykorzystaliśmy do uruchomienia pomiaru ustawiając bit ADSC na 1. I ten bit automatycznie zmieni się na 0 po zakończeniu pomiaru i zapisaniu wyniku. To w sumie też można wyczytać z dokumentacji
Z tego rozdziału możemy też wyczytać jak ustawić preskeler, ponieważ to właśnie w rejestrze ADCSRA znajdują się odpowiednie bity. Gdzieś niżej znajdzie się taka tabelka.
Jak widzisz umiejętność czytania i korzystania z not katalogowych jest bardzo cenna i jak dobrze to opanujesz nie będziesz potrzebować szukać artykułów takich jak ten, który właśnie czytasz.
Wynik pomiaru ADC w Voltach
Do tej pory wyświetliliśmy wynik pomiaru jak podał mikrokontoler. Przydało by się zmienić to na bardziej czytelną dla nas postać w Voltach. Można to zrobić na kilka sposobów, najprościej byłoby wykorzystać tym zmiennoprzecinkowy ale wówczas trzeba by było to obsłużyć na wyświetlaczu. No i takie operacje pochłoną więcej zasobów. Spróbujmy trochę pomnożyć, podzielić, rozbić..
lcdClear();
lcdStr("Wynik: ");
//porównuję z 1.1V
float compare = 1023/1.1;
result = checkVoltage(0);
//obliczam liczbę całkowitą wyniku w V
int r1 = result/compare;
//obliczam liczbę dziesiętną
float r2 = (result - (r1*compare))*100 / compare;
//wyświetlam liczbę całkowitą
lcdInt(r1);
lcdStr(".");
//dopisuję zero jeśli mniej jak 10 bo inaczej wychodzi 1.4 zamiast 1.04
if(r2 < 10){
lcdInt(0);
}
lcdInt(r2);
lcdStr(" V");
_delay_ms(1000);
Tak to można na przykład zrobić. Objaśniłem w komentarzach, ale można to zrobić lepiej. W sumie z doświadczenia wyszło mi że wewnętrzne źródło zasilania na około 1.05V. Po skorygowaniu wzorów w kodzie do tej wartości wynik wyszedł mi całkiem przyzwoicie.
Jak zrobić dzielnik napięcia?
Aby zrobić dzielnik napięcia potrzebujesz w zasadzie tylko dwóch rezystorów o odpowiednich parametrach no i umiejętności posługiwania się odpowiednim wzorem aby te parametry (czytaj rezystancję) wyliczyć. A wzór ten to:
Napięcie wyjściowe = (R2/(R1+R2)) * Napięcie wejściowe
W przypadku naszego zadania mamy cztery paluszki ale powiedzmy że chcielibyśmy być w stanie zmierzyć napięcie do 15V. To będzie można to podłączyć nawet do akumulatora 12V.
Tutaj istotne jest to czy chcemy porównywać napięcie otrzymane z dzielnika do 5V czy do 1.1V. Załóżmy że porównujemy do wewnętrznego 1.1V.
1,1V = (R2/(R1+R2))* 15V
W sumie to można założyć że R2 będzie miał wartość 10K wówczas wystarczy dobrać R1 żeby było dobrze.
1,1 = 10/(10+R1) *15 tak to będzie wyglądać jak podstawię dane które mam
1,1/15 = 10/(10+R1) dziele obie strony przez 15V
0,0733333 * (10+R1) = 10 teraz dzielę przez nawias z niewiadomą
0,733333 + 0,073333R1 = 10
0,07333333R1 = 9,2666666 odejmuję od obu stron liczbę z lewej
R1 = 126 dzielę obie strony przez 0,0733333.. zaokrąglam do pełnych
Podstawiam żeby się upewnić że dobrze mi wyszło
(10/(10+126))*15 = 1,1029… V
Z powyższych obliczeń nawet jak nie chcę koniecznie zastosować 10K to wiem że drugi rezystor powinien być o ok 12,6 razy większy od tego pierwszego.
No i na koniec jeszcze schemat jak należałoby to połączyć.
Czyli według tego co powyżej nasze źródło zasilania podłączamy plusem pod R1 a minusem pod R2 i ze środka wyprowadzamy kabel do naszego pinu albo innego miernika czy czegokolwiek gdzie chcemy takie napięcie uzyskać.
Budujemy dzielnik napięcia
Wiemy już jak to liczyć więc może przyszedł czas aby zbudować taki dzielnik w praktyce. Do prostego ćwiczenia potrzebujemy dwa rezystory (w sumie nawet nie muszą być różne) oraz miernik napięcia. Oczywiście też źródło zasilania, którego napięcie będziemy dzielić.
Mam dwa rezystory; R1 = 2148, R2 = 328, Uwejściowe = 5,597V
Spróbujmy podstawić to pod wzór i policzyć ile powinno wyjść na wyjściu.
U wyjściowe = (328/(328+2148))* 5,597V = 0,741444V
Z powyższych obliczeń wychodzi mi że po zbudowaniu takiego dzielnika i podpięciu miernika powinno mi wyjść około 0,74V. Przeprowadziłem taki eksperyment efekt poniżej. Zaraz spróbuję to zamontować do mojego projektu z pomiarem dzięki czemu będę mógł zmierzyć napięcie do kilku Voltów.
Odchylenie wyszło mi niecałe 0,5% czyli śmiało można uznać wynik za prawie idealny i różnicę przypisać do błędu pomiaru, komponentów lub spadku napięcia na źródle od momentu kiedy mierzyłem pierwszy raz a zmontowałem poprawnie dzielnik napięcia. Pozostało tylko zaimplementować to rozwiązanie do projektu.
Pomiar ADC i dzielnik napięcia
Z powyższego ważne że jeśli chcesz zrobić dzielnik napięcia, który będzie obniżał napięcie o więcej niż połowę to umieszczasz większy rezystor przy plusie a mniejszy przy minusie. W przypadku gdybyś chciał zrobić np z 5V do 4V to należałoby dać większy rezystor przy plusie.
Teraz podłączymy napięcie do pomiaru przez dzielnik napięcia do naszego pinu ADC mikrokontrolera. Należy w tym celu VCC czyli plus naszej baterii podłączyć przez większy rezystor w tym przypadku 2148 Ohmów a następnie GND czyli minus baterii do tego samego pinu przez rezystor mniejszy, u mnie 328 Ohmów. Trzeba też pamiętać o połączeniu GND mikrokontrolera z minusem źródła zasilania czyli w tym przypadku mojego koszyczka z 4 paluszkami (już trochę rozładowanymi). Jeśli poprzednie ćwiczenie z pomiarem małej baterii oraz drugie z dzielnikiem zostały wykonane dobrze to teraz na LCD powinien być zbliżony wynik do tego z pomiaru dzielnika.
Teraz pozostaje uwzględnić dzielnik w wyniku, aby mieć napięcie źródła wejściowego zamiast napięcie z wyjścia dzielnika. W tym celu wynik pomiaru mnożymy proporcjonalnie do działania dzielnika. Jak nam wyszło 0,74V na wyjściu gdy mieliśmy 5,59V na wejściu to wynik z odczytu do wyniku realnego jak 0,74V/5,59V. Czyli to będzie.
Wynik realny = (wynik pomiaru * Napięcie wejściowe) / napięcie wyjściowe
Wr = Wp * 5,59V / 0,74V
Wr = Wp * 7,554
I teraz powyższe wnioski wstawiamy do naszego kodu programu aby policzyć ile Voltów ma naprawdę źródło zasilania. W sumie niewiele to pracy bo wystarczy w jednym miejscu pomnożyć o ile nie poprawiłeś jeszcze mojego powyższego kodu od wyświetlania.
..
//porównuję z 1.1V
float compare = 1023/1.1;
result = checkVoltage(0)*7.554;
//obliczam liczbę całkowitą wyniku w V
int r1 = result/compare;
//obliczam liczbę dziesiętną
float r2 = (result - (r1*compare))*100 / compare;
//wyświetlam liczbę całkowitą
lcdInt(r1);
lcdStr(".");
//dopisuję zero jeśli mniej jak 10 bo inaczej wychodzi 1.4 zamiast 1.04
if(r2 < 10){
lcdInt(0);
}
lcdInt(r2);
lcdStr(" V");
..
Ostatecznie nawet fajnie to wyszło. Mam naprawdę małe odchylenie. Można by tutaj jeszcze sporo powiedzieć o pomiarach na przykład budując amperomierz, ale to zostawię na inną okazję. A jeśli potrzebujesz mierzyć natężenia już teraz to polecam niebiską książkę.
I na koniec fotka jako dowód że u mnie działa 😉