Adding the VL53L1X Driver to an STM32Cube Project

Introduction

image

The VL53L1X Time-of-Flight (ToF) module is a long-distance laser-ranging sensor that is ideal for applications like gesture recognition, user/obstacle detection, camera autofocus, etc. While its predecessor (the VL53L0X) boasted an absolute range of up to 2 meters, the pin-to-pin compatible VL53L1X surpasses it with an absolute range of 4 meters and the ability to reduce its field-of-view (FoV). Prototyping with this sensor has been made simple with the Arduino compatible X-NUCLEO-53L1A1 evaluation board. This shield is populated with one VL53L1X sensor, which may be used with the included glass cover window and spacers to simulate the sensor’s behavior in a practical application. Also included are two VL53L1X breakout boards which may be connected directly to the shield or used independently with flying leads. ST has not published any documentation describing the internal registers of the VL53L1X, meaning the VL53L1X driver (also referred to as the VL53L1X API) must be used for the MCU to interface with the sensor. This includes everything from initializing/configuring the sensor to performing ranging measurements.

At the time of writing, ST’s product page for the X-NUCLEO-53L1A1 states that this expansion board is “for the NUCLEO-F401RE and NUCLEO-L476RG development boards”. This is a bit misleading. What they mean is that the example projects that have been provided for the X-NUCLEO-53L1A1 shield will only work with the NUCLEO-F401RE and NUCLEO-L476RG boards. For those wishing to get started as quickly as possible with the VL53L1X module, it is recommended that they use either of these Nucleo boards and modify ST’s example application. They might even consider purchasing the P-NUCLEO-53L1A1 evaluation kit which includes the X-NUCLEO-53L1A1 shield as well as a NUCLEO-F401RE board.

Requirements

The firmware, hardware, and software used in this tutorial are as follows.

  • STSW-IMG007 (Version 2.4.5)
    • This package contains the platform independent VL53L1X driver files (written in C). It can be found here.
  • X-CUBE-53L1A1 (Version 2.1.0)
    • This is a software expansion package for the X-NUCLEO-53L1A1 expansion board, including a Board Support Package (BSP) that utilizes the VL53L1X driver. An example program that may be run on either the NUCLEO-F401RE and NUCLEO-L476RG development boards is provided. It can be found here.
  • A Nucleo board
  • X-NUCLEO-53L1A1
    • An expansion board for evaluating the VL53L1X module. It can be purchased here.
  • STM32CubeMX* (Version 4.25.1)
    • With this tool, an STM32Cube project can easily be created along with any desired C initialization code. It may be downloaded here.
  • Atollic TrueSTUDIO for STM32* (Version 9.0.0)
    • Feel free to use another IDE if you prefer, e.g. Keil, IAR, etc. However, since ST has recently acquired Atollic, it is likely that they will be placing more and more emphasis on TrueSTUDIO as a development tool as time goes on. It can be found here.

*Since writing this tutorial, ST has released STM32CubeIDE, which like TrueSTUDIO, is also based on Eclipse. It also integrates the project initialization and configuration capabilities of STM32CubeMX, meaning the procedure outlined in this tutorial can still be followed using STM32CubeIDE with minimal alteration. If you have any questions about migrating to STM32CubeIDE, feel free to ask them below.

The X-NUCLEO-53L1A1 expansion board featuring the VL53L1X ToF module. Also shown are the two VL53L1X breakout boards with their 10 pin connectors that may be attached directly to the shield, the two different cover windows, and the spacers to simulate air gaps.

Creating an STM32Cube Project

For those who don’t already have an existing STM32Cube project, this section will quickly walk through setting one up using STM32CubeMX. For those looking for more detailed information and examples on using this tool, its user manual is a great resource.

  1. Open STM32CubeMX and select New Project . Using either the MCU selector tab or the Board selector tab, choose the MCU/board being used for the project. The Board selector tab (Figure 1) is the preferred option when using an ST evaluation board because not only are there fewer options to wade through, but the Pinout view will be initialized with the pin assignments for the LEDs, buttons, communication interfaces, etc. Use the filters to narrow down your choices, make a selection, and click Start Project . When it asks you if you would like to initialize all peripherals with their default mode, click No .

TIP: The MCU selector can be used during the initial design phase of a project to choose an MCU or development board based on your desired criteria.


