1

I'm a bit confused by i2c_write_blocking() and how to specify the device_address, register_address and data to prime a device for reading and then to read data from another 2nd_register_address in the device? I have looked at i2c_write_blocking_internal() in the source i2c.c and I get that it is looping over the bytes in src, to process the register address as a first byte and the transmission will complete or an abort will be triggered.

What then do we do to read data from a different 2nd_register_address that was primed for reading by writing to the first register address? Do we do another single-byte write with the 2nd_register_address as src and then do an i2c_read_blocking() to read the data from the device register address that was setup by the first i2c_write_blocking() call?

Here is the scenario. Initializing the MPU9250 magnetometer (AK8963) with full setup of all clocks and registers, you must put the chip in FUSE mode by writing data to the AK8963_CNTL1 control register. The makes a read from the FUSE ROM possible. Then you need to read the axis sensitivity adjustments from the FUSE ROM starting at register address AK8963_ASAX. I just did a write to put it in FUSE mode, now do I do another write sending the AK8963_ASAX register address before I read the axis sensitivity adjustments?

I think this is right, but I'm sure not certain of it. With other SDK I've used, for the read/write functionality you would just load the device_address as a slave and then load the register address and then kick-off the read or write. You had ample control over the device and register address combination both writing and reading.

Here I get the SDK is shortening that process (which is good), by sending the register address as the first byte in src parameter with the write and for normal reads that's fine, but here where you have to send a write to put an I2C device in a particular mode -- then follow-up to read from a different address is where the confusions is.

I have no way with the read to say, just read from AK8963_ASAX in the call to i2c_read_blocking() (unless I can send the register address as the first byte in the dst parameter) -- but I haven't found any documentation that says I can. hardware_i2c just says dst is limited to "Pointer to buffer to receive data". And its internal source doesn't separate an address from the dst argument.

Below are a couple of wrappers I added to help with i2c_read_blocking() and i2c_write_blocking() given that only the default i2c_default is used along with my best cut at what I need to do to put the chip in FUSE mode and then read from the AK8963_ASAX register:

static const uint8_t mag_addr = AK8963_DEFAULT_ADDRESS;
...
/** i2c helper function for read and write with default values */
int i2c_read (uint8_t addr, const uint8_t *dst, size_t len)
{
  /* i2c_default, addr, and nostop = false (master releases bus control) */
  return i2c_read_blocking (i2c_default, addr, dst, len, false);
}

int i2c_write (uint8_t addr, const uint8_t *src, size_t len)
{
  /* i2c_default, addr, and nostop = true (master retains bus control) */
  return i2c_write_blocking (i2c_default, addr, src, len, true);
}
...

/**
 *  Initialize the AK8963 magnetometer CNTL1 by placing the magnetometer in FUSE mode
 *  to read the Axis Sensitivity Adjustment (ASA) values from FUSE_ROM, power-down
 *  the chip between mode changes and then place the magnetometer in 16-bit adc and
 *  continual measurement 2 for normal operations. Save the computed adjustments
 *  factors in asax, asay, asaz. (Page 51 & 52 of Register_Map)
 */
#ifdef MPU9250
void ak8963_initialize (void)
{
  uint8_t cntl1_mode = AK8963_CNTL1_OUT_BITS | AK8963_CNTL1_CONT_MSRMT2,
          fuse_mode = AK8963_CNTL1_OUT_BITS | AK8963_CNTL1_FUSE_ROM,
          reset = 0x00,
          regdata[2] = {0};
  
  /* set AK8963 in FUSE mode */
  regdata[0] = AK8963_CNTL1;
  regdata[1] = fuse_mode;
  i2c_write (mag_addr, regdata, sizeof regdata);
  sleep_ms (1);
  
  /* Do I do this 2nd write of the 1-byte address to prepare for the 
   * axis sensitivity read?
   */
  regdata[0] = AK8963_ASAX;
  i2c_write (mag_addr, regdata, sizeof *regdata);
  sleep_ms (1);
  
  /* read axis sensitivity adjustments */
  i2c_read (mag_addr, AK8963_ASAX, buffer, 3, false);
  sleep_ms (1);
  ...

Is this how you handle a prepare mode and read from different register_address scenario? Or, did I miss something basic with the dst parameter along the way that is now screwing me up? All thoughts appreciated.

1 Answer 1

1

To write bytes a, b, c to I2C device x register y

output = [y, a, b, c]

i2c_write_blocking (i2c, x, &output, 4, false)

To read 6 bytes from I2C device x register y

output = [y]
input = [0, 0, 0, 0, 0, 0]

i2c_write_blocking (i2c, x, &output, 1, true)
i2c_read_blocking (i2c, x, &input, 6, false)
4
  • Bingo! That's what I was thinking but I damn sure wasn't firm on it. Now I can finish moving my mpu library from TI to Pi.. Thank you. Commented Sep 25, 2022 at 12:43
  • While I'm thinking about it, why is nostop false on the first write of [y, a, b, c]? I thought the Pico would always want to remain master on writes? Commented Sep 25, 2022 at 13:45
  • The documentation for the device should say if a STOP is required or not. Normally a STOP would not be required between specifying a register address and the corresponding read. A lot of devices are quite forgiving in this regard and do not punish a wrong STOP.
    – joan
    Commented Sep 25, 2022 at 15:00
  • Thank you again. That makes sense. It's been a while since I've been through all the NACKs and STOPs for the mpu and magnetometer chips. I suspect they are forgiving given the way the board was brought up in the pico-examples/i2c-mpu example. One of theses days I'll make friends will microcontrollers proving the "old dogs and new tricks" phrase wrong :) Commented Sep 25, 2022 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.