cancel
Showing results for 
Search instead for 
Did you mean: 

VL6180x offset issues

RCres.1
Associate III

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

5 REPLIES 5
John E KVAM
ST Employee

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

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.

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.1
Associate III

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,

John E KVAM
ST Employee

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

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.

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