Figure 1: Creating a new STM32Cube project for the NUCLEO-F103RB board.

  1. The X-NUCLEO-53L1A1 expansion board communicates with the MCU via the I2C1 peripheral on pins PB8 ( I2C1_SCL ) and PB9 ( I2C1_SDA ). In the Pinout view, click on each of these pins and select their I2C alternate functions. When this is done, the pins should be highlighted orange because the I2C1 peripheral has not yet been enabled. To enable it, expand I2C1 in the peripheral tree and select I2C from the dropdown list. Pins PB8 and PB9 should change from orange to green (see Figure 2).
  2. To print ranging data from the VL53L1X sensor to a serial terminal, pins PA2 and PA3 need to be configured as USART2_TX and USART2_RX respectively. If the Board selector tab was used to create the project, the alternate functions for these pins should already be configured. If not, configure them in the same way the I2C1 pins were configured in the previous step. Then, enable the USART2 peripheral by expanding USART2 in the peripheral tree and selecting Asynchronous mode from the dropdown list, as shown in Figure 2. Again, the pins will change from orange to green.

    Don’t worry about peripherals in the list with red crosses or yellow exclamation marks next to them. These are not errors, just FYI’s. For more information, move your mouse over the name of the peripheral with a symbol next to it and a tooltip will appear explaining the reason for the symbol.


Figure 2: Configuring the MCU pins and enabling the corresponding peripherals

  1. Open the settings window (Figure 3) by choosing Project > Settings from the main menu bar. Give your project a name and choose where to save it. Also, select your desired Toolchain/IDE from the dropdown list. Everything else should be fine at its default value. Click OK.


Figure 3: Modifying the project settings

  1. Generate the code for your project by selecting Project > Generate Code from the main menu bar. If all goes well, a message will be displayed confirming successful code generation. Click Close .
  2. Open Atollic TrueSTUDIO and navigate to File > Open Projects from File System . If you are using an older version and this option is not available, navigate instead to File > Import , choose Projects from Folder or Archive and click Next . Clicking the Directory button will open a browser from which you can select the directory which contains your project. Make sure that the box next to your project name is checked, as shown in Figure 4, and click Finish .


Figure 4: Importing the project into TrueSTUDIO

The project should now be available in the Project Explorer. If you expand the project directory and then expand the Src directory, you should find the main.c file. Looking through this file, you will see that along with the peripheral configuration functions, there are comments that designate user sections. Any code placed in these sections will be preserved the next time code is generated in STM32CubeMX.

Adding the VL53L1X Driver

Adding the VL53L1X driver to an STM32Cube project is quite simple once you understand the differences between the STSW-IMG007 package and the X-CUBE-53L1A1 package. The STSW-IMG007 package contains the VL53L1X driver along with a few “platform” files that must be modified by the programmer to port the driver to a specific MCU platform. The X-CUBE-53L1A1, on the other hand, is a complete STM32Cube expansion package that bundles the VL53L1X driver with a board support package (BSP) for the X-NUCLEO-53L1A1 expansion kit. In this package, the platform files have been modified to allow the driver to run on top of the STM32Cube HAL (hardware abstraction layer), thereby porting it to the entire STM32 MCU line. These modified platform files will be added along with the STSW-IMG007 files to our STM32Cube project. This way, we don’t have to modify the platform files ourselves to get the driver working on the STM32 platform.

In the steps below, files from both the STSW-IMG007 and X-CUBE-53L1A1 packages will be copied into the STM32Cube project we created in the previous section. This can easily be done by dragging and dropping files into the desired folder in the Project Explorer.

  1. Unzip the STSW-IMG007 package and add the core and platform directories to your project. For simplicities sake, I added them both to the Drivers directory after creating a subdirectory called VL53L1X, as shown in Figure 5. However, you can add them anywhere that makes the most sense to you. Exploring the X-CUBE-53L1A1 package will give you an idea of how ST chooses to organize these files in their projects.

