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
    • This package contains the platform independent VL53L1X driver files (written in C). It can be found here.
  • X-CUBE-53L1A1
    • 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.

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 .

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, one of the platform files has 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. This modified platform file 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 in Atollic 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 file vl53l1_platform.c in en.X-CUBE-53L1A1/SMT32CubeExpansion_53L1A1_V1.0.0/Projects/Multi/Examples/VL53L1X/SimpleRangingExamples/Src/ . Copy this file into the platform directory of the project, replacing the version that is already there.
  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 Atollic 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 directory and the platform 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 and platform 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.


Figure 8: Including the VL53L1X API in main.c

  1. Open the file vl53l1_platform_user_data.h (Figure 9a). 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" . A few lines below this line, there should be a dummy structure called I2C_HandleTypeDef . Either comment this structure out or delete it. These changes are shown in Figure 9b.

image
a) Before

image
b) After

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" .

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/SMT32CubeExpansion_53L1A1_V1.0.0/Projects/Multi/Examples/VL53L1X/SimpleRangingExamples/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 this 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


/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  ** This notice applies to any and all portions of this file
  * that are not between comment pairs USER CODE BEGIN and
  * USER CODE END. Other portions of this file, whether 
  * inserted by the user or by software development tools
  * are owned by their respective copyright owners.
  *
  * COPYRIGHT(c) 2018 STMicroelectronics
  *
  * Redistribution and use in source and binary forms, with or without modification,
  * are permitted provided that the following conditions are met:
  *   1. Redistributions of source code must retain the above copyright notice,
  *      this list of conditions and the following disclaimer.
  *   2. Redistributions in binary form must reproduce the above copyright notice,
  *      this list of conditions and the following disclaimer in the documentation
  *      and/or other materials provided with the distribution.
  *   3. Neither the name of STMicroelectronics nor the names of its contributors
  *      may be used to endorse or promote products derived from this software
  *      without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  *
  ******************************************************************************
  */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "stm32f1xx_hal.h"

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

/* USER CODE END Includes */

/* 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 */
/* Private function prototypes -----------------------------------------------*/

/* USER CODE END PFP */

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  *
  * @retval None
  */
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;
  RCC_ClkInitTypeDef RCC_ClkInitStruct;

    /**Initializes the CPU, AHB and APB busses clocks 
    */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = 16;
  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(__FILE__, __LINE__);
  }

    /**Initializes the CPU, AHB and APB busses 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(__FILE__, __LINE__);
  }

    /**Configure the Systick interrupt time 
    */
  HAL_SYSTICK_Config(HAL_RCC_GetHCLKFreq()/1000);

    /**Configure the Systick 
    */
  HAL_SYSTICK_CLKSourceConfig(SYSTICK_CLKSOURCE_HCLK);

  /* SysTick_IRQn interrupt configuration */
  HAL_NVIC_SetPriority(SysTick_IRQn, 0, 0);
}

/* I2C1 init function */
static void MX_I2C1_Init(void)
{

  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(__FILE__, __LINE__);
  }

}

/* USART2 init function */
static void MX_USART2_UART_Init(void)
{

  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(__FILE__, __LINE__);
  }

}

/** Configure pins as 
        * Analog 
        * Input 
        * Output
        * EVENT_OUT
        * EXTI
*/
static void MX_GPIO_Init(void)
{

  GPIO_InitTypeDef GPIO_InitStruct;

  /* 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.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.
  * @param  file: The file name as string.
  * @param  line: The line in file as a number.
  * @retval None
  */
void _Error_Handler(char *file, int line)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  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,
     tex: 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 TrueSTUDIO, this can be done by choosing Project > Properties from the main menu bar, navigating to C/C++ Build > Settings , and selecting the Tool Settings tab. In the General settings, you can select your desired runtime library from the dropdown list.

Conclusion

There are a few different 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.