Analogowy czujnik odległości SHARP

Analogowe czujniki odległości firmy SHARP są dosyć popularnymi sensorami często stosowanymi w niewielkich robotach mobilnych np. robotach minisumo. Swoją popularność zawdzięczają stosunkowo dobrą dokładnością pomiarów oraz prostym interfejsem, który wymaga tylko jednego pinu mikrokontrolera – wejścia analogowego. W artykule postaram się przedstawić budowę i zasadę działania czujnika GP2Y0A21YK0F oraz przykład programu odczytującego dane zaimplementowanego na zestawie Nucleo-L476RG z wykorzystaniem ADC, DMA oraz wyzwalania przez Timer.

Budowa i zasada działania

Czujnik odległości SHARP GP2Y0A21YK0F to czujnik, który wykorzystuje do pomiaru światło podczerwone (IR). Zbudowany jest z PSD (detektor położenia), IRED (dioda emitująca podczerwień) i jednostki przetwarzania sygnału.

W procesie pomiaru czujnik wykorzystuje metodę triangulacji, która polega na tym, że odbiornik i nadajnik są oddalone o ustaloną odległość i nachylone względem siebie w taki sposób, aby wiązka światła odbita od przedmiotu padała na detektor. Wykorzystanie tej metody sprawia, że pomiar jest odporny na zmiany temperatury czy długi czas działania czujnika.

Jak sama nazwa wskazuje, czujnik na wyjściu generuje sygnał analogowy, który należy przekonwertować na zmierzoną odległość. Problemem, jaki może pojawić się przy pierwszym kontakcie z czujnikiem, jest to, że zależność pomiędzy napięciem (a więc i wartością odczytaną przez konwerter ADC mikrokontrolera) nie jest liniowa. Rodzi to kłopoty przy implementacji programu i wyznaczeniu odległości na podstawie odczytu ADC. Wykres dostępny w dokumentacji czujnika pokazuje nam zależność jak na poniższym obrazku.

Dokumentacja podpowiada nam jednak pewne rozwiązanie. Jeżeli na osi x wykorzystamy odwrotność odległości, wykres w zakresie pomiarowym czujnika (czyli w przypadku SHARP GP2Y0A21YK0F od 10 do 80 cm) będzie bardzo zbliżony do liniowego.

Teraz pozostaje nam tylko wyznaczyć równanie, które będzie opisywało tę zależność. Ze względu na to, że oś y na wykresie nie jest zbyt dokładnie opisana, najłatwiej będzie nam samodzielnie wykonać pomiary dla kilkunastu odległości i wyznaczyć charakterystykę. Proces ten przedstawię w dalszej części artykułu, gdy będziemy mieli już zaimplementowany pomiar napięcia na wyjściu z czujnika.

W tym miejscu chciałbym wyjaśnić jeszcze jedną kwestię. Czujnik SHARP zasilany jest napięciem 5 V, ale jak możemy zauważyć na wykresach, napięcie wyjściowe nie przekracza ok. 3,1 V, zatem mieści się w zakresie pomiarowym przetwornika ADC. Jeżeli chcielibyśmy zmierzyć sygnał o wyższym napięciu, należałoby zastosować np. dzielnik napięcia.

Konfiguracja mikrokontrolera

Odczyt informacji z czujnika zaimplementujemy dla mikrokontrolera STM32L476RG na zestawie Nucleo-L476RG w środowisku STM32CubeIDE.

Tworzymy zatem nowy projekt wybierając „File->New->STM32 Project”. Przechodzimy przez wstępną konfigurację projektu i zabieramy się za konfigurację wyjść mikrokontrolera. Ja wygenerowałem projekt z domyślną konfiguracją dla płytki Nucleo, dlatego część pinów mam już skonfigurowane. Do obsługi czujnika będziemy potrzebowali wejścia analogowego. W tej roli wykorzystamy wejście 5 przetwornika ADC1 (ADC1_IN5) podłączone do pinu PA0. Aby poprawnie skonfigurować pin, przechodzimy do zakładki po lewej stronie i wybieramy „Analog->ADC1”, a następnie przy wejściu IN5 wybieramy tryb „IN5 Single-ended” (tryb pomiaru na kanale ADC w odniesieniu do masy).

