Beware that this post is related to an SDK in maintenance mode
More Info: Consider nRF Connect SDK for new designs
This post is older than 2 years and might not be relevant anymore
More Info: Consider searching for newer posts

TWI Confusion-Receiving NACK on example code

I'm having a lot of confusion with using TWI on this chip. I have been running example code that I found in this tutorial nRF5 SDK - Tutorial for Beginners Pt 35 B - TWI - I2C with MPU6050 Accelerometer & Gyro Sensor - YouTube. It basically copies the project file from the TWI_scanner and modified the main file to call code in mpu6050.c which actually creates the I2C module and read registers. Currently I've noticed that the scanner example itself worked (ie. writing the device address seemed to function correctly as it returned with no errors and it was in blocking mode so the communication definitely worked "err_code = nrf_drv_twi_rx(&m_twi, address, &sample_data, sizeof(sample_data));". But when I moved to trying to actually get the who_am_i register of the sensor to see the product ID I got a NACK returned by my TWI handler. I tried switching to blocking mode to replicate the conditions of the scanner example but the code never returned it was stuck in the inline nrf_drv_twi_tx() function (I attached the scanner file as well just in case that is of value when referencing "scannerEx.c").

An additional source of confusion for me is that when hooking up to a logic analyzer, the scannerEx.c code shows up just fine with the right data being sent but no signal is found on the main.c file where I try to access the who_am_i register (which doesnt make sense to me because it should be showing a signal with a Nack returned as the error from the handler shows). I'm very confused how this discrepancy happened, is this a tx vs. rx problem where I can receive but not send? I had the same issue with a Max30102 which is why i moved to the Mpu6050 to use a sensor people have gotten working before. Should I just ditch this function? I noticed in the SDK theres another option with nrf_drv_twi_xfer. Maybe that works better? Or maybe switch altogether to TWIM with easy DMA? Honestly I'm very confused about TWI on Nordic's platform, I feel like you have allowed for all these different options to allow for more versatility for the developer to fit their architecture but I feel pulled in so many directions that I don't know the best place to start in dealing with this problem. I feel like the other MCUs I've worked with have simpler architectures so I can understand the low level interactions with the hardware enough to debug applications. I'm on SDK 17.0.2. I'm just very confused. I'm using pins SCL 22 and SDA 23 on the nRF52840 DK.

I have enclosed the main file as well as the mpu6050.c file I call which actually runs the TWI code (calling the nrf_drv_twi_tx() function when the "verify_product_id" function calls register_read() ). I also included the sdk_config.h file in case there is a project configuration misstep but I believe the modules should be enabled as I have all nrfx / nrf twi/twim/ useEasyDMA enables all set to 1. 


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



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




// main code
//SCL 22, SDA 23+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
int main(void)
{

// initialize the logger
    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();
	

// create arrays which will hold x,y & z co-ordinates values of acc and gyro
    static int16_t AccValue[3], GyroValue[3];
    printf("start\n");
    bsp_board_init(BSP_INIT_LEDS | BSP_INIT_BUTTONS); // initialize the leds and buttons

    twi_master_init(); // initialize the twi 
    nrf_delay_ms(1000); // give some delay

    //while (1) {
      
    //}
    while(mpu6050_init() == false) // wait until MPU6050 sensor is successfully initialized
    {
      printf("MPU_6050 initialization failed!!!\n"); // if it failed to initialize then print a message
      nrf_delay_ms(1000);
    }

   printf("MPU6050 Init Successfully!!!\n"); 

   printf("Reading Values from ACC & GYRO\n"); // display a message to let the user know that the device is starting to read the values
   nrf_delay_ms(2000);


  
    
    while (true)
    {
        if(MPU6050_ReadAcc(&AccValue[0], &AccValue[1], &AccValue[2]) == true) // Read acc value from mpu6050 internal registers and save them in the array
        {
          printf("ACC Values:  x = %d  y = %d  z = %d\n", AccValue[0], AccValue[1], AccValue[2]); // display the read values
        }
        else
        {
          printf("Reading ACC values Failed!!!\n"); // if reading was unsuccessful then let the user know about it
        }


        if(MPU6050_ReadGyro(&GyroValue[0], &GyroValue[1], &GyroValue[2]) == true) // read the gyro values from mpu6050's internal registers and save them in another array
        {
          printf("GYRO Values: x = %d  y = %d  z = %d\n", GyroValue[0], GyroValue[1], GyroValue[2]); // display then values
        }

        else
        {
          printf("Reading GYRO values Failed!!!\n");
        }

       nrf_delay_ms(100); // give some delay 


    }
}

/** @} */
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "nrf_drv_twi.h"
#include "mpu6050.h"




//Initializing TWI0 instance
#define TWI_INSTANCE_ID     0

// A flag to indicate the transfer state
static volatile bool m_xfer_done = false;


// Create a Handle for the twi communication
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);




