Każda osoba która, choć chwilę zajmowała się robotyką wie, że pomiar odległości gra w tej dziedzinie niesamowicie istotną rolę. Praktycznie wszędzie, gdzie mamy do czynienia z urządzeniami, które się poruszają wskazana jest jakaś detekcja przeszkód. Często właśnie realizowane jest to przez pomiar odległości.
Pomiaru takiego można dokonać na wiele sposobów. Metody różnią się między sobą szybkością działania, dokładnością czy możliwym do uzyskania maksymalnym mierzonym dystansem.
Wśród takich rozwiązań niesamowicie popularne są czujniki ultradźwiękowe. Już za kilka złotych można nabyć HC-SR04, który idealnie spisuje się nie tylko w amatorskiej elektronice. Jedną z jego wad jest wielkość. Nie nada się on do małego robota, a także trudno jest go schować.
Z pomocą przychodzą małe czujniki laserowe. Na tapet dzisiaj wziąłem czujnik ST o oznaczeniu VL53L0X.
Drugą część z obsługą przerwaniową znajdziesz tutaj
Mikro czujnik ToF VL53L0X
Jest to czujnik typu ToF(Time-of-Flight), czyli pomiaru odległości dokonuje się poprzez mierzenie czas lotu, w tym wypadku wiązki lasera. Czujnik sprawdza ile zajmuje światłu dotarcie do obiektu, odbicie się od niego oraz powrót do sensora odbiorczego. Podobnie zresztą działają czujniki ultradźwiękowe.
ST chwali się, że jest to najmniejszy czujnik laserowy na świecie i faktycznie wymiary robią wrażenie. Cały układzik ma jedynie 4,4 x 2,4 x 1 mm więc jest niewiele większy od rezystora w obudowie 1206.
Laser zastosowany w czujniku o długości fali 940 nm został zbudowany tak, że spełnia normy dotyczące użytkowania go w otoczeniu z “gołym” okiem. Przebywając w jego otoczeniu nic nam nie powinno się stać.
Co ciekawe, lasery takie są czasem stosowane do wspomagania ostrzenia obrazu w aparatach cyfrowych. Niektóre starsze smartfony chwaliły się laserowym autofocusem, a nawet taki miałem. Teraz nie kojarzę, aby któreś z nowszych słuchawek miały laser. Może nie było to aż takie dobre.
Czujnik komunikuje się z hostem za pomocą interfejsu I²C. Oprócz standardowych pinów magistrali dostępne są jeszcze wyjście przerwania oraz wejście wyłączające czujnik.
ST dorzuca do czujnika kompletną bibliotekę, którą dumnie nazywa API. Zawiera ona w sobie chyba wszystko, co można zrobić z układem. Jest to dosyć dobre posunięcie, bo układ jak na zwykły czujnik jest bardzo skomplikowany. Zawiera dziesiątki rejestrów, z którymi można rozmawiać. Początkującym osobom może to narobić niezłego bólu głowy.
Jednak samo skorzystanie z biblioteki też może doprowadzić do migreny. Biblioteka jest napisana w sposób uniwersalny, więc zajmuje masę kodu. Nie od dziś wiadomo, że sporo kodu to sporo pamięci Flash. Sprawdzę ile to faktycznie jest.
Dodatkowo bardzo ciężko jest znaleźć sensowne opisy jak tego czujnika używać. Trzeba przekopywać się przez nieludzko napisane dokumentacje API oraz przykłady dla płytek Nucleo i nakładek z tymi czujnikami.
Getting Started, jakie w większości znalazłem polegają na tym, aby wziąć takie Nucleo jak w przykładach, wrzucić gotową binarkę i cieszyć się działaniem. Nu nu nu panie ST…
Wystarczy mojego marudzenia. Przejdźmy do tego, jak uruchomić te kobylaste API na dowolnym mikrokontrolerze.
Co potrzebujesz?
Będziesz potrzebował oczywiście jakiś mikrokontroler. Jakiś STM32 uściślając. Ja dzisiaj wziąłem STM32F401CCU6 z nowości na moim sklepie. Szerzej o tych płytkach rozpisałem się w jednym z poprzednich artykułów.
Jako narzędzi programistycznych użyłem STM32CubeIDE w wersji 1.1.0, które ma w sobie zintegrowanego STM32CubeMX 5.4.0. Biblioteki HAL natomiast z jakich korzystam to F4 v1.24.2.
Oprócz płytki i IDE potrzebujesz również wspomnianego wyżej API. Znajdziesz je tutaj.
No i na koniec przyda się paczka z dodatkiem do Cube’a dla shielda Nucleo z naszym czujnikiem. Już za chwilę wyjaśnię Ci, po co zarówno API, jak i Cube extension.
Projekt w Cube
Wybierz procek F401CEU6, chyba że masz inny. Najpierw zegar taktujący. Do testów lubię używać najwyższych możliwych zegarów. Ustawiłem 84 MHz HCLK wykorzystując zewnętrzny kwarc 25 MHz znajdujący się na płytce. Aby go użyć, w zakładce RCC musisz wybrać High Speed Clock (HSE) jako Crystal/Ceramic Resonator. Następnie wpisz w Clock Configuration z górnego menu liczbę 84 w polu HCLK i wciśnij Enter. Cube sam przeliczy wszystko 🙂
Do komunikacji będziesz potrzebował I²C, więc skonfiguruj jeden z dostępnych interfejsów. Ja wybrałem I2C1 na pinach PB8 i PB9. Czujnik obsługuje 400 kHz i tego się trzymaj. Dla porównania 100 kHz zostaw mi 🙂
Wyniki pomiarów wysyłać będę na UART. Początkowo chciałem na USB, ale jest z tym nieco więcej zabawy. O USB będzie kiedy indziej 🙂 Wziąłem standardowo USART2 jak w każdym Nucleo i podłączyłem się do PCta przez konwerter. Ustawiłem go na 115200 8n1. Zero zaskoczenia.
Pamiętaj, że czujnik ToF ma jeszcze dwa piny – przerwanie zwany GPIO1 na płytce oraz XSHUT do wyłączania czujnika. Podłączyłem obydwa do mikrokontrolera. O ile przerwanie będę na pewno wykorzystywał tak XSHUT może się aktualnie nie przydać.
Kiedy byłby potrzebny XSHUT? Pierwsze do głowy przychodzi oszczędzanie energii. To fakt, jednak jeszcze jedną sytuacją, w której można go użyć to podłączenie kilku czujników do jednej magistrali. Niestety VL53L0X nie mają konfigurowalnego adresu I²C stąd trzeba kombinować w ten sposób. XSHUT pozwala przełączać się między czujnikami bez ponownej ich inicjalizacji.
Tak prezentuje się “schemat” z Cube’a.
Uruchamianie API VL53L0X
Nie ukrywam, że ciężko było mi znaleźć na stronach ST jak skorzystać z API. Teksty odsyłające do dokumentacji opisującej użycie API prowadziły do bezsensownego 2-stronicowego dokumentu, w którym widniała jedynie struktura katalogów API. Na co to komu? Ostatecznie zlepiłem kilka różnych porad z Internetu, poczytałem nieco wielkiej dokumentacji i zadziałało 🙂 Oto co zrobiłem.
Rozpakuj sobie paczki z API i dodatkeim do Nucleo. Pierwsze co zrób to skopij sobie API do swojego projektu. Jest to cała zawartość ..\VL53L0X_1.0.2\Api\
Ja je wrzuciłem do katalogu Drivers w projekcie.
Teraz musisz dodać do projektu ścieżki z plikami nagłówkowymi i źródłami. Zrobisz to wchodząc w Project > Properties > C/C++ General > Paths and Symbols. W zakładce Includes dodajesz foldery inc z API, a w zakładce Source Location foldery src
Nagłówek, który trzeba dołączyć do main.c to vl53l0x_api.c. Niestety próba skompilowania z dodanym API wyrzuci kilka błędów.Kompilator zgłaszam nam, że brakuje mu pliku… windows.h?! Otóż okazuje się, że wewnątrz API są takie pliki odpowiadające za platformę, na jakiej chcemy go użyć. Pobierając API ze strony ST domyślnie jest ono skonfigurowane do działania pod Windowsem i takie zawiera przykłady działania. Według mnie jest to trochę dziwne, ale ok. Trzeba doprowadzić do tego, aby platforma ta była skonfigurowana jako nasz mikrokontroler.
Do tego posłuży nam właśnie paczka rozszerzenia do Cube dla naszego czujnika. Tak się składa, że jest ona napisana z użyciem HAL, więc nie ważne, na jakie Nucleo były pisane przykłady, portowanie będzie proste.
Po pierwsze wyrzuć plik vl53l0x_i2c_win_serial_comms.c który związany jest z Windowsem.
Po drugie rozpakuj X-CUBE-53L0A1 i z folderu ..\X-CUBE-53L0A1\STM32CubeExpansion_VL53L0X_V1.2.0\Drivers\BSP\X-NUCLEO-53L0A1 skopiuj pliki vl53l0x_platform.h i vl53l0x_platform.c na odpowiednie miejsca w projekcie. Nadpisz je.
Jesteśmy blisko 🙂 Teraz podobnie skopiuj parę vl53l0x_platform_log. h i .c tym razem ze ścieżki ..\X-CUBE-53L0A1\STM32CubeExpansion_VL53L0X_V1.2.0\Drivers\BSP\Components\vl53l0x.
Te pliki odpowiadają za logowanie działania API na platformie. Domyślnie są wyłączone, a te z API były napisane dla Windowsa. Nie testowałem ich działania.
Jeśli teraz skompilujesz to pozostanie Ci prawdopodobnie jeden błąd. Kompilator nie może znaleźć stm32xxx_hal.h, aby dołączyć go do vl53l0x_platform.h i .c. Pliki dla platformy były pisane jakiś czas temu, gdy pliki HALa były lekko inaczej nazywane.
Wystarczy linijkę #include “stm32xxx_hal.h” w plikach vl53l0x_platform.h i .c zamienić na odpowiedni dla Twojego mikrokontrolera nagłówek. W moim wypadku dla rodziny F4 będzie to #include “stm32f4xx_hal.h”.
Teraz kompilacja przeszła bez błędów. Należałoby jeszcze uruchomić jakiś prosty pomiar.
Użycie API do pojedynczego pomiaru
Jeśli wcześniej nie dołączyłeś nagłówka vl53l0x_api.h w pliku main.c to zrób to teraz. Następnie będziesz potrzebował kilku zmiennych do działania czujnika.
VL53L0X_RangingMeasurementData_t RangingData; VL53L0X_Dev_t vl53l0x_c; // center module VL53L0X_DEV Dev = &vl53l0x_c;
Pierwsza to struktura z pomiarami. Druga to struktura urządzenia. Trzecia to wskaźnik na strukturę z punktu nr 2.
Do inicjalizacji potrzebujesz kilku zmiennych pomocniczych. Wsadziłem je w sekcji użytkownika nr 1.
/* USER CODE BEGIN 1 */ // // VL53L0X initialisation stuff // uint32_t refSpadCount; uint8_t isApertureSpads; uint8_t VhvSettings; uint8_t PhaseCal; /* USER CODE END 1 */
oraz inicjalizacja czujnika
/* USER CODE BEGIN 2 */ MessageLen = sprintf((char*)Message, "msalamon.pl VL53L0X test\n\r"); HAL_UART_Transmit(&huart2, Message, MessageLen, 100); Dev->I2cHandle = &hi2c1; Dev->I2cDevAddr = 0x52; HAL_GPIO_WritePin(TOF_XSHUT_GPIO_Port, TOF_XSHUT_Pin, GPIO_PIN_RESET); // Disable XSHUT HAL_Delay(20); HAL_GPIO_WritePin(TOF_XSHUT_GPIO_Port, TOF_XSHUT_Pin, GPIO_PIN_SET); // Enable XSHUT HAL_Delay(20); // // VL53L0X init for Single Measurement // VL53L0X_WaitDeviceBooted( Dev ); VL53L0X_DataInit( Dev ); VL53L0X_StaticInit( Dev ); VL53L0X_PerformRefCalibration(Dev, &VhvSettings, &PhaseCal); VL53L0X_PerformRefSpadManagement(Dev, &refSpadCount, &isApertureSpads); VL53L0X_SetDeviceMode(Dev, VL53L0X_DEVICEMODE_SINGLE_RANGING); // Enable/Disable Sigma and Signal check VL53L0X_SetLimitCheckEnable(Dev, VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, 1); VL53L0X_SetLimitCheckEnable(Dev, VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, 1); VL53L0X_SetLimitCheckValue(Dev, VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, (FixPoint1616_t)(0.1*65536)); VL53L0X_SetLimitCheckValue(Dev, VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, (FixPoint1616_t)(60*65536)); VL53L0X_SetMeasurementTimingBudgetMicroSeconds(Dev, 33000); VL53L0X_SetVcselPulsePeriod(Dev, VL53L0X_VCSEL_PERIOD_PRE_RANGE, 18); VL53L0X_SetVcselPulsePeriod(Dev, VL53L0X_VCSEL_PERIOD_FINAL_RANGE, 14); /* USER CODE END 2 */
Na początku przedstawiam się 😉 Później ważne jest, aby zainicjować pola w strukturze Dev. Trzeba podać, z którego I²C korzystasz poprzez referencję do struktury z HALa. Podajesz też adres czujnika(który de facto nie da się zmienić).
Dalej macham pinem XSHUT. To jest trochę nad wyraz, ale przecież mogę.
Na samym końcu inicjalizacja czujnika do działania w trybie pojedynczego pomiaru. Wziąłem to z tych przykładów dla Windowsa 🙂 Jednak na coś się przydały. Z tego, co widać inicjalizujemy trochę stałych czasowych oraz zasięgowych. Te czujniki są naprawdę mocno rozbudowane i jeszcze będę to analizował głębiej.
Po tej całej inicjalizacji można w pętli głównej programu wsadzić pomiar.
VL53L0X_PerformSingleRangingMeasurement(Dev, &RangingData); if(RangingData.RangeStatus == 0) { MessageLen = sprintf((char*)Message, "Measured distance: %i\n\r", RangingData.RangeMilliMeter); HAL_UART_Transmit(&huart2, Message, MessageLen, 100); }
Niestety jest to pomiar blokujący, który nie wykorzystuje pinu przerwania. Jednak działa! Pomiar dokonywany jest w milimetrach.
Wartość RangingData.RangeStatus zawiera w sobie informacje, czy pomiar się udał. Fajna rzecz, aby odsiewać błędne pomiary.
Odległość znajdziesz pod RangingData.RangeMilliMeter.
Trochę pomiarów i liczb
Nie byłbym sobą, gdybym nie zerknął w analizator, co nie? Na początek cały “cykl” pomiarowy. Na szczęście pin przerwania chodzi cały czas, więc łatwiej mi złapać odpowiednie czasy.
100 kHz
400 kHz
Między kolejnymi “przerwaniami” jest 41 ms dla 100 kHz oraz 36 ms dla 400 kHz. Według dokumentacji próbkowanie i przeliczanie dystansu trwa ~31 ms, więc dodając czas na transmisję I²C mogłoby się zgadzać. Jak widzisz trochę danych idzie po magistrali. Mniejsze słupki to odpytywanie czujnika czy już skończył. Jasna sprawa, gdy działamy w trybie pollingu. Są jednak dwa szersze słupki. Pierwszy to zlecenie pomiaru.
100 kHz
3.14 ms dla 100 kHz vs 0.84 ms dla 400 kHz. Zgadza się z teorią 🙂
Pozostało jeszcze sprawdzić w ile MCU uwinie się zebrać pomiar.
2.8 ms dla 100 kHz vs 0.795 ms dla 400 kHz.
Jak widzisz jest dosyć sporo danych do zebrania z takiego czujnika. Idealnie byłoby zaciągnąć do tego tryb przerwaniowy, a najlepiej razem z DMA. Póki co nie wiem czy API to wspiera. Trochę już spędziłem czasu na walkę z jego uruchomieniem.
Rozmiar kodu
Na początku wspomniałem o tym, że to API trochę waży. W tabeli ująłem goły kod ze skonfigurowanymi peryferiami bez dołączonego API oraz kody z API. Obydwie wersje z optymalizacją -O1 i bez -O0. Oto liczby.
-O0 | -O1 | |
---|---|---|
Goły kod | 8,73 KB | 5,57 KB |
z API | 36 KB | 22,52 KB |
Jak widzisz trochę to APi waży… Czy warto? Jeszcze nie jestem w stanie na to odpowiedzieć. Będę próbował na nim zrobić coś z przerwaniami lub DMA 🙂
Podsumowanie
Czujnik VL53L0X jest fajny, mały, ale trochę skomplikowany. API pomaga sporo, ale kosztuje bardzo dużo miejsca na mikokontrolerze. Sprawdzę jeszcze, jakie są możliwości używania tej biblioteki z przerwaniami lub DMA.
Istnieją też biblioteki dla Arduino pisane na podstawie tego API. Widziałem, że są trochę lżejsze. Być może im również się przyjrzę.
Drugą część z obsługą przerwaniową znajdziesz tutaj
Pełny projekt wraz z biblioteką znajdziesz jak zwykle na moim GitHubie: link
Jeśli zauważyłeś jakiś błąd, nie zgadzasz się z czymś, chciałbyś coś dodać istotnego lub po prostu uważasz, że chciałbyś podyskutować na ten temat, napisz komentarz. Pamiętaj, że dyskusja ma być kulturalna i zgodna z zasadami języka polskiego.
8 komentarzy
Sirk · 16/06/2022 o 20:17
Czy próbowałeś uruchomić więcej niż 1 sensor na jednej magistrali i2c?
Mateusz Salamon · 20/06/2022 o 14:21
Nie, ale to jest do ogarnięcia 🙂 Z tego co pamietam, to trzeba sobie napisać procedurę startu. Każdy czujnik ma na starcie ten sam adres. Trzeba po kolei każdy włączać pinem SHDN (pozostałe nieaktywne) i mu zmieniać adresy (ma je programowe). Jak zmienisz każdemu, to już możesz mieć włączone wszystkie naraz 🙂
Gunt · 09/01/2020 o 18:25
całkiem fajny czujniczek … masz jakiś namiar na niego ?? czy może u ciebie w sklepie ??
Mateusz Salamon · 09/01/2020 o 18:27
Możesz go kupić u mnie 🙂 Pozostało mi jeszcze kilka sztuk https://sklep.msalamon.pl/produkt/vl52l0x-laserowy-czujnik-odleglosci-tof/
Gunt · 23/01/2020 o 16:18
trochę późno zauważyłem u ciebie i zamówiłem na aliexpresie z całą resztą nieprzydasi 🙂
Mateusz Salamon · 23/01/2020 o 16:31
To chociaż pochwal się co tam zamówiłeś 😛 Może do siebie bym coś wziął 🙂
Kürşad · 09/01/2020 o 10:14
hi, that is perfect project , i test it your code in stm32f103c8 and ili9341 not work “pls help” 🙁 thanks for now
Mateusz Salamon · 09/01/2020 o 18:25
Hi! I don’t know what did you do for now 😀 Please send me an e-mail with all details: mateusz@msalamon.pl.