cancel
Showing results for 
Search instead for 
Did you mean: 

Help with Using Interrupts on the VL53L0X Sensor with the NUCLEO-64 F411 Board

Hello,
I am using a VL53L0X sensor with a NUCLEO-64 F411 board, and I am trying to achieve accurate measurements and low power consumption for the development of a LiDAR system.
I have one problem and one question:

  • Problem:
    The measurements are a bit inaccurate. For example, when I measure an object at a real distance of 5 cm, the sensor detects it at around 7 cm. Also, for some objects beyond 20–25 cm, the readings become irregular.

  • Question:
    I would like to use the sensor with interrupts, but the User Manual for the API is not very clear on how to implement the interrupt feature.
    I am attaching my main.c file and the library I am using, which includes the API and its .h file.

Thank you very much for your help!

//This is the main.c
/* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file           : main.c
 * @brief          : Main program body
 ******************************************************************************
 * @attention
 *
 * Copyright (c) 2025 STMicroelectronics.
 * All rights reserved.
 *
 * This software is licensed under terms that can be found in the LICENSE file
 * in the root directory of this software component.
 * If no LICENSE file comes with this software, it is provided AS-IS.
 *
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "uart.h"
#include "lidarvl53.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
uint16_t milimetros;
float distancia;
char texto[100];
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;

UART_HandleTypeDef huart2;

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
 * @brief  The application entry point.
 * @retval int
 */
int main(void) {

	/* USER CODE BEGIN 1 */

	/* USER CODE END 1 */

	/* MCU Configuration--------------------------------------------------------*/

	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();

	/* USER CODE BEGIN Init */

	/* USER CODE END Init */

	/* Configure the system clock */
	SystemClock_Config();

	/* USER CODE BEGIN SysInit */

	/* USER CODE END SysInit */

	/* Initialize all configured peripherals */
	MX_GPIO_Init();
	MX_I2C1_Init();
	MX_USART2_UART_Init();
	/* USER CODE BEGIN 2 */
	lidar_init(dir_s1);

	/* USER CODE END 2 */

	/* Infinite loop */
	/* USER CODE BEGIN WHILE */
	while (1) {
		distancia = lidar_lee_mm(dir_s1);

		if (distancia < 0) {
			uartx_write_text(&huart2, "SENSOR NO CONECTADO\n");
		} else {
			sprintf(texto, "\r%f cm\n", distancia); // 2 decimales
			uartx_write_text(&huart2, texto);
		}

		/* USER CODE END WHILE */

		/* USER CODE BEGIN 3 */
	}
	/* USER CODE END 3 */
}

/**
 * @brief System Clock Configuration
 * @retval None
 */
void SystemClock_Config(void) {
	RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
	RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };

	/** Configure the main internal regulator output voltage
	 */
	__HAL_RCC_PWR_CLK_ENABLE();
	__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

	/** Initializes the RCC Oscillators according to the specified parameters
	 * in the RCC_OscInitTypeDef structure.
	 */
	RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
	RCC_OscInitStruct.HSIState = RCC_HSI_ON;
	RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
	RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
	if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) {
		Error_Handler();
	}

	/** Initializes the CPU, AHB and APB buses clocks
	 */
	RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
			| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
	RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
	RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
	RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
	RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

	if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != HAL_OK) {
		Error_Handler();
	}
}

/**
 * @brief I2C1 Initialization Function
 *  None
 * @retval None
 */
static void MX_I2C1_Init(void) {

	/* USER CODE BEGIN I2C1_Init 0 */

	/* USER CODE END I2C1_Init 0 */

	/* USER CODE BEGIN I2C1_Init 1 */

	/* USER CODE END I2C1_Init 1 */
	hi2c1.Instance = I2C1;
	hi2c1.Init.ClockSpeed = 400000;
	hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
	hi2c1.Init.OwnAddress1 = 0;
	hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
	hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
	hi2c1.Init.OwnAddress2 = 0;
	hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
	hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
	if (HAL_I2C_Init(&hi2c1) != HAL_OK) {
		Error_Handler();
	}
	/* USER CODE BEGIN I2C1_Init 2 */

	/* USER CODE END I2C1_Init 2 */

}

