How to transfer large amounts of data over I2C

Hello,

I am using the vl53l5cx TOF sensor and implementing the ULD using zephyr. My setup is a bl654 board using zephyr on sdk 1.9.1. However I am running into an issue, since the sensor is RAM based in the init() it's required to upload ~82kB of firmware over the I2C connection into the sensor. Writing and reading single bytes are not a problem for me, but looping the I2C function continue to give me NACK on the I2C line and I cannot even transfer the internal register address to read or write from. Any ideas on how I can implement this. Here is an example of my multi-write function:

uint8_t WrMulti(
	VL53L5CX_Platform *p_platform,
	uint16_t RegisterAddress,
	uint8_t *p_values,
	uint32_t size)
{
	uint8_t status = 255;
	int offset = 0;
	uint8_t writeBuf[MAX_TX_BUFFER];

	while(size > 0){
		writeBuf[0] = RegisterAddress>>8;
    	writeBuf[1] = RegisterAddress & 0xff;
		memcpy(&(writeBuf[2]), &(p_values[offset]), MAX_TX_BUFFER - 2);
		if (size >= 256){
			status = i2c_write(p_platform->i2c_dev, writeBuf, MAX_TX_BUFFER, p_platform->address);
			if (status)
			{
				printk("%d\n", size);
			}
			offset += 256;
			size -= 256;
		} else{
			status = i2c_write(p_platform->i2c_dev, writeBuf, size, p_platform->address);
			if (status)
			{
				printk("%d\n", size);
			}
		}
	}

	return status;
}

Thanks,

Jacob

  • I suggest to get a logic analyzer trace and see the data transferred on the twi bus, then you can also see the start, and re-start bits, and the device address and if the register address are incremented as expected. Alternatively you need to write it on byte level using direct address access, something like this:

    https://devzone.nordicsemi.com/f/nordic-q-a/28367/i2c-driver-from-scratch-using-direct-register-access 

    Kenneth

  • Don't be tempted to break the transfer into smaller blocks, because the ST sensor will not run properly if you do.  The main trick is that the DMA cannot handle transfers from FLASH, you must first copy the transfer to RAM.  The next trick is that this will result in a stack size that is far too large, so you need to make the buffer static:

    uint8_t WrMulti(
    VL53L5CX_Platform *p_platform,
    uint16_t RegisterAdress,
    uint8_t *p_values,
    uint32_t size)
    {

    uint8_t status=VL53L5CX_STATUS_OK;
    static uint8_t localBuf[0x8002];

    localBuf[0] = RegisterAdress/256;
    localBuf[1] = RegisterAdress;
    memcpy(&localBuf[2], p_values, size);

    /* Need to be implemented by customer. This function returns 0 if OK */
    status |= i2c_write(p_platform->i2c, localBuf, size+2, p_platform->address);


    return status;
    }

    Lastly, the examples from ST often require too much stack space.  This will frustrate you because the error will not occur when the offending function is started, it will fail when you attempt to use a variable inside the function, or called from that function.  You need to make their local variables static, so in example1(), make these static:

        static VL53L5CX_Configuration   Dev ;           /* Sensor configuration */
        static VL53L5CX_ResultsData     Results;        /* Results data from VL53L5CX */

    I also have this running with Nordic's SDK.  That requires some coding outside of Nordic's API because the function only has an 8-bit size, but the driver can handle 16 bits.
    uint8_t WrMulti(
    VL53L5CX_Platform *p_platform,
    uint16_t RegisterAdress,
    uint8_t *p_values,
    uint32_t size)
    {
    int32_t status_int;
    uint8_t status = VL53L5CX_STATUS_OK;
    uint8_t localBuf[size + 2];

    /* Need to be implemented by customer. This function returns 0 if OK */

    /*Copy from FLASH into RAM, as required by EasyDMA, and create address in first two bytes*/
    memcpy(&localBuf[2], p_values, size);
    localBuf[0] = RegisterAdress / 256;
    localBuf[1] = RegisterAdress;

    /*Wait for any current operations*/
    while (nrf_drv_twi_is_busy(p_platform->i2c));

    /*Create TWIM descriptor to get larger than 8 bit size, must be constant*/
    nrfx_twim_xfer_desc_t const twim_xfer_desc =
    {
    .type = (nrfx_twim_xfer_type_t)NRF_DRV_TWI_XFER_TX,
    .address = p_platform->address,
    .primary_length = size + 2,
    .p_primary_buf = localBuf,
    };
    status = nrfx_twim_xfer(&p_platform->i2c->u.twim, &twim_xfer_desc, 0);

    while (nrf_drv_twi_is_busy(p_platform->i2c));

    return status;
    }
    I was frustrated for days by this, so I hope I've been able to help somebody else in trouble.
  • Oh, one last thing.  The Zephyr I2C driver has a timeout that you'll need to extend.  I set mine for 1000ms.  Without the change the driver will kill the transfer after 500ms.

    #define I2C_TRANSFER_TIMEOUT_MSEC       K_MSEC(1000)
  • I stand corrected, you CAN break the transfer into smaller blocks and the firmware WILL load properly.

  • Yes I struggled with this as well mostly because it was poorly documented in the datasheet for the sensor. You can break it up you just need to increment the register address accordingly to every transfer. I have it working with zephyr so let me know if need any help.

    -Jacob

Related