//Event Handler
void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)
{
    printf("handled!\n");
    //Check the event to see what type of event occurred
    switch (p_event->type)
    {
        //If data transmission or receiving is finished
	case NRF_DRV_TWI_EVT_DONE:
        m_xfer_done = true;//Set the flag
        printf("event done\n");
        break;

        case NRF_DRV_TWI_EVT_ADDRESS_NACK:
        printf("Address nack\n");
        nrf_delay_ms(500);
        ret_code_t err_code;
        uint8_t register_address = MPU_DEVICE_ID_REG;// change this as youre debugging, comment out otherwise
        err_code = nrf_drv_twi_tx(&m_twi, MPU6050_ADDRESS, &register_address, 1, true);
        break;
        
        case NRF_DRV_TWI_EVT_DATA_NACK:
        printf("data nack\n");
        break;

        default:
          break;
    }
}



//Initialize the TWI as Master device
void twi_master_init(void)
{
    ret_code_t err_code;

    // Configure the settings for twi communication
    const nrf_drv_twi_config_t twi_config = {
       .scl                = TWI_SCL_M,  //SCL Pin
       .sda                = TWI_SDA_M,  //SDA Pin
       .frequency          = NRF_DRV_TWI_FREQ_400K, //Communication Speed
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH, //Interrupt Priority(Note: if using Bluetooth then select priority carefully)
       .clear_bus_init     = false //automatically clear bus
    };


    //A function to initialize the twi communication
    err_code = nrf_drv_twi_init(&m_twi, &twi_config, twi_handler, NULL); 
    APP_ERROR_CHECK(err_code);
    
    //Enable the TWI Communication
    nrf_drv_twi_enable(&m_twi);
}



/*
   A function to write a Single Byte to MPU6050's internal Register
*/ 
bool mpu6050_register_write(uint8_t register_address, uint8_t value)
{
    ret_code_t err_code;
    uint8_t tx_buf[MPU6050_ADDRESS_LEN+1];
	
    //Write the register address and data into transmit buffer
    tx_buf[0] = register_address;
    tx_buf[1] = value;

    //Set the flag to false to show the transmission is not yet completed
    m_xfer_done = false;
    
    //Transmit the data over TWI Bus
    err_code = nrf_drv_twi_tx(&m_twi, MPU6050_ADDRESS, tx_buf, MPU6050_ADDRESS_LEN+1, false);
    
    //Wait until the transmission of the data is finished
    while (m_xfer_done == false)
    {
      }

    // if there is no error then return true else return false
    if (NRF_SUCCESS != err_code)
    {
        return false;
    }
    
    return true;	
}