A screenshot of the project explorer in the TrueSTUDIO IDE. The Drivers folder has been expanded to reveal the CMSIS folder, STM32F1xx_HAL_Driver folder, and the VL53L1X folder. The VL53L1X folder has been expanded to reveal the core folder and the platform folder.
Figure 5: The STM32Cube project’s directory structure after adding the VL53L1X driver files

  1. To port the VL53L1X driver to the STM32Cube project, unzip the X-CUBE-53L1A1 package and locate the “SimpleRanging” example project. It doesn’t matter which Nucleo board the project is based on. This tutorial utilizes the project located at en.X-CUBE-53L1A1_v2.1.0\STM32CubeExpansion_53L1A1_V2.1.0\Projects\STM32F401RE-Nucleo\Examples\53L1A1\SimpleRanging.

    a) Copy the Src/vl53l1_platform.c file into the platform/src directory of the project (see Figure 5), replacing the version that is already there.

    b) Copy the Inc/vl53l1_platform_user_data.h file into the platform/inc directory of the project (see Figure 5), replacing the version that is already there.

    c) Delete the vl53l1_platform_init.h and vl53l1_platform_init.c files from the platform/inc and platform/src directories, respectively.

  2. The driver files we just added contain many header files that will not be found by the compiler unless we add the directories that contain them to the list of include directories. This can easily be done in Eclipse-based IDEs by right-clicking on the directory in the Project Explorer and selecting Add/remove include path… , as shown in Figure 6a. This will result in the window shown in Figure 6b. Make sure both boxes are checked and click OK . For this example, this needs to be done for both the core/inc directory and the platform/inc directory.


a) Right-click on the folder and select Add/remove include path…


b) If the box is checked, the path will be added. If the box is unchecked, the path will be removed.

Figure 6: Adding a directory to the list of include directories

  • Alternatively, one could navigate to Project > Properties in the menu bar and from the resulting window, navigate to C/C++ General > Paths and Symbols . There, the desired directories can be added manually as shown in Figure 7.


Figure 7: The core/inc and platform/inc directories added to the list of included directories

  1. Open the main.c file in the editor. In the user section for includes, include the file vl53l1_api.h , as shown in Figure 8.

cubeide_includeApi
Figure 8: Including the VL53L1X API in main.c

  1. Open the file vl53l1_platform_user_data.h. Find the line where #include "stm32xxx_hal.h" is commented out. Replace this line with an include statement for the xx_hal.h file for your MCU. In my case, I added #include "stm32f1xx_hal.h", as shown in Figure 9.

cubeide_f1xx
Figure 9: Modifying the vl53l1_platform_user_data.h file

  1. Similarly, open the file vl53l1_platform.c . Replace #include "stm32xxx_hal.h" with the appropriate xx_hal.h file (e.g., #include "stm32f1xx_hal.h").

  2. Finally, build the project to locate an existing error in the vl53l1_wait.c file (Figure 10a). The first argument must be removed from each of the VL53L1_GetTickCount() invocations (Figure 10b).

cubeide_waitBugPresent
a) After building the project, two error are shown in the vl53l1_wait.c file.

cubeide_waitBugAbsent
b) Resolve the error by removing the first argument (“Dev”) from the function calls and rebuild the project.

Figure 10: Revising the vl53l1_wait.c file.

NOTE: If you are using a different version of the VL53L1X driver, this bug may not be present.

The project should now build successfully.

Using the VL53L1X Driver

Once the driver has been added to the project, you may wish to consult the VL53L1X API user manual to understand the different initialization, calibration, and ranging functions. However, while this manual provides plenty of excellent information about the driver and capabilities of the module, it lacks any example code. Luckily, as previously mentioned, the X-CUBE-53L1A1 package contains an example project that runs on two of the Nucleo boards. The main.c file found in en.X-CUBE-53L1A1_v2.1.0\STM32CubeExpansion_53L1A1_V2.1.0\Projects\STM32F401RE-Nucleo\Examples\53L1A1\SimpleRanging\Src\ contains a function called AutonomousLowPowerRangingTest() that demonstrates how the module can be configured and used in either interrupt or polling mode.

For those not using either the NUCLEO-F401RE or the NUCLEO-L476RG, it requires a bit of extra work getting the aforementioned example project to a run on a different board. This is because the BSP for the X-NUCLEO-53L1A1 shield used in the example has not been ported to any of the other Nucleo boards. Rather than port it to my board, however, I chose to re-write the example without the BSP. The only annoyance in doing so is interfacing with the GPIO expanders on the X-NUCLEO-53L1A1 shield. These expanders (ST’s STMPE1600 expanders) are essentially leftover from the previous iteration of this shield which utilized the VL53L0X module and included a 4-digit display. The final result is the main.c file provided below in Listing 1.

Listing 1: main.c

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * <h2><center>&copy; Copyright (c) 2021 STMicroelectronics.
  * All rights reserved.</center></h2>
  *
  * This software component is licensed by ST under BSD 3-Clause license,
  * the "License"; You may not use this file except in compliance with the
  * License. You may obtain a copy of the License at:
  *                        opensource.org/licenses/BSD-3-Clause
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "vl53l1_api.h"

