fbpx

W ostatnim artykule przedstawiłem Ci perypetie związane z oryginalnym API dostarczanym do laserowego czujnika odległości VL53L0X. Dotarłem do tego, że udało mi się uruchomić pojedynczy pomiar. Jednak chciałbym nieco odciążyć mikrokontroler, aby nie musiał on tyle czekać na zakończenie pomiaru. Czujnik ma wyprowadzone wyjście przerwaniowe, więc czemu by z niego nie skorzystać?Pierwszą część z opisem działania czujnika i prostą obsługą blokującą znajdziesz tutaj

Tryby pracy VL53L0X

Nasz bohater potrafi pracować w dwóch trybach. Pojedynczego pomiaru (Single measurement) oraz ciągłego pomiaru (Continuous measurement).

W pierwszym z nich to my jako programiści musimy zadbać o to, aby pomiar wystartować, poczekać na jego zakończenie oraz odczytać wynik.

W trybie ciągłym pomiar dokonywany jest cały czas, a naszym zadaniem jest jedynie odczytywanie zmierzonych wartości.

Naturalnie nasuwa się tutaj myśl o wykorzystaniu przerwania. Dostajemy przerwanie za każdym razem, gdy czujnik zakończy pomiar i ma już przygotowane dla nas dane. Co ciekawe przerwanie takie wystawiane jest zarówno w trybie pojedynczego pomiaru, jak i ciągłego. Pewnie API w obydwu przypadkach domyślnie włącza przerwanie w czujniku.

Po co przerwanie w trybie pojedynczym? Po pierwsze po to, aby nie czekać na zakończenie pomiaru. Po drugie użycie trybu pojedynczego pozwoli nam rzadziej mierzyć odległość. W trybie ciągłym czujnik jedzie tyle, ile fabryka dała.

Platforma testowa

Do testów pracy ciągłej użyłem tej samej konfiguracji, co w poprzednim wpisie. Jeżeli jeszcze go nie widziałeś, zachęcam do przeczytania -> Tani laserowy pomiar odległości z czujnikiem ToF VL53L0X

W dużym skrócie używam:

Praca w trybie ciągłym

Na początek wypadałoby sprawdzić jak działa tryb ciągły z przykładów. Tak jak poprzednio, wezmę przykład z tego, co udostępniane jest w API. 

