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

TWI master driver stops sending after successful transfer of 2 bytes

Dear support team

Please note my sample application code attached, and the trace of the I2C transmission

I am facing the issue that the twi driver does not write more than slave-address + 1 byte of data onto I2C at once, although the application requests to transmit 2 data bytes.

Setup:

HW: nRF52840 DK connected to Sensirion sensor of sgp40 via I2C. 

SW:

sdk_config.h:

#define TWI_ENABLED 1

#define TWI_DEFAULT_CONFIG_FREQUENCY 26738688

#define TWI0_ENABLED 1

I2C config:

const nrf_drv_twi_config_t twi_sgp40_config = {
.scl = ARDUINO_SCL_PIN,
.sda = ARDUINO_SDA_PIN,
.frequency = NRF_DRV_TWI_FREQ_100K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
.clear_bus_init = false
};

I2C trace; after address and first byte (0x36) have been transmitted successfully the I2C SCK line remains low. the content of the second data byte does not get sent out.

uint8_t cmd[2u] = { 0x36u, 0x82u } ; 

(void)nrf_drv_twi_tx(&m_twi, 0x59u, cmd, 2, false); 

main.c:

#include <stdio.h>
#include "boards.h"
#include "app_util_platform.h"
#include "app_error.h"
#include "nrf_drv_twi.h"
#include "nrf_delay.h"


#include "nrf_log.h"
#include "nrf_log_ctrl.h"
#include "nrf_log_default_backends.h"

#define TWI_INSTANCE_ID     0

#define SENSIRION_WORD_SIZE 2
#define SENSIRION_NUM_WORDS(x) (sizeof(x) / SENSIRION_WORD_SIZE)
#define SENSIRION_MAX_BUFFER_WORDS 32

#define CRC8_POLYNOMIAL 0x31
#define CRC8_INIT 0xFF
#define CRC8_LEN 1

#define SGP40_CMD_MEASURE_RAW_DURATION_US 100000
#define SGP40_SERIAL_ID_NUM_BYTES 6

typedef enum {
  WAIT,
  READ,
  WRITE,
  PRINT
}state_t;

static uint8_t serial[6u] = { 0 };
static state_t state = WRITE;
static uint32_t tick = 0u;
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);

static int8_t sensirion_i2c_read(uint8_t address, uint8_t* data, uint16_t count) {
  
  ret_code_t err_code = nrf_drv_twi_rx(&m_twi, address, data, count);
  if (err_code == 0)
  {
    return 0;
  }
  return -1;
}

static uint8_t sensirion_common_generate_crc(const uint8_t* data, uint16_t count) {
  uint16_t current_byte;
  uint8_t crc = CRC8_INIT;
  uint8_t crc_bit;

  /* calculates 8-Bit checksum with given polynomial */
  for (current_byte = 0; current_byte < count; ++current_byte) {
    crc ^= (data[current_byte]);
    for (crc_bit = 8; crc_bit > 0; --crc_bit) {
      if (crc & 0x80)
        crc = (crc << 1) ^ CRC8_POLYNOMIAL;
      else
        crc = (crc << 1);
    }
  }
  return crc;
}

static int8_t sensirion_common_check_crc(const uint8_t* data,
  uint16_t count,
  uint8_t checksum) {
  if (sensirion_common_generate_crc(data, count) != checksum)
    return 1;
  return 0;
}

static int16_t sensirion_i2c_read_words_as_bytes(uint8_t address,
  uint8_t* data,
  uint16_t num_words) {
  int16_t ret;
  uint16_t i, j;
  uint16_t size = num_words * (SENSIRION_WORD_SIZE + CRC8_LEN);
  uint16_t word_buf[SENSIRION_MAX_BUFFER_WORDS];
  uint8_t* const buf8 = (uint8_t*)word_buf;

  ret = sensirion_i2c_read(address, buf8, size);
  if (ret != 0)
    return ret;

  /* check the CRC for each word */
  for (i = 0, j = 0; i < size; i += SENSIRION_WORD_SIZE + CRC8_LEN) {

    ret = sensirion_common_check_crc(&buf8[i],
      SENSIRION_WORD_SIZE,
      buf8[i + SENSIRION_WORD_SIZE]);
    if (ret != 0)
      return ret;

    data[j++] = buf8[i];
    data[j++] = buf8[i + 1];
  }

  return 0;
}