/* USER CODE END Includes */

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

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* 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 */
/* Private variables ---------------------------------------------------------*/
// I2C addresses of GPIO expanders on the X-NUCLEO-53L1A1
#define EXPANDER_1_ADDR 0x84 // 0x42 << 1
#define EXPANDER_2_ADDR 0x86 // 0x43 << 1

/* 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 */
  uint8_t buff[50];
  VL53L1_RangingMeasurementData_t RangingData;
  VL53L1_Dev_t  vl53l1_c; // center module
  VL53L1_DEV    Dev = &vl53l1_c;

  /* 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 */

  // initialize vl53l1x communication parameters
  Dev->I2cHandle = &hi2c1;
  Dev->I2cDevAddr = 0x52;

  /*** Initialize GPIO expanders ***/
  // Unused GPIO should be configured as outputs to minimize the power consumption
  buff[0] = 0x14; // GPDR (GPIO set direction register)
  buff[1] = 0xFF; // GPIO_0 - GPIO_7
  buff[2] = 0xFF; // GPIO_8 - GPIO_15
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 3, 0xFFFF );
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_2_ADDR, buff, 3, 0xFFFF );

  // clear XSHUT (disable center module) -> expander 1, GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state register)
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  HAL_I2C_Master_Receive( &hi2c1, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  buff[1] = buff[0] & ~( 1 << ( 15 - 8 ) ); // clear GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state register)
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 2, 0xFFFF );

  HAL_Delay( 2 ); // 2ms reset time

  // set XSHUT (enable center module) -> expander 1, GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state)
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  HAL_I2C_Master_Receive( &hi2c1, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  buff[1] = buff[0] | ( 1 << ( 15 - 8 ) ); // set GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state register)
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 2, 0xFFFF );

  HAL_Delay( 2 );

  /*** VL53L1X Initialization ***/
  VL53L1_WaitDeviceBooted( Dev );
  VL53L1_DataInit( Dev );
  VL53L1_StaticInit( Dev );
  VL53L1_SetDistanceMode( Dev, VL53L1_DISTANCEMODE_LONG );
  VL53L1_SetMeasurementTimingBudgetMicroSeconds( Dev, 50000 );
  VL53L1_SetInterMeasurementPeriodMilliSeconds( Dev, 500 );
  VL53L1_StartMeasurement( Dev );

  /* USER CODE END 2 */

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

    /* USER CODE BEGIN 3 */
	VL53L1_WaitMeasurementDataReady( Dev );

	VL53L1_GetRangingMeasurementData( Dev, &RangingData );

	sprintf( (char*)buff, "%d, %d, %.2f, %.2f\n\r", RangingData.RangeStatus, RangingData.RangeMilliMeter,
			 ( RangingData.SignalRateRtnMegaCps / 65536.0 ), RangingData.AmbientRateRtnMegaCps / 65336.0 );
	HAL_UART_Transmit( &huart2, buff, strlen( (char*)buff ), 0xFFFF );

	VL53L1_ClearInterruptAndStartMeasurement( Dev );
  }
  /* USER CODE END 3 */
}

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

  /** 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_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI_DIV2;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL16;
  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_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

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

/**
  * @brief I2C1 Initialization Function
  * @param 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 = 100000;
  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
  * @param 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
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : B1_Pin */
  GPIO_InitStruct.Pin = B1_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  HAL_GPIO_Init(B1_GPIO_Port, &GPIO_InitStruct);

  /*Configure GPIO pin : LD2_Pin */
  GPIO_InitStruct.Pin = LD2_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LD2_GPIO_Port, &GPIO_InitStruct);

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

}

/* 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.
  * @param  file: pointer to the source file name
  * @param  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 */

/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

To run this example on your board, start by follow the steps outlined in the sections above. Once you have an STM32Cube project with the VL53L1X driver added, copy and paste the user code sections from the main.c file above to the main.c file in your project. These sections will be designated by comments of the form /* USER CODE BEGIN <section> */ and /* USER CODE END <section> */. You cannot copy the entire main.c unless you are also using the NUCLEO-F103RB board.

By default, my STM32Cube project was using the newlib-nano library, which decreases the functionality of the printf function. In order to print all of the ranging data to the serial terminal, I had to switch to the newlib standard library. In STM32CubeIDE, this can be done by right-clicking on the project name in the Project Explorer and choosing Properties. Then, choose Settings in the “C/C++ Build” category and select MCU Settings under the Tool Settings tab. Check the box next to “Use float with printf from newlib-nano”, as shown in Figure 11 below. Click Apply and Close.


Figure 11: Enabling float formatting support.