/*
  A Function to read data from the MPU6050
*/ 
bool mpu6050_register_read(uint8_t register_address, uint8_t * destination, uint8_t number_of_bytes)
{
    ret_code_t err_code;

    //Set the flag to false to show the receiving is not yet completed
    m_xfer_done = false;
    
    // Send the Register address where we want to write the data
    err_code = nrf_drv_twi_tx(&m_twi, MPU6050_ADDRESS, &register_address, 1, false);
     if (NRF_SUCCESS != err_code)
    {
        return false;
    }
	
    //    // If transmission was not successful, exit the function with false as return value
    //if (NRF_SUCCESS != err_code)
    //{
    //    return false;
    //}      
    //Wait for the transmission to get completed
    while (m_xfer_done == false){}
    
    // If transmission was not successful, exit the function with false as return value
    if (NRF_SUCCESS != err_code)
    {
        return false;
    }

    //set the flag again so that we can read data from the MPU6050's internal register
    m_xfer_done = false;
	  
    // Receive the data from the MPU6050
    err_code = nrf_drv_twi_rx(&m_twi, MPU6050_ADDRESS, destination, number_of_bytes);
	
          
    //wait until the transmission is completed
    while (m_xfer_done == false){}
	
    // if data was successfully read, return true else return false
    if (NRF_SUCCESS != err_code)
    {
        return false;
    }
    
    return true;
}



/*
  A Function to verify the product id
  (its a basic test to check if we are communicating with the right slave, every type of I2C Device has 
  a special WHO_AM_I register which holds a specific value, we can read it from the MPU6050 or any device
  to confirm we are communicating with the right device)
*/ 
bool mpu6050_verify_product_id(void)
{
    uint8_t who_am_i; // create a variable to hold the who am i value


    // Note: All the register addresses including WHO_AM_I are declared in 
    // MPU6050.h file, you can check these addresses and values from the
    // datasheet of your slave device.
    if (mpu6050_register_read(ADDRESS_WHO_AM_I, &who_am_i, 1))
    {
        if (who_am_i != MPU6050_WHO_AM_I)
        {
            return false;
        }
        else
        {
            return true;
        }
    }
    else
    {
        return false;
    }
}


/*
  Function to initialize the mpu6050
*/ 
bool mpu6050_init(void)
{   
  bool transfer_succeeded = true;
	
  //Check the id to confirm that we are communicating with the right device
  transfer_succeeded &= mpu6050_verify_product_id();
	
  if(mpu6050_verify_product_id() == false)
    {
	return false;
      }

  // Set the registers with the required values, see the datasheet to get a good idea of these values
  (void)mpu6050_register_write(MPU_PWR_MGMT1_REG , 0x00); 
  (void)mpu6050_register_write(MPU_SAMPLE_RATE_REG , 0x07); 
  (void)mpu6050_register_write(MPU_CFG_REG , 0x06); 						
  (void)mpu6050_register_write(MPU_INT_EN_REG, 0x00); 
  (void)mpu6050_register_write(MPU_GYRO_CFG_REG , 0x18); 
  (void)mpu6050_register_write(MPU_ACCEL_CFG_REG,0x00);   		

  return transfer_succeeded;
}



/*
  Read the Gyro values from the MPU6050's internal Registers
*/ 
bool MPU6050_ReadGyro(int16_t *pGYRO_X , int16_t *pGYRO_Y , int16_t *pGYRO_Z )
{
  uint8_t buf[6]; 
  
  bool ret = false;	
	
  if(mpu6050_register_read(MPU6050_GYRO_OUT,  buf, 6) == true)
  {
    *pGYRO_X = (buf[0] << 8) | buf[1];
    if(*pGYRO_X & 0x8000) *pGYRO_X-=65536;
		
    *pGYRO_Y= (buf[2] << 8) | buf[3];
    if(*pGYRO_Y & 0x8000) *pGYRO_Y-=65536;
	
    *pGYRO_Z = (buf[4] << 8) | buf[5];
    if(*pGYRO_Z & 0x8000) *pGYRO_Z-=65536;
		
    ret = true;
	}

  return ret;
}	




