This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

Most Significative Bit in TWIM RX transfer is always 1

While trying to communicate through I2C/TWI with a MCP39F521 using a NRF52 DK (NRF52832), the first bit of the received data is always 1. The NRF52 DK is the TWI Master and the MCP39F521 is the TWI slave.

According to the MCP39F521 datasheet, the first byte in a response is the ACK/NAK, which is 0x06 on an ACK and 0x15 on a NAK, but I am receiving 0x86, no matter the command I sent to the MCP39F521. However, the remaining of the transfer is received correctly and corresponds to what is expected for the sent command. A command, on this context, is a set of bytes sent in a given order and with a given value to the MCP39F521.

Moreover, the last byte of a command is the checksum of the sent data. This checksum is sent either from MCP39F521 to NRF52DK on an RX command and vice-versa on a TX command. According to the datasheet, the MCP39F521 only processes commands once the command is fully sent (STOP bit sent by the master) and only if the checksum is correct. From this, I conclude that the commands are being correctly sent to the TWI bus by the NRF52 DK and correctly received by the MCP39F521.

I do not have a scope or a logical analyzer, but debugging also shows the correct data being sent by the nrfx_twim_xfer.

On section 4.3, page 16 of the MCP39F521 datasheet, it is indicated that the MCP39F521 performs clock stretching and "In the case of the MCP39F521, after a frame is received, the device will hold the clock low until the frame has been processed. The maximum clock stretching duration is less than 10 milliseconds"

Tested with an Arduino and its Wire library, the first byte received is 0x06, and this problem does not occur.

From the previous, and to the best of my understanding, I believe that the NRF52 DK is sampling the SDA line too soon after a response from the MCP39F521 and this timing issue results on the SDA line not yet been driven low by theMCP39F521. Can someone confirm if my conclusion makes sense?

Also, I also believe to be experiencing the Errata 149 -  TWIM: First clock pulse after clock stretching may be too long or too short.

Comments or suggestions on how to tackle this issue are very much appreciated.

I am using the SDK v16.0.0, and the nrfx_twim drivers. I have already tried using the nrf_drv_twi and nrfx_twi, both blocking and non-blocking and the problem persists.

The TWIM config:

/**
 * @brief TWIM initialization.
 */
void twim_init (void)
{
    ret_code_t err_code;

    const nrfx_twim_config_t twim_config = {
       .scl                 = ARDUINO_SCL_PIN,
       .sda                 = ARDUINO_SDA_PIN,
       .frequency           = NRF_TWIM_FREQ_100K,
       .interrupt_priority  = NRFX_TWIM_DEFAULT_CONFIG_IRQ_PRIORITY,
       .hold_bus_uninit     = NRFX_TWIM_DEFAULT_CONFIG_HOLD_BUS_UNINIT
    };

    err_code = nrfx_twim_init(&m_twi_master, &twim_config, twim_handler, NULL);
    APP_ERROR_CHECK(err_code);

    nrfx_twim_enable(&m_twi_master);
}

The twim handler:

/**
 * @brief TWI events handler.
 */
void twim_handler(nrfx_twim_evt_t const * p_event, void * p_context)
{
  switch (p_event->type)
  {
      case NRFX_TWIM_EVT_DONE:
      test = 1;
          if (p_event->xfer_desc.type == NRFX_TWIM_XFER_TX) // TX Done
          {
            m_xfer_done = true;
          }
          if (p_event->xfer_desc.type == NRFX_TWIM_XFER_RX) // RX Done
          {
            m_xfer_done = true;
          }
          break;
      default:
          break;
  }
}

And the instruction to read a command frame. rx_data is passed by argument and resides in .data (global variable defined in main).