Conclusion

There are several ways one could go about adding the VL53L1X driver to their project, but I tried to select the simplest. Using both the STSW-IMG007 and X-CUBE-53L1A1 packages seems to require the least amount of file manipulation and reorganization. Hopefully you find the tutorial and example code helpful.

Hi

Thanks for this tutorial, I’ve followed it and it’s a perfect start to me.

However, ST released a newer version of STSW-IMG007 (v2.4.5), and it’s different in core somehow. Can you guide how to import the newer version, too?

The 53L1A1 examples uses an outdated version of the STS@-IMG007.

Thanks

Thank you for pointing this out @HamzaHajeir! I’ve been trying for the last several hours to use these updated drivers without much luck. They are certainly not backwards compatible with the old version. I’ll keep working and once I figure it out, I’ll provide an update.

1 Like

Thanks @Matt_Mielke for your efforts.

I’ve sent an inquiry to ST community, they responded with this guide file. Until I try it out, I doubt it’ll be useful.

Here’s the discussion.

Thanks again.
VL53L1X_Driver_Update_Guide.pdf (304.8 KB)

I’ve updated the article to work with the latest driver versions. Let me know if there are still any issues.

1 Like

Hello! thanks for your great contribution. I have followed all the steps and it has gone very well, but the sensor does not give me any data through the serial port. I am working with a stm32f103c8 with Stm32cubeide.
When the sensor is disconnected from the SDA pb7 and SCL pb6 pins, the serial port reads: 255,-1, ,

Could you help me ?
Thanks

Hi @passarellimaxi,

I’d be happy to help! Would you mind exporting your STM32CubeIDE project (choose FIle > Export) and sending me the .zip file? A schematic/drawing showing how the sensor is wired to the MCU would also be helpful. You can send these files to me in a private message by clicking on my name.

Hello, thank you very much for responding so quickly. I already sent you all the information.

9-STM32-pinout-diagram
sensor.zip (9,0 MB)

Hello @passarellimaxi, your schematic shows the VL53L0X sensor. Is that a typo or are you not using the newer VL53L1X?

Hi, i am using vl53l0x sensor.

Then by following this tutorial, you are using the wrong driver. You want the VL53L0X API.

If the problem is that, I could buy the vl53l1x.
Could you confirm if this is the article:

Is it possible that the fault is due to a different model? in the program enter the function: VL53L1_WaitDeviceBooted( Dev ); and it stays there.

Thanks a lot. Is it enough with that api to be able to make the sensor work? Should I follow the same steps as here?

I can’t guarantee the steps are the same as in this tutorial, though I’m sure they are similar.

ok, I’m going to start again!!. Anyway I’m going to buy the vl53l1x as it has better features. Thank you very much for all the support you are giving me, the gratitude is infinite! In a short time I have to present the finished work for a university subject.
Then I tell you the results of the vl53l0x

Hello!

This is my first post. I found your instructions and this is helping already so much!

However i have trouble understanding how i could apply this to my specific MCU. I am using a ‘LoRa-E5-STM32WLE5JC’.
I am new to stm32 and my understanding of C is very basic. After getting a successful blink program, I managed to get a serial print via usb and wrote a i2c address scanner. My ToF has the address 0x41.

Now i wanted to get the driver to work and get some measurements from it. Apparently this is not as trivial as i thought. I am struggling with the plattform.c and plattform.h files since a few days. My understanding of C and how to use the HAL commands is not enough to ‘just simply write the i2c commands’.

I looked at the examples you are giving but i feel like i have a different version of the driver? i have downloaded the driver: en.STSW-IMG009_v3.5.1 .

