cancel
Showing results for 
Search instead for 
Did you mean: 

Using VL53L4CX with Jetson Nano running Ubuntu 20.4

nate_s
Associate II

Hello!

I have never used a Jetson Nano before and am rather unfamiliar with lower level programming. I have a VL53L4CX attached to the correct Jetson nano pins and can see the device when I run i2cdetect -r -y 1 at address 0x29. My question is whats next. I've done a lot of research online and have no idea how to incorporate this into an actual program that reads the distance detected by the sensor. The goal of the project is to attach 8 of these sensors to a MUX and then get data from all of them by changing i2c ports. I have seen posts referring to the linux driver, which I have downloaded but don't know how to install or use. I have a python program that sends bytes over i2c, is it as simple as sending a specific message over i2c and then reading a response? If so, what is that message?

1 ACCEPTED SOLUTION

Accepted Solutions
John E KVAM
ST Employee

If you are a Linux god (lower case) and you are fully capable of installing a driver into the Android or Linux operating system, then the Linux driver is for you. Generally you need this when you want to build a system where the users cannot access the I2C bus. Think what could happen if Cellphone apps could get hold of the EEPROMs in your cellphone. 

But if you have a RaspberryPi or a Jetson Nano and you are in charge, and users cannot simply add code to your system, you don't have to protect that I2C. And you can use the Standard API. It's user code, you can just run it.

 

But it's not perfect!! 

This API does not assume a CPU or MCU, so it does not know how to communicate with your I2C. YOU have to tell it.

So, look at the Platform.c file. Notice it is unpopulated. It's your job to write that - or find it on GitHub. It's there - lots of people have written it. 

So go to GitHub and find a project for any of the VL53 sensors that run on a Linux platform and borrow the platform.c. 

The platform.c in the example directory resolves to:

HAL_I2C_Master_Transmit() calls - the I2C function on an STM32.

You will need functions that make IOCTL calls.

Fill that out, and you should be good.

Your program will make IOCTL calls to address 29 on the I2C bus. And it will work.

- john

 


In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question. It helps the next guy.

View solution in original post

8 REPLIES 8
John E KVAM
ST Employee

If you are a Linux god (lower case) and you are fully capable of installing a driver into the Android or Linux operating system, then the Linux driver is for you. Generally you need this when you want to build a system where the users cannot access the I2C bus. Think what could happen if Cellphone apps could get hold of the EEPROMs in your cellphone. 

But if you have a RaspberryPi or a Jetson Nano and you are in charge, and users cannot simply add code to your system, you don't have to protect that I2C. And you can use the Standard API. It's user code, you can just run it.

 

But it's not perfect!! 

This API does not assume a CPU or MCU, so it does not know how to communicate with your I2C. YOU have to tell it.

So, look at the Platform.c file. Notice it is unpopulated. It's your job to write that - or find it on GitHub. It's there - lots of people have written it. 

So go to GitHub and find a project for any of the VL53 sensors that run on a Linux platform and borrow the platform.c. 

The platform.c in the example directory resolves to:

HAL_I2C_Master_Transmit() calls - the I2C function on an STM32.

You will need functions that make IOCTL calls.

Fill that out, and you should be good.

Your program will make IOCTL calls to address 29 on the I2C bus. And it will work.

- john

 


In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question. It helps the next guy.

Hi John, 

Thanks for your answer. I am looking at the generic api you sent and it seems like vl53lx_platform.c and the .h file is already filled out. Are those the files you are referring too when you say "So, look at the Platform.c file. Notice it is unpopulated. It's your job to write that - or find it on GitHub. It's there - lots of people have written it."? Or are there different platform.c and platform.h files?

 

-Nate

John E KVAM
ST Employee

If those files are filled out, they are probably not filled out correctly for your computer.

ST writes them so they work with the STM32. And worse running under XCube_ToF which is ST software.

If your processor is running a Linux variant, you need those functions to make calls to IOCTL().

you can use that code, but you need to re-write the Init, the DeInit, and the WrMulti() functions. 

Might be easier to just start from scratch and replace this file with one you find on GitHub.

- john


In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question. It helps the next guy.

Hello John!

 

I have been struggling with this for a while now and still can't get i2c working. I've tried various github repositories to no luck, so I tried writing my own. Most of the data I get back is useless and I am stuck looking for help. Here is what I have for the readMulti and writeMulti functions:

VL53LX_Error VL53LX_WriteMulti(
	VL53LX_Dev_t *pdev,
	uint16_t      index,
	uint8_t      *pdata,
	uint32_t      count)
{
	VL53LX_Error status         = VL53LX_ERROR_NONE;
	uint32_t     position       = 0;
	uint32_t     data_size      = 0;

	_LOG_STRING_BUFFER(register_name);
	_LOG_STRING_BUFFER(value_as_str);

	if(global_comms_type == VL53LX_I2C)
	{
		for(position=0; position<count; position+=VL53LX_COMMS_CHUNK_SIZE)
		{
			if (count > VL53LX_COMMS_CHUNK_SIZE)
			{
				if((position + VL53LX_COMMS_CHUNK_SIZE) > count)
				{
					data_size = count - position;
				}
				else
				{
					data_size = VL53LX_COMMS_CHUNK_SIZE;
				}
			}
			else
			{
				data_size = count;
			}

			if (status == VL53LX_ERROR_NONE)
			{
				struct i2c_msg message;
				message.addr = 0x29;
				message.flags = 0;
				message.len = data_size;
				message.buf = pdata;

				struct i2c_rdwr_ioctl_data transfer;
				transfer.msgs = &message;
				transfer.nmsgs = 1;

				int success = ioctl(i2c_file, I2C_RDWR, &transfer);
				if(success < 0) {
					printf("VL53LX_WriteMulti: Writing failed: {i2c_file= %d, data_size= %d}\n", i2c_file, data_size);
					perror("Failed to write to i2c device.");
					status = VL53LX_ERROR_CONTROL_INTERFACE;
					close(i2c_file);
					return status;
				}
			}
		}
	}
	else
	{
		printf("VL53LX_WriteMulti: Comms must be set to VL53LX_I2C\n");
		status = VL53LX_ERROR_CONTROL_INTERFACE;
		close(i2c_file);
		return status;
	}

	return status;
}


VL53LX_Error VL53LX_ReadMulti(
	VL53LX_Dev_t *pdev,
	uint16_t      index,
	uint8_t      *pdata,
	uint32_t      count)
{
	VL53LX_Error status         = VL53LX_ERROR_NONE;
	uint32_t     position       = 0;
	uint32_t     data_size      = 0;

	_LOG_STRING_BUFFER(register_name);
	_LOG_STRING_BUFFER(value_as_str);

	if(global_comms_type == VL53LX_I2C)
	{
		for(position=0; position<count; position+=VL53LX_COMMS_CHUNK_SIZE)
		{
			if(count > VL53LX_COMMS_CHUNK_SIZE)
			{
				if((position + VL53LX_COMMS_CHUNK_SIZE) > count)
				{
					data_size = count - position;
				}
				else
				{
					data_size = VL53LX_COMMS_CHUNK_SIZE;
				}
			}
			else
				data_size = count;

			if(status == VL53LX_ERROR_NONE)
			{

				struct i2c_msg message;
				message.addr = 0x29;
				message.flags = I2C_M_RD;
				message.len = data_size;
				message.buf = pdata;

				struct i2c_rdwr_ioctl_data transfer;
				transfer.msgs = &message;
				transfer.nmsgs = 1;
				int success = ioctl(i2c_file, I2C_RDWR, &transfer);
				if(success < 0) {
					status = VL53LX_ERROR_CONTROL_INTERFACE;
					printf("VL53LX_ReadMulti: Reading failed: {i2c_file= %d, data_size= %d}\n", i2c_file, data_size);
					perror("Failed to read from i2c device.");
					close(i2c_file);
					return status;
				}
			}
		}
	}
	else
	{
		printf("VL53LX_ReadMulti: Comms must be set to VL53LX_I2C\n");
		status = VL53LX_ERROR_CONTROL_INTERFACE;
		close(i2c_file);
		return status;
	}

	return status;
}

 

Here is the output I get for running the i2c test you have posted in other forums:

expected   = eacc10ff,
read_multi =  0,  0, eb, aa
read_bytes = 10, ff, fe, 21
read words =  406, 3c3f
read dword =  1000114
expected   = 11223344,
read_multi = ff, ff, ff, ff
read_bytes = ff, fe, ff, ff
read words = ffff, fddf
read dword = f7eef7ef
expected   = ffeeddcc,
read_multi =  0,  0,  0,  0
read_bytes =  0,  0,  0,  0
read words =    0,    0
read dword =        0
expected   = 55667788,
read_multi =  0,  0,  0,  0
read_bytes =  0,  0,  0,  0
read words =    0,    0
read dword =        0
expected   = 11223344,
read_multi =  0,  0,  0,  0
read_bytes =  0,  0,  0,  0
read words =    0,    0
read dword =        0
i2c test failed - please check it. Status = 1

 

I have run out of ideas, any help is appreciated!

Oh, goodness. 

The I2C has two things that might be construed as address. 

One is the chip address. (0x29 which turns into a write address of 0x52 and a read address of 0x53)

The other is the index. The location within the sensor that you want to deposit data in or read from. 

In most chips this is a single byte - but in the ToF sensors this is a 16 bit word.

When one does a write one, puts the 0x52 on the bus, then the index (2 bytes) and then the number of bytes he wants to write. 

In your code, you need an array that is at least two bytes longer than the data you want to send.

In your code, I did not see the index in the write. That means the chip will take the first two bytes of data as an index, and you end up unhappy.