Teraz możemy przejść do konfiguracji ustawień przetwornika. Konwerter ADC w mikrokontrolerach STM32 to zaawansowany i dość rozbudowany system. Daje nam wiele możliwości, ale co za tym idzie, na początku trudniej jest się odnaleźć w „gąszczu” ustawień. Podstawowym wyborem jest wybór rodzaju konwersji. Mamy do dyspozycji dwa tryby: regularny (regular) i wstrzykiwany (injected). Podstawową różnicą jest to, że tryb wstrzykiwany ma wyższy priorytet i w przypadku wywołania jednoczesnej konwersji na kanale regularnym i wstrzykiwanym, to kanał wstrzykiwany będzie obsłużony w pierwszej kolejności. Poza wyniki konwersji kanałów wstrzykiwanych są przechowywane w indywidualnych rejestrach, a kanałów regularnych w jednym wspólnym rejestrze, który trzeba dostatecznie szybko odczytać, aby nie został nadpisany przez kolejny pomiar. Do naszego zastosowania w zupełności wystarczy nam konwersja w trybie regularnym.

Wśród podstawowych ustawień przetwornika ADC możemy wyróżnić:

  • Ustawienia ogólne ADC (ADC Settings):
    • Clock Prescaler – dzielnik zegara taktującego przetwornik. Wyższa wartość spowoduje, że pomiary będą wykonywane wolniej.
    • Resolution – rozdzielczość pomiaru (6, 8, 10 lub 12 bitów).
    • Data alignment – sposób wyrównania bitów danych w rejestrze wyjściowym (do prawej lub do lewej).
    • Scan Conversion Mode – tryb skanowania dostępny w przypadku wykonywania pomiarów na kilku kanałach. Powoduje, że pomiar wykonywany jest na całej grupie kanałów jeden po drugi, czyli po wykonaniu pomiaru na jednym kanale, przetwornik automatycznie wykona pomiar na kolejnym (i tak aż przejdzie wszystkie kanały).
    • Continuous Conversion Mode – tryb ciągły pomiarów, umożliwia automatyczne wystartowanie pomiarów na kanale (grupie kanałów) zaraz po ukończeniu poprzedniego.
    • Discontinuous Conversion Mode – umożliwia wykonanie konkretnej ilości pomiarów (podgrupy) wewnątrz określonej grupy konwersji.
    • DMA Continuous Request – ustawia przesyłanie danych przez DMA w trybie ciągłym, czyli ADC generuje żądanie transferu DMA za każdym razem, gdy nowe dane są dostępne w rejestrze, nawet jeśli DMA wykonał ostatni transfer w grupie.
    • End Of Conversion Selection – określa, jakie zdarzenie generuje ustawienie flagi końca konwersji (pojedyncza konwersja czy konwersja całej grupy).
  • Ustawienia konwersji w trybie regularnym (ADC Regular Conversion Mode):
    • Enable Regular Conversion – włącza tryb konwersji regularnej
    • Nubmer of Conversion – określa liczbę konwersji do wykonania
    • External Trigger Conversion Source – wybór zdarzenia, które będzie rozpoczynało konwersję
    • External Trigger Conversion Edge – wybór zbocza, na którym będzie następowało wywołanie konwersji

Dla każdej konwersji możemy indywidualnie ustawić takie parametry, jak:

  • Channel – kanał pomiaru, możemy tutaj zdecydować w jakiej kolejności będą się wykonywały pomiary
  • Sampling Time – czas pomiaru zapisany w cyklach zegara, im dłuższy czas, tym dane są dokładniejsze

Powyżej przedstawiłem tylko najważniejsze parametry, z których będziemy korzystali najczęściej przy konfiguracji ADC.

W naszym przypadku będziemy wykonywali pomiar tylko na jednym kanale. Aby zautomatyzować odczyt danych o odległości, wykorzystamy zewnętrzne źródło wyzwalania pomiarów w postaci timera, który co 100 ms generował sygnał dla ADC.

W celu poprawnego skonfigurowania przetwornika wybieramy zatem brak dodatkowego Prescalera, rozdzielczość 12-bitów oraz wyrównanie danych do prawej. Ponieważ mamy tylko jeden pomiar, nie będziemy potrzebowali trybu skanowania. Pomiar ADC będziemy chcieli mieć wywoływany co 100 ms przez timera, dlatego tryb ciągły też nie będzie nam potrzebny. Analogicznie nie potrzebujemy też trybu Discontinuous. Włączamy natomiast DMA Continuous Request Request i End Of Conversion Mode jako zakończenie pojedynczej konwersji.

