О том, как подключить VL53L0 к STM32F103C8T6 и сделать так, чтобы всё работало.

Для чего нужны ToF-датчики?

ToF-датчики (Time-of-Flight) используются для точного измерения расстояния до объектов на основе времени прохождения светового сигнала. Они применяются в различных сферах, включая робототехнику, где помогают избегать препятствий, в смартфонах, а также в системах дополненной реальности для точного позиционирования виртуальных объектов. В промышленности ToF-датчики используют для контроля качества и автоматизации процессов, а в умных домах — для обнаружения присутствия людей и управления освещением. Их ключевые преимущества — высокая точность, быстродействие и стабильная работа в разных условиях освещенности.

Практическая часть

Изначально я хотел использовать времяпролетный датчик VL53L5, но нашел VL53L0, который заметно дешевле и попроще. Затем начал искать для него библиотеку и нашел её на официальном сайте STMicroelectronics, однако запустить VL53L0 с ней не вышло. В итоге, отыскал рабочую библиотеку на GitHub с использованием STM32F401 и переделал её для работы с STM32F103.

Прежде всего, запускаем CubeIDE и настраиваем проект

  1. Во вкладке RCC выбираю Crystal/Ceramic Resonator

  2. Во вкладке SYS выбираю Serial Wire

  3. Во вкладке Connectivity включаю I2C1 и USART2

    Включение I2C1
    Включение I2C1
    Включение USART2
    Включение USART2
  4. Выход PB9 устанавливаю, как GPIO_EXTI9

  5. Затем перехожу во вкладку NVIC и ставлю галочку на EXTI line interrupts

  6. Во вкладе GPIO ставлю следующие настройки

  7. Выставляю максимальную частоту 72 Гц

  8. И создаю проект

Затем необходимо добавить саму библиотеку для VL53L0. Для этого:

  1. Нужно скачать библиотеку с GitHub - https://github.com/lamik/VL53L0X_API_INT_STM32_HAL

  2. Заходим в архив VL53L0X_API_INT_STM32_HAL, каталог Drivers и копируем папку VL53L0X.

  3. Затем находим папку с проектом и переходим в каталог Drivers и помещаем в него скопированную папку VL53L0X.

  4. В папке VL53L0X переходим в каталог platform, потом в папку src, открываем файл vl53l0x_platform.c, например, в блокноте или любом другом редакторе текста и меняем строку #include "stm32f4xx_hal.h" на #include "stm32f1xx_hal.h". После этого, возвращаемся в каталог platform и в папке inc открываем файл vl53l0x_platform.h и также меняем строку #include "stm32f4xx_hal.h" на #include "stm32f1xx_hal.h".

    Изменение файла vl53l0x_platform.c
    Изменение файла vl53l0x_platform.c
    Изменение файла vl53l0x_platform.h
    Изменение файла vl53l0x_platform.h
  5. Теперь переключаемся в CubeIDE. В боковой панеле справа один раз нажимаем на наш проект и жмем сочетание клавиш Alt + Enter или нажимаем на File в верхнем левом углу и жмем Properties

  6. Раскрываем список, нажимая на галочку возле C/C++ General, и из выпадающего списка выбираем пункт Paths and Symbols

  7. Нажимаем на кнопку Add справа и добавляем путь до папки Inc, находящейся в каталоге Drivers, в папке VL53L0, в каталоге Core

    Жмем на File system
    Жмем на File system
    Нажимаем ОК
    Нажимаем ОК

    То же самое проделываем для папки Inc, только находящейся в каталоге platform

    Добавление папки Inc из каталога platform
    Добавление папки Inc из каталога platform
    Результат
    Результат

    Жмём Apply.

  8. Теперь нажмем на галочку возле C/C++ Build и в выпадающем списке нажмем на Settings. Затем перейдем в MCU/MPU Settings и поставим галочку возле Use float with printf from newlib-nano. Также нам нужен hex-файл, для этого перейдем в MCU/MPU Post build outputs и поставим галочку возле Convert to Intel Hex file

    Жмем Apply и Apply and Close

Наконец, переходим к коду.

Подключаем библиотеку для работы с VL53L0

/* USER CODE BEGIN Includes */
#include "vl53l0x_api.h"
#include "stdio.h"
/* USER CODE END Includes */

Инициализируем переменные для вывода измерений и работы датчика

/* USER CODE BEGIN PV */
uint8_t Message[64];
uint8_t MessageLen;

VL53L0X_RangingMeasurementData_t RangingData;
VL53L0X_Dev_t  vl53l0x_c; // center module
VL53L0X_DEV    Dev = &vl53l0x_c;

volatile uint8_t TofDataRead;
/* USER CODE END PV */

Добавил собственные функции для вывода данных через UART