ret_code_t mcp39f521_reg_read(const uint16_t reg_address, const uint8_t n_bytes, uint8_t * rx_data)
{
  if (n_bytes > CMD_FRAME_MAX_REG_READ_BYTES)
  {
    return NRF_ERROR_INVALID_PARAM;
  }

  m_xfer_done = false;
  ret_code_t err_code;

  // partially Init Array (Checksum field not initialized)
  uint8_t cmd_frame[REG_READ_CMD_N_BYTES] = {MCP39F521_HEADER_BYTE, \
                                             REG_READ_CMD_N_BYTES, \
                                             MCP39F521_SET_ADR_PTR_INSTR, \
                                             (reg_address & LEFTMOST_BYTE_BIT_MASK) >> BYTE_SHIFT, \
                                             reg_address & RIGHTMOST_BYTE_BIT_MASK, \
                                             MCP39F521_REG_READ_INSTR, \
                                             n_bytes, \
                                            };

  uint8_t bytes = n_bytes + MCP39F521_RESPONSE_BASE_N_BYTES;
  cmd_frame[REG_READ_CMD_N_BYTES - 1] = __compute_byte_addition_checksum(cmd_frame);      // Compute Command Checksum

  nrfx_twim_xfer_desc_t tx_xfer = NRFX_TWIM_XFER_DESC_TX(MCP39F521_BASE_ADDRESS, cmd_frame, sizeof(cmd_frame));
  nrfx_twim_xfer_desc_t rx_xfer = NRFX_TWIM_XFER_DESC_RX(MCP39F521_BASE_ADDRESS, rx_data, bytes);

  err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &tx_xfer, 0  );
  if (err_code != NRF_SUCCESS) {
    NRF_LOG_DEBUG("TX Error Code: %d", err_code);
  }

  while(m_xfer_done == false){
    __WFE();
  };

  m_xfer_done = false;
  err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &rx_xfer, 0 );
  if (err_code != NRF_SUCCESS) {
    NRF_LOG_DEBUG("RX Error Code: %d", err_code);
  }
 
  while(m_xfer_done == false){
    __WFE();
  };
  
  return err_code;
}

Thank you in advance!

Parents
  • Hi,

    Your reasoning sounds plausible, but it would really help if you could get logic/scope traces of the bus to determine if this could be what is happening. It is strange if you see this every time if it is related to clock stretching unless the slave performs clock stretching on every transfer. 

    Have you tried other slaves to see if they experience the same behavior?

    Best regards,
    Jørgen

  • Your reasoning sounds plausible, but it would really help if you could get logic/scope traces of the bus to determine if this could be what is happening.

    Thank you for confirming . I am trying to get my hands on a scope/logic analyzer. If I can get one during the next week, I update my answer with scopes.

    t is strange if you see this every time if it is related to clock stretching unless the slave performs clock stretching on every transfer. 

    From my understanding of the MCP39F521 datasheet, this is indeed true. The device performs clock stretching on every transfer. Regarding clock stretching, can you please inform how well are the twim drivers "prepared to deal with with"?

    I noticed several failed reads from the NRF52 DK and it seems that the MCU fails to wait for a response of the MCP39F521. This behavior happens on blocking and non-blocking mode and results on NRF_ERROR_INTERNAL or NRF_DEVICE_NACK.

    Also, despite the device supporting up to 400 kHz, the MCU only receives "garbage data" if the line is configured to 250 or 400 kHz.

    Have you tried other slaves to see if they experience the same behavior?

    Not yet, but I will during the following days.

  • martinspedro said:
    Regarding clock stretching, can you please inform how well are the twim drivers "prepared to deal with with"?

    As far as I know, handling of clock stretching performed by the TWI slave is handled purely in HW. There are no timeouts in the driver, so it should always wait for either a STOPPED or ERROR event to be generated (or SUSPENDED event in case of TX without STOP condition). It would really help if you could get some logic traces of the bus to see what is actually going on.

Reply
  • martinspedro said:
    Regarding clock stretching, can you please inform how well are the twim drivers "prepared to deal with with"?

    As far as I know, handling of clock stretching performed by the TWI slave is handled purely in HW. There are no timeouts in the driver, so it should always wait for either a STOPPED or ERROR event to be generated (or SUSPENDED event in case of TX without STOP condition). It would really help if you could get some logic traces of the bus to see what is actually going on.