/**
 * @brief USART2 Initialization Function
 *  None
 * @retval None
 */
static void MX_USART2_UART_Init(void) {

	/* USER CODE BEGIN USART2_Init 0 */

	/* USER CODE END USART2_Init 0 */

	/* USER CODE BEGIN USART2_Init 1 */

	/* USER CODE END USART2_Init 1 */
	huart2.Instance = USART2;
	huart2.Init.BaudRate = 115200;
	huart2.Init.WordLength = UART_WORDLENGTH_8B;
	huart2.Init.StopBits = UART_STOPBITS_1;
	huart2.Init.Parity = UART_PARITY_NONE;
	huart2.Init.Mode = UART_MODE_TX_RX;
	huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
	huart2.Init.OverSampling = UART_OVERSAMPLING_16;
	if (HAL_UART_Init(&huart2) != HAL_OK) {
		Error_Handler();
	}
	/* USER CODE BEGIN USART2_Init 2 */

	/* USER CODE END USART2_Init 2 */

}

/**
 * @brief GPIO Initialization Function
 *  None
 * @retval None
 */
static void MX_GPIO_Init(void) {
	GPIO_InitTypeDef GPIO_InitStruct = { 0 };
	/* USER CODE BEGIN MX_GPIO_Init_1 */
	/* USER CODE END MX_GPIO_Init_1 */

	/* GPIO Ports Clock Enable */
	__HAL_RCC_GPIOA_CLK_ENABLE();
	__HAL_RCC_GPIOB_CLK_ENABLE();

	/*Configure GPIO pin : PA5 */
	GPIO_InitStruct.Pin = GPIO_PIN_5;
	GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

	/* EXTI interrupt init*/
	HAL_NVIC_SetPriority(EXTI9_5_IRQn, 0, 0);
	HAL_NVIC_EnableIRQ(EXTI9_5_IRQn);

	/* USER CODE BEGIN MX_GPIO_Init_2 */
	/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
 * @brief  This function is executed in case of error occurrence.
 * @retval None
 */
void Error_Handler(void) {
	/* USER CODE BEGIN Error_Handler_Debug */
	/* User can add his own implementation to report the HAL error return state */
	__disable_irq();
	while (1) {
	}
	/* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  *   file: pointer to the source file name
  *   line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

//Archive .h 
/*
 * lidarvl53.h
 *
 *  Created on: Jul 20, 2023
 *      Author: alcid
 */

#define lidar_max_mm 2000

#ifndef LIBERRIAS_VL53L0X_LIDARVL53_H_
#define LIBERRIAS_VL53L0X_LIDARVL53_H_

#define dir_s1 0x52  //direccion del 1° sensor
#define dir_s2 0x53  //direccion del 2° sensor
#define dir_s3 0x54  //direccion del 3° sensor
#define dir_s4 0x55  //direccion del 4° sensor
#define dir_s5 0x56  //direccion del 5° sensor
#define dir_s6 0x57  //direccion del 6° sensor
#define dir_s7 0x58  //direccion del 7° sensor
#define dir_s8 0x59  //direccion del 8° sensor



#include "vl53l0x_api.h"
#include "main.h"


void lidar_init(uint8_t dir);
float lidar_lee_mm(uint8_t dir);
float lidar_lee_cm(uint8_t dir);
uint8_t lidar_set_dir(uint8_t dir);

#endif /* LIBERRIAS_VL53L0X_LIDARVL53_H_ */
//Library
#include "lidarvl53.h"

#define HIGH_ACCURACY_MODE 200000


VL53L0X_RangingMeasurementData_t RangingData;
VL53L0X_Dev_t  vl53l0x_c; // center module
VL53L0X_DEV    Dev = &vl53l0x_c;
uint32_t refSpadCount;
  uint8_t isApertureSpads;
  uint8_t VhvSettings;
  uint8_t PhaseCal;

extern I2C_HandleTypeDef hi2c1;


void lidar_init(uint8_t dir)
{
	  VL53L0X_Error Status = VL53L0X_ERROR_NONE;
	  Dev->I2cHandle = &hi2c1;//el puerto i2c a usar
	  Dev->I2cDevAddr = dir;
      Dev->comms_type=1;
      Dev->comms_speed_khz=400;//  i2c a 400khz

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

	  VL53L0X_SetDeviceMode(Dev, VL53L0X_DEVICEMODE_CONTINUOUS_RANGING);

	  // Enable/Disable Sigma and Signal check
	  if (Status == VL53L0X_ERROR_NONE) {
		  Status = VL53L0X_SetLimitCheckValue(Dev, VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE,(FixPoint1616_t)(0.25*65536));
	  }
	  if (Status == VL53L0X_ERROR_NONE) {
		  Status = VL53L0X_SetLimitCheckValue(Dev,VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE,(FixPoint1616_t)(18*65536));
	  }
	  if (Status == VL53L0X_ERROR_NONE) {
		  Status = VL53L0X_SetMeasurementTimingBudgetMicroSeconds(Dev ,200000);
	  }
	  if(Status == VL53L0X_ERROR_NONE){
		  Status = VL53L0X_SetGpioConfig(Dev, 0, VL53L0X_DEVICEMODE_CONTINUOUS_RANGING, VL53L0X_GPIOFUNCTIONALITY_NEW_MEASURE_READY, VL53L0X_INTERRUPTPOLARITY_LOW);
	  }
	  if (Status == VL53L0X_ERROR_NONE) {
	      Status = VL53L0X_StartMeasurement(Dev);
	  }

}

float lidar_lee_mm(uint8_t dir)
{
    float lidarmil;
    float offset = 24.848;
    Dev->I2cDevAddr = dir;
    VL53L0X_GetRangingMeasurementData(Dev, &RangingData);
    lidarmil = (float)RangingData.RangeMilliMeter - offset;
    if (lidarmil > lidar_max_mm) lidarmil = lidar_max_mm;
    return lidarmil;
}



float lidar_lee_cm(uint8_t dir)
{
	return((float)lidar_lee_mm(dir)/10.0);
}

uint8_t lidar_set_dir(uint8_t dir)
{
	uint8_t status = VL53L0X_SetDeviceAddress(Dev, dir << 1);
	return(status);

}
1 REPLY 1
John E KVAM
ST Employee

You have a number of issues here. 

But basically, accuracy and power consumption are trade-offs. You can make ranges with very short integration times, which saves power - but they are not as accurate. You can make ranges with longer integration times which are more accurate - but the consume more power. 

As for the interrupt the sensor drops the Interrupt pin when the range is finished, and lifts it when you make a call to Clear_interrupt. (not sure of the exact call - look for something with clear interrupt in the name.)

But the code on the STM32 is a bit more complex. You need to set up the interrupt - and it looks to me like you did. 

You also need to have a interrupt service routine - and you need to link that service routine to that interrupt pin. 

I think this is the bit you left out. 

Would you consider switching to the VL53L4CD? It has a field of view of 18 degrees (narrower than the L0). And a max distance of 1.3 meters. But it's more accurate at the close distances. It's our least expensive sensor. And the code is a lot simpler. (The 1.3 max here is and effective max. The L0 has a stated 2M max, but the effective max is about the same.) But you will still need an interrupt service routine.

We even have a way to set the system up for very low power consumption, and when something comes into view, switch to the higher-power, higher-accuracy mode and give you a range.  

Good luck. 


If this or any post solves your issue, please mark them as 'Accept as Solution' It really helps. And if you notice anything wrong do not hesitate to 'Report Inappropriate Content'. Someone will review it.