Working with Long Characteristic Values


According to the Bluetooth specification, the maximum size of any attribute is 512 bytes. However, the Maximum Transmission Unit (MTU), i.e., the largest amount of data that can be exchanged in a single GATT operation is usually less than this value. As a result, some characteristics may not be read/written with a single GATT operation. If the data to be read or written is larger than the MTU, a long read/write operation must be used. This example demonstrates how to implement this. The attached application handles reading and writing a characteristic of 512 bytes.


When reading a user characteristic longer than MTU, multiple gatt_server_user_read_request events will be generated on the server side, each containing the offset from the beginning of the characteristic. The application code must use the offset parameter to send the correct chunk of data.


Characteristics can be written by calling gecko_cmd_gatt_write_characteristic_value. If the characteristic data fits within MTU – 3 bytes, a single operation used. Otherwise, the write long procedure is used. The write long procedure consists of a prepare write request operation and an execute write request operation. A maximum of MTU – 5 bytes can be sent in a single prepare_value_write operation. The application can also access these operations directly by calling gecko_cmd_gatt_prepare_characteristic_value_write() and gecko_cmd_gatt_execute_characteristic_value_write(). This is a useful approach if the size of the characteristic is greater than 255 bytes.


Notifications and indications are limited to MTU – 3 bytes. Since all notifications and indications must fit within a single GATT operation, the application does not demonstrate them.

Setting up

  1. Get started by creating an SoC-Empty application for your chosen device in Simplicity Studio.

  2. After that’s complete, take the attached app.c and copy it to your project folder.

  3. Open app.h and change the definition of DEBUG_LEVEL from 0 to 1 to enable printing.

  4. Copy gpiointerrupt.c file from the SDK directories (C:\SiliconLabs\SimplicityStudio\vX\developer\sdks\gecko_sdk_suite\vY\platform\emdrv\gpiointerrupt\src) to the project's platform/emdrv/gpiointerrupt/src/

  5. Open the .isc file for your project and import the gatt.xml file attached by clicking the import icon on the right side of the GATT Configurator.

  6. When the file has been imported, Save, click Generate, and build the project.

  7. Flash the application onto two evaluation boards and open a terminal window, such as Teraterm or Simplicity Studio console for each. One will be the master and the other one will be the slave.


The attached application can operate in either slave or master mode. The application starts in slave mode. To switch to master mode, press PB0 on the WSTK.


As soon as the device is switched to master mode, it begins scanning for a device advertising a service with the following UUID: cdb5433c-d716-4b02-87f5-c49263182377. When a device advertising this service is found, a connection is formed. The gecko_evt_gatt_mtu_exchanged event saves the MTU size for the connection, which is needed for writing the long characteristic later.

The master now discovers service and characteristic handles. After the long_data characteristic is found, the master performs a read of this characteristic by calling gecko_cmd_gatt_read_characteristic_value(). The size of this characteristic is 512 bytes so the read long procedure is always used.

After this process is complete, you’ll see a message indicating that the read has finished and to press PB1 to write a block of test data to the slave. Pressing PB1 on the WSTK triggers a write of an array of test data to this long characteristic. This action is handled by a helper function called write_characteristic(), which in turn uses a helper function called queue_characteristic_chunk. This function can handle any data size up to 512 bytes. Writing the characteristic data is handled by queuing data with as many calls to gecko_cmd_gatt_prepare_characteristic_value_write() as necessary. After all data is queued up, it is written with a call to gecko_cmd_gatt_execute_characteristic_value_write(). Because only one GATT operation can take place at a time for a given connection, the gatt_procedure_completed event is used to drive the process of queuing writes. To get the process started, queue_characteristic_chunk() is called directly from write_characteristic(). After that queue_characteristic_chunk() is called from the gatt_procedure_completed event. This ensures that the previous procedure is finished before attempting to start another. The master displays messages indicating how many bytes are written to the slave in each operation and the message “exec_result = 0x00” when complete.


Upon startup, the slave begins advertising the service mentioned above. This service contains a single user-type characteristic of 512 bytes. The gecko_evt_gatt_server_user_read_request event handler handles read requests from the master. Because the characteristic is larger than an MTU, this event handler uses the connection mtu size and offset parameters passed to the event to send the correct portion of the array to the master. This event will be generated as many times as necessary to allow reading the entire characteristic.

A gatt_server_attribute_value event is generated for each queued write performed by the master and a gatt_server_execute_write_completed event is generated when all of the queued writes have been completed. The result parameter indicates whether an error has occurred. A user_write_request response must be sent by the application for each queued write, which is handled in the user_write_request event handler.