/* USER CODE BEGIN 0 */
void String_write(char sx[])
{
    HAL_UART_Transmit(&huart2, (uint8_t *)sx, strlen(sx), 100);
}

void String_writeln(char sx[])
{
    String_write(sx);
    char cr[] = "\r\n";
    HAL_UART_Transmit(&huart2, (uint8_t *)cr, strlen(cr), 100);
}

void Int_write(int32_t x)
{
    char buf[BUFSIZ];
    sprintf(buf, "%ld", x);
    String_write(buf);
}

void Int_writeln(int32_t x)
{
    Int_write(x);
    char cr[] = "\r\n";
    HAL_UART_Transmit(&huart2, (uint8_t *)cr, strlen(cr), 100);
}

void Float_write(float x)
{
    char buf[BUFSIZ];
    sprintf(buf, "%f", x);
    String_write(buf);
}

void Float_writeln(float x)
{
    Float_write(x);
    char cr[] = "\r\n";
    HAL_UART_Transmit(&huart2, (uint8_t *)cr, strlen(cr), 100);
}
/* USER CODE END 0 */

Инициализация переменных

/* USER CODE BEGIN 1 */
	uint32_t refSpadCount;
	uint8_t isApertureSpads;
	uint8_t VhvSettings;
	uint8_t PhaseCal;
  /* USER CODE END 1 */

Вывод сообщения через UART и объявление функций для начала измерений

/* USER CODE BEGIN 2 */
  MessageLen = sprintf((char*)Message, "msalamon.pl VL53L0X Continuous mode\n\r");
  HAL_UART_Transmit(&huart2, Message, MessageLen, 100);

  Dev->I2cHandle = &hi2c1;
  Dev->I2cDevAddr = 0x52;

  HAL_NVIC_DisableIRQ(EXTI9_5_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(EXTI9_5_IRQn);
  /* USER CODE END 2 */

Вывод измерений через UART в мм на одной строке

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

Вывод измерений в реальном времени при помощи прерываний

/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
	if(GPIO_Pin == GPIO_PIN_9)
	{
		VL53L0X_GetRangingMeasurementData(Dev, &RangingData);
		VL53L0X_ClearInterruptMask(Dev, VL53L0X_REG_SYSTEM_INTERRUPT_GPIO_NEW_SAMPLE_READY);
		TofDataRead = 1;
	}
}
/* USER CODE END 4 */

Нажимаем на молоток на верхней панели. В консоли должна быть похожая картина

Схема подключения

Теперь надо загрузить программу в микроконтроллер, для этого буду использовать CubeProgrammer. Подключаем STM32F103C8T6 при помощи st-link к компьютеру

Подключение к STM32
Подключение к STM32

Укажем путь до hex-файла, который находится в папке Debug. Перед этим переведем джампер на плате в положение boot 1 и нажмем на кнопку.

Нажимаем на кнопку Start Programming

Для того, чтобы увидеть результат измерений понадобится программа Putty. Подключаем UART к ПК и заходим в диспетчер устройств и смотрим, под каким com-портом он определился, в моем случае COM7

Жму на него два раза и выставляю, чтобы значение Bits per second было 115200.

Затем, захожу в Putty. Выбираю Connection type: serial, указываю com-порт и скорость. Нажимаю open.

Получаю расстояние в миллиметрах, которое изменяется в реальном времени

Вот таким образом можно подключить VL53L0 к STM32F103 и получать расстояние до объектов, которое изменяется в реальном времени.

Комментарии (6)


  1. VT100
    05.07.2025 18:40

    Нахождение на ГитХабе другого варианта кода вместо вендорского (а что не работало-то?), прокликивание пунктов в Кубе и рисование наброска схемы во Фритцинге - никак не тянут на плашку "Тьюториал".


    1. d_nine
      05.07.2025 18:40

      Да тут вообще по-хорошему даже без либы работы на вечер (очень не торопясь), поднять конфиг в кубе, завести i2c шину, да обработчик прерывания состряпать.


  1. miksoft
    05.07.2025 18:40

    Что такое "ToF-датчик" и что такое "времяпролетный датчик" ?


    1. ilshk Автор
      05.07.2025 18:40

      Времяпролетный датчик, или ToF (Time-of-Flight) датчик, это тип датчика, который измеряет расстояние до объекта, измеряя время, за которое свет или другой сигнал проходит от датчика до объекта и обратно.


    1. hrabrahrabr
      05.07.2025 18:40

      Присоединяюсь! Авторы хабра, вы задолбали писать статьи о том , как делать что то полезное, но не писать ,хотя бы кратко ,зачем это вообще нужно!

      Пришлось гуглить :

      https://dzen.ru/a/XCHuCzitawCp7Mqb


  1. x89377
    05.07.2025 18:40

    Шедеврально. Тянет на кандидатскую диссертацию. Точно !