- zakres pomiarowy: 0…50 V,
- rozdzielczość pomiaru: 20 mV,
- dokładność pomiaru: 1%,
- zakres ustawień dla funkcji alarmowania: 0…50 V,
- rozdzielczość ustawień dla funkcji alarmowania: 10 mV,
- zasilanie: 4,5…10 V, ok. 25 mA.
Systemem steruje niewielki, ale nowoczesny mikrokontroler firmy Microchip (dawniej Atmel) typu ATXmega8E5. Wybór tego konkretnego układu z szerokiej palety rodziny AVR nie był przypadkowy. Zależało mi na tym, aby urządzenie odznaczało się dużą dokładnością pomiaru nie okupioną stosowaniem zewnętrznych przetworników ADC. To spowodowało, że odrzuciłem starsze konstrukcje mikrokontrolerów AVR (ATmega i ATtiny), gdyż wyposażono je w niezbyt dokładny, jak na nasze potrzeby, 10-bitowy przetwornik ADC oraz nieprecyzyjne źródło napięcia odniesienia.
Uwagę skierowałem na najnowsze konstrukcje mikrokontrolerów AVR z rodziny ATXmega, serii E. W przypadku tych układów mamy do dyspozycji wbudowany, 12-bitowy przetwornik ADC o zdecydowanie lepszych parametrach elektrycznych, jak i dość dokładne (w porównaniu z poprzednim) źródło napięcia odniesienia, z którego w końcowej aplikacji jednak nie skorzystałem. Zastosowałem tutaj zewnętrzną, bardzo dokładną referencję pod postacią układu LM4040CYM3-2.5 (U3), który nie dość, że bardzo tani, to dodatkowo odznacza się świetnymi parametrami elektrycznymi, co najmniej o rząd wielkości lepszymi, niż źródła wbudowane w strukturę mikrokontrolera ATXmega.
Budowa i działanie
Schemat ideowy układu pokazano na rysunku 1. Jest to bardzo prosty system mikroprocesorowy, w którym wykorzystano niewielką część wyprowadzeń mikrokontrolera, jak i jego zasobów sprzętowych. Jednak mikrokontroler realizuje następujące zadania: wykonuje cykliczny pomiar napięcia (10 razy/sek) doprowadzonego z wejścia VIN poprzez rezystancyjny dzielnik napięcia na wejście PA7 układu.
Pomiar wykonywany jest przy pomocy 12-bitowego przetwornika ADC pracującego w trybie single-ended. Co ciekawe i charakterystyczne dla mikrokontrolerów typu ATXmega, cała akwizycja danych odbywa się w pełni automatycznie, przy użyciu zasobów sprzętowych układu. Jest to możliwe dzięki powiązaniu ze sobą kilku podsystemów mikrokontrolera: timera TCC5, kanału 0 systemu zdarzeń oraz przetwornika ADC. Powiązanie pokazano schematycznie na rysunku 2.
Podstawowym podsystemem mikrokontrolera ATXmega8E5 inicjującym pomiar przetwornika ADC jest układ czasowo-licznikowy TCC5, który jest taktowany wysokostabilnym przebiegiem zegarowym o częstotliwości 250 kHz. Sygnał jest uzyskiwany dzięki podzieleniu zegara 2 MHz taktującego rdzeń mikrokontrolera przez preskaler o podziale 8. Zdarzenie przepełnienia licznika (OVF) jest generoweane co 25000 taktów zegara (PER=24999), czyli 10 razy na sekundę, i jest połączone z kanałem CH0 systemu zdarzeń (EVENT SYSTEM), dla którego stanowi źródło zdarzeń. Kanał CH0 systemu zdarzeń jest z kolei wyzwalaczem (TRIG) dla wbudowanego w strukturę mikrokontrolera przetwornika ADC, dzięki czemu możliwe jest próbkowanie wejściowego sygnału VIN w równych odstępach czasu (10 Hz). Dalej, przerwanie od zakończenia konwersji przetwornika ADC (ADCA_CH0_vect) odpowiedzialne jest wyłącznie za ustawienie flagi programowej ADCready dla programu obsługi aplikacji.
Warto również wspomnieć o innej możliwości automatycznej akwizycji danych, którą to początkowo wykorzystywałem w programie obsługi aplikacji. Mowa o podsystemie EDMA, dzięki któremu możliwa staje się akwizycja danych i ich automatyczne umieszczanie w buforze programowym bez udziału rdzenia mikrokontrolera. Dzieje się tak dzięki specjalnym wyzwalaczom podsystemu EDMA, którym dla przykładu może być zdarzenie zakończenia konwersji przetwornika ADC. Muszę przyznać, że początkowo wykorzystywałem tą zaawansowaną możliwość mikrokontrolera rodziny Xmega, lecz z uwagi na fakt, iż program obsługi aplikacji nie wykonuje absolutnie żadnych zadań poza zbieraniem i wyświetlaniem danych w końcowym programie zrezygnowałem z usług podsystemu EDMA, gdyż nie wnosił on żadnych usprawnień. Ostatecznie system akwizycji urządzenia Volta wygląda dokładnie tak, jak to pokazano na rysunku 2.
Kod odpowiedzialny za inicjalizację układu czasowo-licznikowego TCC5 pokazano na listingu 1, zaś kod odpowiedzialny za powiązanie zdarzenia przepełnienia licznika TCC5 z kanałem CH0 systemu zdarzeń pokazano na listingu 2.
//Uruchomienie timera TCC5
//by przepełniał się 10 razy/sek
//sterowanie akwizycją danych
TCC5.CTRLB = TC_WGMODE_NORMAL_gc;
TCC5.PER = 24999; //10Hz @ 2MHz
TCC5.CTRLA = TC_CLKSEL_DIV8_gc;
//Kanał 0 systemu zdarzeń będzie
//przekazywał zdarzenie przepełnienia TCC5
EVSYS.CH0MUX = EVSYS_CHMUX_TCC5_OVF_gc;
Dalej, na listingu 3 pokazano kod odpowiedzialny za inicjalizację przetwornika ADC mikrokontrolera. Jak widać wybrano 12-bitowy tryb SINGLE ENDED ze znakiem, sprzętowe uśrednianie wyników 16 pomiarów wraz ze sprzętowym przesunięciem wyniku o 4 bity w prawo (dla zwiększenia odstępu sygnału od szumu), zewnętrzne źródło napięcia odniesienia podłączone do portu PD0 mikrokontrolera oraz jako wyzwalacz konwersji kanał CH0 systemu zdarzeń. Ponadto uruchomiono przerwanie po konwersji przetwornika ADC odpowiedzialne za akwizycję danych, którego to ciało pokazano na listingu 4.
void initADC(void){
//ADC config: 12bit single ended, High current limit,
//External reference, gain = x1, trigger - event ch0
//ADC Enabled
ADCA.CTRLA = ADC_ENABLE_bm;
ADCA.CTRLB = ADC_CURRLIMIT_HIGH_gc|ADC_RESOLUTION_12BIT_gc|ADC_CONMODE_bm;
//50ksps max sampling rate, signed 12-bit right-adjusted result
//External reference on PORT D
ADCA.REFCTRL = ADC_REFSEL_AREFD_gc;
//First event triggers channel conversion
ADCA.EVCTRL = ADC_EVSEL_0_gc|ADC_EVACT_CH0_gc;
//500kHz @ 2MHz
ADCA.PRESCALER = ADC_PRESCALER_DIV4_gc;
ADCA.CH0.CTRL = ADC_CH_GAIN_1X_gc|ADC_CH_INPUTMODE_SINGLEENDED_gc;
//pin PA7
ADCA.CH0.MUXCTRL = ADC_CH_MUXPOS_PIN7_gc;
//Interrupt on conversion complete
//High level
ADCA.CH0.INTCTRL = ADC_CH_INTMODE_COMPLETE_gc|ADC_CH_INTLVL_HI_gc;
//Averaged 16 samples and right shifted by 4
ADCA.CH0.AVGCTRL = (4<<4)|ADC_CH_SAMPNUM_16X_gc;
}
//Makra akwizycji danych
#define DATA_ACQUISITION_ON TCC5.CTRLA = TC_CLKSEL_DIV8_gc
#define DATA_ACQUISITION_OFF TCC5.CTRLA = TC_CLKSEL_OFF_gc
ISR(ADCA_CH0_vect){
ADCready = 1;
//Wyłączenie akwizycji danych
DATA_ACQUISITION_OFF;
}
Uważny Czytelnik zastanowi się zapewne, dlaczego wybrano tryb ze znakiem, skoro nie zamierzamy mierzyć napięć ujemnych względem masy. Takie ustawienie zmniejsza wynikową rozdzielczość przetwornika ADC z 12 na 11 bitów, gdyż jeden z bitów „poświęcany” jest na znak (+/–) wartości wynikowej? Otóż przetwornik ADC wbudowany w strukturę mikrokontrolera ATXmega8E5 w konfiguracji SINGLE ENDED i bez znaku (czyli w pełnej 12-bitowej rozdzielczości) jest w stanie mierzyć napięcia z zakresu –ΔV…Vref–ΔV, gdzie ΔV jest offsetem wprowadzanym do pomiaru równym w przybliżeniu 0,05×Vref, co daje w naszym przypadku wartość 125 mV (gdyż Vref=2,5 V). Funkcjonalność tą wprowadzono głównie po to by mikrokontroler był w stanie w łatwy sposób wykrywać przejście mierzonego sygnału przez 0, co w założeniach miało uprościć implementację wszelkiego rodzaju falowników, jak i innych układów sterujących. Finalnie sprowadza się do tego, że napięciu równemu 0 V (GND) nie odpowiada oczekiwana wartość wynikowa ADC równa 0 a wielkość offsetu zbliżona do bezwzględnej wartości w zakresie 200…205. Co więcej, wartość ta zależna jest od egzemplarza danego mikrokontrolera i należałoby ją wyznaczyć w procedurze kalibracji polegającej na zwarciu wejścia pomiarowego ADC do masy i pomiarze wartości wynikowej ADC. Kolejnym ograniczeniem tego trybu pracy jest maksymalny zakres pomiarowy przetwornika ADC, który w tym wypadku wynosi 0,95×Vref (2,375 V), co po przeskalowaniu przez wejściowy dzielnik napięciowy odpowiada wejściowemu napięciu o wartości 47,5 V. Biorąc to wszystko pod uwagę ostatecznie zrezygnowałem z pełnej rozdzielczości równej 12 bitów ograniczonej w wyżej przytoczony sposób lecz zdecydowałem się na tryb SINGLE ENDED ze znakiem, który po pierwsze nie wymaga kalibracji a po drugie zmniejsza wynikową rozdzielczość pomiaru do 11 bitów.
Zakończeniu konwersji danych ADC towarzyszy zatrzymanie akwizycji danych (wyłączenie timera TCC5) oraz ustawienie flagi ADCready, dzięki czemu możliwe jest przetworzenie danych (rejestr ADCA.CH0RES mikrokontrolera) przez program główny aplikacji. Tyle w kwestii wspomnianej akwizycji danych.
Kolejne podsystemy mikrokontrolera, które używane są w aplikacji urządzenia Volta, to dwa dodatkowe układy czasowo-licznikowe a mianowicie TCC4 i TCD5. Pierwszy ze wspomnianych timerów (TCC4) pracuje w trybie PWM typu Single Slope i odpowiada za obsługę buzzera piezo generując przebieg prostokątny o częstotliwości 4 kHz na wyprowadzeniu PC2 mikrokontrolera, zaś drugi (TCD5) pracuje w trybie normalnym skonfigurowany tak, by przepełniał się 100 razy/sek i odpowiedzialny jest za obsługę klawiatury (debouncing) jak i timery programowe. Kod odpowiedzialny za inicjalizację obu układów czasowo-licznikowych pokazano na listingu 5.
//Uruchomienie timera TCC4 w trybie PWM typu Single Slope
//obsługa buzzera piezo
TCC4.CTRLB = TC_WGMODE_SINGLESLOPE_gc;
TCC4.PER = 249; //4kHz @ 2MHz
TCC4.CCC = 125; //Duty cycle 50%
//Output Compare enabled on OC4C pin
TCC4.CTRLE = TC_CCCMODE_COMP_gc;
PORTC.DIR |= PIN2_bm; //OC4C as output
//Uruchomienie timera TCD5 by przepełniał się 100 razy/sek
//obsługa klawiatury i timery programowe
TCD5.CTRLB = TC_WGMODE_NORMAL_gc;
TCD5.PER = 2499; //100Hz @ 2MHz
TCD5.CTRLA = TC_CLKSEL_DIV8_gc;
TCD5.INTCTRLA = TC_OVFINTLVL_MED_gc;
W tym miejscu warto poświęcić kilka słów konfiguracji timera TCD5 oraz przerwaniu od przepełnienia licznika, które jest wywoływane 100 razy na sekundę i odpowiada za obsługę klawiatury urządzenia. I właśnie tutaj kryje się mały „haczyk” na który i ja dałem się złapać. Otóż, w przypadku mikrokontrolera Xmega, wywołaniu funkcji obsługi przerwania od przepełnienia licznika TCD5 nie towarzyszy automatyczne skasowanie flagi tego przerwania (OVFIF w rejestrze INTFLAGS), jak miało to miejsce w przypadku starszych mikrokontrolerów AVR (rodziny Mega i Tiny). Flagę tą musimy skasować programowo (TCD5.INTFLAGS = TC5_OVFIF_bm), w innym wypadku przerwanie od przepełnienia będzie wywoływane non stop uniemożliwiając w zasadzie poprawne działanie urządzenia. To dość istotna właściwość timerów mikrokontrolera Xmega serii E, którą warto mieć na uwadze.
Ostatnim podsystemem mikrokontrolera, jaki wykorzystuje urządzenie Volta jest interfejs szeregowej transmisji danych TWI będący odpowiednikiem interfejsu I2C. Interfejs ten jest wykorzystywany do współpracy z wyświetlaczem OLED o rozdzielczości 128×32 piksele, stanowiącym element graficznego interfejsu użytkownika. Wspomniany interfejs występuje w większości mikrokontrolerów AVR, zarówno tych starszych, jak i najnowszych konstrukcji z rodziny ATXmega, jednak w wykonaniu tych ostatnich został rozbudowany w stosunku do jego pierwszych implementacji oferując dodatkowe funkcjonalności. Przede wszystkim TWI wspiera wysokie prędkości zegara (aż do 1 MHz), tryb multi-master (z obsługą arbitrażu), możliwość pracy w standardzie SMBus, adresowanie 7 i 10-bitowe, wybudzanie procesora ze stanu uśpienia i wiele, wiele innych. Warto zauważyć, że dokumentacja w tym zakresie jest bardzo obszerna jak i drobiazgowa, więc kompletny opis interfejsu wykraczałby poza ramy niniejszego artykułu. Co więcej, producent dostarcza gotowy sterownik do obsługi sprzęgu TWI (dość rozbudowany i korzystający z przerwań) oraz dedykowaną notę aplikacyjną o oznaczeniu „AVR1308: Using the XMEGA TWI”. My, z racji prostoty urządzenia, posłużymy się elementarnymi funkcjami narzędziowymi wykorzystującymi tzw. pooling.
void TWIinit(void){
//Ustawienie kierunków portów SDA i SCL
SDA_DIR_REG |= (1<<SDA_PIN);
SCL_DIR_REG |= (1<<SCL_PIN);
//Ustawienie częstotliwości zegara SCL = 200kHz
//FTWI = F_CPU/2*(5+BAUD)
TWIC.MASTER.BAUD = 0;
//Odblokowanie modułu TWI master
TWIC.MASTER.CTRLA = TWI_MASTER_ENABLE_bm;
//Początkowe wykasowanie wszystkich flag
TWIC.MASTER.STATUS |= TWI_MASTER_RIF_bm|TWI_MASTER_WIF_bm| TWI_MASTER_ARBLOST_bm|TWI_MASTER_BUSERR_bm;
//Ustawienie stanu wyjściowego interfejsu TWI
TWIC.MASTER.STATUS |= TWI_MASTER_BUSSTATE_IDLE_gc;
}
uint8_t TWIstart(uint8_t slaveAddress){
//Inicjujemy wygenerowanie sygnału Start
//i przesłanie adresu układu Slave
TWIC.MASTER.ADDR = slaveAddress;
//Czekamy na przesłanie adresu układu Slave
while(!(TWIC.MASTER.STATUS & (TWI_MASTER_WIF_bm)));
//Zwracamy NACK, jeśli odebrano sygnał NACK,
//przegrano arbitraż lub też wystąpił inny błąd magistrali TWI
if(TWIC.MASTER.STATUS &
(TWI_MASTER_RXACK_bm|
TWI_MASTER_ARBLOST_bm|
TWI_MASTER_BUSERR_bm)) return NACK;
else return ACK;
}
Zaczniemy od funkcji inicjującej interfejs TWI mikrokontrolera Xmega, której to ciało przedstawiono na listingu 6. Dalej, na listingu 7 pokazano funkcję odpowiedzialną za wygenerowanie sygnału Start interfejsu TWI i przesłanie adresu układu Slave. Prostą funkcję narzędziową, której zadaniem jest wygenerowanie sygnału Stop magistrali TWI i tym samym zakończenie transmisji danych pokazano na listingu 8.
void TWIstop(void) {
//Inicjujemy wygenerowanie sygnału Stop poprzedzonego NACK
TWIC.MASTER.CTRLC = TWI_MASTER_ACKACT_bm|TWI_MASTER_CMD_STOP_gc;
}
Na listingach 9 i 10 pokazano kolejne, dwie proste funkcje umożliwiające wysłanie i odebranie bajta danych na magistrali TWI. Funkcja wysyłająca bajt jako rezultat swojego działania zwraca wartość bitu potwierdzenia (ACK) po stronie odbiornika danych (w tym przypadku układu Slave), zaś funkcja pozwalająca na odbiór bajta przyjmuje, jako argument wywołania wartość bitu potwierdzenia po stronie odbiornika danych (w tym przypadku układu Master).
uint8_t TWIwriteByte(uint8_t Data) {
//Inicjujemy przesłanie bajta danych
TWIC.MASTER.DATA = Data;
//Czekamy na przesłanie bajta danych
while(!(TWIC.MASTER.STATUS & (TWI_MASTER_WIF_bm)));
//Zwracamy NACK, jeśli odebrano sygnał NACK,
//przegrano arbitraż lub też wystąpił inny błąd magistrali TWI
if(TWIC.MASTER.STATUS &
(TWI_MASTER_RXACK_bm|
TWI_MASTER_ARBLOST_bm|
TWI_MASTER_BUSERR_bm)) return NACK;
else return ACK;
}
uint8_t TWIreadByte(uint8_t ACKNACK) {
uint8_t Data;
//Czekamy na odbiór bajta danych
while(!(TWIC.MASTER.STATUS & TWI_MASTER_RIF_bm));
//Odczytujemy przesłany bajt danych
Data = TWIC.MASTER.DATA;
//Wysyłamy sygnał ACK/NACK
if (ACKNACK == ACK) TWIC.MASTER.CTRLC = TWI_MASTER_CMD_RECVTRANS_gc;
return Data;
}
Urządzenie wyposażono w niewielki ale efektowny wyświetlacz OLED. Powodem wybrania tego typu wyświetlacza jest jego doskonała widoczność nawet w świetle słonecznym, jak i bardzo atrakcyjna cena. Zastosowany moduł wykorzystuje panel OLED o przekątnej 0,91” i rozdzielczości 128×32 piksele wyposażony w popularny sterownik ekranu SSD1306 skonfigurowany do pracy, jako element sterowany przy pomocy magistrali I2C. Jest to dość wygodne rozwiązanie, gdyż wymaga zaangażowania wyłącznie 2 wyprowadzeń mikrokontrolera do przeprowadzenia transmisji z modułem wyświetlacza, lecz nie pozbawione wad, gdzie jedną z nich jest niewielka prędkość transmisji wpływająca na częstość odświeżania ekranu. Po szczegóły implementacyjne dotyczące tegoż peryferium odsyłam do mojego artykułu opisującego urządzenie USBtester opublikowanego w numerze EP05/19, gdzie drobiazgowo opisano zagadnienia programowe związane z obsługą tego rodzaju wyświetlacza OLED.
Montaż i uruchomienie
Schemat montażowy urządzenia pokazano na rysunku 3. Zaprojektowano niewielką, dwustronną płytkę drukowaną, która swoim rozmiarem zbliżona jest do zastosowanego wyświetlacza OLED udostępniając dodatkowo niezbędne otwory montażowe. Warto również podkreślić, iż dla zminimalizowania rozmiaru obwodu drukowanego przewidziano tutaj montaż elementów po obu stronach laminatu. Aplikację urządzenia rozpoczynamy od przylutowania mikrokontrolera (warstwa BOTTOM). Dalej lutujemy pozostałe elementy półprzewodnikowe, potem rezystory i kondensatory jak i inne elementy bierne. Na warstwie TOP montujemy układ U3, następnie buzzer i pozostałe elementy bierne, a na końcu mikroprzełączniki PLUS i MINUS oraz gniazdo podłączeniowe VIN.
Z uwagi na zagęszczenie wyprowadzeń układów scalonych przed pierwszym podłączeniem układu do zasilania należy jeszcze raz sprawdzić jakość wykonanych połączeń, aby nie dopuścić do ewentualnych zwarć. Wspomniana kontrola będzie znacznie łatwiejsza, jeśli zmontowaną płytkę przemyjemy alkoholem izopropylowym w celu wypłukania nadmiaru kalafonii lutowniczej.
Do tak przygotowanej płytki, montujemy wyświetlacz OLED, zwyczajnie lutując jego wyprowadzenia w przeznaczone do tego celu pola lutownicze (należy sprawdzić polaryzację zasilania), gdyż połączenia elektryczne zapewniają mu jednocześnie wystarczająco stabilny montaż mechaniczny. Poprawnie zmontowany układ powinien działać tuż po włączeniu zasilania.
Na fotografii 2 pokazano zmontowane urządzenie od strony BOTTOM.
Obsługa
Po włączeniu zasilania (zaciski +/– na obwodzie drukowanym) urządzenie pracuje w trybie pokazywania wartości mierzonego napięcia (rozdzielczość 20 mV), przy czym ekran w tym trybie odświeżany jest 10 razy na sekundę. Każdorazowe wciśnięcie klawiszy PLUS/MINUS powoduje chwilowe (2 s) przełączenie urządzenia w tryb pokazywania wartości alarmu dla mierzonego napięcia co symbolizowane jest poprzez wyświetlenie symbolu dzwoneczka po lewej stronie wartości napięcia. W trybie tym krótkie naciśnięcie wspomnianych klawiszy powoduje każdorazową regulację wartości napięcia alarmowania o 10 mV, długie naciśnięcie o 100 mV, zaś naciśnięcie i przytrzymanie o 1 V. Ustawiona wartość progu alarmowania zapamiętywana jest każdorazowo w nieulotnej pamięci EEPROM mikrokontrolera i wczytywana podczas włączania urządzenia. Przekroczenie ustawionej wartości alarmowania przez mierzone napięcia powoduje generowanie pojedynczych dźwięków poprzez wbudowany buzzer piezoelektryczny.
Podczas włączania urządzenia uruchamiany jest proces autokalibracji przetwornika ADC w związku z tym nie należy w tym czasie podłączać jakichkolwiek obwodów pomiarowych do wejścia VIN urządzenia. Wygląd graficznego interfejsu użytkownika urządzenia Volta pokazano na rysunku 4.
Robert Wołgajew, EP
- R1, R2: 4,7 kΩ
- R3: 11,3 kΩ 0,1%
- R4: 215 kΩ 0,1%
- R5: 1 kΩ
- C1, C2, C4: 100 nF X7R
- C3: 1 μF X7R
- U1: ATXmega8E5-AU (TQFP32)
- U2: TS9011SCX (SOT23)
- U3: LM4040CYM3-2.5 (SOT23)
- OLED: wyświetlacz OLED 128×32 px, 0,91", SSD1306, I2C, 38×12 mm
- L1: dławik SMD 10 μH (SMD 0805)
- PLUS, MINUS: mikroprzełącznik SMD typu DTSM31NB
- BUZZ: przetwornik piezoelektryczny typu LPT9018BS-HL-03-4.0-12-R
- VIN: AK500/2