/*
  A Function to read accelerometer's values from the internal registers of MPU6050
*/ 
bool MPU6050_ReadAcc( int16_t *pACC_X , int16_t *pACC_Y , int16_t *pACC_Z )
{
  uint8_t buf[6];
  bool ret = false;		
  
  if(mpu6050_register_read(MPU6050_ACC_OUT, buf, 6) == true)
  {
    mpu6050_register_read(MPU6050_ACC_OUT, buf, 6);
    
    *pACC_X = (buf[0] << 8) | buf[1];
    if(*pACC_X & 0x8000) *pACC_X-=65536;

    *pACC_Y= (buf[2] << 8) | buf[3];
    if(*pACC_Y & 0x8000) *pACC_Y-=65536;

    *pACC_Z = (buf[4] << 8) | buf[5];
    if(*pACC_Z & 0x8000) *pACC_Z-=65536;
		
    ret = true;
    }
  
  return ret;
}





mpu6050.h2526.sdk_config.h

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



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

/* TWI instance ID. */

#define TWI_INSTANCE_ID     0

#define TWI_INSTANCE_ID_1     1

 /* Number of possible TWI addresses. */
 #define TWI_ADDRESSES      127

/* TWI instance. */
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);
static const nrf_drv_twi_t m_twi_1 = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID_1);

/**
 * @brief TWI initialization. ADU SCL =27, ADU SDA = 26 
 */
void twi_init (void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t twi_config = {
       .scl                = 27,
       .sda                = 26,
       .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_config, NULL, NULL);
    APP_ERROR_CHECK(err_code);

    nrf_drv_twi_enable(&m_twi);
}

/**
 * @brief TWI initialization. inst 1  ScL 25, SDA 24 
 */
void twi_init_1 (void)
{
    ret_code_t err_code;

    const nrf_drv_twi_config_t twi_config_1 = {
       .scl                = 25,
       .sda                = 24,
       .frequency          = NRF_DRV_TWI_FREQ_100K,
       .interrupt_priority = APP_IRQ_PRIORITY_HIGH,
       .clear_bus_init     = false
    };

    err_code = nrf_drv_twi_init(&m_twi_1, &twi_config_1, NULL, NULL); 
    APP_ERROR_CHECK(err_code);

    nrf_drv_twi_enable(&m_twi_1);
}

/**
 * @brief Function for main application entry.
 */
int main(void)
{
    ret_code_t err_code;
    uint8_t address;
    uint8_t sample_data;
    bool detected_device = false;

    APP_ERROR_CHECK(NRF_LOG_INIT(NULL));
    NRF_LOG_DEFAULT_BACKENDS_INIT();

    NRF_LOG_INFO("TWI scanner started");
    NRF_LOG_FLUSH();
    twi_init();
    address = 0x57; //change to temp sensor
        err_code = nrf_drv_twi_rx(&m_twi, address, &sample_data, sizeof(sample_data));
        if (err_code == NRF_SUCCESS)
        {
            detected_device = true;
            NRF_LOG_INFO("TWI device detected at address 0x%x.", address);
        }
        NRF_LOG_FLUSH();

    if (!detected_device)
    {
        NRF_LOG_INFO("No device was found.");
        NRF_LOG_FLUSH();
    }

    NRF_LOG_INFO("TWI second scanner started");
    NRF_LOG_FLUSH();
    twi_init_1();
    address = 0x57;
        err_code = nrf_drv_twi_rx(&m_twi_1, address, &sample_data, sizeof(sample_data));
        if (err_code == NRF_SUCCESS)
        {
            detected_device = true;
            NRF_LOG_INFO("TWI device detected at address 0x%x.", address);
        }
        NRF_LOG_FLUSH();

    if (!detected_device)
    {
        NRF_LOG_INFO("No device was found.");
        NRF_LOG_FLUSH();
    }


    while (true)
    {
       //empty loop
    }
}

/** @} */

