This discussion has been locked.
You can no longer post new replies to this discussion. If you have a question you can start a new discussion

SPI & Bluetooth best practices

Hi All,

I'm a little rusty on my embedded C and was wondering if I could get a few broad pointers on the best way to organize this research system I'm putting together.

The system is basic: I have an NRF52840 that talks to a custom IC via SPI and then sends whatever it reads out to an NRF52 dongle via bluetooth. If this was one way then it would be especially straight forward but unfortunately, the NRF52840 can also receive commands from the dongle (over bluetooth) that may or may not trigger specific SPI transactions. It seems to me that the best way to organize the NRF52840 (that's talking to the custom IC) as the peripheral and the dongle as the central device. This way the peripheral can use an interrupt to query the IC and then notify the central device with a new packet.

I'm having a little more trouble with the other main function though (dongle sending commands to the peripheral) though since it's really important that this bluetooth connection runs as fast as possible without dropping any packets.

Is it fair to have a main while loop on all my devices that spins (just checking if there's been an incoming command) when they're in their main interrupt-driven operating mode. Then if there has been a command, they break out of their interrupt-driven mode and work on whatever the command is? I worry that this constant spinning may burn unnecessary power and also may slow down the main BLE operation (which needs to run as fast as possible!). Are there other alternatives that may allow the bluetooth and spi to operate as fast as possible in parallel?

Thanks so much for any advice/help. If anything was unclear please let me know and I can clarify. I tried to distill everything to not be too confusing but I may have distilled too much.

- Ryan

Parents
  • Hi Karl, 

    This was incredibly helpful! I'll play with the SPIM peripheral/drivers and take a look at the SAADC example! 

    I have one more question regarding best practices - this time with my 'main mode of operation'. Eventually, I will have a timer triggering a SPI transaction every 500us. The resulting data from this SPI transaction will then be beamed via bluetooth.

    To make things simpler for now, I am leaving the SPI out and just using my 500us timer to increment a counter. I then have an if statement in my main while loop that sends the updated counter to another device over bluetooth. Is this the best way to organize fast transactions through bluetooth? I must be doing something wrong because I'm losing a lot of data (I suspect the bluetooth transactions are taking too long - or the cpu is getting halted for too long so the counter increments more than once between bluetooth transfers. I tried putting the ble_nus_data_send() command in my timer's interrupt handler but that just caused nrf52840 to hang and become unresponsive.

    I'm currently building off of the ble_app_uart examples.

    Thanks again for all of your help!

    Ryan

  • Hello,

    ryerye120 said:
    This was incredibly helpful!
    ryerye120 said:
    Thanks again for all of your help!

    No problem at all, Ryan - I am happy to hear that you found my comment helpful!

    ryerye120 said:
    To make things simpler for now, I am leaving the SPI out and just using my 500us timer to increment a counter. I then have an if statement in my main while loop that sends the updated counter to another device over bluetooth. Is this the best way to organize fast transactions through bluetooth?

    In general, the best way to do this is to handle it as part of an event handler, rather than having it happen in the main loop. This way you can easily control its priority, and compartmentalize the code better (not have everything running in the main loop).
    Additionally, for power saving, you should only have the SYSTEM_ON sleep (idle_state_handler function in the BLE Peripheral examples) happen in the main loop, since this will make your system go to SYSTEM_ON sleep whenever there are no other work to be done. 
    I would therefore recommend that you either have the transfers happens as the handler to a TIMER CC event, or similar.

    ryerye120 said:
    I must be doing something wrong because I'm losing a lot of data (I suspect the bluetooth transactions are taking too long - or the cpu is getting halted for too long so the counter increments more than once between bluetooth transfers. I tried putting the ble_nus_data_send() command in my timer's interrupt handler but that just caused nrf52840 to hang and become unresponsive.

    Could you possibly show some code of how you are doing this? Are you getting any error codes returned from your call to queue the data for sending? You could see the likely returned error codes in the sd_ble_gatts_hvx API reference documentation.
    Since data is never lost in the link it is likely that you are dropping the data in the case that it fails to queue - for example if the HVN queue is already full. This could happen if you are queueing notifications faster than you are sending them, or if your hvn queue is not big enough to accommodate all the notifications generated between each connection event.
    What connection parameters are you using for your application, and how big is your BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT?

    Best regards,
    Karl

Reply
  • Hello,

    ryerye120 said:
    This was incredibly helpful!
    ryerye120 said:
    Thanks again for all of your help!

    No problem at all, Ryan - I am happy to hear that you found my comment helpful!

    ryerye120 said:
    To make things simpler for now, I am leaving the SPI out and just using my 500us timer to increment a counter. I then have an if statement in my main while loop that sends the updated counter to another device over bluetooth. Is this the best way to organize fast transactions through bluetooth?

    In general, the best way to do this is to handle it as part of an event handler, rather than having it happen in the main loop. This way you can easily control its priority, and compartmentalize the code better (not have everything running in the main loop).
    Additionally, for power saving, you should only have the SYSTEM_ON sleep (idle_state_handler function in the BLE Peripheral examples) happen in the main loop, since this will make your system go to SYSTEM_ON sleep whenever there are no other work to be done. 
    I would therefore recommend that you either have the transfers happens as the handler to a TIMER CC event, or similar.

    ryerye120 said:
    I must be doing something wrong because I'm losing a lot of data (I suspect the bluetooth transactions are taking too long - or the cpu is getting halted for too long so the counter increments more than once between bluetooth transfers. I tried putting the ble_nus_data_send() command in my timer's interrupt handler but that just caused nrf52840 to hang and become unresponsive.

    Could you possibly show some code of how you are doing this? Are you getting any error codes returned from your call to queue the data for sending? You could see the likely returned error codes in the sd_ble_gatts_hvx API reference documentation.
    Since data is never lost in the link it is likely that you are dropping the data in the case that it fails to queue - for example if the HVN queue is already full. This could happen if you are queueing notifications faster than you are sending them, or if your hvn queue is not big enough to accommodate all the notifications generated between each connection event.
    What connection parameters are you using for your application, and how big is your BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT?

    Best regards,
    Karl

Children
  • Hi Karl,

    What are you currently using as your central?

    I'm using an nrf52 dongle that's acting like a BLE<->USB pipe. I built this based off of the ble_uart_c example and took stuff from the usbd_ble_uart peripheral when needed. 

    The best tool for debugging connectivity and throughput issues is the nRF Sniffer tool which lets you monitor on-air BLE traffic. Are you familiar with this tool already?

    I've heard of it but haven't used it - I'll order another dev kit and set a sniffer up! I completely forgot this was a thing.

    Great - this is the first step in resolving the issue. Could you confirm for me that you've added the DEBUG define in the common build configuration, as mentioned earlier? I notice that your logger is not outputting a complete error message.

    Oh you were spot on - I added it to the release build config. This time I added it to the projects common build config and I'm getting a proper error out now.

    <error> app: ERROR 19 [NRF_ERROR_RESOURCES] at C:\Users\...\main.c:235
    PC at: 0x0002B1F5
    <error> app: End of error report

    Now after some googling I found this thread where someone seems to have the same problem and the issue is - you guessed it "Too many notifications queued."  For that user it seems to have boiled down to their notification queue being too small but while trying to learn how they've changed it I've come across a few new questions:

    • The linked thread implies that this queue size is part of the BLE stack. If that's the case the BLE 'stack' includes not just the BLE drivers but also the memory interface between the CPU & the drivers. Is that right? 
    • In ble_stack_init() (in main.c), there is nrf_sdh_ble_default_cfg_set(APP_BLE_CONN_CFG_TAG, &ram_start); The linked thread implies this sets a default queue size to  BLE_GATTS_HVN_TX_QUEUE_SIZE_DEFAULT (which is 1). Firstly, is there a max queue size? Ideally i'd just set it to the max using sd_ble_cfg_set() - BUT I can't find a single reference to sd_ble_cfg_set() in the existing solution. Do you have any pointers to where I may be able to find an example so I can safely call sd_ble_cfg_set() with a new queue size?
    • Now, assuming I get that set, in order to maximize my throughput, I can set my connection interval to as small as possible (7.5ms, right?), maximize my MTU (to 251, right?), set my PHY to 2M, and then set my total number of links to just 1 (so that I don't have to share bandwidth). Once I get a sniffer up and running, I'll hopefully be able to confirm that all of that is the case after trying to test it out.

    As always, thank you for your patience and help! I realize a lot of these are basic questions - I can't describe how appreciative I am of your time/effort.

    Kindest regards,

    Ryan

Related