TWIM Errata 109

Hello Nordic,

I've been attempting to convert our system's TWIM implementation from the blocking to the non-blocking mode. I have been able to communicate fine with the external device (an LIS2MDL Magnetometer) while in the blocking mode, however, when I attempt to convert my implementation to the non-blocking mode, it appears that the MCU is not correctly sending the configurations that I am interested in.

When I use the blocking mode, I use a NRFX_TWIM_XFER_DESC_TX() descriptor with the nrfx_twim_xfer() to transfer a configuration, and then use the NRFX_TWIM_XFER_DESC_RX() descirptor with the nrfx_twim_xfer() to check if the configuration was applied, and it has. When in non-blocking mode, I use the NRFX_TWIM_XFER_DESC_TXTX descriptor and wait until the handler provides a NRFX_TWIM_EVT_DONE event where I check the p_event information. The information stored there appears to be the correct information (aka the value of the configuration I wanted to set on the LIS2MDL). However, when I then check this using the NRFX_TWIM_XFER_DESC_TXRX descriptor, the value of the register that I just tried to change remains unchanged (in this case, the LIS2MDL is idle and won't send me sensor data).

From doing a little research, I found this document on Anomaly/Errata 109 which appears to be related to this issue. I've tried to enable the TWIM_NRF52_ANOMALY_109_WORKAROUND_ENABLED macro in both the apply_old_config.h and the sdk_config.h, but this did not fix the issue.

Any help would be greatly appreciated.

Joseph

Parents Reply Children
  • Vidar,

    Thanks for your response, and thanks for letting me know that this errata isn't an issue for me.

    Following are the relevant code snippets of this process:

    // 0 = Non-blocking mode enabled
    // 1 = Blocking mode enabled
    #define I2C_BLOCKING_MODE	1
    
    static const nrfx_twim_t m_twim = NRFX_TWIM_INSTANCE(0);
    
    static volatile uint8_t tx_buff[I2C_BUFF_SIZE] = {0};
    static volatile uint8_t rx_buff[I2C_BUFF_SIZE] = {0};
    static bool m_xfer_done = true;
    
    ret_code_t	twim_init(void)
    {
        ret_code_t status_code = NRF_SUCCESS;
        
    	// Setup the twi configuration structure for initialization
    	const nrfx_twim_config_t twi_config = {
    		.scl                = I2C_SCL,  // Macro to the NRF Pin
    		.sda                = I2C_SDA,  // Macro to the NRF Pin
    		.frequency          = NRF_TWIM_FREQ_400K,
    		.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
    		.hold_bus_uninit     = false
    		};
    
    	// Initialize the nrfx_twim module
    #if(1 == I2C_BLOCKING_MODE)
    	status_code = nrfx_twim_init(&m_twim, &twi_config, NULL, NULL);
    #elif(0 == I2C_BLOCKING_MODE)
    	status_code = nrfx_twim_init(&m_twim, &twi_config, I2C_Evt_Handler, NULL);
    #endif	
    	APP_ERROR_CHECK(status_code);
    
    	// Enable the TWI module
    	nrfx_twim_enable(&m_twim);
    
    	// Initialization wait to allow module to settle
    	vTaskDelay(pdMS_TO_TICKS(5));
    	
    	return status_code;
    }
    
    
    int32_t twi_write(	void * p_dev_addr, uint8_t reg_addr, 
    							const uint8_t *reg_data, uint16_t cnt)
    { 
    	ret_code_t status_code = NRF_SUCCESS;
    
    	// Get the slave address
    	static uint8_t loc_slave_addr 	= 0;
    	static uint8_t loc_reg_addr 	= 0;
    	static uint16_t loc_cnt 		= 0;
    
    	// Don't continue unless the previous transfer is complete
    	while(false == m_xfer_done)
    	{
    		vTaskDelay(pdMS_TO_TICKS(1));
    	}	/*	end while loop	*/
    
    	// Update the local xfer data once the handler has finished
    	loc_slave_addr 	= *(uint8_t*)p_dev_addr;
    	loc_reg_addr 	= reg_addr;
    	loc_cnt 		= cnt;
    
    #if(1 == I2C_BLOCKING_MODE)
    	// Store data in the tx buffer
    	tx_buff[0] = reg_addr;
    	memcpy(&tx_buff[1], reg_data, cnt);
    
    	// Define a transmit transfer descriptor to be used in the next twi transfer
    	nrfx_twim_xfer_desc_t xfer_desc =
    		NRFX_TWIM_XFER_DESC_TX(loc_slave_addr, tx_buff, loc_cnt+1);
    
    	// Transmit the register address and the data to be stored there. 
    	status_code |= nrfx_twim_xfer(&m_twim, &xfer_desc, 0);
    	//APP_ERROR_CHECK(status_code);
    
    	// Reset the transmit buffer once it is clear the data was sent
    	memset(tx_buff, 0, I2C_BUFF_SIZE);
    
    #elif(0 == I2C_BLOCKING_MODE)
    
    	memcpy(tx_buff, reg_data, loc_cnt);
    
    	// Let the module know their is a transfer in progress
    	m_xfer_done = false;
    
    	// Define a transmit transfer descriptor to be used in the next twi transfer
    	nrfx_twim_xfer_desc_t xfer_desc =
    		NRFX_TWIM_XFER_DESC_TXTX(loc_slave_addr, &loc_reg_addr, 1, tx_buff, loc_cnt);
    
    	// Transmit the register address and the data to be stored there. 
    	do
    	{
    		status_code = nrfx_twim_xfer(&m_twim, &xfer_desc, 0);
    		if(NRFX_ERROR_BUSY == status_code)
    		{
    			LOG_I2C("Sending I2C message...\r\n");
    		}	/*	end if block	*/
    	}while(NRFX_ERROR_BUSY == status_code);
    	APP_ERROR_CHECK(status_code);
    
    #endif
    
    	return status_code;
    }
    
    
    int32_t twi_read(	void * p_dev_addr, uint8_t reg_addr, 
    							uint8_t *reg_data, uint16_t cnt)
    { 
    	ret_code_t status_code = NRF_SUCCESS;
    
    	// Get the slave address
    	static uint8_t loc_slave_addr 	= 0;
    	static uint8_t loc_reg_addr 	= 0;
    	static uint16_t loc_cnt 		= 0;
    
    	// Don't continue unless the previous transfer is complete
    	while(false == m_xfer_done)
    	{
    		vTaskDelay(pdMS_TO_TICKS(1));
    	}	/*	end while loop	*/
    
    	// Update the local xfer data once the handler has finished
    	loc_slave_addr 	= *(uint8_t*)p_dev_addr;
    	loc_reg_addr 	= reg_addr;
    	loc_cnt 		= cnt;
    
    #if(1 == I2C_BLOCKING_MODE)
    	/*	Define a transmit transfer descriptor to be used in the next twi 
    		transfer. This only sends the register address	*/
    	nrfx_twim_xfer_desc_t xfer_desc1 =
    		NRFX_TWIM_XFER_DESC_TX(loc_slave_addr, &reg_addr, 1);
    	
    	/*	Send the register address with NO STOP! This is part of the I2C protocol
    		on the LSM6DSOX and LIS2MDL chips.	*/
    	status_code |= nrfx_twim_xfer(&m_twim, &xfer_desc1, NRFX_TWIM_FLAG_TX_NO_STOP);
    	//APP_ERROR_CHECK(status_code);
    
    	/*	Define a receive transfer descriptor to be used in the next twi 
    		transfer.	*/
    	nrfx_twim_xfer_desc_t xfer_desc2 = 
    		NRFX_TWIM_XFER_DESC_RX(loc_slave_addr, rx_buff, cnt);
    
    	/* This sends the slave address + read bit, and then receives
    		the next cnt bytes.	*/
    	status_code |= nrfx_twim_xfer(&m_twim, &xfer_desc2, 0);
    	//APP_ERROR_CHECK(status_code);
    
    #elif(0 == I2C_BLOCKING_MODE)
    
    	// Let the module know their is a transfer in progress
    	m_xfer_done = false;
    
    	/*	Define a receive transfer descriptor to be used in the next twi 
    		transfer.	*/
    	nrfx_twim_xfer_desc_t xfer_desc = 
    		NRFX_TWIM_XFER_DESC_TXRX(loc_slave_addr, &loc_reg_addr, 1, rx_buff, loc_cnt);
    
    	/* This transfer will send the register byte and then receive the data from
    		that register.	*/
    	do
    	{
    		// The while allows for the data to not be passed over
    		status_code = nrfx_twim_xfer(&m_twim, &xfer_desc, 0);
    		if(NRFX_ERROR_BUSY == status_code)
    		{
    			LOG_I2C("Receiving I2C message...\r\n");
    		}	/*	end if block	*/
    	}while(NRFX_ERROR_BUSY == status_code);
    	APP_ERROR_CHECK(status_code);
    
    #endif
    
    
    	/*	Copy the data received in the rx_buff from the last transfer to the
    		buffer passed into this function	*/
    	memcpy(reg_data, rx_buff, cnt);
    
    	return status_code;
    }
    
    static void I2C_Evt_Handler(nrfx_twim_evt_t const * p_event, void * p_context)
    {
    	switch (p_event->type)
    	{
    		case(NRFX_TWIM_EVT_DONE):
    		{
    			if(NRFX_TWIM_XFER_TXRX == p_event->xfer_desc.type)
    			{
    				// Do nothing right now
    			}	/*	end if block	*/
    			else if(NRFX_TWIM_XFER_TXTX == p_event->xfer_desc.type)
    			{
    				// Reset the transmit buffer once it is clear the data was sent
    				memset(tx_buff, 0, I2C_BUFF_SIZE);
    			}	/*	end else-if block	*/
    
    			m_xfer_done = true;
    			break;
    		}	/*	end case block	*/
    		case(NRFX_TWIM_EVT_ADDRESS_NACK):
    		case(NRFX_TWIM_EVT_DATA_NACK):
    		case(NRFX_TWIM_EVT_OVERRUN):
    		case(NRFX_TWIM_EVT_BUS_ERROR):
    		default:
    		{
    			LOG_I2C("I2C Error Event: \r\naddress = %d\r\nprimary[0] = %d\r\n secondary[0] = %d\r\n", 
    			        p_event->xfer_desc.address, p_event->xfer_desc.p_primary_buf[0], 
    			        p_event->xfer_desc.p_secondary_buf[0]);
    			break;
    		}	/*	end case block	*/
    	}	/*	end switch-case block	*/
    }

    I've taken out the extra stuff and tried to leave the important parts. The macro I2C_BLOCKING_MODE is what I am using to test the blocking/non-blocking modes. When I have it set to 1 (blocking mode enabled), then the configurations send correctly and a subsequent read from the same register shows that the TWI transfer was successful. When set to 0 (non-blocking mode enabled) however, the TWI transfer does not actually change the LIS2MDL configuration.

    Thanks for your help!

    Joseph

  • Joseph,

    Sorry if I'm overlooking something obvious, but I'm not sure I'm understanding why the transfer descriptor needs to be different for blocking and non-blocking transfers in twi_write(). Have you tried to see if it works if you use the same descriptor created with the NRFX_TWIM_XFER_DESC_TX() macro for non-blocking? A logic analyzer can be very helpful for troubleshooting, if you have access to one. You could then compare the blocking transfers with the non-blocking ones to see if there are any differences.

    Vidar

  • Hello again Vidar,

    Thanks for the advice! I attempted this fix for the read and write functions, then played around with a couple of combinations of the fixes. It appears that the NRFX_TWIM_XFER_TXRX descriptor in the read is working fine as long as the device has started (aka I modify the write to be just one NRFX_TWIM_XFER_TX descriptor like that in the blocking mode). The NRFX_TWIM_XFER_TXTX appears to be what is causing the issue since when I use it, the configurations on the magnetometer are not being updated. When I use the simpler NRFX_TWIM_XFER_TX descriptor and just add the register address as the first byte in the tx_buff, then I can get it to work in non-blocking mode.

    This workaround can work, though I'd still like to know if I was somehow using the NRFX_TWIM_XFER_TXTX descriptor wrong. Is there anything you can see that I may have missed with this?

    Joseph

  • Hello Joseph,

    Thanks for the update. I think the problem is that the "TXTX" descriptor performs two transfers while the LIS2MDL slave expects the register to be set in a single transfer. 

    Vidar

  • Vidar,

    Thanks again for all of your help! Looking at the LIS2MDL datasheet, I can see that it indeed is not expecting a repeated start, and so the repeated start on the TXTX descriptor would interfere with the expected behavior for the LIS2MDL. The implementation described a few messages ago will work, so thanks for helping get to the solution and the explanation!

    Joseph

Related