I2C requestFrom gives 0 available bytes

I’m trying to talk to an board via I2C. The board is called Dr. Wattson and is based on Microchip’s MCP39F521. I have an Arduino library called UpbeatLabs_MCP39F521 that I ported to Particle.

The board has bidirectional level shifters for the SDA/SCL lines using BSS138 . The device works fine with Arduino Uno (5v), Arduino Pro Mini 3.3v, Raspberry Pi (3.3v with an equivalent Python library), Arduino Nano 33 BLE Sense, Raspberry Pi Pico (3.3v) with CircuitPython. The board is powered by 3.3v and the MCU side has a VIN for logic level voltage to pull up to - 3.3v or 5v.

However, with the Particle Photon (running DeviceOS 3.3.0), I’m not getting any data response from the device. After writing to some registers to request data, I call Wire.requestFrom to read the data, followed by Wire.available(), which returns 0.

I’ve attached the high-level schematic. Logic analyzer shows proper message being sent by Particle, but then there’s no response.

If I pull the SCL wire and reconnect it, I can get a response (whose data also looks ok), but subsequent calls fail in the same way as described above (Wire.available gives 0), which seems to suggest issues with pulling down the signal to low?

I’ve tried with external pullups (above the 10k of the bidirectional level shifters) - 2k, 10k, no pullups. setting the pinMode on the I2C pins (to disable any pullups) seems to have no effect - getPinMode always returns 5 (AF_OUTPUT_DRAIN) The comments say

// Used internally for Alternate Function Output Drain(I2C etc). External pullup resistors required.

I thought I2C has internal pullups enabled by default, and I also tried to set pinMode differently to disable any internal pullups, but getPinMode always returns 5.

I’m not sure definitively if there is any internal pullup enabled on the I2C pins of the Particle Photon (the older version), and if so, how I may be able to disable the pullup (seeing as pinMode above has no effect when using Wire library).

The code is fairly straightforward:

int UpbeatLabs_MCP39F521::registerReadNBytes(int addressHigh, int addressLow,
                                             int numBytesToRead, uint8_t *byteArray,
                                             int byteArraySize)
{
  uint8_t i2c_bus_Status = 0;
  uint8_t aucWriteDataBuf[8];
  int i;
  uint32_t checksumTotal = 0;
  
  if (byteArraySize < numBytesToRead + 3) {
    return ERROR_INSUFFICIENT_ARRAY_SIZE;
  }
    
  aucWriteDataBuf[0] = 0xa5; // Header
  aucWriteDataBuf[1] = 0x08; // Num bytes
  aucWriteDataBuf[2] = COMMAND_SET_ADDRESS_POINTER; // Command - set address pointer
  aucWriteDataBuf[3] = addressHigh;
  aucWriteDataBuf[4] = addressLow;
  aucWriteDataBuf[5] = COMMAND_REGISTER_READ_N_BYTES; // Command - read register, N bytes
  aucWriteDataBuf[6] = numBytesToRead;
  aucWriteDataBuf[7] = 0; // Checksum - computed below
  for(i=0; i<7;i++) {
    checksumTotal += aucWriteDataBuf[i];
  }
  aucWriteDataBuf[7] = checksumTotal % 256;

  Wire.beginTransmission(i2c_addr);
  for(i=0; i< 8; i++) {
    Wire.write(aucWriteDataBuf[i]);
  }
  i2c_bus_Status = Wire.endTransmission();
  wireErrors(i2c_bus_Status);  

  delay(1);
  
  //
  // Read the specified length of data - numBytesToRead + 3 bytes
  //

  Wire.requestFrom(i2c_addr, numBytesToRead + 3); <<<-----------------
  int requestDataLength = Wire.available();  <<<<<<-----------------------  this is always 0

  Serial.print("Num bytes to read2 + 3 = ");Serial.print(numBytesToRead + 3); Serial.println();
  Serial.print("Requested data length = ");Serial.print(requestDataLength); Serial.println();
 
  if (requestDataLength==(numBytesToRead + 3)) {
    for (i = 0; i < numBytesToRead + 3 ; i++) {
      byteArray[i] = Wire.read();
      Serial.print(byteArray[i], HEX); Serial.print(" ");
    }
    Serial.print("\n"); 

    // Check header and checksum
    return checkHeaderAndChecksum(numBytesToRead, byteArray, byteArraySize);   

  } else {
    // Unexpected. Handle error  
    return ERROR_UNEXPECTED_RESPONSE; 
  }

  return SUCCESS;
}

Any help will be appreciated!
Thanks,
Sridhar