W ustawieniach trybu regularnej konwersji, wybieramy zewnętrzne zdarzenie jako Timer 3 Trigger Out Event (czyli przepełnienie od timera 3 – lista dostępnych źródeł przerwań jest dostępna w liście rozwijanej obok parametru External Trigger Conversion Source oraz w dokumentacji „Reference Manual” na stronie 528 w tabeli 108.) oraz zbocze narastające. W ustawieniach pomieru wybieramy kanał 5 (wejście na pinie PA0) oraz najdłuższy czas konwersji, czyli 640,5 cykli, co zapewni nam większą dokładność. Pełna konfiguracja widoczna jest poniżej.

Żeby wykorzystać przesyłanie danych za pomocą DMA, w zakładce DMA Settings wybieramy ADC1 i DMA1 Channel 1. W ustawieniach może pozostać tryb Circular, bez inkrementacji adresów (mamy tylko jeden pomiar) oraz długość danych Half Word.

Na koniec włączamy jeszcze przerwania od DMA, dzięki czemu będziemy wiedzieli kiedy zakończył się pomiar, dane są gotowe w naszej zmiennej i można już obliczyć odległość.

Teraz powinniśmy skonfigurować jeszcze Timer 3 w taki sposób, aby wywoływał nam pomiar na ADC co 100 ms. Wybieramy zatem Timers->TIM3. W górnej części zaznaczamy Clock Source jako Internal Clock.

Aby skonfigurować licznik, musimy odpowiednio ustawić wartości: Prescaler i Counter Period. Potrzebujemy zatem informacji o częstotliwości taktowania Timera 3. Zgodnie z dokumentacją (Datasheet) mikrokontrolera na stronie 17, TIM3 podłączony jest do szyny taktującej APB1 i zgodnie z konfiguracją zegara, taktowany będzie z częstotliwością 80 MHz.

Zgodnie ze wzorem:

TIM_Freq = APB1_Freq / (Prescaler * Counter Period )

Aby uzyskać częstotliwość 10 Hz musimy podzielić zegar przez 8 000 000. Biorąc pod uwagę maksymalne wartości, jakie możemy wpisać do ustawień Prescaler i Counter Period, konfiguracja Timer 3 będzie wyglądała następująco:

Aby Timer mógł generować zdarzenie dla ADC, zaznaczamy jeszcze opcję Trigger Event Selection TRGO jako Update Event. Widok wyjść mikrokontrolera w konfiguratorze będzie się przedstawiał jak na poniższym obrazku.

Przy tak skonfigurowanych peryferiach możemy wygenerować projekt („Project->Generate Code” lub „Alt+K„) i przejść do napisania kodu programu.

Implementacja

Kod programu w dzisiejszym projekcie będzie bardzo prosty i sprowadza się tylko do kilku czynności, dlatego nie będę wydzielał w tym celu dodatkowych plików, a całość umieszczę w pliku main.c. Po pierwsze stworzymy w dwie zmienne do przechowywania wartości pomiaru ADC oraz zmierzonej odległości.

/* USER CODE BEGIN PV */
static uint32_t adc_measurement = 0;
static volatile uint32_t distance_cm = 0;
/* USER CODE END PV */

Następnie aby wystartować pomiar, wykonujemy dwie czynności przed pętlą while(1) – uruchamiamy przetwornik ADC z wykorzystaniem DMA oraz startujemy Timer 3.

  /* USER CODE BEGIN 2 */
  HAL_ADC_Start_DMA(&hadc1, &adc_measurement, 1);
  HAL_TIM_Base_Start(&htim3);
  /* USER CODE END 2 */

W tym momencie w zmiennej adc_measurement będziemy mieli przechowywany aktualny odczyt z ADC. Jak wspomniałem wcześniej, aby przeliczyć go na odległość, musimy wyznaczyć równanie opisujące zależność między odczytem z ADC a dystansem w cm. Do tego celu wykorzystamy Excela.

Najpierw musimy jednak zebrać dane. Poniżej przedstawiam schemat połączenia czujnika z Nucleo-L476RG.

