Skip to main content
Visitor II
October 13, 2020
Question

VL6180x offset issues

  • October 13, 2020
  • 3 replies
  • 3092 views

Hi,

I am facing some issues related with VL6180x offset calibration (I am not using cover glass, and I have tested many sensors on breakout boards from Adafruit and Pololu).

First, with default SYSRANGE__PART_TO_PART_RANGE_OFFSET value, range measurement is 150:350mm instead of 0:100mm. (With target at 160mm I get 10mm from sensor, etc). So first 0-150 mm I get 0 as range measure.

If applying calibration procedure :  https://www.st.com/resource/en/application_note/dm00122600-vl6180x-basic-ranging-application-note-stmicroelectronics.pdf I get:

  • Initial measurement with default factory offset when target is at 50mm is 0
  • Changing 0x24 to 0 and collecting more than 10 measurements ( All measurements = 0 mm range)
  • Offset = 50 - 0 = 50
  • AppLying offset = 50 into 0x24 in C2s format --> Now measured range when target is at 50 mm is 25 (target from 0 to 50mm I get 25mm as range measurement), and maximum length measured is 225
  • Applying offset = 60 -> Range measurement from 35 to 235
  • Applying offset = 70 -> Range measurement from 45 to 245
  • Applying offset = 80 -> Range measurement from 55 to 255

Any idea of what could be happening?

Kind Regards,

