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
  • Hello Joseph,

    The timing conditions needed to trigger this errata usually makes it pretty hard to reproduce the DMA corruption (official link: [109] DMA: DMA access transfers might be corrupted). Also, the nRF52840 is not affected by this either. The errata is for the nRF52832. 

    Are you able to share relevant code snippets here or in a private ticket so I can try to review them? 

    Thanks,

    Vidar

  • 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

Reply
  • 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

Children
Related