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

TWI driver remains busy right after the nrf_drv_twi_tx() even though TX transfer is completed

I am using a nrf_drv_twi_tx() and nrf_drv_twi_rx() for a simple I2C transaction but I'm noticing a strange behavior.

  1. If I don't use APP_ERROR_CHECK(err_code); right after read(), I see the interrupt is firing as expected and I also see the read bytes sent and actual sensor data is received in response in the logic analyzer. However, when I use APP_ERROR_CHECK(err_code);, I see the execution stops with an error code 0x11 which indicates the driver is busy.
  2. Adding a random delay of 2s seemed to have solved the issue regarding the busy driver error. Even though the interrupt is finished executing, what's still causing the driver to be busy right after the write()?
  3. if I include ulTaskNotifyTake() after read(), for some weird reason, the read() function doesn't seem to fully execute i.e don't see the read bytes sent in the logic analyzer. As soon as I put the breakpoint on a read(), I see things working as expected i.e read bytes are sent and interrupt is fired. 

void twi_handler(nrf_drv_twi_evt_t const * p_event, void * p_context)	   
{
    Sensor *obj = static_cast<Sensor*>(p_context);
    
    switch (p_event->type)
    {
        case NRF_DRV_TWI_EVT_DONE:
            if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_RX)
            {
	            vTaskNotifyGiveFromISR(obj->taskHandle, pdFALSE);
            }
    	    else if (p_event->xfer_desc.type == NRF_DRV_TWI_XFER_TX)
    	    {
                // ...
    	    }
            m_xfer_done = true; 
            break;
        default:
            break;
    }
}

void Sensor::read()
{
    ret_code_t err_code = nrf_drv_twi_rx(&m_twi, ADDR, _buffer, 2);
    APP_ERROR_CHECK(err_code);
}

void Sensor::write(uint8_t reg, uint8_t *buffer, uint8_t size)
{
    ret_code_t err_code;

    err_code = nrf_drv_twi_tx(&m_twi, ADDR, &reg, size, false);
    APP_ERROR_CHECK(err_code);
    while (m_xfer_done == false);
}


void Sensor::readTask()
{
    write();  

    while(true)
    {   
        read();

        uint32_t taskNotify = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); // block till data is read
        
        processData();
    }
}

Any help is greatly appreciated


Edit:

One other thing I noticed was if I include the m_xfer_done flag inside read(), I see everything working fine as expected but then I wonder what purpose is ulTaskNotifyTake serving since the m_xfer_done flag is mainly used for synchronizing the ISR and the task? Without the m_xfer_done flag used, looks to me the readTask remains blocked as there are no read bytes sent resulting in NO ISR being fired

void Sensor::read()
{
    ret_code_t err_code = nrf_drv_twi_rx(&m_twi, ADDR, _buffer, 2);
    APP_ERROR_CHECK(err_code);
    while(!m_xfer_done);  // flag that's set to true inside ISR
}

Parents
  • Hi

    Which RTOS are you using?

    Is there any difference if you just use a flag variable instead? 
    Just set the variable before reading the sensor, clear it in the interrupt, and wait for it to clear in the readTask loop. 

    Remember to declare the flag variable volatile:
    static volatile twi_busy_flag=false;

    Best regards
    Torbjørn

  • It's a FreeRTOS.

    using a flag sort of kills the purpose of an RTOS (I think) since from what I've seen, the task should be mostly blocking if it's not being used which using a flag doesn't help with.

  • My initial hunch was I was reading too fast but I have a delay of 3s between each read (for now). I wouldn't want to be reading without any delay anyways...just seems too abrupt for no particular reason.

    I don't see any BUSY error; it's just for some reason excluding the while(!m_xfer_done) from the read() call causes issues with subsequent reads, and I'm unable to understand why would that be. I don't see any error. If I don't exclude it, it works fine but then I don't see the point of using it with task notifications.

    Plus it's hard to debug because if I put the breakpoint everywhere, it would probably work.

    Timer is a good option too but then i'd need to decide between using a thread and a timer in that case. 

    nrf_drv_twi_rx()  enables the interrupt which fires the ISR and the handler, isn't it?

  • Hi

    morpho said:
    nrf_drv_twi_rx()  enables the interrupt which fires the ISR and the handler, isn't it?

    That, and quite a bit more. 

    Can you confirm which nRF device you are using, and whether or not you are using the nrfx_twim or the nrfx_twi driver under the hood?

    Are you able to show me how you initialize the driver, and what configuration you are using?

    Best regards
    Torbjørn 

  • it's nRF52840, and I'm using nrfx_twi driver. In the other thread for the BLE advertisement issue, I attached the project. There, you can check the mcp9808.cpp file. The following is how I configure it.

        i2cConfig = {
           .scl                = TWI_SCLK,
           .sda                = TWI_SDA,
           .frequency          = static_cast (TWI_FREQ),	  
           .interrupt_priority = TWI_IRQ_PRIORITY,
           .clear_bus_init     = false
        };
        
        ret_code_t err_code = nrf_drv_twi_init(&m_twi, &i2cConfig, twi_handler, this);


  • Hi

    Do you have the datasheet for your sensor?

    It seems a bit odd the way you split the write and read operations into two separate events, several seconds apart. 
    Most I2C sensors use a tx_rx scheme, where you write the internal register address and then immediately issue a read to get the data. 

    Best regards
    Torbjørn

  • I do have the datasheet, yes. 

    it mentions the device doesn't support sequential register read/write but seems to support repeated read which is what I'm doing (I believe so..) (also how's sequential read different from repeated?)

Reply Children
  • Hi 

    Could you include the datasheet in the case?

    morpho said:
    also how's sequential read different from repeated?

    By sequential read I assume they mean the capability to read multiple registers in a single read operation, by having the internal register address increment automatically between each read byte. 

    Repeated just means you can read the same register over and over without writing the address for each read, which I agree looks more or less like what you are doing. 

    Have you tried to scope the TWI lines to see what is happening on the bus?

    Best regards
    Torbjørn

  • Here's the datasheet
    Your points do make sense, and in my case I'm doing more of a repeated read and also since sequential isn't supported.

    I have looked at the SDA and SCL lines in the logic analyzer and I don't see any read byte being sent at some point.

  • Hi 

    Thanks for sharing the datasheet. 

    Looking at it I notice that the pseudo code for reading the temperature registers shows a more traditional tx_rx sequence, where you write the register address (0x05), and then immediately issue a repeated start to read the result. 

    Could you try this and see if it works better than doing reads only?

    If you use the nrf_drv_twi_xfer(..) function you are able to issue a TX->RX operation in a single call, which means you don't have to wait for the TX operation to complete before issuing an RX operation. 

    Best regards
    Torbjørn

  • Are you implying to invoke write before every read? In the datasheet, they only show examples for sending and receiving once?

    My main concern is the same code for repeatedly reading in a loop NOT working only when task notification is used, which does imply repeated reads are supported. Can you see that?

Related