Rafa

    This topic has been closed for replies.

    3 replies

    ST Employee
    October 15, 2020

    Sorry this took so long...

    One response I got was:

    "The maximum measurement for a babybear is 255mm unless you turn on a multiplier. So, if he is doing an offset test, you need to take into account what multiplier you used for the calibration. This value then has to be compensated in the final answer.

    "My guess is that the calibration was done with a multiplier of 1 and then they are running it with a different multiplier when running. "

    But I'm not so sure. If you get 0 at 50mm that is due to crosstalk, not offset. The worst offset i have ever seen is 20mm. 50 is out of the question.

    But you say you don't have a coverglass - so in theory crosstalk is not possible.

    The only thing left is that the light is bouncing off your structure somehow and immediately returning. Could that be possible?

    If you embed the sensor into your structure and don't have a wide enough apperature, it's possible for light to hit he sides and bounce back. (It happend to another custoemer.)

    Solutions to this problem would be to polish the walls of the structure so eliminate an reflections, painting the structure black, or simply making the apperature wider.

    • john
    RCres.1Author
    Visitor II
    October 20, 2020

    Hi @John E KVAM​ 

    I have tried crosstalk calibration and keeps not working.

    We are not embedding the sensor in any structure, as we are using the breakout boards of Adafruit and Pololu, where sensor is on the top.

    However, we have realised that we get "Raw Ranging Algo Overflow" in RESULT__RANGE_STATUS register. That could be related?

    Kind Regards,

    Rafa

    RCres.1Author
    Visitor II
    December 18, 2020

    Hi @John E KVAM​ 

    I have been testing the same vl6180x breakout board with a Raspberry Pi and adafruit_vl6180x.py software and with it that's working (distance measurements are right).

    However, I am adapted the adafruit library for using in the STM32 and keeps not working. I have replicated too the I2C timming characteristics from Raspberry on the STM32.

    This is the algorithm that is working on RPI but not on the STM32:

    1. Read(0x016). If 0x01, write init configuration and Write(0x016,0x00), else, repeat.
    2. Read(0x04D). If 0x01, Write(0x018,0x01, else, repeat.
    3. Read(0x04F). If 0x04, Read(0x062), else, repeat.
    4. Write(0x015,0x07)
    5. sleep(1second)
    6. Repeat from 2

    I have compared data frames sent/received on RPI and STM32 and both are identical, excepting the 0x062 response, where on RPI is the right distance, and on STM32 it's not.

    Please find the I2C timming data on the attached .zip

    What I am missing? II don't know what else to do.

    Kind Regards,

    ST Employee
    December 18, 2020

    RCres.1 -

    It is always better to ask a new question in a new thread. Then others can find it.

    • john

    When communicating with the VL6180 there are two things you have to solve. The first is the I2C traffic itself.

    Hardware set up...

    When you move to the STM32, the easy way to set things up is to use the STM32CubeMX code from ST. It's free - just download it from the ST site. This graphical tool will allow you to configure your MCU into the exact, valid configuration - and it writes all the initialization code for you. It even goes so far as to tell you "your code goes here".

    And the best part - if you want to change something it re-writes the initialization code - leaving your code intact.

    So changing an I2C bus speed after the project is almost completed is trivial. Heck you can even change the MCU itself and it leaves your code intact.

    By doing this I can guarantee your I2C configuration will be valid.

    Software set up...

    Now download the ST API

    STSW-IMG011 VL6180V1 Application Programming Interface API

    You will get a bunch of functions that control the chip.

    This should make it easy for you.

    There is almost a one-to-one correspondence with the AdaFruit library.

    You can use this or the Adafruit - that is up to you.

    But either way, look at main.c in the example code.

    You will see the I2C_Read, and I2C_write functions that are valid for an ST chip.

    if you use the example code, you are practically done. Just run it.

    if you use the AdaFruit code, extract the following from the ST code and insert it into the ada fruit.

    /**
     * VL6180x CubeMX F401 multiple device i2c implementation
     */
     
    #define i2c_bus (&hi2c1)
    #define def_i2c_time_out 100
     
    int VL6180x_I2CWrite(VL6180xDev_t dev, uint8_t *buff, uint8_t len) {
     int status;
     status = HAL_I2C_Master_Transmit(i2c_bus, dev->I2cAddr, buff, len, def_i2c_time_out);
     if (status) {
     XNUCLEO6180XA1_I2C1_Init(&hi2c1);
     }
     return status? -1 : 0;
    }
     
    int VL6180x_I2CRead(VL6180xDev_t dev, uint8_t *buff, uint8_t len) {
     int status;
     status = HAL_I2C_Master_Receive(i2c_bus, dev->I2cAddr, buff, len, def_i2c_time_out);
     if (status) {
     XNUCLEO6180XA1_I2C1_Init(&hi2c1);
     }
     
     return status? -1 : 0;
    }
     
    /**
     * platform and application specific for XNUCLEO6180XA1 Expansion Board
     */
    void XNUCLEO6180XA1_WaitMilliSec(int n) {
     WaitMilliSec(n);
    }

    So now you have a valid hardware set up.

    And you have valid software to read and write the I2C.

    That should do it.

    Digging into the I2C timing is really hard. Then you have to figure out what to change in the I2C initialization code, and it's a real pain.

    The STM32CubeMX really is one of the best things ST has ever done.

    Use it once and you will be an ST customer for life.

    (And I don't work for the MCU group. I used to dread changing MCUs - and I did it a lot. This app makes that job completely obsolete.)

    • john
    RCres.1Author
    Visitor II
    December 22, 2020

    Hi @John E KVAM​ , thanks a lot for the information.

    However, it keeps answering with wrong distances (with vl6180_simple_ranging.c and using I2C1 interface). I am using a STM32F769-Discovery board.

    As you said, I have used ST32Cube for adjusting I2C timmings and GPIO config. I also have followed the API Integration guide:

    https://www.st.com/content/ccc/resource/sales_and_marketing/presentation/product_presentation/cc/96/42/b5/56/60/4d/e0/VL6180X_API_IntegrationGuide.pdf/files/VL6180X_API_IntegrationGuide.pdf/jcr:content/translations/en.VL6180X_API_IntegrationGuide.pdf

    My GPIO config:

    // Enable I2C interface clock
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;
     
    // Configuring I2C Hardware
    I2C_PORT->MODER 	|= 0x02<<I2C_SDA*2 | 0x02<<I2C_SCL*2;	 //Alternate function
    I2C_PORT->OTYPER	|= 0x01<<I2C_SDA | 0x01<<I2C_SCL;		 //Open Drain
    I2C_PORT->PUPDR		|= (0x01<<I2C_SDA*2) | (0x01<<I2C_SCL*2); //pull-up
    I2C_PORT->OSPEEDR	|= 0x11<<I2C_SDA*2 | 0x11<<I2C_SCL*2;		 // Very High Speed
    I2C_PORT->AFR[1]	|= 0x44<<GPIO_AFRH_AFRH0_Pos;			 // AF4 for SDA and SCL (pins 8 & 9)

    My I2C Config:

    void i2c_init(I2C_TypeDef* interface,unsigned char slave){
     
    // Disable I2C (PE bit)
    interface->CR1 &= ~I2C_CR1_PE;
     
    // Disable Analog noise filter
    interface->CR1 |= I2C_CR1_ANFOFF;
     
    // Disable Digital noise filter
    interface->CR1 &= ~I2C_CR1_DNF;
     
    // Set timmings		 //100kHz	//62,5kHz
    interface->TIMINGR = 0x20404768;//0x10B0AAFE;
     
    interface->CR1 |= I2C_CR1_NOSTRETCH;
     
    // Enable I2C (PE bit)
    interface->CR1 |= I2C_CR1_PE;
     
    // 7-Bit Addressing mode
    interface->CR2 &= ~I2C_CR2_ADD10;
     
    // Set slave address
    interface->CR2 |= slave<<1;
     
    }

    I2C routines (I have tested and they work well):

    /**
    ****************************************************************************
    * FUNCTION: i2c_write
    *
    * DESCRIPTION: Writes data and reads ACK/NAK
    *
    * ARGUMENTS:
    * interface		I2C Interface used
    * len			Number of bytes sent (<=255)
    * data			data to send
    ***************************************************************************/
    void i2c_write(I2C_TypeDef* interface,unsigned char len, unsigned char* data){
     
    	// Clear interrupts
    	interface->ICR = 0x00FF;
     
    	// Write mode
    	interface->CR2 &= ~I2C_CR2_RD_WRN;
     
    	// Number of bytes to be written
    	interface->CR2 |= len<<I2C_CR2_NBYTES_Pos;
     
    	// Send START condition
    	i2c_start(interface);
     
    	for(unsigned char i = 0; i<len;i++){
    		//while(!(interface->ISR & I2C_ISR_TXIS));
    		interface->TXDR = *data;
    		data++;
    		while(!(interface->ISR & I2C_ISR_TXE));
    	}
     
     
    	// Send STOP condition
    	i2c_stop(interface);
     
    	// Transfer complete - Disable interrupts
    	interface->CR1 &= ~I2C_CR1_TXIE & ~I2C_CR1_TCIE & ~I2C_CR1_NACKIE;
     
     
    }
     
    /**
    ****************************************************************************
    * FUNCTION: i2c_read
    *
    * DESCRIPTION: Reads Byte and sends NACK
    *
    * ARGUMENTS:
    * interface 	I2C Interface used
    * return 		Byte received
    ***************************************************************************/
    char i2c_read(I2C_TypeDef* interface){
    	 char ret = -1 ;
     
    	// Clear interrupts
    	interface->ICR = 0x00FF;
     
    	// Read mode
    	interface->CR2 |= I2C_CR2_RD_WRN;
     
    	// Number of bytes to be read
    	interface->CR2 |= 1<<I2C_CR2_NBYTES_Pos;
     
    	// Send START condition
    	i2c_start(interface);
     
    	// Get data
    	while(!(interface->ISR & I2C_ISR_RXNE));
    	ret = interface->RXDR;
     
    	// Send Stop condition
    	i2c_stop(interface);
     
    	// Disable interrupts
    	interface->CR1 &= ~I2C_CR1_RXIE & ~I2C_CR1_TCIE;
     
    	return ret;
    }
     
     
    /**
    ****************************************************************************
    * FUNCTION: i2c_start
    *
    * DESCRIPTION: Sends start command to I2C Bus
    *
    * ARGUMENTS:
    * interface		I2C Interface used
    ***************************************************************************/
    void i2c_start(I2C_TypeDef* interface){
     
    	interface->CR2 |= I2C_CR2_START;
    	while(interface->CR2 & I2C_CR2_START);
     
    }
     
    /**
    ****************************************************************************
    * FUNCTION: i2c_stop
    *
    * DESCRIPTION: Sends stop command to I2C Bus
    *
    * ARGUMENTS:
    * interface	- I2C Interface used
    ***************************************************************************/
    void i2c_stop(I2C_TypeDef* interface){
     
    	interface->CR2 |= I2C_CR2_STOP;
    	while(interface->CR2 & I2C_CR2_STOP);
     
    }

    And the vl6180_i2c.c modifications:

    /**
     * @brief Write data buffer to VL6180 device via i2c
     * @param dev The device to write to
     * @param buff The data buffer
     * @param len The length of the transaction in byte
     * @return 0 on success
     * @ingroup cci_i2c
     */
    int VL6180_I2CWrite(VL6180Dev_t dev, uint8_t *buff, uint8_t len){
     
    	i2c_write(I2C,len,buff);
     
    	return 0;
     
    }
     
    /**
     *
     * @brief Read data buffer from VL6180 device via i2c
     * @param dev The device to read from
     * @param buff The data buffer to fill
     * @param len The length of the transaction in byte
     * @return 0 on success
     * @ingroup cci_i2c
     */
    int VL6180_I2CRead(VL6180Dev_t dev, uint8_t *buff, uint8_t len){
     
    	for(unsigned char i=0;i<len;i++){
    		*buff = i2c_read(I2C);
    		buff++;
    	}
     
    	return 0;
     
    }

    Please find attached the API files I have added to my project to support it, and the Sample_SimpleRanging.c (Sample_SimpleRanging is executing every 1s).

    As I said, all works as expected, excepting that for a target to 100mm, I get a distance of 39 mm, for a target to 180 mm, I get 59 mm, etc. And this value is checked with the one on the SDA line, through oscilloscope.

    Kind Regards,

    Rafa