In I2C mode the pinMode() call will have no effect as I2C overrides its effect as it's a different mode of operation.
Also the internal pull-ups (~40k) would be too weak for I2C and the Photon does not have external I2C pull-ups (unlike Arduino).

BTW, there would be a more convenient (IMO) way to populate your aucWriteDataBuf
e.g.

  uint8_t aucWriteDataBuf[8] = 
  { 0xa5                          // Header
  , 0x08                          // Num bytes
  , COMMAND_SET_ADDRESS_POINTER   // Command - set address pointer
  , addressHigh
  , addressLow
  , COMMAND_REGISTER_READ_N_BYTES // Command - read register, N bytes
  , numBytesToRead
  , 0                             // Checksum - computed below
  };
  for(i=0; i<sizeof(aucWriteDataBuf)-1; aucWriteDataBuf[7]+=aucWriteDataBuf[i++]);

and you can use the block write too

  Wire.write(aucWriteDataBuf, sizeof(aucWriteDataBuf));

Have you considered adding a delay between Wire.requestFrom() and checking Wire.available()?

Alternatively you could also use Wire.readyBytes() to let the Stream class handle the waiting and timeout logic.

BTW, there also exists a Serial.printf() to streamline this

into a single command

  Serial.printf("Num bytes to read2 + 3 = %d\r\n"
                "Requested data length  = %d\r\n"
               , numBytesToRead + 3
               , requestDataLength
               ); 
1 Like

Thank you for your reply and the coding/cleanup suggestions! I will certainly take those in account when doing the cleanup after I have things working with basic communications.

I have played around with all manners of delays between Wire.write/endTransmission and Wire.requestFrom, and between Wire.requestFrom and Wire.available() - it is very consistent in that none of it works! I’ve also experimented with different speeds - 100k, 400k and even custom values.

The fact is that I do get some data when I perturb the SCL line which makes me wonder about if/how the internal pullup may be affecting things, however small it is (or some other but perhaps related factor). Correct me if I’m wrong, but won’t the 40k internal pullup change the effective pullup resistance on the MCU-side of the level shifter (which has 10k pullups on either side) to 8k on that side? If I could somehow ensure that the internal pullup is not being activated, that’s an additional point that I can check to see if it would resolve the issue (other than reworking the board to change the level shifters’ pullups, which do work on a variety of boards as I’d mentioned).

Thanks,
Sridhar

I came across this comment in one of the forum threads - I2C LCD freezes Photon - #3 by pra - of course the issue is from way back when and the firmware has had many updates since then.

Time to bust out the logic analyzer and compare between a working MCU and the photon - I didn’t save the result from the last time unfortunately.

Here is the output from the logic analyzer. The first one is an example of what it should look like (using a working board like Pico or Arduino). The second shows what’s happening with the Photon. There’s no wire activity corresponding to the requestFrom call.

Screen Shot 2022-09-23 at 10.55.29 AM

Hmmm :thinking: that’s really weird.
May be is worth to try downgrade to lets say 2.0.1 with this tool and check the effects

I tried that - I went back to 2.3.0, 2.2.0, and even 1.0.0 (using Workbench) - same results.

Here is the MCP39F521 datasheet, btw, showing the protocol (see page 15).

How did you downgrade?
Merely pushing an application firmware targeted at these versions won't downgrade the device OS.

Ha! That’s exactly what I did, being the particle neophyte that I am! :slight_smile:

After your message above, I went and followed instructions from How to update the Photon Firmware? - #6 by Moors7 and also ensured in my particle console that the firmware version was appropriate.

I tried with 2.2.0 and 1.0.0 - the results are still exactly the same, unfortunately. :frowning:

Here is an update!

I thought I would get rid of the write and just do the read and see what happens - interestingly enough, I was getting the data in the expected format!! When I added the Wire write to it, I was not getting any data to read, as before!

I’ve simplified the example I have into a single file with straight calls to wire - if I get this to work, I can focus on the library porting later.


#include <Wire.h>

// setup() runs once, when the device is first turned on.
void setup() {
  // Put initialization like pinMode and begin functions here.
  Wire.begin();
  Serial.println("TEST");
}