Aby podejrzeć odczyt z czujnika bez dodawania do projektu wyświetlacza, wykorzystamy narzędzie STM32CubeMonitor. Jest to dość rozubudowany program do akwizycji danych. Dzisiaj przedstawię tylko prostą konfigurację prowadzącą do ustawienia podglądu zmiennej w widżecie typu Gauge.

Po uruchomieniu programu otrzymujemy ekran startowy. Tworzymy schemat przepływu danych analogicznie to przedstawionego poniżej. Do prezentacji danych wybieramy obiekt „gauge”. Ponieważ widżet ten może przyjmować jako element wejściowy tylko jedną zmienną, a blok Processing przekazuje na wyjściu tablicę ze zmiennymi, pomiędzy Processing, a Gauge dodajemy blok Single value.

Następnie konfigurujemy zmienne, jakie chcemy wyświetlić. Klikamy dwa razy w pole „myVariables” i ustawiamy plik „Executable” klikając podając ścieżkę i plik „*.elf”. Wybieramy zmienną adc_measurement. Następnie w polu „my_Probe_Out” i „myProbe_In” ustawiamy nasz programator. W polu Variable_Filter jako varfilter wybieramy zmienną do wyświetlania. W konfiguracji myGauge możemy jeszcze dobrać jednostki do wyświetlania oraz progi dla kolorów, dzięki czemu nasz wykres będzie się zmieniał w zależności od zmierzonej odległości. Zatwierdzamy wszystko przyciskiem „Deploy” i otwieramy klikając w „Dashboard”. Na ekranie pojawi nam się okno z wykresem. Aby rozpocząć pomiary należy kliknąć przycisk START ACQUISITION. Pamiętaj aby przed uruchomieniem STM32CubeMonitor-a zaprogramować Nucleo (należy skompilować kod Project->Build Project i go uruchomić Run->Run).

Do pomiarów wykorzystałem czujnik umocowany do biurka, metrówkę oraz przeszkodę w postaci kartonu. Wykonałem pomiary dla kilkunastu odległości, które umieściłem w tabeli w Excelu. Pamiętaj, że im dokładniejsze pomiary wykonasz, tym dokładniej czujnik będzie działał.

Odległość [cm]Pomiar ADC
103000
151900
201480
251270
301070
35910
40810
50700
60570
70520
80440

Jak opisywałem w części dotyczącej czujnika, łatwiej będzie nam wyznaczyć równanie, gdy weźmiemy odwrotność pomiarów, ponieważ wówczas zależność będzie liniowa. Ponieważ naszą daną wejściową będzie pomiar ADC, dla osi X zaznaczamy dane z wartościami 1/Pomiar ADC, a dla osi Y odległość w cm. Tworzymy wykres liniowy, a następnie wyznaczamy linię trendu wraz z równaniem (prawym przyciskiem na wykres Seria->Linia 1, a następnie zaznaczamy Linia trendu i poniżej Etykieta->Użyj równania – tak to wygląda w Arkuszu online). Wykres wraz z linią trendu i wyznaczonym równaniem wygląda jak poniżej.

Teraz możemy zaimplementować równanie w kodzie programu. Dodajemy zatem makro.

/* USER CODE BEGIN PM */
#define CONVERT_ADC_TO_DISTANCE(adc_val)	((37376UL / adc_val) - 4)
/* USER CODE END PM */

A następnie obsługę przerwania.

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
	if(adc_measurement == 0)
		return;

	distance_cm = CONVERT_ADC_TO_DISTANCE(adc_measurement);
}
/* USER CODE END 4 */

Po ponownym skompilowaniu programu oraz wgraniu na płytkę możemy odczytać zmierzoną odległość. Pamiętaj, aby w STM32CubeMonitor w bloku „myVariables” wybrać zmienną distance_cm.

Podsumowanie

Pomiar wartość z czujnika analogowego jest jedną z podstawowych czynności używanych w programach sterujących robotami. W artykule przedstawiłem sposób odczytu danych z konwertera ADC oparty na wykorzystaniu mechanizmu DMA oraz wyzwalania przez timer, dzięki czemu obsługa w samym kodzie programu jest krótka i prosta. Mam nadzieję, że ułatwi Ci to implementację obsługi czujników analogowych we własnych projektach.

Do pobrania

Repozytorium GitHub

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *