Lekcja 3: Wykrywanie obrotu
CanSat NeXT ma dwa układy czujników na płytce CanSat NeXT. Jeden z nich to barometr, którego używaliśmy w poprzedniej lekcji, a drugi to jednostka pomiaru inercyjnego LSM6DS3. LSM6DS3 to 6-osiowy IMU, co oznacza, że jest w stanie wykonywać 6 różnych pomiarów. W tym przypadku jest to przyspieszenie liniowe na trzech osiach (x, y, z) oraz prędkość kątowa na trzech osiach.
W tej lekcji przyjrzymy się przykładowi IMU w bibliotece, a także wykorzystamy IMU do małego eksperymentu.
Przykład biblioteki
Zacznijmy od przyjrzenia się, jak działa przykład z biblioteki. Załaduj go z File -> Examples -> CanSat NeXT -> IMU.
Początkowa konfiguracja jest ponownie taka sama - dołącz bibliotekę, zainicjuj serial i CanSat. Skupmy się jednak na pętli. Pętla wygląda bardzo znajomo! Odczytujemy wartości tak jak w poprzedniej lekcji, tylko tym razem jest ich znacznie więcej.
float ax = readAccelX();
float ay = readAccelY();
float az = readAccelZ();
float gx = readGyroX();
float gy = readGyroY();
float gz = readGyroZ();
Każda oś jest faktycznie odczytywana z kilkuset mikrosekundowym opóźnieniem. Jeśli potrzebujesz, aby były aktualizowane jednocześnie, sprawdź funkcje readAcceleration i readGyro.
Po odczytaniu wartości możemy je wydrukować jak zwykle. Można to zrobić za pomocą Serial.print i println tak jak w poprzedniej lekcji, ale ten przykład pokazuje alternatywny sposób drukowania danych, z dużo mniejszą ilością ręcznego pisania.
Najpierw tworzony jest bufor o rozmiarze 128 znaków. Następnie jest on inicjowany tak, aby każda wartość była 0, za pomocą memset. Po tym wartości są zapisywane do bufora za pomocą snprintf()
, która jest funkcją umożliwiającą zapisanie ciągów znaków w określonym formacie. Na koniec jest to po prostu drukowane za pomocą Serial.println()
.
char report[128];
memset(report, 0, sizeof(report));
snprintf(report, sizeof(report), "A: %4.2f %4.2f %4.2f G: %4.2f %4.2f %4.2f",
ax, ay, az, gx, gy, gz);
Serial.println(report);
Jeśli powyższe wydaje się mylące, możesz po prostu użyć bardziej znanego stylu z użyciem print i println. Jednak staje się to trochę uciążliwe, gdy jest wiele wartości do wydrukowania.
Serial.print("Ax:");
Serial.println(ay);
// itd.
Na koniec ponownie jest krótka przerwa przed ponownym rozpoczęciem pętli. Jest to głównie po to, aby zapewnić, że wyjście jest czytelne - bez opóźnienia liczby zmieniałyby się tak szybko, że trudno byłoby je odczytać.
Przyspieszenie jest odczytywane w jednostkach G, czyli wielokrotnościach . Prędkość kątowa jest w jednostkach .
Spróbuj zidentyfikować oś na podstawie odczytów!
Wykrywanie swobodnego spadku
Jako ćwiczenie, spróbujmy wykryć, czy urządzenie jest w swobodnym spadku. Pomysł polega na tym, że rzucimy płytkę w powietrze, CanSat NeXT wykryje swobodny spadek i włączy diodę LED na kilka sekund po wykryciu zdarzenia swobodnego spadku, abyśmy mogli stwierdzić, że nasza kontrola została uruchomiona nawet po ponownym złapaniu.
Możemy zachować konfigurację taką, jaka była, i skupić się tylko na pętli. Wyczyśćmy starą funkcję pętli i zacznijmy od nowa. Dla zabawy, odczytajmy dane za pomocą alternatywnej metody.
float ax, ay, az;
readAcceleration(ax, ay, az);
Zdefiniujmy swobodny spadek jako zdarzenie, gdy całkowite przyspieszenie jest poniżej wartości progowej. Możemy obliczyć całkowite przyspieszenie z poszczególnych osi jako
Co w kodzie wyglądałoby mniej więcej tak.
float totalSquared = ax*ax+ay*ay+az*az;
float acceleration = Math.sqrt(totalSquared);
I chociaż to by działało, należy zauważyć, że obliczanie pierwiastka kwadratowego jest naprawdę wolne obliczeniowo, więc powinniśmy unikać tego, jeśli to możliwe. W końcu możemy po prostu obliczyć
i porównać to do zdefiniowanej wartości progowej.
float totalSquared = ax*ax+ay*ay+az*az;
Teraz, gdy mamy wartość, zacznijmy kontrolować diodę LED. Moglibyśmy mieć diodę LED włączoną zawsze, gdy całkowite przyspieszenie jest poniżej wartości progowej, jednak łatwiej byłoby to odczytać, gdyby dioda LED pozostała włączona przez chwilę po wykryciu. Jednym ze sposobów na to jest utworzenie innej zmiennej, nazwijmy ją LEDOnTill, w której po prostu zapisujemy czas, do którego chcemy, aby dioda LED była włączona.
unsigned long LEDOnTill = 0;
Teraz możemy zaktualizować czasomierz, jeśli wykryjemy zdarzenie swobodnego spadku. Użyjmy progu 0.1 na razie. Arduino zapewnia funkcję millis()
, która zwraca czas od uruchomienia programu w milisekundach.
if(totalSquared < 0.1)
{
LEDOnTill = millis() + 2000;
}
Na koniec możemy po prostu sprawdzić, czy obecny czas jest większy czy mniejszy niż określony LEDOnTill
, i kontrolować diodę LED na tej podstawie. Oto jak wygląda nowa funkcja pętli:
unsigned long LEDOnTill = 0;
void loop() {
// Odczyt przyspieszenia
float ax, ay, az;
readAcceleration(ax, ay, az);
// Obliczanie całkowitego przyspieszenia (kwadratowego)
float totalSquared = ax*ax+ay*ay+az*az;
// Aktualizacja czasomierza, jeśli wykryjemy spadek
if(totalSquared < 0.1)
{
LEDOnTill = millis() + 2000;
}
// Kontrola diody LED na podstawie czasomierza
if(LEDOnTill >= millis())
{
digitalWrite(LED, HIGH);
}else{
digitalWrite(LED, LOW);
}
}
Próbując ten program, możesz zobaczyć, jak szybko teraz reaguje, ponieważ nie mamy opóźnienia w pętli. Dioda LED włącza się natychmiast po opuszczeniu ręki podczas rzutu.
- Spróbuj ponownie wprowadzić opóźnienie w funkcji pętli. Co się dzieje?
- Obecnie nie mamy żadnego drukowania w pętli. Jeśli po prostu dodasz instrukcję drukowania do pętli, wyjście będzie naprawdę trudne do odczytania, a drukowanie znacznie spowolni czas cyklu pętli. Czy możesz wymyślić sposób, aby drukować tylko raz na sekundę, nawet jeśli pętla działa nieprzerwanie? Wskazówka: spójrz, jak zaimplementowano czasomierz diody LED.
- Stwórz własny eksperyment, używając przyspieszenia lub obrotu do wykrycia jakiegoś zdarzenia.
W następnej lekcji opuścimy domenę cyfrową i spróbujemy użyć innego stylu czujnika - analogowego miernika światła.