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

app_twi_schedule with Soft Device returning error

I'm hoping this is just something silly that I've overlooked, but I'm running into an issue: Whenever I try to prompt the scheduling of a TWI transaction an error occurs

What I'm trying to do is initialise the transaction when a certain value is sent to the characteristic. What I'm seeing in the debugger though is an NRF_ERROR_BUSY being generated when doing the app_twi_schedule in the function below (called when the write is made), located within my service file:

void rh_read(void)
{
	//printf("rh read...  ");
	
	static app_twi_transfer_t transfers[] = 
	{
		SHT21_RH_READ(&m_sht21_buffer[0])
	};
	static app_twi_transaction_t const transaction =
	{
	.callback            = rh_read_callback,
	.p_user_data         = NULL,
	.p_transfers         = transfers,
	.number_of_transfers = sizeof(transfers) / sizeof(transfers[0])
	};
	
	APP_ERROR_CHECK(app_twi_schedule(&m_app_twi, &transaction));

}

The documentation says that this error is thrown when the queue is full, but no other twi transactions would have been scheduled at this point. When the slave device is set up it's set up with app_twi_perform, and I see them complete on with my scope.

Here's my TWI setup function:

static void twi_init(void)
{
	ret_code_t err_code;
	
	nrf_drv_twi_config_t const config = {
		.scl						= I2C_SCL,
		.sda						= I2C_SDA,
		.frequency					= NRF_TWI_FREQ_100K,
		.interrupt_priority			= APP_IRQ_PRIORITY_HIGH
	};
	
	APP_TWI_INIT(&m_app_twi, &config, MAX_PENDING_TRANSACTIONS, err_code);
	APP_ERROR_CHECK(err_code);
}

Any idea what's happening here?

Parents
  • Thanks. I tried your example now. I don't have access to a SHT21 sensor so I just made some minor changes to make the example work with an MPU I have instead. Otherwise the flow in the example is pretty much unaltered. Anyway, I saw the same behaviour as you. So I consulted with the creator of the library and he pointed out a mistake in your code:

    You are declaring

    static app_twi_t m_app_twi = APP_TWI_INSTANCE(0);
    

    in hardware.h and then you are including this file in both hardware.c and our_service.c. This will in effect cause two instances of m_app_twi to be created in static memory. One instance is used by hardware.c and the other by our_serivce.c. Then you initialize m_app_twi with APP_TWI_INIT() in hardware.c and hence, hardware.c is working perfectly since it is using a properly declared and initialized m_app_twi, but our_service.c is using a completely different, uninitialized m_app_twi. To solve it you have two options:

    1. Declare m_app_twi in e.g. main.c and pass a pointer to this instance to every function that would need it.

    2. An easier way would be to us the extern keyword and declare:

      app_twi_t m_app_twi = APP_TWI_INSTANCE(0);

    in hardware.c and:

    extern app_twi_t m_app_twi;
    

    in hardware.h. This way you won't need to rewrite all your functions relying on m_app_twi. I tried it and it seemed to work.

Reply
  • Thanks. I tried your example now. I don't have access to a SHT21 sensor so I just made some minor changes to make the example work with an MPU I have instead. Otherwise the flow in the example is pretty much unaltered. Anyway, I saw the same behaviour as you. So I consulted with the creator of the library and he pointed out a mistake in your code:

    You are declaring

    static app_twi_t m_app_twi = APP_TWI_INSTANCE(0);
    

    in hardware.h and then you are including this file in both hardware.c and our_service.c. This will in effect cause two instances of m_app_twi to be created in static memory. One instance is used by hardware.c and the other by our_serivce.c. Then you initialize m_app_twi with APP_TWI_INIT() in hardware.c and hence, hardware.c is working perfectly since it is using a properly declared and initialized m_app_twi, but our_service.c is using a completely different, uninitialized m_app_twi. To solve it you have two options:

    1. Declare m_app_twi in e.g. main.c and pass a pointer to this instance to every function that would need it.

    2. An easier way would be to us the extern keyword and declare:

      app_twi_t m_app_twi = APP_TWI_INSTANCE(0);

    in hardware.c and:

    extern app_twi_t m_app_twi;
    

    in hardware.h. This way you won't need to rewrite all your functions relying on m_app_twi. I tried it and it seemed to work.

Children
  • First solution: This links to a problem I encountered before... Once the service is set up via our_service_init(), main.c has no other interface with the service; Everything else occurs within that service. So I could get the m_app_twi into that function, but not the the timeout handler or rh_read(). How would I pass this in? Could I pass it into the service init function then assign a local (to the file) m_app_twi it's same address?

    Second solution: I've been led to believe that using extern is generally not advised and can also be a little dangerous?

    Additionally, can you see any better way to structure this project? I have so far just been trying to get something working but I feel like it's not an ideal software structure. In the future I will also have other services that require access to the TWI instance too..

  • I had a thought!

    What if I instantiate m_app_twi in hardware.c rather than hardware.h, then created a wrapper function in hardware.c called transact_twi() or something, that used the locally available m_app_twi and took the transaction type as an argument to call app_twi_schedule. Then I could pass transactions from any service into the hardware.c file and let it take care of the scheduling?

  • That solution on occasion will generate a hard fault, not sure why....

  • Could I pass it into the service init function then assign a local (to the file) m_app_twi it's same address? Yes. This is in fact what is done in many of the drivers and libraries in the SDKs where you declare an instance in main.c and pass it to some init function and/or other functions that needs it. If you in the future will have several services, defined in several files, depending on the same TWI instance I think this will be a good solution.

    Second solution: I am not aware of anything wrong in using the extern keyword. As long as you know what you are doing.

    Regarding your structure I think maybe your own solution might be a good idea. Make your own, clean transaction library with a wrapper function on top of the app_twi_scheduler. Then you can initialize everything there and include it in all your service files.

Related