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

Approach for serial over BLE?

Hello, we have kind of an in-house serial command protocol that we have used over Rs232, sockets, wifi, etc, and we would like to implement it over BLE. I am inheriting a good Nordic BLE project from a coworker, so I'm not expert in BLE yet, so please bear with me...

Our serial protocol is simple and framed, something like [command param1, param2... paramN|crc]. There is no max message length.

My understanding of BLE (from research and looking at my inherited code) is that a BLE interface is made up of one or more attributes which are a fixed length and act more or less like mailboxes... one side writes to an attribute, other side gets a notification that it has been updated and can read/use the data, and vice versa.

I need this interface to act like a serial port. My understanding from posts such as this:

devzone.nordicsemi.com/.../

is that if I want this, I need to implement it myself. So, the approach I'm toying around with is something like the below, which breaks the message up into several updates and uses the first two bytes as a length parameter. Pseudocode below.

Questions:

  • Do I suffer from some gross misunderstanding?
  • Is this a good way to implement serial over BLE (honestly this feels like the long way around; I'm surprised I have to do all this, maybe this is square peg round hole?)
  • Will I have timing issues? Seems like if the sender sends too fast, old updates could get overwritten with new before properly processed by other end (which is okay for a measurement, but very bad for serial data). For robustness do I have to incorporate some sort of "ready to receive" message back (what a pain this would be), or does BLE buffer these updates for me somehow?

Thanks very much for any thoughts.

SENDER PSEUDOCODE:

define ATTRIBUTE_LENGTH 20

void SerialOverBLEServer_TX(char *data, int length)
{
   while (length > 0)
   {
       if (length >= ATTRIBUTE_LENGTH)
       {   
          [set first two bytes to 20]
          [set rest of attribute to the next 18 bytes of serial data]
          length-=ATTRIBUTE_LENGTH);
          *data+=ATTRIBUTE_LENGTH);
       }
       else
       {   
          [set first two bytes to remaining length]
          [set rest of attribute to remaining serial data]
          length=0;
       }
       send_value (using sd_ble_gatts_hvx?)
   }       
 }

RECEIVER PSEUDOCODE:

void SerialOverBLE_RxHandler()  // notification on receiver that attribute is updated
{
    [Read the first two bytes as a length]
    [Process the appropriate remaining data as serial data]
}
  • You have a fairly good understanding of the situation I'd say.

    You may or may not need to implement anything though. Nordic were kind enough to give us a pre-programmed uart service. It can be found in the latest SDK docs here: this is a really good read

    Embrace this well-crafted and nearly comprehensive documentation. If you want to check out another SDK version, that's available too. I recommend getting a BLE book as well depending on how much you care to learn about the spec.

  • Thank you! I will read that thoroughly. But one question, in case you're familiar:

    Does the Nordic service (a) handle the timing issue I ask about above? Or (b) doesn't have to because BLE handles it natively (eg buffers/queues attributes updates, to guarantee notification of each) (so it would also apply to my code above), or (c) neither of the above, and you just have to be careful not to send too fast (in either approach)?

    The reason I'm asking is because my predecessor already implemented two custom attributes we are using as RX/TX for a specific handshake. It could be extended fairly easily to general serial I/O as I describe above. Although what you're proposing seems to be the "right" way, the quicker path to success may be my code above.

    What would make the decision for me is if their service is guaranteed to work with fast input, in a way that my code above is not.

  • A little more info. I reviewed both the Nordic iOS app (which they provide as an example to use with the NUS -- Nordic UART service) source code and the NUS code itself. Below is an excerpt. Both ends seem to send strings with no regard for length, which suggests to me that the calling code has to be careful not to send too-long strings, so for long input I'd have to slice and dice it myself, as above, anyway. Correct? And if the NUS assumes data that fits into a message. I would presume that it's not doing anything for me in terms of timing high-speed input. Would you agree?

    The valuable thing I've learned pulling that thread is that there is a "length" parameter, maybe I could use this instead of my two-byte encoding?

    (code to follow)

  • Excerpt from ble_nus.c. Seems to rely on the caller to manage too-long or too-fast input.

    uint32_t ble_nus_send_string(ble_nus_t * p_nus, uint8_t * string, uint16_t length)
    {
     ble_gatts_hvx_params_t hvx_params;
    if (length > BLE_NUS_MAX_DATA_LEN)
    {
    return NRF_ERROR_INVALID_PARAM;
    }
    
    hvx_params.handle = p_nus->rx_handles.value_handle;
    hvx_params.p_data = string;
    hvx_params.p_len = &length;
    hvx_params.type = BLE_GATT_HVX_NOTIFICATION;
    return sd_ble_gatts_hvx(p_nus->conn_handle, &hvx_params);
    }
    
  • As syntroniks says, I would use the ble_app_uart project in the SDK. There are some short tutorials on how to set up the project and debug it.

    You are correct in that the caller of ble_nus_send_string should make sure the length of the string is within the limits. This is handled in the uart_event_handle function in main.c in the example, so sending long strings with the example-code works out of the box. Your pseudocode looks good, and you also figured out that length is a seperate parameter when sending data. hvx_params.p_len says how much data is actually transmitted.

    The throughput depends on your connection parameters, but but you should theoretically be able to send 6 packets of 20 bytes every connection interval (minimum 7.5ms). You can also read some more about the throughput of the project here

    Also, packets are sent and received in-place, so the timing-issues you mention are handled in the BLE protocol.

Related