Children
  • I recognize that not having the scope available makes more difficult to figure this one out. 

    As far as I know, handling of clock stretching performed by the TWI slave is handled purely in HW. There are no timeouts in the driver, so it should always wait for either a STOPPED or ERROR event to be generated (or SUSPENDED event in case of TX without STOP condition)

    Regarding the driver timeouts, if the "managing" of the clock stretch is fully performed by the HW and if I fully understood the TWIM driver documentation, then:

    • I should use the nrfx_twim_xfer to send a transfer;
    • In my case, since I want to performed a write followed by a read, I should use the NRFX_TWIM_XFER_DESC_TXRX macro to encapsulate the data to be sent
    • The data buffers p_tx and r_tx on the transfer descriptor should reside .data memory section of RAM, since the TWIM driver uses EasyDMA.

    If this is true, consider that I do not use an event handler and want a blocking transfer. My TWIM config would be the following:

    void twim_init (void)
    {
        ret_code_t err_code;
    
        const nrfx_twim_config_t twim_config = {
           .scl                 = ARDUINO_SCL_PIN,
           .sda                 = ARDUINO_SDA_PIN,
           .frequency           = NRF_TWIM_FREQ_100K,
           .interrupt_priority  = NRFX_TWIM_DEFAULT_CONFIG_IRQ_PRIORITY,
           .hold_bus_uninit     = NRFX_TWIM_DEFAULT_CONFIG_HOLD_BUS_UNINIT
        };
    
        err_code = nrfx_twim_init(&m_twi_master, &twim_config, NULL, NULL);
        APP_ERROR_CHECK(err_code);
    
        nrfx_twim_enable(&m_twi_master);
    }

    and I am executing the transfer by doing the following (cmd_frame2 is formatted to be a valid MCP39F521. More info on the MCP39F521 datasheet)

      // Init cmd_frame2, a global variable array
      cmd_frame2[0] = MCP39F521_HEADER_BYTE;
      cmd_frame2[1] = REG_READ_CMD_N_BYTES;
      cmd_frame2[2] = MCP39F521_SET_ADR_PTR_INSTR;
      cmd_frame2[3] = (reg_address & LEFTMOST_BYTE_BIT_MASK) >> BYTE_SHIFT;
      cmd_frame2[4] = reg_address & RIGHTMOST_BYTE_BIT_MASK;
      cmd_frame2[5] = MCP39F521_REG_READ_INSTR;
      cmd_frame2[6] = n_bytes;
    
      // Number of bytes required on the response
      uint8_t bytes = n_bytes + MCP39F521_RESPONSE_BASE_N_BYTES;
      
      // Compute Command Checksum
      cmd_frame2[REG_READ_CMD_N_BYTES - 1] = __compute_byte_addition_checksum(cmd_frame2);      
    
      nrfx_twim_xfer_desc_t txrx_xfer = NRFX_TWIM_XFER_DESC_TXRX(MCP39F521_BASE_ADDRESS, cmd_frame2, REG_READ_CMD_N_BYTES, rx_data, bytes);
    
      err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &txrx_xfer, 0  );

    Then, I should expect nrfx_twim_xfer to either finish the transfer successfully or return an error code, correct? However the call to nrfx_twim_xfer never returns.

    Debugging, I am stuck on the while loop on line 523, on nrfx_twim.c, which is the following:

            while (!nrf_twim_event_check(p_twim, evt_to_wait))
            {
                if (nrf_twim_event_check(p_twim, NRF_TWIM_EVENT_ERROR))
                {
                    NRFX_LOG_DEBUG("TWIM: Event: %s.", EVT_TO_STR_TWIM(NRF_TWIM_EVENT_ERROR));
                    nrf_twim_event_clear(p_twim, NRF_TWIM_EVENT_ERROR);
                    nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_RESUME);
                    nrf_twim_task_trigger(p_twim, NRF_TWIM_TASK_STOP);
                    evt_to_wait = NRF_TWIM_EVENT_STOPPED;
                }
            }

    Is my reasoning correct? If so, what am I missing on the driver and TWIM module behavior?

  • I think that could happen with older driver versions if the slave is misbehaving. Some fixes were introduced in nrfx v1.8.2 and should be included in nRF5 SDK v17.0.0 (which uses nrfx v1.8.4). Can you try to update to the latest SDK, to see if this solves some of your issues?

  • Hi ,

    Some fixes were introduced in nrfx v1.8.2 and should be included in nRF5 SDK v17.0.0 (which uses nrfx v1.8.4). Can you try to update to the latest SDK, to see if this solves some of your issues?

    Thank you for the suggestion. I successfully migrated from the NRF5 SDK version 16.0.0 to 17.0.0, to update the nfrx drivers.

    The behavior described in my message above (the nrfx_twim_xfer stays stuck on a TXRX transfer), is solved, since the transfer either successfully or unsuccessfully finishes.


    However, when doing a TXRX transfer, the RX buffer is empty, i.e., no data was read from the slave. This behavior occurs either if I set up the TWIM module to operate in a blocking or non-blocking mode. An example of a TXRX transfer is below:

    // partially Init Array (Checksum field not initialized)
    cmd_frame2[0] = MCP39F521_HEADER_BYTE;
    cmd_frame2[1] = REG_READ_CMD_N_BYTES;
    cmd_frame2[2] = MCP39F521_SET_ADR_PTR_INSTR;
    cmd_frame2[3] = (reg_address & LEFTMOST_BYTE_BIT_MASK) >> BYTE_SHIFT;
    cmd_frame2[4] = reg_address & RIGHTMOST_BYTE_BIT_MASK;
    cmd_frame2[5] = MCP39F521_REG_READ_INSTR;
    cmd_frame2[6] = n_bytes;
    
    uint8_t bytes = n_bytes + MCP39F521_RESPONSE_BASE_N_BYTES;
    cmd_frame2[REG_READ_CMD_N_BYTES - 1] = __compute_byte_addition_checksum(cmd_frame2);      // Compute Command Checksum
    
    nrfx_twim_xfer_desc_t txrx_xfer = NRFX_TWIM_XFER_DESC_TXRX(MCP39F521_BASE_ADDRESS, cmd_frame2, REG_READ_CMD_N_BYTES, rx_data, bytes);
    
    err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &txrx_xfer, 0);
    if (err_code != NRF_SUCCESS) {
      NRF_LOG_DEBUG("TX Error Code: %d", err_code);
    }
    
    
    

    Nonetheless, if I split the TXRX transfer into a TX and a RX transfer, either using the TWIM module in blocking or non-blocking configurations, the RX buffer successfully contains the device response. 

    // partially Init Array (Checksum field not initialized)
    cmd_frame2[0] = MCP39F521_HEADER_BYTE;
    cmd_frame2[1] = REG_READ_CMD_N_BYTES;
    cmd_frame2[2] = MCP39F521_SET_ADR_PTR_INSTR;
    cmd_frame2[3] = (reg_address & LEFTMOST_BYTE_BIT_MASK) >> BYTE_SHIFT;
    cmd_frame2[4] = reg_address & RIGHTMOST_BYTE_BIT_MASK;
    cmd_frame2[5] = MCP39F521_REG_READ_INSTR;
    cmd_frame2[6] = n_bytes;
    
    uint8_t bytes = n_bytes + MCP39F521_RESPONSE_BASE_N_BYTES;
    cmd_frame2[REG_READ_CMD_N_BYTES - 1] = __compute_byte_addition_checksum(cmd_frame2);      // Compute Command Checksum
    
    nrfx_twim_xfer_desc_t tx_xfer = NRFX_TWIM_XFER_DESC_TX(MCP39F521_BASE_ADDRESS, cmd_frame2, REG_READ_CMD_N_BYTES);
    nrfx_twim_xfer_desc_t rx_xfer = NRFX_TWIM_XFER_DESC_RX(MCP39F521_BASE_ADDRESS, rx_data, bytes);
    
    err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &tx_xfer, 0);
    if (err_code != NRF_SUCCESS) {
      NRF_LOG_DEBUG("TX Error Code: %d", err_code);
    }
    
    
    err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &rx_xfer, 0 );
    if (err_code != NRF_SUCCESS) {
      NRF_LOG_DEBUG("TX Error Code: %d", err_code);
    }
    

    As the TXRX transfer sends a Repeated Start instead of a Start + Stop bit, I have also tested sending a TX transfer with a repeated start, using the NRFX_TWIM_FLAG_TX_NO_STOP, and then performing the RX transfer. For that, I changed the line 

    err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &tx_xfer, 0);

    to

    err_code = nrfx_twim_xfer(g_mcp39f521_twim_instance, &tx_xfer, NRFX_TWIM_FLAG_TX_NO_STOP);

    And successfully received the expected data response on the RX buffer.

    Please consider that when I am enabling the non-blocking mode, I add a while loop after each nrfx_twim_xfer call. This loop waits for a flag variable that is toggled on the twim callback routine when the NRFX_TWIM_EVT_DONE occurs. This implementation is to simplify testing and is similar to the twim_sensor example.

    Can you provide info about this recent issue? 

    Also, why does a TX transfer with repeated start + a RX transfer succeeds and retrieves the correct data and a TXRX transfer does not?

    Please notice that the underlying issue, the "Most Significative Bit in TWIM RX transfer is always 1", still occurs.

    Thank you for your time and help

  • The difference between using NRFX_TWIM_XFER_DESC_TXRX and separate NRFX_TWIM_XFER_DESC_TX/RX is that the first option will by default not generate a STOP condition between the TX and RX operations, while the second option will generate a STOP condition. It can be that the slave device you are using is expecting a STOP condition in this case, meaning that the NRFX_TWIM_XFER_DESC_TXRX option will not work.

    A transfer with NRFX_TWIM_XFER_DESC_TX can take a flag (NRFX_TWIM_FLAG_TX_NO_STOP) to also not generate a STOP condition, but there is no similar flag for NRFX_TWIM_XFER_DESC_TXRX to force a STOP condition between the transfers.

  • Thank you for the clarification, .

    From the TWIM documentation, I was expecting the behavior you described in the usage of the flags and transfer types.

    However, note that sending a TX transfer without a stop condition and immediately after sending a RX transfer results in the data being correctly received from the slave, while sending a TXRX doesn't get me any data in the secondary buffer.

    So, the fact that a STOP condition is not being generated does not seem to affect the behavior of the slave, otherwise, to my understanding, it wouldn't work on a TX with no stop condition + RX communication.

    Are there any other differences between a TX+RX and a TXRX transfer?

Related