Jump to content

Raspberry Pi Microcontrollers: Difference between revisions

Adding information about BLE GATT tables and BTstack's GATT files.
Line 111: Line 111:
#endif // MY_BTSTACK_CONFIG_H
#endif // MY_BTSTACK_CONFIG_H
</syntaxhighlight>
</syntaxhighlight>
==== GATT Profiles, Services, Characteristics, and Descriptors ====
A Bluetooth Low Energy Peripheral organizes the served data in a linear attributes table or database, referred to as  ATT. The GATT (Generic Attributes) profile used in BLE is, contrary to what the name suggests, a specific organizational structure imposed on the ATT table, where the data table as a whole is referred to as "profile". The profile attributes can be broken down into consecutive "service" attributes, the service attributes into "characteristic" attributes, the characteristics into "descriptor" attributes. So, all in all there linear ATT table becomes a tree structure with four levels in GATT. A BLE peripheral, acting as a GATT server when connected to, will advertise its profile over GAP. A BLE central, acting as a GATT client after establishing a connection to the peripheral, can read the complete GATT hierarchy to find out about the profile contents.
BTstack allows defining a GATT table as a human-readable text file. The translation of that file into a C header by a Python script is a build step that can be included in ''CMakeLists.txt'' using the function ''pico_btstack_make_gatt_header()''. The generated C header contains the GATT table as an array of hex-coded bytes, laid out one line per attribute, with comments between the lines for orientation. Application code that needs to access the GATT table data then simply "#include"s this generated header file.
When defining the GATT table in BTstack's text format, we can use the following keywords for the attribute permissions of a characteristic:
* READ
* WRITE
* WRITE_WITH_RESPONSE
* NOTIFY: Adds a ''Client Configuration'' descriptor to the characteristic that allows a GATT client to control whether a notification is to be sent to the client each time the value of the characteristic changes.
* DYNAMIC


==== The BTstack runloop ====
==== The BTstack runloop ====

Revision as of 2025-05-08T22:19:15

Pico Series

  • Raspberry Pi Pico and Pico W
  • Raspberry Pi Pico 2 and Pico 2 W

Development Setup

Using the Raspberry Pi Debug Probe

The Debug Probe is a RP2040 based USB-to-UART/SWD board for connecting the USB slot of a development machine to the SWD or UART pins of a target device.

Using A Pico To Debug Another Pico

A Pico can be used to debug another Pico (a.k.a. target device) by flashing it with the debugprobe binary and wiring to the target Pico as shown below.

A Raspberry Pi Pico wired to debug another Pico

From the debugger Pico to the target Pico,

  • pin #4 (GP2) connects to pin SWCLK,
  • pin #5 (GP3) connects to SWDIO,
  • pin #6 (GP4, UART1 TX) connects to pin #2 (GP1, UART0 RX),
  • pin #7 (GP5, UART1 RX) connects to pin #1 (GP0, UART0, TX).

The debugger Pico serves both as a USB-to-SWD and USB-to-UART bridge to the target device. The development machine, therefore, needs to be connected via USB only to the debugger Pico. Console output on the target Pico is relayed through the debugger. It is possible, though, to connect a second USB cable from the development machine to the target device for (simultaneous) direct access to the console output. This debugger setup works not just with Pico targets but with any microcontroller that supports the CMSIS-DAP protocol over SWD.

Software Tools for Debugging

Whether we use the Raspberry Pi Debug Probe or a spare Pico as debug probe, the software tools used for debugging are the same:

  • openocd

Notes on Programming with the C SDK

Async Context

In the C SDK, an async_context is used in concurrent programming to make sure that functions are executed on the same thread and in the order they were submitted, similar to dispatch queues in Apple's Dispatch framework. The async_context data structure is a container of linked lists of worker functions that are executed within a single thread of a single processor core.

Bluetooth with BTstack

The C SDK integrates the third-party BTstack framework as its Bluetooth API that interfaces with the Infineon CYW43 wireless communication module (on Pico W models) via the Host-Controller Interface (HCI) protocol.

Setup

Before we can call any BTstack functions (and hope for some meaningful outcome), we need to add a btstack_config.h header file to our project. In it, we add all the compile-time switches that fit our application. On a microcontroller, a frequent use case for BTstack is to implement a BLE peripheral. For this we, our config header should look something like this:

#ifndef MY_BTSTACK_CONFIG_H
#define MY_BTSTACK_CONFIG_H

#ifndef ENABLE_BLE
#error In CMakeLists.txt add the 'pico_btstack_ble' library to target_link_libraries(...)
#endif

// logging
#define ENABLE_LOG_INFO
#define ENABLE_LOG_ERROR
#define ENABLE_PRINTF_HEXDUMP // required for hci_dump_init()

#if 1 // configure as peripheral (GATT server)
	#define ENABLE_LE_PERIPHERAL
	#define MAX_NR_GATT_CLIENTS 0
#else // configure as central (GATT client)
	#define ENABLE_LE_CENTRAL
	#define MAX_NR_GATT_CLIENTS 1
//#define ENABLE_GATT_CLIENT_PAIRING
#endif