static void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
  switch (p_event->type)
  {
  case NRF_DRV_TWI_EVT_DONE:
    if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_RX)
    {
      state = PRINT;
    }
    else if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_TX)
    {
      state = READ;
    }
    else
    {
    }
    break;
    
  default:
    break;
  }
}

static void twi_init (void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t twi_sgp40_config = {
       .scl                = ARDUINO_SCL_PIN,
       .sda                = ARDUINO_SDA_PIN,
       .frequency          = NRF_DRV_TWI_FREQ_100K,
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
       .clear_bus_init     = false
    };

  err_code = nrf_drv_twi_init(&m_twi, &twi_sgp40_config, twi_handler, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_twi_enable(&m_twi);
}

int main(void)
{
  uint8_t cmd[2u] = { 0x36u, 0x82u } ;    // command: get serial Id
  
    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();
    NRF_LOG_INFO("\r\nTWI sensor example started.");
    NRF_LOG_FLUSH();
  
    twi_init();

    while (true)
    {
      switch (state)
      {
        
      case WRITE:
        state = WAIT;
        // send command and wait for succesful transmission
        (void)nrf_drv_twi_tx(&m_twi, 0x59u, cmd, 2, false); 
        break;
        
      case READ:
        state = WAIT;
        nrf_delay_ms(1);
        // read datat and wait for succesful receiption
        memset(serial, 0, 6);
        (void)sensirion_i2c_read_words_as_bytes(0x59u, serial, 3u /* words, not bytes */); 
        break;
        
      case PRINT:
        state = WRITE;
        NRF_LOG_INFO("\r\nserial Id");
        NRF_LOG_HEXDUMP_INFO(serial, 6u);
        NRF_LOG_FLUSH();
        nrf_delay_ms(1000u);
        break;
        
      default:
        break;
      }
   }
}

/** @} */

Any ideas as to why this happens? Am I using the twi API correctly?

Thanks for your help

Marko

  • All, please note the heading of this ticket is incorrect.

    A better title is:

    TWI master driver stops sending after successful transfer of slave address + 1 data byte

  • Hi Marko,

    I haven't really studied the data sheet for this particular sensor yet, but I have a few quick follow up questions I was hoping you could answer first:

    1. Does the clock signal low remain low after you have sent the last 0x36 byte?

    2. Have you able to send multiple bytes for other commands?

    3. What is 0x36 0x82 supposed to do. I didn't find 0x82 mentioned anywhere in the datasheet.

    Best regards,

    Vidar

  • Hi Vidar

    thanks for you reply.

    1. Yes, the I2C SCK line remains low after the byte of 36. It should still send the byte of 0x82.

    2. No, I have not. It always stops after address + 1 byte of data.

    3. 0x36 0x82 is the command of getting the serial identifier. it is not documented in the data sheet, but is used in the sample code provided by Sensirion: https://github.com/Sensirion/embedded-sgp. I verified on a STM32L462RE device this command is working fine.

    Anyway, in the meantime I switched to using the Zephyr device driver model analog to the example of "v1.5.0\zephyr\samples\boards\bbc_microbit\line_follower_robot".

    This working fine for me.

    Although I am curious as to why using the twi driver directly does not work for me, I am good for now and we can close this ticket. I will let you know in case I need to use the driver directly again.

    Thanks again for your help.

    Marko

  • Thanks for the update Marko, glad to hear that you found  a way to get around this. To me it looks like the problem could be that the slave doesn't release the clock line for some reason and thus preventing the master from clocking out the last byte.

    I think it could be interesting to try compare the scope traces of when it works and when it doesn't.

Related