// loop() runs over and over again, as quickly as it can execute.
void loop() {
  // The core of your code will likely live here.
  uint8_t writeDataBuf[8] = {0xa5, 0x08, 0x41, 0x00, 0x02, 0x4e, 28, 0};
  uint8_t byteArray[31];
  int i = 0;

  for(i=0; i<7;i++) {
    writeDataBuf[7] += writeDataBuf[i];
  }

  Wire.beginTransmission(0x74);
  for(i=0; i< 8; i++) {
    Wire.write(writeDataBuf[i]);
  }
  Wire.endTransmission();  

  int numBytes = Wire.requestFrom(0x74, 31);
  Serial.print("requestFrom return val = "); Serial.println(numBytes);

  int requestDataLength = Wire.available();
  uint16_t voltageRMS, lineFrequency;
  int16_t powerFactor;
  uint32_t currentRMS; 

  Serial.print("Requested data length = ");Serial.print(requestDataLength); Serial.println();
 
  if (requestDataLength==(31)) {
    for (i = 0; i < 31 ; i++) {
      byteArray[i] = Wire.read();
      Serial.print(byteArray[i], HEX); Serial.print(" ");
    }
    Serial.print("\n");     

    voltageRMS = ((byteArray[7] << 8) | byteArray[6]);
    lineFrequency = ((byteArray[9] << 8) | byteArray[8]);
    powerFactor = (((signed char)byteArray[13] << 8) +
                            (unsigned char)byteArray[12]);
  
    currentRMS =  ((uint32_t)(byteArray[17]) << 24 |
                            (uint32_t)(byteArray[16]) << 16 |
                            (uint32_t)(byteArray[15]) << 8 |
                            byteArray[14]);
    float pf;
    unsigned char ch;

    pf = ((powerFactor & 0x8000)>>15) * -1.0;
  
    for(ch=14; ch > 3; ch--)
      pf += ((powerFactor & (1 << ch)) >> ch) * 1.0 / (1 << (15 - ch));

    Serial.printf("VoltageRMS = %f\r\n"
                "CurrentRMS  = %f\r\n"
                "Line Frequency = %f\r\n"
                "Power Factor = %f\r\n"
               , voltageRMS/10.0f
               , currentRMS/10000.0f
               , lineFrequency/1000.0f
               , pf
               );   
  } else {
    // Unexpected. Handle error  
    Serial.print("Return data length doesn't match : "); Serial.println(requestDataLength);     
  }

  delay(1000);

}

Just commenting out the write portion, ie,

  Wire.beginTransmission(0x74);
  for(i=0; i< 8; i++) {
    Wire.write(writeDataBuf[i]);
  }
  Wire.endTransmission();

caused the readFrom to work.

-Sridhar

Output with the problem:

TEST
requestFrom return val = 0
Requested data length = 0
Return data length doesn't match : 0
requestFrom return val = 0
Requested data length = 0
Return data length doesn't match : 0

Output when the write is commented out:

TEST
requestFrom return val = 31
Requested data length = 31
6 1F 1 0 7 F8 4 0 0 0 EC 0 FF 7F C6 3 0 0 4 0 0 0 0 0 0 0 0 0 0 0 60 
VoltageRMS = 0.400000
CurrentRMS  = 0.096600
Line Frequency = 0.000000
Power Factor = 0.999512
requestFrom return val = 31
Requested data length = 31
6 1F 1 0 7 F8 4 0 0 0 EC 0 FF 7F C6 3 0 0 4 0 0 0 0 0 0 0 0 0 0 0 60 
VoltageRMS = 0.400000
CurrentRMS  = 0.096600
Line Frequency = 0.000000
Power Factor = 0.999512

-Sridhar

When the write is commented out, the read returns cached data, so the data is the same each time. If I unplug and replug the board back in, everything goes to 0, as expected.

This got me to thinking, and I added a second requestFrom call (and keeping the write uncommented) - this time it worked, and I was getting updated data (it would change with every call, and it would also return values if unplugged and replugged back in).

So it looks like the bus is getting stuck in some weird state, which a second call to requestFrom gets it out from.

Hopefully this will help shine the light on what the weirdness might be! Seems an ok workaround, but I’d like to know (and fix) what the issue was in the first place!

Thanks,
Sridhar
PS - This is back on deviceOS 3.3.0, the latest with Photon support.

Here is the output from the logic analyzer. Note that the readFrom on the bus happens much later (even though both requestFrom calls are done one right after the other). So the second call to requestFrom is blocking for a while.



Thanks,
Sridhar

@sridhar_rajagopal, some questions:

  • What is the time between the first requestFrom and the second (no timing ticks on your screenshot)?
  • What side of the level shifters is the logic analyzer connected to - Photon or MCP?

I wonder if the MCP is causing a clock-stretch condition after the command packet is sent and the Photon I2C is timing out. This might explain the period where SCK is held LOW for some time and then rises again, after which the Pico/Arduino kicks in but the Photon does not. What is the time period for this SCK low condition?

Also, I don’t see in the MCP datasheet that a repeated start condition is needed after the command packet so that is likely not the cause.