Note for read one puts 0x53 on the bus, then the index. Then it does the read.

In your case you don't need to worry about the 52 or 53. That is handled by the I2C driver.

But I don't see where you have told the driver about the index.

- john


In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question. It helps the next guy.

Thanks for your response!

 

I had tried something similar but got mostly Remote I/O errors using read() and write() to make the i2c calls (a lot of the github examples used this method). The meat of the read function looked like this:

				uint8_t buffer[count + sizeof(index)];
				memcpy(buffer, &index, sizeof(index));
				memcpy(buffer + sizeof(index), pdata, count);
				int success = write(i2c_file, buffer, count + sizeof(index));

A lot of the errors come from the read method, which looks like this:

				int success = read(i2c_file, pdata, data_size);
				if(success != data_size) {
					status = VL53LX_ERROR_CONTROL_INTERFACE;
					printf("VL53LX_ReadMulti: Reading failed: {i2c_file= %d, data_size= %d}\n", i2c_file, data_size);
					perror("Failed to read from i2c device.");
					close(i2c_file);
					return status;
				}

I'm guessing im doing something wrong with read. Do I need to add the index to pdata before I read? Im assuming the linux driver will cover the 52 and 53 here as well.

John E KVAM
ST Employee


This is why I cannot do it for you. Every driver does things a little differently. 

A Read involves writing the index to address 0x29<<1 +1 (left shift 1 and add set the LSbit read/write to 1)

and then you do the read. 

But perhaps the IOCTL does the write for you before doing the read. Perhaps it does the conversion from 0x29 to 0x53. I (alas) do not know. 

But what I do know is that MOST of these drivers use only a single byte index. 

You need something different. 

(it's why I suggested you try to steal a driver off something from GitHub.)

Lots of scopes can sniff the SDA and SCL and you can see what is on the bus. 

There are also cheap I2C sniffers that use a logic analyzer approach and dump the data on your PC. 

Then you know what's on the bus and you can check it against the datasheet.

 


In order to give better visibility on the answered topics, please click on 'Accept as Solution' on the reply which solved your issue or answered your question. It helps the next guy.
nate_s
Associate II

After a few more days of trial and error I finally got it to work! @John E KVAM Thank you so much for all your help (Using the scope to sniff the lines was a game changer). Turns out the problem was that the Jetson Nano uses little-endian but the sensor is expecting big-endian. I also had to update the VL53LX_Dev_t struct to contain my I2C information. Posting my working read/write functions here for anyone else who may try this in the future:

VL53LX_Error VL53LX_WriteMulti(
	VL53LX_Dev_t *pdev,
	uint16_t      index,
	uint8_t      *pdata,
	uint32_t      count)
{
	VL53LX_Error status         = VL53LX_ERROR_NONE;

	_LOG_STRING_BUFFER(register_name);
	_LOG_STRING_BUFFER(value_as_str);

	int i2c_file = pdev-> i2c_file_descriptor;
	uint16_t bigEndianIndex = ((index & 0xFF) << 😎 | ((index & 0xFF00) >> 8);

	uint8_t buffer[count + sizeof(bigEndianIndex)];
	memcpy(buffer, &bigEndianIndex, sizeof(bigEndianIndex));
	memcpy(buffer + sizeof(bigEndianIndex), pdata, count);

	int success = write(i2c_file, buffer, count + sizeof(bigEndianIndex));
	if(success != count + sizeof(bigEndianIndex)) {
		perror("Failed to write to i2c device.");
		status = VL53LX_ERROR_CONTROL_INTERFACE;
	}

	return status;
}


VL53LX_Error VL53LX_ReadMulti(
	VL53LX_Dev_t *pdev,
	uint16_t      index,
	uint8_t      *pdata,
	uint32_t      count)
{
	VL53LX_Error status         = VL53LX_ERROR_NONE;

	_LOG_STRING_BUFFER(register_name);
	_LOG_STRING_BUFFER(value_as_str);

	int i2c_file = pdev-> i2c_file_descriptor;
	uint16_t bigEndianIndex = ((index & 0xFF) << 😎 | ((index & 0xFF00) >> 8);
	
	int success = write(i2c_file, &bigEndianIndex, sizeof(bigEndianIndex));
	if (success != sizeof(bigEndianIndex)) {
		perror("Failed to write to i2c device.");
		status = VL53LX_ERROR_CONTROL_INTERFACE;
	}
	
	success = read(i2c_file, pdata, count);
	if(success != count) {
		status = VL53LX_ERROR_CONTROL_INTERFACE;
		perror("Failed to read from i2c device.");
	}

	return status;
}

 

One other note, to get data from the sensor, I needed to create a class for the sensor that gets allocated on the heap. All the calls then needed to go through that object. This post goes into slightly more detail about that: https://community.st.com/t5/imaging-sensors/unable-to-get-measures-from-vl53l4cx/td-p/103106