NeuralSPOT BLE Library Subsystem
This library simplifies the instantiation of BLE (Bluetooth Low Energy) services on bluetooth-enable Ambiq EVBs in order to enable BLE-based demonstration applications. Based on the Cordio BLE software stack, it adds a API and wrapping software to make creating BLE services easier. Features include:
- Easy definition of service behaviors and discoverability
- Easy definition of data elements ("characteristics" in BLE terms) that can be read, written, or subsribed to by BLE clients.
- Easy handling of read, write, and subscription event handling via application-definable callbacks
Current Limitations
- Only 1 service.
- Only 1 connection at a time.
- No "Out of band" connection protocol (in other words, the service will pair with the first client that requests it, rather than asking for a pin or other out-of-band confirmation).
NOTE: this library is in experimental phase - it is missing many features such as error handling and disconnections.
Using NS BLE to Create a BLE Service
Creating a service involves:
- Creating and configuring basic Service settings
- Defining BLE "characteristics" (data sources/sinks), including read/write handlers
- Adding the characteristics to the service
- Starting the service.
A bit of BLE Terminology
A BLE Server is a peripheral that serves data (think heartrate monitors, thermometers, etc). Conversely, a BLE Client is the device that is interacting with that peripheral (think PC, smartphones, etc).
A Service runs on the BLE Server, and is a collection of data sources and sinks (aka Characteristics). It has a unique UUID.
A Characteristic is a data source (e.g. thermometer reading) or sink (e.g. thermometer units setting) associated with a service.
- There can be many characteristics associated with a Service, but each Characteristic can only be associated with one Service
- A data source can be read from the BLE Client, and a data sink can be written to. A Characteristic may be read-only, write-only, or read-write.
- A third type of Characteristics can be subscribed to by the Client - we call these Notify characteristics for simplicity. Once subscribed to, the Server will send value updates to the Client periodically.
Creating a Service
The code snippet below creates a minimal Service, with a read-only, a read-write, and a notify Characteristic ('webble' refers to WebBLE, but this example can be connected to by any Client). For a full example, see here.
#include "ns_ble.h"
#include "FreeRTOS.h"
#include "task.h"
float temperature = 0.0; // Read Only
float accel[3] = {0.0, 0.0, 0.0}; // Notify
uint8_t rgb[] = {0, 0, 0}; // Read/Write
float x = 0.0; // mock-up sensor 'time'
/**
* @brief Dummy up some interesting sensor values
* (called every time the Notify period expires)
*/
static void webbleUpdateSensorValues(void) {
// ns_lp_printf("webbleUpdateSensorValues\n");
x += 0.1;
if (x > 2 * PI) {
x = 0.0;
}
accel[0] = arm_sin_f32(x);
accel[1] = arm_cos_f32(x);
accel[2] = arm_sin_f32(1 - x);
temperature = arm_sin_f32(x / 2);
}
// WSF buffer pools are a bit of black magic. More
// development needed.
#define WEBBLE_WSF_BUFFER_POOLS 4
#define WEBBLE_WSF_BUFFER_SIZE (WEBBLE_WSF_BUFFER_POOLS * 16 + 16 * 8 + 32 * 4 + 64 * 6 + 280 * 14) / sizeof(uint32_t)
static uint32_t webbleWSFBufferPool[WEBBLE_WSF_BUFFER_SIZE];
static wsfBufPoolDesc_t webbleBufferDescriptors[WEBBLE_WSF_BUFFER_POOLS] = {
{16, 8}, // 16 bytes, 8 buffers
{32, 4},
{64, 6},
{512, 14}};
// WSF/Cordio is built on top of FreeRTOS. We need to create a task
TaskHandle_t radio_task_handle;
TaskHandle_t my_xSetupTask;
#define webbleUuid(uuid) "19b10000" uuid "537e4f6cd104768a1214"
// BLE Structs, populated by webble_service_init()
ns_ble_service_t webbleService; // Webble Service
// Webble Service Characteristics
ns_ble_characteristic_t webbleTemperature;
ns_ble_characteristic_t webbleAccel;
ns_ble_characteristic_t webbleRgb;
/**
* @brief Handle a read request from the client. Invoked by the BLE
* stack when a client requests a read of a characteristic value.
*
* @param s - service handle
* @param c - characteristic being read
* @param dest - a buffer to copy the characteristic value into
* @return int
*/
int webbleReadHandler(ns_ble_service_t *s, struct ns_ble_characteristic *c, void *dest) {
// ns_lp_printf("webbleReadHandler\n");
memcpy(dest, c->applicationValue, c->valueLen);
return NS_STATUS_SUCCESS;
}
/**
* @brief Handle a write request from the client. Invoked by the BLE
* stack when a client requests a write of a characteristic value.
*
* @param s - service handle
* @param c - handle of the characteristic being written
* @param src - a buffer containing the value to be written
* @return int
*/
int webbleWriteHandler(ns_ble_service_t *s, struct ns_ble_characteristic *c, void *src) {
ns_lp_printf("webbleWriteHandler value %x\n", *(uint8_t *)src);
memcpy(c->applicationValue, src, c->valueLen);
return NS_STATUS_SUCCESS;
}
/**
* @brief Handle a notification from the client. Invoked when notification timer expires.
* Meant to be used to update the characteristic values if that is the desired app behavior.
* @param s - service handle
* @param c - handle of the characteristic being notified
* @return int
*/
int webbleNotifyHandler(ns_ble_service_t *s, struct ns_ble_characteristic *c) {
// ns_lp_printf("webbleNotifyHandler\n");
webbleUpdateSensorValues();
return NS_STATUS_SUCCESS;
}
/ Must be invoked from the main RadioTask
int webble_service_init(void) {
char webbleName[] = "Webble";
// Customize Service-specific fields
ns_ble_char2uuid(webbleUuid("0000"), &(webbleService.uuid128));
memcpy(webbleService.name, webbleName, sizeof(webbleService.name));
webbleService.nameLen = sizeof(webbleName) - 1; // exclude null terminator
webbleService.baseHandle = 0x0800;
webbleService.poolConfig = &webbleWsfBuffers;
webbleService.numAttributes = 0;
// Define characteristics to add to service.
// The ns_ble_create_characteristics parameters are:
/*
c - config struct, populated by this function
uuidString - a 16-byte UUID string
applicationValue - a pointer to the application's value store
valueLength - the length of the value store, in bytes
properties - a bitmask of properties for the characteristic, from the enum above
readHandlerCb - a callback function for read requests if the characteristic is readable
writeHandlerCb - a callback function for write requests i
notifyHandlerCb - a callback function for notify requests
periodMs - the period of the notify timer, in milliseconds
attributeCount - a pointer to the service's attribute count, incremented in function.
*/
ns_ble_create_characteristic(
&webbleTemperature, webbleUuid("2001"),
&temperature, sizeof(temperature), NS_BLE_READ,
&webbleReadHandler, NULL, NULL, 0, &(webbleService.numAttributes));
ns_ble_create_characteristic(
&webbleAccel, webbleUuid("5001"), accel, sizeof(accel),
NS_BLE_READ | NS_BLE_NOTIFY, NULL,
NULL, &webbleNotifyHandler, 200, &(webbleService.numAttributes));
ns_ble_create_characteristic(
&webbleRgb, webbleUuid("8001"), rgb, sizeof(rgb), NS_BLE_READ | NS_BLE_WRITE,
&webbleReadHandler, &webbleWriteHandler, NULL, 0, &(webbleService.numAttributes));
// Create the service
webbleService.numCharacteristics = 3; // needed to allocate memory for characteristics
ns_ble_create_service(&webbleService);
// Add characteristics defined above to the service
ns_ble_add_characteristic(&webbleService, &webbleTemperature);
ns_ble_add_characteristic(&webbleService, &webbleAccel);
ns_ble_add_characteristic(&webbleService, &webbleRgb);
ns_ble_start_service(&webbleService); // Initialize BLE, create structs, start service
return NS_STATUS_SUCCESS;
}
void RadioTask(void *pvParameters) {
webble_service_init();
while (1) {
wsfOsDispatcher();
}
}
void setup_task(void *pvParameters) {
ns_lp_printf("setup_task\n");
ns_ble_pre_init(); // Set NVIC priorities
xTaskCreate(RadioTask, "RadioTask", 512, 0, 3, &radio_task_handle);
vTaskSuspend(NULL);
while (1);
}
int main(void) {
ns_core_config_t ns_core_cfg = {.api = &ns_core_V1_0_0};
NS_TRY(ns_core_init(&ns_core_cfg), "Core init failed.\b");
am_bsp_low_power_init();
ns_itm_printf_enable();
ns_interrupt_master_enable();
xTaskCreate(setup_task, "Setup", 512, 0, 3, &my_xSetupTask);
vTaskStartScheduler();
while (1);
}