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]
}
Parents Reply Children
  • 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);
    }
    
Related