[3] JAVA i STM32 – ekspresowy kurs programowania z MicroEJ – obsługa ADC i DAC
List. 1. Generacja GUI w metodzie main
public static void main(String[] args) { MWT.RenderingContext.add(new PlainTheme()); Desktop desktop = new Desktop(); Panel panel = new Panel(); GridComposite gc = new GridComposite(MWT.VERTICAL, 4); TitleLabel title = new TitleLabel("ADC/DAC EXAMPLE"); title.setFontSize(LookExtension.GET_X_BIG_FONT_INDEX); gc.add(title); FlowComposite fcDac = new FlowComposite(); TitleLabel labelDacTitle = new TitleLabel("Dac Value [mV]:"); fcDac.add(labelDacTitle); TextField dacValueField = new TextField("--------"); fcDac.add(dacValueField); Scale scaleDac = new Scale(0, 300); fcDac.add(scaleDac); gc.add(fcDac); FlowComposite fcAdc = new FlowComposite(); TitleLabel labelAdcTitle = new TitleLabel("ADC Value [mV]:"); fcAdc.add(labelAdcTitle); TextField adcValueField = new TextField("--------"); fcAdc.add(adcValueField); Button buttonAdc = new Button("Get ADC value"); fcAdc.add(buttonAdc); gc.add(fcAdc); panel.setWidget(gc); panel.show(desktop, true); desktop.show(); }
W pierwszej instrukcji (MWT.RenderingContext.add(new PlainTheme());) ustawiany jest wygląd aplikacji. Obiekt PlainTheme zawiera instrukcje dotyczące m.in. koloru rysowanych komponentów i wykorzystywanych czcionek. Jest on zdefiniowany w bibliotece, jednak nic nie stoi na przeszkodzie aby utworzyć własny motyw graficzny.
Kolejne instrukcje tworzą krok po kroku interfejs graficzny. Jako pierwszy tworzony jest obiekt Desktop. Jest on przypisany do konkretnego wyświetlacza, przy czym każdy wyświetlacz może mieć przypisany tylko jeden obiekt tego typu. W systemie z jednym wyświetlaczem, obiekt Desktop jest przypisywany do niego domyślnie w konstruktorze. W przypadku większej ilości wyświetlaczy można wskazać do którego ma zostać przypisany nowo tworzony obiekt.
Kolejnym tworzonym obiektem jest Panel. Jest to kontener dla jednego, dowolnego widgetu. Panele są przypisywane do obiektów Desktop, dzięki czemu można je pokazywać, chować lub wymieniać dynamicznie w odpowiedzi na zdarzenia zewnętrzne.
Do obiektu Panel dołączany jest pierwszy z widgetów: GridComposite. Jest to jeden z widgetów, których zadaniem jest przechowywanie i pozycjonowanie innych obiektów. Dzięki temu na jednym panelu może znaleźć się wiele elementów poukładanych w różnych konfiguracjach. GridComposite układa elementy pionowo lub poziomo w siatce o określonym rozmiarze, w zależności od argumentów jego konstruktora. Jego elementami mogą być zarówno zwykłe widgety widoczne dla użytkownika, jak dodawany w następnej kolejności obiekt TitleLabel wyświetlający napis, jak i kolejne widgety pozycjonujące. Dzięki temu można swobodnie zaprojektować kompletny interfejs graficzny. W ten sposób zostały przygotowane kolejne dwie grupy widgetów grupujące elementy sterujące przetwornikami wewnątrz obiektów FlowComposite wyświetlających ich zawartość obok siebie w kolejności dodawania i przechodząc do nowej linii w przypadku braku miejsca. Po utworzeniu i dodaniu wszystkich potrzebnych elementów, cały interfejs jest wyświetlany na ekranie. Efekt można zobaczyć na rysunku 5.
Rys. 5. Interfejs graficzny do kontroli przetworników
Komentarza wymaga jeszcze konstruktor obiektu scaleDac:
Scale scaleDac = new Scale(0, 300);
Przyjmuje od dwie wartości odpowiadające zakresie suwaka, a w tym przypadku także ustawieniom wartości na wyjściu przetwornika cyfrowo-analogowego. Suwak został wyskalowany tak aby pojedynczy skok odpowiadał 10 mV, przez co wartość maksymalna powinna wygenerować 3000 mV na wyjściu. Dzięki temu aplikacja generuje mniej zdarzeń przy zmianie położenia suwaka kosztem rozdzielczości przetwornika.
Obsługa zdarzeń
Po wykonaniu interfejsu użytkownika należy jeszcze przygotować obsługę pochodzących od niego zdarzeń. W przedstawionej aplikacji są dwa komponenty mogące generować zdarzenia: buttonAdc typu Button oraz scaleDac typu Scale. Zasada obsługi ich zdarzeń jest identyczna jak w przypadku klasy EventGenerator z poprzedniego przykładu. Trzeba zatem utworzyć nowe klasy: DACScaleListener i ADCButtonListener implementujące ten sam co poprzednio interfejs Listener z metodami performAction. Tym razem jednak należy zaimplementować metodę przyjmującą dwa argumenty (performAction(int value, Object object)) – pierwszym z nich jest wartość, a drugim obiekt, który wygenerował zdarzenie. Oba obiekty słuchaczy mogą także ustawiać wartości pól tekstowych, odpowiednio dacValueField oraz adcValueField w odpowiedzi na zdarzenie. Obiekty te są przekazywane w konstruktorach słuchaczy.
Obsługa przetworników (Java)
Ostatnią częścią aplikacji Javy jest kontrola przetworników. W przykładzie będzie się ona ograniczać do konfiguracji i odczytu wartości ADC lub ustawienia wartości DAC. Do tego zadania powstaną dwie dodatkowe klasy udostępniające wymienione metody pozostałym obiektom aplikacji.
Kod odpowiedzialny za obsługę peryferii zostanie dodany do warstwy BSP, jednak aplikacja Javy musi być w stanie wywołać odpowiednie funkcje C. Do tego celu w MicroEJ został udostępniony mechanizm o nazwie SNI (Simple Native Interface), który umożliwia m.in. wywoływanie funkcji C z poziomu aplikacji Javy. Aplikacja nie wie nic na temat zawartości tych funkcji – zna jedynie ich nazwy, listy argumentów oraz typy wartości zwracanych. Wystarczy je zadeklarować wewnątrz klasy z użyciem słów kluczowych ’static native’, tak jak zostało to pokazane na listingach 2 i 3, zawierających kod klas ADC i DAC.
List. 2. Kod klasy ADC
public class ADC { static native void ADCInit(); static native int ADCGetValue(); public void init() { ADC.ADCInit(); } public int getValue() { return ADC.ADCGetValue(); } }
List. 3. Kod klasy DAC
public class DAC { static native void DACInit(); static native void DACSetValue(int value); public void init() { DAC.DACInit(); } public void setValue(int value) { DAC.DACSetValue(value); } }
Dzięki obiektom klas ADC i DAC, obiekty słuchaczy interfejsu użytkownika mogą uzyskać dostęp do przetworników i odczytywać oraz zmieniać ich wartość. Kod słuchaczy pokazany został na listingach 4 i 5.
List. 4. Kod klasy ADCButtonListener
public class ADCButtonListener implements Listener { private TextField valueField; private ADC adc; public ADCButtonListener(TextField valueField, ADC adc) { this.valueField = valueField; this.adc = adc; } @Override public void performAction() { // TODO Auto-generated method stub } @Override public void performAction(int value) { // TODO Auto-generated method stub } @Override public void performAction(int value, Object object) { int adc_value = this.adc.getValue(); this.valueField.setText(Integer.toString(adc_value)); } }
List. 5. Kod klasy DACScaleListener
public class DACScaleListener implements Listener { private TextField valueField; private DAC dac; public DACScaleListener(TextField valueField, DAC dac) { this.valueField = valueField; this.dac = dac; } @Override public void performAction() { // TODO Auto-generated method stub } @Override public void performAction(int value) { // TODO Auto-generated method stub } @Override public void performAction(int value, Object object) { this.valueField.setText(Integer.toString(value*10)); this.dac.setValue(value*10); } }
Na koniec pozostało już tylko utworzyć i dodać słuchaczy do komponentów graficznych oraz utworzyć obiekty klas ADC i DAC oraz zainicjalizować przetworniki. Wszystkie te czynności można wykonać w metodzie main, której pełen kod został przedstawiony na listingu 6.