[172] Arduino i oscyloskop cz. 2
Czas na zaawansowaną współpracę Arduino z oscyloskopem, a więc na wykorzystanie wspomnianemu ostatnio wejścia X. Po to był nam potrzebny drugi filtr. Z pinu 10 wyjdziemy z sygnałem prosto na to wejście. Na panelu oscyloskopu musimy jeszcze wyłączyć generator lokalny i przekierować wejście wzmacniacza odchylania poziomego na owo wejście X.
Obrazując współpracę dwóch przebiegów wyświetlimy najprostszą figurę, czyli okrąg. Jak się to robi – dowiemy się, analizując poniższy program.
const byte inputY = 9; // Port wejścia Y oscyloskopu (Kanał 2).
const byte inputX = 10; // Port wejścia X oscyloskopu (Kanał 1).
const byte points = 255; // Liczba punktów tworzących okrąg.
const byte radius = 126; // Promień okręgu. Odejmij jeden ze względu na zaokrąglenia.
void setup() {
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10); // Zmiana częstotliwości pracy PWM na 62,5 kHz
TCCR1B = _BV(WGM12) | _BV(CS10);
pinMode(inputY, OUTPUT); // Inicjuj port wejścia Y oscyloskopu.
pinMode(inputX, OUTPUT); // Inicjuj port wejścia X oscyloskopu.
}
void loop() {
for (byte n = 0; n < points; n++) { // Powtarzaj dla wszystkich punktów, z których składa się okrąg.
float angle = (2 * PI * n) / points; // Oblicz kąt w radianach.
byte positionX = (sin(angle) * radius) + 128; // Oblicz współrzędne kolejnego punktu dla osi X
byte positionY = (cos(angle) * radius) + 128; // Oblicz współrzędne kolejnego punktu dla osi Y
analogWrite(inputX, positionX); // Wystaw napięcie dla osi X
analogWrite(inputY, positionY); // Wystaw napięcie dla osi Y
}
}
Stała points określa liczbę punktów, z których będzie składać się nasz okrąg. Im mniej, tym szybsze rysowanie, ale koło będzie coraz bardziej kanciaste. Następnie deklarujemy promień okręgu – gdybyśmy chcieli go zmieniać. Ponieważ Arduino traktuje trygonometrię mało dokładnie, lepiej będzie zmniejszyć rozmiar o jeden, by uniknąć zaokrągleń, które mogłyby pojawiać się jako przypadkowe błyski na ekranie. Samo rysowanie wygląda następująco: w pętli o długości równej ilości punków składających się na całość najpierw obliczamy kąt dla aktualnie rysowanego punktu. Następnie wyliczamy wartości dla osi X oraz Y. Koło będzie rysowane wówczas, gdy na jedną z wartości wystawimy sinus kąta, a na drugą – kosinus. Należy jeszcze wynik przeskalować zgodnie z parametrem promienia oraz przesunąć na środek, dodając 128. Na końcu obie wartości należy wystawić na kanały PWM.
Trygonometria na Arduino liczy się powoli, więc nie musimy tu dodawać żadnych opóźnień. Mało tego, takie parametry sprawiają, że kółko migocze. Zmniejszenie ilości punktów o połowę rozwiąże problem przy minimalnej degradacji rysunku. Jak widać, kółko jest trochę nierówne, ale wynika to ze wspomnianej niskiej jakości samego oscyloskopu. Znaczne przyspieszenie rysowania osiągnęlibyśmy, wypełniając na wstępie tablicę parametrów zaokrągleniami stałoprzecinkowymi, ale to nie jest tematem dzisiejszych rozważań. Zaznaczam, że często się tak robi, nie zawracając sobie głowy tak skomplikowanymi obliczeniami na żywo.
Obejrzyjmy sobie teraz słynne krzywe Lissajous. Powstają, gdy przebiegi pozostają względem siebie w stosunku prostych liczb całkowitych. Był to ulubiony motyw wczesnych filmów science fiction i zaraz sobie coś takiego narysujemy.
const byte inputY = 9; // Port wejścia Y oscyloskopu (Kanał 2).
const byte inputX = 10; // Port wejścia X oscyloskopu (Kanał 1).
const byte points = 255; // Liczba punktów tworzących figurę.
const byte radius = 126; // Promień okręgu. Odejmij jeden ze względu na zaokrąglenia.
const float multiplierX = 1; // Mnożnik dla osi X, który decyduje o kształcie figur.
const float multiplierY = 2; // Mnożnik dla osi Y, który decyduje o kształcie figur.
float phase; // Współczynnik fazy wystawianych napięć.
const float speedPhase = 0.1; // Prędkość zmian fazy.
void setup() {
TCCR1A = _BV(COM1A1) | _BV(COM1B1) | _BV(WGM10); // Zmiana częstotliwości pracy PWM na 62,5 kHz
TCCR1B = _BV(WGM12) | _BV(CS10);
pinMode(inputY, OUTPUT); // Inicjuj port wejścia Y oscyloskopu.
pinMode(inputX, OUTPUT); // Inicjuj port wejścia X oscyloskopu.
}
void loop() {
for (byte n = 0; n < points; n++) { // Powtarzaj dla wszystkich punktów, z których składa się figura.
float angle = (2 * PI * n) / points; // Oblicz kąt w radianach.
byte positionX = (sin(angle * multiplierX) * radius) + 128; // Oblicz współrzędne kolejnego punktu dla osi X
byte positionY = (cos(angle * multiplierY + phase) * radius) + 128; // Oblicz współrzędne kolejnego punktu dla osi Y
analogWrite(inputX, positionX); // Wystaw napięcie dla osi X
analogWrite(inputY, positionY); // Wystaw napięcie dla osi Y
}
phase += speedPhase; // Zwiększaj fazę o ustalony krok.
}
W tym celu musimy dołożyć trzy kolejne zmienne: niezależne współczynniki dla napięć osi X i Y oraz jeszcze jeden, dokładany dla osi Y, który będzie cyklicznie zmieniał fazę napięcia. Reszta pracuje podobnie jak w przypadku rysowania okręgu. Jak widać, współczynniki znalazły swoje miejsce przy obliczeniach sinusa i kosinusa.
Dla współczynników X=1 Y=2 figura będzie wyglądać tak. Mamy tu już duży problem z brakiem mocy i obraz migocze, acz wciąż jest czytelny.
Współczynniki 2 i 3 dają coś takiego – pojawia się wrażenie trójwymiarowości. Zwiększanie współczynników będzie komplikować kształt figur. Możemy także zamienić je miejscami. Wówczas figura obróci się o 90 stopni. W programie jest pewne uproszczenie: parametr phase będzie rósł w nieskończoność. Jednak zanim pojawi się katastrofa, minie dużo czasu, więc nie musimy dbać o to, by przy wielkości dwa pi zerować go.
Podmieńmy moduł na Uno R4. Ma on sprzętowe przetworniki analogowe, jednak dla zachowania kompatybilności postanowiłem ich na razie nie wykorzystywać. Program jednak zmieni się, ponieważ w tym wypadku do zmiany częstotliwości pracy PWM wykorzystuje się bibliotekę i obiekty.
#include "pwm.h" // Biblioteka PWM dla architektury Uno R4
const byte inputY = 9; // Port wejścia Y oscyloskopu (Kanał 2).
const byte inputX = 10; // Port wejścia X oscyloskopu (Kanał 1).
const byte points = 255; // Liczba punktów tworzących figurę.
const byte radius = 126; // Promień okręgu. Odejmij jeden ze względu na zaokrąglenia.
const float multiplierX = 2; // Mnożnik dla osi X, który decyduje o kształcie figur.
const float multiplierY = 3; // Mnożnik dla osi Y, który decyduje o kształcie figur.
float phase; // Współczynnik fazy wystawianych napięć.
const float speedPhase = 0.02; // Prędkość zmian fazy.
PwmOut pwmY(inputY); // Obiekt sterujący sprzętowym PWM dla pinu 9 (oś Y).
PwmOut pwmX(inputX); // Obiekt sterujący sprzętowym PWM dla pinu 10 (oś X).
void setup() {
pinMode(inputY, OUTPUT); // Inicjuj port wejścia Y oscyloskopu.
pinMode(inputX, OUTPUT); // Inicjuj port wejścia X oscyloskopu.
pwmY.begin(100000.0f, 0.0f); // Częstotliwość pracy 100 kHz, początkowe wypełnienie = 0
pwmX.begin(100000.0f, 0.0f);
}
void loop() {
for (byte n = 0; n < points; n++) { // Powtarzaj dla wszystkich punktów, z których składa się figura.
float angle = (2 * PI * n) / points; // Oblicz kąt w radianach.
byte positionX = (sin(angle * multiplierX) * radius) + 128; // Oblicz współrzędne kolejnego punktu dla osi X
byte positionY = (cos(angle * multiplierY + phase) * radius) + 128; // Oblicz współrzędne kolejnego punktu dla osi Y
float procentX = (positionX / 255.0) * 100.0; // Przelicz współrzędne X na procenty - wymaganie dla pulse_perc
float procentY = (positionY / 255.0) * 100.0; // Przelicz współrzędne Y na procenty - wymaganie dla pulse_perc
pwmX.pulse_perc(procentX); // Wystaw napięcie dla osi X
pwmY.pulse_perc(procentY); // Wystaw napięcie dla osi Y
}
phase += speedPhase; // Zwiększaj fazę o ustalony krok.
}
W części wstępnej deklarujemy częstotliwość pracy – tutaj 100 kHz i początkowe wypełnienie, który to parametr jest obowiązkowy. W tym wypadku wysyłanie danych wymaga, by wielkość była określona w procentach. Stąd te linie, które przeliczają te wartości z zakresu ośmiobitowego. Po skompilowaniu możemy się cieszyć niezwykle płynnym obrazem i zarazem ostrzejszym – z racji podbicia częstotliwości PWM, lepiej tłumionych przy filtrze o ustalonych tu parametrach.