// Setting various buffer sizes related to HCI communication
#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
#define HCI_ACL_PAYLOAD_SIZE (255 + 4)
#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
#define MAX_NR_HCI_CONNECTIONS 1
#define MAX_NR_SM_LOOKUP_ENTRIES 3
#define MAX_NR_WHITELIST_ENTRIES 16
#define MAX_NR_LE_DEVICE_DB_ENTRIES 16

// Choosing a fixed-size attributes table over using malloc.
// We want to use BTstack without heap memory allocations.
#if 1
#define MAX_ATT_DB_SIZE 512
#else
#define HAVE_MALLOC
#endif

// Enabling timers
#if 1
#define HAVE_EMBEDDED_TIME_MS
#else
#define HAVE_EMBEDDED_TICK
#endif

// Limiting the amount of nonvolatile (flash) memory used for storing peer bonding information
#define NVM_NUM_DEVICE_DB_ENTRIES 16
#define NVM_NUM_LINK_KEYS 16

// Configuring HCI Controller-to-Host flow control and related buffers to avoid overrunning the cyw43 shared bus
#define ENABLE_HCI_CONTROLLER_TO_HOST_FLOW_CONTROL
#define HCI_HOST_ACL_PACKET_LEN (255+4)
#define HCI_HOST_ACL_PACKET_NUM 3
#define HCI_HOST_SCO_PACKET_LEN 120
#define HCI_HOST_SCO_PACKET_NUM 3
#define MAX_NR_CONTROLLER_SCO_PACKETS 3
#define MAX_NR_CONTROLLER_ACL_BUFFERS 3

// Enabling btstack_assert, acting just like normal assert()
#define HAVE_ASSERT

// Setting HCI resend timeout in case the Bluetooth module needs more time to response.
#define HCI_RESET_RESEND_TIMEOUT_MS 800

// Configuring security
#define ENABLE_SOFTWARE_AES128
#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS

#endif // MY_BTSTACK_CONFIG_H

GATT Profiles, Services, Characteristics, and Descriptors

A Bluetooth Low Energy Peripheral organizes the served data in a linear attributes table or database, referred to as ATT. The GATT (Generic Attributes) profile used in BLE is, contrary to what the name suggests, a specific organizational structure imposed on the ATT table, where the data table as a whole is referred to as "profile". The profile attributes can be broken down into consecutive "service" attributes, the service attributes into "characteristic" attributes, the characteristics into "descriptor" attributes. So, all in all there linear ATT table becomes a tree structure with four levels in GATT. A BLE peripheral, acting as a GATT server when connected to, will advertise its profile over GAP. A BLE central, acting as a GATT client after establishing a connection to the peripheral, can read the complete GATT hierarchy to find out about the profile contents.

BTstack allows defining a GATT table as a human-readable text file. The translation of that file into a C header by a Python script is a build step that can be included in CMakeLists.txt using the function pico_btstack_make_gatt_header(). The generated C header contains the GATT table as an array of hex-coded bytes, laid out one line per attribute, with comments between the lines for orientation. Application code that needs to access the GATT table data then simply "#include"s this generated header file.

When defining the GATT table in BTstack's text format, we can use the following keywords for the attribute permissions of a characteristic:

  • READ
  • WRITE
  • WRITE_WITH_RESPONSE
  • NOTIFY: Adds a Client Configuration descriptor to the characteristic that allows a GATT client to control whether a notification is to be sent to the client each time the value of the characteristic changes.
  • DYNAMIC

The BTstack runloop

The following articles by Hunter Adams offer a detailed analysis of how BTstack works on the Pico:

  1. BTstack and RP2040: HCI (Host Controller Interface).
    This article delves, among other things, into how the BTstack runloop is executed inside a Pico async_context.

    [...] we have just one when_pending_worker in our async_context. This worker is called cyw43_poll_worker, and its do_work function points to cyw43_poll_func(), listed above. This function ends up calling btstack_run_loop_poll_data_sources_from_irq(), about which we'll learn more shortly. This worker gets marked as pending in a GPIO interrupt. So, this provides a mechanism by which the CYW43 can tell the RP2040 to run btstack_run_loop_poll_data_sources_from_irq() which, as we're going to learn, goes and gets data from the device.

  2. Building a Bluetooth GATT Server on the Pi Pico W

GATT Characteristic read and write operations

The struct att_service_handler_t represents a GATT service. After the fields of the struct have been filled in by the application code, it holds all the information needed for the BLE peripheral to make it available to the BLE central. Some of the fields of the struct hold the ATT handles that define the start and end of the collection of characteristics belonging to the service. Other fields of the struct (read_callback and write_callback) hold pointers to functions that handle reading from and writing to characteristic values.

Note that the read callback for a descriptor of a GATT characteristic (i.e., an ATT attribute) is called twice. In the first call, the buffer pointer passed to the callback function is NULL. In the second call, it's a valid pointer. The first call is made by BTstack to query the size of the data that will be returned by the second call. BTstack needs to make sure its transmission buffers can hold the amount of data before making the second call.

At the end of a writing operation, if the characteristic was configured to send back notifications to the BLE peer, one more callback function comes into play. It is part of another struct, btstack_context_callback_registration_t, that is used for the deferred sending of characteristic update notification to the BLE central. In this callback, att_server_notify() is called to finally send the write notification to the peer.

Cookies help us deliver our services. By using our services, you agree to our use of cookies.