Bluetooth Low Energy (BLE) is a powerful technology that enables wireless communication between devices, and the nRF52840 microcontroller from Nordic Semiconductor is one of the most popular choices for developing BLE applications. One of the key features of BLE is the ability to create custom services and characteristics, allowing developers to tailor communication to their specific needs. In this blog, we will guide beginners through the process of creating custom BLE services and characteristics using the nRF52840, adding characteristics, and handling data transmission between devices.
Overview of BLE Services and Characteristics
In BLE, services and characteristics are essential for organizing and transmitting data between devices.
- Service: A service is a collection of related characteristics that define a specific functionality. For example, a temperature service might include characteristics for the temperature measurement and the threshold settings.
- Characteristic: A characteristic is a data structure that contains a value and properties that define how that value can be accessed. Each characteristic can have properties such as read, write, notify, or indicate.
Creating a Custom BLE Service on the nRF52840
1. Define the Service UUID
Every service in BLE is identified by a unique UUID (Universally Unique Identifier). You can create a custom UUID for your service or use a predefined one. For example:
#define CUSTOM_SERVICE_UUID 0x1234 // Example UUID for custom service
2. Initialize the Service
In your application code, you need to initialize the BLE stack and create the service. The nRF52840 SDK provides APIs to help you with this. Here’s how to initialize a custom service:
#include "ble.h"
#include "ble_srv_common.h"
static uint16_t custom_service_handle; // Handle for the custom service
void custom_service_init(void) {
ble_uuid_t service_uuid;
service_uuid.uuid = CUSTOM_SERVICE_UUID;
service_uuid.type = BLE_UUID_TYPE_VENDOR_BEGIN; // Vendor specific UUID type
// Add the custom service
sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, &service_uuid, &custom_service_handle);
}
3. Add Characteristics to the Service
Characteristics are added to the service during initialization. Each characteristic must have its own UUID and properties.
#define CUSTOM_CHARACTERISTIC_UUID 0x5678 // Example UUID for custom characteristic
void custom_characteristic_add(void) {
ble_gatts_char_md_t char_md;
ble_gatts_attr_md_t attr_md;
ble_uuid_t char_uuid;
uint16_t char_handle;
// Initialize characteristic UUID
char_uuid.uuid = CUSTOM_CHARACTERISTIC_UUID;
char_uuid.type = BLE_UUID_TYPE_VENDOR_BEGIN;
// Initialize characteristic metadata
memset(&char_md, 0, sizeof(char_md));
char_md.char_props.read = 1; // Enable read property
char_md.char_props.write = 1; // Enable write property
// Initialize attribute metadata
memset(&attr_md, 0, sizeof(attr_md));
attr_md.read_perm = SEC_OPEN; // Set read permission
attr_md.write_perm = SEC_OPEN; // Set write permission
// Add the characteristic
sd_ble_gatts_characteristic_add(custom_service_handle, &char_md, &attr_md, &char_handle);
}
Handling Data Transmission
Once you have defined your service and characteristics, you need to handle data transmission between devices. This involves reading from and writing to the characteristics.
1. Writing Data to a Characteristic
To write data to a characteristic, you typically handle a write request from a connected device. Here’s an example of how to do this:
void on_characteristic_write(ble_evt_t const *p_ble_evt) {
if (p_ble_evt->header.evt_id == BLE_GATTS_EVT_WRITE) {
// Check if the write was to your characteristic
if (p_ble_evt->evt.gatts_evt.params.write.handle == char_handle) {
// Process the incoming data
uint8_t *data = p_ble_evt->evt.gatts_evt.params.write.data;
uint16_t length = p_ble_evt->evt.gatts_evt.params.write.len;
process_data(data, length); // Custom function to handle data
}
}
}
2. Reading Data from a Characteristic
To read data from a characteristic, you need to respond to read requests. Here’s an example:
void on_characteristic_read(ble_evt_t const *p_ble_evt) {
if (p_ble_evt->header.evt_id == BLE_GATTS_EVT_READ) {
// Check if the read was to your characteristic
if (p_ble_evt->evt.gatts_evt.params.read.handle == char_handle) {
uint8_t response_data[] = { /* Your data */ };
// Send the response back
sd_ble_gatts_value_set(p_ble_evt->evt.gatts_evt.params.read.handle, &response_data, sizeof(response_data));
}
}
}
3. Notifications and Indications
If your characteristic supports notifications or indications, you can send updates to connected devices when the data changes. Here’s how to send a notification:
void notify_characteristic(uint8_t *data, uint16_t length) {
sd_ble_gatts_hvx(conn_handle, &hvx_params); // Send notification to the connected device
}
Best Practices for Custom BLE Services
- Use Meaningful UUIDs: Ensure that your custom service and characteristic UUIDs are meaningful and easy to identify. Consider using a UUID generator to create unique identifiers.
- Define Clear Data Structures: Clearly define the data structure for your characteristics. This will make it easier to process incoming data and send responses.
- Handle Errors Gracefully: Implement error handling for read and write operations to ensure that your application can respond appropriately to unexpected conditions.
- Optimize Data Transmission: Minimize the amount of data transmitted over BLE to conserve power and improve performance. Use efficient data formats and consider compressing data if necessary.
- Test Extensively: Test your custom services and characteristics with various BLE devices to ensure compatibility and reliability. Use tools like nRF Connect to debug and verify communication.
Conclusion
Creating custom BLE services and characteristics on the nRF52840 enables developers to tailor communication to their specific application needs. By following the steps outlined in this blog, beginners can define custom services, add characteristics, and handle data transmission effectively. As BLE technology continues to evolve, the ability to create custom services will remain a powerful tool for developers looking to build innovative and efficient wireless applications.

Leave a comment