It is smaller in memory and seemed easier to use as it has less files and code in general. However the plattform.c and plattform.h file were only placeholders. I tried to replicate the code from this thread but i am bumping into so many differences that i feel its hopeless. i managed to include the correct HAL library [#include “stm32wlxx_hal.h”] and know that i somehow have to use the ‘HAL_I2C_Master_Transmit’ functions etc but i just dont know where to start.

It already starts with the header plattform file. there is a struct defined and i am not sure if i need to use it as is or if i have to add my own code:

typedef struct {
	uint32_t dummy;
} VL53L1_Dev_t;

is the dummy supposed to be there?
the other functions look like this:

header:
/** @brief VL53L1_WriteMulti() definition.\n
 * To be implemented by the developer
 */
int8_t VL53L1_WriteMulti(
		uint16_t 			dev,
		uint16_t      index,
		uint8_t      *pdata,
		uint32_t      count);

c file:
int8_t VL53L1_WriteMulti( uint16_t dev, uint16_t index, uint8_t *pdata, uint32_t count) {
	return 0; // to be implemented
}

i saw many implementations and tutorials about i2c and they used a similiar dev argument to pass a struct with the device i2c address and some other info. but here we have an uint16_t ? Shouldn’t it be of type VL53L1_Dev_t? I am so lost here.

My questions is how would i start to fill in the plattform code? And why doesn’t the manufacturer provide the code? both sensor and MCU is produced by them. I feel like i am missing something. Am i supposed to code the plattform file at all?

I don’t know it it makes sense but i attached the API files that i imported into my project. It contains the unfinished plattform files. Also it all looks slightly different from the code provided in OP post.
API.rar (771,9 KB)

I am thinking that i just need one simple function that can send/receive some i2c and from this i could build the rest of the functions?

from another nucleo example i got these two functions in plattform.c:

int _I2CWrite(uint16_t Dev, uint8_t *pdata, uint32_t count) {
    int status;
    int i2c_time_out = I2C_TIME_OUT_BASE+ count* I2C_TIME_OUT_BYTE;

    status = HAL_I2C_Master_Transmit(&hi2c2, Dev, pdata, count, i2c_time_out);
    if (status) {
        //VL6180x_ErrLog("I2C error 0x%x %d len", dev->I2cAddr, len);
        //XNUCLEO6180XA1_I2C1_Init(&hi2c1);
    }
    return status;
}

int _I2CRead(uint16_t Dev, uint8_t *pdata, uint32_t count) {
    int status;
    int i2c_time_out = I2C_TIME_OUT_BASE+ count* I2C_TIME_OUT_BYTE;

    status = HAL_I2C_Master_Receive(&hi2c2, Dev|1, pdata, count, i2c_time_out);
    if (status) {
        //VL6180x_ErrLog("I2C error 0x%x %d len", dev->I2cAddr, len);
        //XNUCLEO6180XA1_I2C1_Init(&hi2c1);
    }
    return status;
}

however i get the error that &hi2c2 is undeclared. i know that this is my i2c as configured in main since i could do an i2c address scan and it worked. but i did that in the main and now i don’t know how i would pass this reference/pointer to the plattform.c .

Hello @schirmcharmemelone,

Welcome to the TechForum! I’m glad you’re getting some use out of this article and I’m sorry to hear you’re having trouble. ST’s ToF sensors are very nice, but also somewhat complicated. :slightly_smiling_face:

ST provides template platform files because they want the driver to be portable to all MCUs, not just their own. That being said, they do provide example platform files for projects utilizing the HAL libraries. As I mentioned in the article:

Since you are new to C and STM32, I would highly recommend you not start by improvising with the ULD API (STSW-IMG009) and instead follow the steps as written in the Adding the VL53L1X Driver section using the STSW-IMG007 and X-NUCLEO-53L1A1 packages (listed in the Requirements section). The STM32WLE5JC should have plenty of memory for the full driver. Once you get the project working with the full driver, you should have a much easier time switching over to the ULD driver.

If you are still having issues after following the directions exactly, let me know and we’ll take a closer look!

ok i followed your guide and got everything working up until the printing of the measurement data. there was an error about not being able to print floats so i removed the floats from the print statement:

	/*sprintf( (char*)buff, "%d, %d, %.2f, %.2f\n\r", RangingData.RangeStatus, RangingData.RangeMilliMeter,
			 ( RangingData.SignalRateRtnMegaCps / 65536.0 ), RangingData.AmbientRateRtnMegaCps / 65336.0 );
	HAL_UART_Transmit( &huart1, buff, strlen( (char*)buff ), 0xFFFF );
*/
	sprintf( (char*)buff, "%d, %d,  \n\r", RangingData.RangeStatus, RangingData.RangeMilliMeter);
	HAL_UART_Transmit( &huart1, buff, strlen( (char*)buff ), 0xFFFF );

however the output is only showing me this value over and over:

255, -1,

What could be the problem here?
since i dont have any GPIO expanders i removed the expander code as it gave me errors. Could this be it?

Here is my complete main code:

/* USER CODE BEGIN Header */

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include <stdio.h>
#include "vl53l1_api.h"
/* USER CODE END Includes */

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

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */

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

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c2;

UART_HandleTypeDef huart1;

/* USER CODE BEGIN PV */
void print(UART_HandleTypeDef *huart, char _out[]);
void println(UART_HandleTypeDef *huart, char _out[]);
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C2_Init(void);
static void MX_USART1_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 */

	 uint8_t buff[50];
	  VL53L1_RangingMeasurementData_t RangingData;
	  VL53L1_Dev_t  vl53l1_c; // center module
	  VL53L1_DEV    Dev = &vl53l1_c;

  /* 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_I2C2_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */

  println(&huart1, "begin init");

  // initialize vl53l1x communication parameters
  Dev->I2cHandle = &hi2c2;
  Dev->I2cDevAddr = 0x41;

  /*-[ I2C Bus Scanning ]-*/
  uint8_t i = 0, ret;
  char text[100];
  for(int i=1; i<128; i++)
  {
      ret = HAL_I2C_IsDeviceReady(&hi2c2, (uint16_t)(i<<1), 3, 5);
      if (ret != HAL_OK) /* No ACK Received At That Address */
      {
    	  //sprintf(text, "no %d", i);
          //println(&huart1,text);
      }
      else if(ret == HAL_OK)
      {
    	  sprintf(text, "%d", i);
    	  println(&huart1,text);
      }
  }
  println(&huart1,"done");
  /*--[ Scanning Done ]--*/




/*
 * 	uint8_t      major;
	uint8_t      minor;
	uint8_t      build;
	uint32_t     revision;

  sprintf(text, "version %d, %d, %d, %d", pVersion.major, pVersion.minor, pVersion.build, pVersion.revision);
  println(&huart1,text);


  sprintf(text, "sensor ret %d", i);
  println(&huart1,text);

  HAL_Delay(50);

  println(&huart1, "sensor initialized");
 */
  //VL53L1X_ERROR res = VL53L1X_GetDistanceMode(Dev, uint16_t *pDistanceMode);



  /*
  VL53L1X_DataInit( Dev );
  VL53L1X_StaticInit( Dev );
  VL53L1X_SetDistanceMode( Dev, 2 );
  VL53L1X_SetMeasurementTimingBudgetMicroSeconds( Dev, 50000 );
  VL53L1X_SetInterMeasurementPeriodMilliSeconds( Dev, 500 );
  VL53L1X_StartMeasurement( Dev );
   */


  /*** Initialize GPIO expanders
  // Unused GPIO should be configured as outputs to minimize the power consumption
  buff[0] = 0x14; // GPDR (GPIO set direction register)
  buff[1] = 0xFF; // GPIO_0 - GPIO_7
  buff[2] = 0xFF; // GPIO_8 - GPIO_15
  HAL_I2C_Master_Transmit( &hi2c2, EXPANDER_1_ADDR, buff, 3, 0xFFFF );
  HAL_I2C_Master_Transmit( &hi2c2, EXPANDER_2_ADDR, buff, 3, 0xFFFF );

  // clear XSHUT (disable center module) -> expander 1, GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state register)
  HAL_I2C_Master_Transmit( &hi2c2, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  HAL_I2C_Master_Receive( &hi2c2, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  buff[1] = buff[0] & ~( 1 << ( 15 - 8 ) ); // clear GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state register)
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 2, 0xFFFF );

  HAL_Delay( 2 ); // 2ms reset time

  // set XSHUT (enable center module) -> expander 1, GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state)
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  HAL_I2C_Master_Receive( &hi2c1, EXPANDER_1_ADDR, buff, 1, 0xFFFF );
  buff[1] = buff[0] | ( 1 << ( 15 - 8 ) ); // set GPIO_15
  buff[0] = 0x13; // GPSR + 1 ( GPIO set pin state register)
  HAL_I2C_Master_Transmit( &hi2c1, EXPANDER_1_ADDR, buff, 2, 0xFFFF );

  HAL_Delay( 2 );
***/
  /*** VL53L1X Initialization ***/
  VL53L1_WaitDeviceBooted( Dev );
  VL53L1_DataInit( Dev );
  VL53L1_StaticInit( Dev );
  VL53L1_SetDistanceMode( Dev, VL53L1_DISTANCEMODE_LONG );
  VL53L1_SetMeasurementTimingBudgetMicroSeconds( Dev, 50000 );
  VL53L1_SetInterMeasurementPeriodMilliSeconds( Dev, 500 );
  VL53L1_StartMeasurement( Dev );


  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  println(&huart1, "running loop");
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	//println(&huart1, "running loop");

	HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin); // toggles led pin
	HAL_Delay(1000); // adds 1000ms i.e. 1s of delay

	VL53L1_WaitMeasurementDataReady( Dev );

	VL53L1_GetRangingMeasurementData( Dev, &RangingData );

	/*sprintf( (char*)buff, "%d, %d, %.2f, %.2f\n\r", RangingData.RangeStatus, RangingData.RangeMilliMeter,
			 ( RangingData.SignalRateRtnMegaCps / 65536.0 ), RangingData.AmbientRateRtnMegaCps / 65336.0 );
	HAL_UART_Transmit( &huart1, buff, strlen( (char*)buff ), 0xFFFF );
*/
	sprintf( (char*)buff, "%d, %d,  \n\r", RangingData.RangeStatus, RangingData.RangeMilliMeter);
	HAL_UART_Transmit( &huart1, buff, strlen( (char*)buff ), 0xFFFF );
	VL53L1_ClearInterruptAndStartMeasurement( Dev );


  }
  /* 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_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE2);
  /** Initializes the CPU, AHB and APB busses clocks
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_MSI;
  RCC_OscInitStruct.MSIState = RCC_MSI_ON;
  RCC_OscInitStruct.MSICalibrationValue = RCC_MSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.MSIClockRange = RCC_MSIRANGE_6;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure the SYSCLKSource, HCLK, PCLK1 and PCLK2 clocks dividers
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK3|RCC_CLOCKTYPE_HCLK
                              |RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1
                              |RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_MSI;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.AHBCLK3Divider = RCC_SYSCLK_DIV1;

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

/**
  * @brief I2C2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_I2C2_Init(void)
{

  /* USER CODE BEGIN I2C2_Init 0 */

  /* USER CODE END I2C2_Init 0 */

  /* USER CODE BEGIN I2C2_Init 1 */

  /* USER CODE END I2C2_Init 1 */
  hi2c2.Instance = I2C2;
  hi2c2.Init.Timing = 0x00000E14;
  hi2c2.Init.OwnAddress1 = 0;
  hi2c2.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
  hi2c2.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
  hi2c2.Init.OwnAddress2 = 0;
  hi2c2.Init.OwnAddress2Masks = I2C_OA2_NOMASK;
  hi2c2.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
  hi2c2.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
  if (HAL_I2C_Init(&hi2c2) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Analogue filter
  */
  if (HAL_I2CEx_ConfigAnalogFilter(&hi2c2, I2C_ANALOGFILTER_ENABLE) != HAL_OK)
  {
    Error_Handler();
  }
  /** Configure Digital filter
  */
  if (HAL_I2CEx_ConfigDigitalFilter(&hi2c2, 0) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN I2C2_Init 2 */

  /* USER CODE END I2C2_Init 2 */

}

/**
  * @brief USART1 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 9600;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  huart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
  huart1.Init.ClockPrescaler = UART_PRESCALER_DIV1;
  huart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetTxFifoThreshold(&huart1, UART_TXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_SetRxFifoThreshold(&huart1, UART_RXFIFO_THRESHOLD_1_8) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_UARTEx_DisableFifoMode(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};

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

  /*Configure GPIO pin Output Level */
  HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);

  /*Configure GPIO pin : LED_Pin */
  GPIO_InitStruct.Pin = LED_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
  HAL_GPIO_Init(LED_GPIO_Port, &GPIO_InitStruct);

}

/* USER CODE BEGIN 4 */
void print(UART_HandleTypeDef *huart, char _out[]){
 HAL_UART_Transmit(huart, (uint8_t *) _out, strlen(_out), 10);
}

void println(UART_HandleTypeDef *huart, char _out[]){
 HAL_UART_Transmit(huart, (uint8_t *) _out, strlen(_out), 10);
 char newline[2] = "\r\n";
 HAL_UART_Transmit(huart, (uint8_t *) newline, 2, 10);
}
/* 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.
  * @param  file: pointer to the source file name
  * @param  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 */


edit: with float print i get this output:

255, -1, 65536.00, 65736.61

edit2:
i am using the VL53L1X sensor.

Hello @schirmcharmemelone,

Are you using STM32CubeIDE? The very end of the article shows how to enable floating point support if you want to display those last two values.

If you look at the comments for the GPIO expander code, you’ll see that it resets the XSHUT pin, waits a couple milliseconds, and then sets the XSHUT pin. If the XSHUT pin stays in the reset state (logic low), the the sensor is in standby mode and will not respond.

So, what board are you using for the VL53L1X? Some boards include pull-up resistors for the I2C pins and the XSHUT pin while others don’t. If yours doesn’t, you’ll have to adjust the code accordingly.