Kod wygląda następująco.

  //
  // VL53L0X init for Continuous 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_CONTINUOUS_RANGING);

  VL53L0X_StartMeasurement(Dev);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {

	  VL53L0X_Error Status = WaitMeasurementDataReady(Dev);

      if(Status == VL53L0X_ERROR_NONE)
      {
          Status = VL53L0X_GetRangingMeasurementData(Dev, &RangingData);

          // Clear the interrupt
          VL53L0X_ClearInterruptMask(Dev, VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
          VL53L0X_PollingDelay(Dev);
      } else {
          break;
      }

	  if(RangingData.RangeStatus == 0)
	  {
		  MessageLen = sprintf((char*)Message, "Measured distance: %i\n\r", RangingData.RangeMilliMeter);
		  HAL_UART_Transmit(&huart2, Message, MessageLen, 100);
	  }

    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

Po ustawieniu w tryb ciągły wystarczy wystartować pomiar i maszyna rusza. Niestety w przykładzie jest aktywne oczekiwanie na skończony pomiar. API nie przewiduje pracy z przerwaniem od strzała i jest dopisana dodatkowa funkcja WaitMeasurementDataReady, która czynnie czeka. Szkoda, bo pomiar ciągły w tej chwili wygląda dokładnie tak samo, jak pomiar pojedynczy z tą różnicą, że nie trzeba startować konwersji.

Przyznasz, że jest to bezsens, prawda? Jak więc użyć przerwania? Oczywiście trzeba wykonać kilka drobnych kroków 🙂

Skorzystanie z przerwań przy użyciu API

Ok. Przejdź najpierw do skonfigurowania przerwania w Cube. Ustaw linię, do której wpięty jest pin GPIO1 z czujnika jako wejście przerwania. Tak – GPIO1 to pin przerwania w VL53L0X.

W moim projekcie jest to pin PB3, więc uruchamiam go jako GPIO_EXTI3. Pamiętaj, aby włączyć te przerwanie w konfiguracji NVIC.

I to by było na tyle z konfiguracji. Wygeneruj kod.

Teraz bardzo ważna rzecz. Nie wiem czemu, ale API włącza przerwania w samym czujniku od samego początku jego działania. Powoduje to problem polegający na tym, że już podczas inicjalizacji pojawiają się pierwsze przerwania. Nie zagłębiałem się czy oni faktycznie wykorzystują te przerwanie w inicjalizacji. Tak wygląda sama procedura inicjalizacji.

Teraz, jeśli masz włączone w STM32 przerwanie przed inicjalizacją, a tak jest, bo NVIC inicjowany jest zawsze z włączonym przerwaniem, to będzie problem. Taki problem, że inicjalizacja stanie i się zapętli w taki sposób.

Musisz więc WYŁĄCZYĆ przerwanie na czas inicjalizacji. Wtedy wszystko działa cacy 🙂

Całość inicjalizacji będzie wyglądać następująco.

  //
  // VL53L0X init for Single Measurement
  //

  HAL_NVIC_DisableIRQ(EXTI3_IRQn);

  VL53L0X_WaitDeviceBooted( Dev );
  VL53L0X_DataInit( Dev );
  VL53L0X_StaticInit( Dev );
  VL53L0X_PerformRefCalibration(Dev, &VhvSettings, &PhaseCal);
  VL53L0X_PerformRefSpadManagement(Dev, &refSpadCount, &isApertureSpads);
  VL53L0X_SetDeviceMode(Dev, VL53L0X_DEVICEMODE_CONTINUOUS_RANGING);
  VL53L0X_StartMeasurement(Dev);

  HAL_NVIC_EnableIRQ(EXTI3_IRQn);

Obsługa przerwania

No dobra, ale teraz jak napisać obsługę przerwania? Zobacz w mainie co jest wykonywane.

	  VL53L0X_Error Status = WaitMeasurementDataReady(Dev);

      if(Status == VL53L0X_ERROR_NONE)
      {
          Status = VL53L0X_GetRangingMeasurementData(Dev, &RangingData);

          // Clear the interrupt
          VL53L0X_ClearInterruptMask(Dev, VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
          VL53L0X_PollingDelay(Dev);
      } else {
          break;
      }

Najpierw czekamy na dane – to ma nam załatwić przerwanie. Następnie pobieramy dane, czyścimy przerwanie w czujniku i czekamy(??? tak było w przykłAdzie :D).

Wystarczy wyrzucić do obsługi przerwania odczyt danych i czyszczenie flagI przerwania i powinno działać, co? Cała obsługa przerwania EXTI3 będzie więc wyglądała tak.

void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == TOF_INT_Pin)
	{
		VL53L0X_GetRangingMeasurementData(Dev, &RangingData);
		VL53L0X_ClearInterruptMask(Dev, VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
		TofDataRead = 1;
	}
}

Dołożyłem jeszcze zmienNą pomocniczą, aby nie printować w mainie mnóstwa danych, a jedynie wtedy, gdy są odczytane.

Możesz więc wyrzucić z pętli głównej obsługę trybu ciągłego, bo w zasadzie cała jest teraz w obsłudze przerwania. W pętli zajmujesz się jedynie sprawdzeniem, czy była nowa próbka oraz jej wyprintowaniem na terminal.

  while (1)
  {

	  if(TofDataRead == 1)
	  {
		MessageLen = sprintf((char*)Message, "Measured distance: %i\n\r", RangingData.RangeMilliMeter);
		HAL_UART_Transmit(&huart2, Message, MessageLen, 100);
		TofDataRead = 0;
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }

Czy to działa?

– Paaanie napisał się Pan, ale czy to działa?
– Paanie jeszcze jak!

Wystarczy teraz uruchomić kod a naszym oczom na terminalu ukarzą się nadlatujące dane. No ok tak samo było w trybie pojedynczego strzału powiesz. Zobaczymy co się dzieje na drutach.

Pięknie! Mikrokontroler jedynie miesza interfejsem I²C wtedy, gdy gotowa jest nowa próbka w czujniku. O to nam chodziło! 🙂

Czas między przerwaniami to niecałe 32 milisekundy. Podobnie było w trybie pojedynczym.

Obsługa przerwania

Zobaczmy jeszcze jak wygląda i ile trwa sama obsługa przerwania.

Trwa ona około 638 µs przy prędkości zegara I²C 400 kHz. Trochę danych chodzi po magistrali.

Podsumowanie

Obsługa przerwaniowa w trybie ciągłym mocno odciąża mikrokontroler w znacznym stopniu. Jednak nadal kolejne próbki przychodzą w odstępie około 30 milisekund. Nie jest to demon prędkości.

Od ostatniego wpisu dostaję mnóstwo pytań w związku z tym czujnikiem. Najczęściej pojawiają się pytania o porównanie takiego lasera z klasycznym ultradźwiękowcem HC-SR04. Pomyślałem, że fajnie byłoby zrobić taki test 🙂 Jeśli masz jakieś sugestie, w jakich warunkach sprawdzić obydwa czujniki, to napisz w komentarzu.

Pierwszą część z opisem działania czujnika i prostą obsługą blokującą 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.

5/5 - (6 votes)

Podobne artykuły

.

4 komentarze

Robert · 04/08/2021 o 20:46

Próbuje przenieść ten projekt na Nucleo411. Robię trochę w ciemno.
Czy planujesz zrobić artykuł o przenoszeniu pomiędzy procesorami? Ciężko mieć zawsze taką płytkę jak w przykładzie jest.

    Robert · 07/08/2021 o 11:55

    Zaczyna coś działać.
    Prób kompilacji wyrzuca błąd. Nie pasuje mu linia w pliku “sys.c” w której jest odwołanie #include “sys.h”.
    Bez tego działa na terminalu jest cały czas zerowa odległość.

slawek · 24/01/2020 o 19:56

Napisałeś coś takiego “Jednak chciałbym nieco odciążyć mikrokontroler,” Mógłbyś wytłumaczyć jak wyznaczyć obciążenie procesora? Nie rozumiem jak to się określa. Mówi się też że brakuje mocy obliczeniowej, skąd to wiadomo?

    Mateusz Salamon · 24/01/2020 o 22:01

    Ważna tutaj jest dalsza część zdania 🙂 W poprzednim wpisie procesor wykonywał mnóstwo “pustych” cykli zegara, oczekując na zakończenie pomiaru. W tym samym czasie mógłby wykonać miliony innych, bardziej przydatnych rzeczy. Otrzymanie przerwań z czujnika właśnie na to mu pozwala. Trochę skrótowo nazwałem to “odciążeniem”, bo w sumie ciężkiej pracy nie robił. Bardziej precyzyjnie byłoby “zwolnienie czasu” lub coś w tym stylu.

    Mocy obliczeniowej będzie brakowało jak procesor nie będzie wyrabiał się z zadaniami, które ma do zrobienia. Może to być właśnie brak mocy lub – co chyba częstsze – słaba optymalizacja. Takie aktywne czekanie na pomiar będzie właśnie słabą optymalizacją kodu 🙂

    Co do wyliczeń na ile obciążany jest, to jedno jest pewne – zawsze na 100%. Należałoby doprecyzować to “obciążenie” jako jakąś pracę. Coś co jest poza stanem Idle(bezczynności). Takim Idle może być ten nieszczęsny aktywny Delay 🙂 Łatwiej liczyć takie obciążenie w RTOS, gdzie jest jakiś task Idle i po prostu zliczać mu czas.

    Udało mi się odpowiedzieć na pytanie? 😀

Dodaj komentarz

Avatar placeholder

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