Did you try after the write:

 Wire.endTransmission(false);  

Passing false for endTransmission keeps the I2C bus active, and is typically what’s done if you have a write followed by a read. If you don’t pass a value the default is true which releases the I2C bus.

For example, this is how I do a write/read pair in the MCP23008 library:

1 Like

Thanks for your reply! Sorry, I missed screenshotting the time ticks! I reran the experiment by noting millis before/after calls.

The first requestFrom seems to be timing out (100 ms) - always 100 ms. The documentation on the overloaded version of requestFrom specified setting a timeout of 100ms, so it got me wondering if that was the default timeout if none was specified.

I replaced the requestFrom call with the overloaded version and specified the timeout as 1ms:

int numBytes = Wire.requestFrom(WireTransmission(0x74).quantity(31).timeout(1ms).stop(true));

and in that case, the first requestFrom call would timeout after 1ms and the second call would succeed.

The logic analyzer is connected to the output pins of the board - i.e. the Photon side of things.

The MCP can clock stretch, but it hasn’t been an issue with other boards like the Arduino via the use of some delays between calls. The fact that I can call requestFrom the first time with 1ms timeout and have the second call succeed seems to indicate that it is perhaps not an issue (I’ve also tried with having varying delays between the write and request from calls - they have no effect)

The above seem to suggest that the requestFrom call is perhaps blocking on some resource that was not released by the write/endTransmission calls, and hitting the timeout caused it to release the resource?

I haven’t looked at the implementation details of the Wire library/hal so this is just conjecture from observation at this point.

Thanks,
Sridhar

I did try Wire.endTransmission(false)! (I think I’ve tried all permutations/combinations at this point).

However, the MCP39F521 protocol expects a stop bit at the end of the write call and doesn’t use restart bits, so as expected, the subsequent call failed.

Thanks,
Sridhar

Calling Wire.reset() after Wire.endTransmission() and before Wire.requestFrom also works. However, it takes 50 ms to reset the bus (vs. calling Wire.requestFrom twice with the first call having a timeout of 1ms).

Thanks,
Sridhar


reset()

Wire.reset, reset

Since 0.4.6:

Attempts to reset the I2C bus. This should be called only if the I2C bus has has hung. In 0.4.6 additional rework was done for the I2C bus on the Photon and Electron, so we hope this function isn’t required, and it’s provided for completeness.

It seems to be fairly responsive after that, even at a high rate of read with minimal delays.

-Sridhar

Digging more into the hal layer, I see where it hits the timeout in the first call to requestFrom - this results in a softwareReset, so the subsequent call ends up succeeding. The reason the timeout occurs is because the I2C_FLAG_BUSY is set (one would think the endTransmission() should have cleared that??):

Btw, I checked toolchains/deviceOS/3.3.0/hal/src/stm32f2xx/i2c_hal.c - I thought this might be the appropriate place as the photon uses the STM32 as its MCU (correct me if I’m wrong).

int32_t hal_i2c_request_ex(hal_i2c_interface_t i2c, const hal_i2c_transmission_config_t* config, void* reserved)
 {
    if (!config) {
        return 0;
    }

    hal_i2c_lock(i2c, NULL);
    size_t bytesRead = 0;
    size_t quantity = config->quantity;
    int state;

    /* Implementation based on ST AN2824
     * http://www.st.com/st-web-ui/static/active/jp/resource/technical/document/application_note/CD00209826.pdf
     */

    // clamp to buffer length
    if (quantity > i2cMap[i2c]->rxBufferSize) {
        quantity = i2cMap[i2c]->rxBufferSize;
    }

    // Pre-configure ACK/NACK
    I2C_AcknowledgeConfig(i2cMap[i2c]->peripheral, ENABLE);
    I2C_NACKPositionConfig(i2cMap[i2c]->peripheral, I2C_NACKPosition_Current);

    if (i2cMap[i2c]->prevEnding != I2C_ENDING_START) {
        /* While the I2C Bus is busy */
        if (!WAIT_TIMED(config->timeout_ms, I2C_GetFlagStatus(i2cMap[i2c]->peripheral, I2C_FLAG_BUSY))) {
            /* SW Reset the I2C Peripheral */
            softwareReset(i2c);
            hal_i2c_unlock(i2c, NULL);
            return 0; 
// ^ <- returns 0 length after timeout. The default timeout is 100ms (HAL_I2C_DEFAULT_TIMEOUT_MS)
// performs softwareReset, so the next call succeeds
// need to figure out why the I2C_FLAG_BUSY is set
        }
....
more stuff ....