Parents Reply Children
  • Hi Edvin! Thank you for your response. This was very helpful the example worked out of the box which was amazing. One problem I noticed however was that Martin's code doesn't verify the productID at the beginning to ensure it is communicating with the right device. I'm starting to suspect this was why I was receiving a Nack with my code because once I added that functionality in it performed exactly as my own code did. I was wondering if you had any thoughts on why this wouldn't work, the function I added can be seen here, the register is 0x75 which matches my datasheet:

    uint32_t app_mpu_read_who_am_i(uint8_t * product_id)
    {
    return nrf_drv_mpu_read_registers(MPU_REG_WHO_AM_I, product_id, 1);
    }

    Everything else in my code matches the github you linked me- I'm running the nrf5-mpu-simple example. The accelerometer itself works its just this readProductID() function giving me trouble

  • Hello,

    I don't have the sensor, so I can't check, but are you saying that the example (from Martin) doesn't check the WHO_AM_I register, or that the sensor doesn't reply to the WHO_AM_I register?

    If it is the last case, are you sure it is the correct register? Does it match the datasheet on the sensor?

    Best regards,

    Edvin

  • It doesn't reply. It is the correct register-Martin had it in a header file and it matches the data sheet, so it's confusing why this is happening. It seems to only be an issue with this register (either that or I just haven't called his "read_registers" function properly- which doesnt make sense because I copied his example where he read data in another function).

  • Hello,

    I found an MPU6050 laying around, and I tested the WHO_AM_I register, and it worked for me.

    Can you try the unmodified example from Martin's repo and use SDK14.2.0:

    Use the project nrf5-ble-mpu-simple\pca10056

    I needed to change the memory settings, according to the log output. I don't remember what it was, but I changed the RAM start address to 0x20002A18 and the size to 0x3D5E8. Then you need to replace the preprocessor definition "MPU9150" to "MPU60x0" (keep the 'x'), and then add these lines to the top of app_mpu.c:

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

    Then I modified the app_mpu_init() function:

    uint32_t app_mpu_init(void)
    {
        uint32_t err_code;
    	
    		uint8_t buffer;
    	
    	// Initate TWI or SPI driver dependent on what is defined from the project
    	err_code = nrf_drv_mpu_init();
        if(err_code != NRF_SUCCESS) return err_code;
    
        uint8_t reset_value = 7; // Resets gyro, accelerometer and temperature sensor signal paths.
        err_code = nrf_drv_mpu_write_single_register(MPU_REG_SIGNAL_PATH_RESET, reset_value);
        if(err_code != NRF_SUCCESS) return err_code;
    
        // Chose  PLL with X axis gyroscope reference as clock source
        err_code = nrf_drv_mpu_write_single_register(MPU_REG_PWR_MGMT_1, 1);
        if(err_code != NRF_SUCCESS) return err_code;
    	
    		err_code = nrf_drv_mpu_read_registers(MPU_REG_WHO_AM_I, &buffer, 1);
    		NRF_LOG_INFO("read err_code = %d", err_code);
    		NRF_LOG_INFO("buffer: 0b%d%d%d%d%d%d", (1 && (buffer & 1<<1)),
                                                   (1 && (buffer & 1<<2)),
                                                   (1 && (buffer & 1<<3)),
                                                   (1 && (buffer & 1<<4)),
                                                   (1 && (buffer & 1<<5)),
                                                   (1 && (buffer & 1<<6)));
            NRF_LOG_INFO("buffer hex value: %02x", buffer);
    
        return NRF_SUCCESS;
    }

    When I run this, the log prints the WHO_AM_I register value 0x68, which according to this document is the default value:

    https://invensense.tdk.com/wp-content/uploads/2015/02/MPU-6000-Register-Map1.pdf

    (page 45)

    Best regards,

    Edvin

    I am not sure why it doesn't reply as it should. It could be a knock off copy of the sensor for all I know. You may try to contact the manufacturer and ask why it doesn't reply to that register. 

  • Thank you for looking into this! That is a good point that I didn't think about. I never questioned my source but maybe the sensor I got was a knock off- will investigate this.

    Thanks for your help Edvin. I will leave this as verified for now and may make a new thread if my attempts to solve this still don't work.

Related