2021-07-09 09:32 AM
Hello everyone,
I am developing a project involving an Arduino Zero-like microcontroller and some I2C sensors, including the ST VL53L0X ToF sensor.
One of my requirements is not to use the Arduino's Wire library for I2C communication, so I'm almost obliged to put my effort trying to understand the ST API for this sensor.
What I'm trying to achieve can be summarized as follows:
On one side, I've been able to write an I2C driver to communicate with my sensors, including VL53L0X and MPU6050 accel-gyroscope. However, for VL53L0X I got stuck in the initialization process.
Here is the expected workflow, using ST API and following "vl53l0x_SingleRanging_Example.c" included with the library:
The actual workflow is the same until point 2, but then VL53L0X_PerformRefSpadManagement returns the error code -6 (VL53L0X_ERROR_RANGE_ERROR).
Here is my code, where I basically go through the initialization process and print on serial the status code for each step:
void VL53L0X_singleRanging() {
VL53L0X_Error Status = VL53L0X_ERROR_NONE;
VL53L0X_Dev_t MyDevice;
VL53L0X_Dev_t *pMyDevice = &MyDevice;
VL53L0X_Version_t Version;
VL53L0X_Version_t *pVersion = &Version;
VL53L0X_DeviceInfo_t DeviceInfo;
int32_t status_int;
uint8_t VhvSettings;
uint8_t PhaseCal;
uint32_t refSpadCount;
uint8_t isApertureSpads;
VL53L0X_RangingMeasurementData_t RangingMeasurementData;
pMyDevice->I2cDevAddr = 0x29;
pMyDevice->comms_type = 1;
pMyDevice->comms_speed_khz = 400;
status_int = VL53L0X_GetVersion(pVersion);
SerialUSB.print("Status GetVersion = ");
SerialUSB.println(status_int);
Status = VL53L0X_DataInit(pMyDevice);
SerialUSB.print("Status DataInit = ");
SerialUSB.println(Status);
Status = VL53L0X_GetDeviceInfo(&MyDevice, &DeviceInfo);
SerialUSB.print("Status GetDeviceInfo = ");
SerialUSB.println(Status);
SerialUSB.print("VL53L0X_GetDeviceInfo:\n");
SerialUSB.print("Device Name : ");
SerialUSB.println(DeviceInfo.Name);
SerialUSB.print("Device Type : ");
SerialUSB.println(DeviceInfo.Type);
/*
printf("Device ID : %s\n", DeviceInfo.ProductId);
printf("ProductRevisionMajor : %d\n", DeviceInfo.ProductRevisionMajor);
printf("ProductRevisionMinor : %d\n", DeviceInfo.ProductRevisionMinor);
*/
Status = VL53L0X_StaticInit(pMyDevice); // Device Initialization
SerialUSB.print("Status StaticInit = ");
SerialUSB.println(Status);
Status = VL53L0X_PerformRefSpadManagement(pMyDevice,
&refSpadCount, &isApertureSpads); // Device Initialization
SerialUSB.print("Status PerformRefSpadManagement = ");
SerialUSB.println(Status);
Status = VL53L0X_PerformRefCalibration(pMyDevice,
&VhvSettings, &PhaseCal);
SerialUSB.print("Status PerformRefCalibration = ");
SerialUSB.println(Status);
Status = VL53L0X_SetDeviceMode(pMyDevice, VL53L0X_DEVICEMODE_SINGLE_RANGING); // Setup in single ranging mode
SerialUSB.print("Status SetDeviceMode = ");
SerialUSB.println(Status);
Status = VL53L0X_SetLimitCheckEnable(pMyDevice,
VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, 1);
SerialUSB.print("Status SetLimitCheckEnable = ");
SerialUSB.println(Status);
Status = VL53L0X_SetLimitCheckEnable(pMyDevice,
VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, 1);
SerialUSB.print("Status SetLimitCheckEnable = ");
SerialUSB.println(Status);
Status = VL53L0X_SetLimitCheckEnable(pMyDevice,
VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, 1);
SerialUSB.print("Status SetLimitCheckEnable = ");
SerialUSB.println(Status);
Status = VL53L0X_SetLimitCheckValue(pMyDevice,
VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD,
(FixPoint1616_t)(1.5*0.023*65536));
SerialUSB.print("Status SetLimitCheckValue = ");
SerialUSB.println(Status);
while(true) {
Status = VL53L0X_PerformSingleRangingMeasurement(pMyDevice,
&RangingMeasurementData);
SerialUSB.print("Status PerformSingleRangingMeasurement = ");
SerialUSB.println(Status);
SerialUSB.print("Measured distance: ");
SerialUSB.print(RangingMeasurementData.RangeMilliMeter);
SerialUSB.println(" millimeters");
SerialUSB.print("Range Status: ");
SerialUSB.println(RangingMeasurementData.RangeStatus);
VL53L0X_State palState;
Status = VL53L0X_GetPalState(pMyDevice, &palState);
char stateString[50];
memset(stateString, 0, 50);
VL53L0X_GetPalStateString(palState, stateString);
SerialUSB.println(stateString);
delay(1000);
}
}
Another thing that might be important is my I2C communication logic: no matter which PAL function gets called (RdByte, WrWord, etc), one and only one stop condition is always issued at the end of each of them.
Every form of help will be very appreciated.
Thanks in advance,
Alberto
2021-07-13 10:13 AM
In these days I enabled the ST API logging functionality and reorganized my code like down below.
I'm convinced that the problem is inside VL53L0X_GetInterruptMaskStatus, which returns -6 if some byte read from somewhat register has bit 3 or 4 set.
Here is the code:
void print_VL53L0X_DeviceParameters(VL53L0X_Dev_t *dev) {
VL53L0X_Error Status = VL53L0X_ERROR_NONE;
VL53L0X_DeviceParameters_t params;
LOG_FUNCTION_START("");
Status = VL53L0X_GetDeviceParameters(dev, ¶ms);
LOG_FUNCTION_END_FMT(Status, "MeasurementTimingBudgetMicroseconds: %u, InterMeasurementPeriodMilliSeconds: %u",
params.MeasurementTimingBudgetMicroSeconds, params.InterMeasurementPeriodMilliSeconds);
}
void init_VL53L0X_parameters(VL53L0X_DEV dev) {
VL53L0X_DeviceParameters_t params;
// TCC, DSS and MSRC disabled by default.
VL53L0X_SetSequenceStepEnable(dev, VL53L0X_SEQUENCESTEP_TCC, 0);
VL53L0X_SetSequenceStepEnable(dev, VL53L0X_SEQUENCESTEP_MSRC, 0);
VL53L0X_SetSequenceStepEnable(dev, VL53L0X_SEQUENCESTEP_DSS, 0);
params.MeasurementTimingBudgetMicroSeconds = 199999; // 200ms max to perform a reading
VL53L0X_SetDeviceParameters(dev, ¶ms);
}
void VL53L0X_singleRanging() {
VL53L0X_Error Status = VL53L0X_ERROR_NONE;
VL53L0X_Dev_t MyDevice;
VL53L0X_Dev_t *pMyDevice = &MyDevice;
VL53L0X_Version_t Version;
VL53L0X_Version_t *pVersion = &Version;
VL53L0X_DeviceInfo_t DeviceInfo;
int32_t status_int;
uint8_t VhvSettings;
uint8_t PhaseCal;
uint32_t refSpadCount;
uint8_t isApertureSpads;
VL53L0X_RangingMeasurementData_t RangingMeasurementData;
pMyDevice->I2cDevAddr = 0x29;
pMyDevice->comms_type = 1;
VL53L0X_ResetDevice(pMyDevice);
init_VL53L0X_parameters(pMyDevice);
print_VL53L0X_DeviceParameters(pMyDevice);
status_int = VL53L0X_GetVersion(pVersion);
Status = VL53L0X_DataInit(&MyDevice);
Status = VL53L0X_GetDeviceInfo(&MyDevice, &DeviceInfo);
Status = VL53L0X_StaticInit(pMyDevice); // Device Initialization
Status = VL53L0X_PerformRefSpadManagement(pMyDevice,
&refSpadCount, &isApertureSpads); // Device Initialization
Status = VL53L0X_PerformRefCalibration(pMyDevice,
&VhvSettings, &PhaseCal);
Status = VL53L0X_SetDeviceMode(pMyDevice, VL53L0X_DEVICEMODE_SINGLE_RANGING); // Setup in single ranging mode
Status = VL53L0X_SetLimitCheckEnable(pMyDevice,
VL53L0X_CHECKENABLE_SIGMA_FINAL_RANGE, 1);
Status = VL53L0X_SetLimitCheckEnable(pMyDevice,
VL53L0X_CHECKENABLE_SIGNAL_RATE_FINAL_RANGE, 1);
Status = VL53L0X_SetLimitCheckEnable(pMyDevice,
VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD, 1);
Status = VL53L0X_SetLimitCheckValue(pMyDevice,
VL53L0X_CHECKENABLE_RANGE_IGNORE_THRESHOLD,
(FixPoint1616_t)(1.5 * 0.023 * 65536));
delay(1000);
Status = VL53L0X_PerformSingleRangingMeasurement(pMyDevice,
&RangingMeasurementData);
SerialUSB.print("Status PerformSingleRangingMeasurement = ");
SerialUSB.println(Status);
SerialUSB.print("Measured distance: ");
SerialUSB.print(RangingMeasurementData.RangeMilliMeter);
SerialUSB.println(" millimeters");
SerialUSB.print("Range Status: ");
SerialUSB.println(RangingMeasurementData.RangeStatus);
VL53L0X_State palState;
Status = VL53L0X_GetPalState(pMyDevice, &palState);
char stateString[50];
memset(stateString, 0, 50);
VL53L0X_GetPalStateString(palState, stateString);
SerialUSB.println(stateString);
}
void setup() {
// put your setup code here, to run once:
SerialUSB.begin(9600);
while (!SerialUSB);
MyI2C_SERCOM3.init();
delay(1000);
SerialUSB.println("I2C ready");
VL53L0X_singleRanging();
}
void loop() {
}
I've also attached a logfile to see results. (Note the strange parameters passed to VL53L0X_SetMeasurementTimingBudget())
However, I would really appreciate support from ST engineers that developed this cumbersome sensor. I find it very annoying that they didn't provide a register map for this device.
As always, any kind of help will be surely appreciated.
Best regards,
Alberto
2021-07-15 12:30 PM
Alberto -
The official stance from ST is that that you could build a driver from the register map. But in the end you would absolutely hate ST. Using only the registers is really hard.
Both Pololu and Sparkfun reverse engineered the driver. They did it by running the ST driver and instrementing the I2C transactionss.
They both did a pretty darn good job of writing some simple, clean code. And they are happy to share.
On your issue of the error with the VL53L0X_PerformRefSpadManagement().
I've seen that happen. But it only happens on some some sensors and it occurs when the pre-calibrated data matches your result. So, in effect, you can skip that error knowing that the pre-calibrated data is fine.
I don't understand your comment, "Another thing that might be important is my I2C communication logic: no matter which PAL function gets called (RdByte, WrWord, etc), one and only one stop condition is always issued at the end of each of them."
Does the datasheet not fully describe the the I2C correctly?
(I do all my work on the STM32, and that might be why I've not seen any issues.)
2022-11-06 03:51 AM
Hello John,
As I'm using ESP32. I'd to migrate I2C communication logic to use higher level functions. I do understand what Alberto says and also I notice there is something in the documentation that is not clear.
The documentation says the device default address is 0x52. But the I2C scan recognises a device at address 0x29 (which is 0x52 >> 1).
Later, the documentation states to use the LSB bit of the address to indicate Read or Write condition. Looking at the source code provided from ST, I don't see the flip of that bit performed when doing a Write or a Read. I may be missing something because I'm not an expert in I2C but I also experience problems using this device as I've indicated in another thread.
Thank you for your support!
2022-11-06 06:45 AM
Doesn't everyone love the I2C bus?
You are exactly right - 0x29 base address. 0x52 is the write address, and 0x53 is the read address.
But depending on your MCUs software, any of those addreses can be used.
In the STM32, the write address is stored in the file handle and that's what is send to the function that actually does the work. But if you do a read, the LSB is set before putting the address out on the bus, creating the 0x53.
It's just a matter of which system the creators of the low level routines decided on.