Developing Bluetooth Applications

Bluetooth applications are developed using the common infrastructure and approach that is described in the Application Development section of the documentation.

Additional information that is only relevant to Bluetooth applications can be found in this page.

Hardware setup

This section describes the options you have when building and debugging Bluetooth applications with Zephyr. Depending on the hardware that is available to you, the requirements you have and the type of development you prefer you may pick one or another setup to match your needs.

There are 4 possible hardware setups to use with Zephyr and Bluetooth:

  1. Embedded

  2. QEMU with an external Controller

  3. Native POSIX with an external Controller

  4. Simulated nRF52 with BabbleSim

Embedded

This setup relies on all software running directly on the embedded platform(s) that the application is targeting. All the Configurations and Build Types are supported but you might need to build Zephyr more than once if you are using a dual-chip configuration or if you have multiple cores in your SoC each running a different build type (e.g., one running the Host, the other the Controller).

To start developing using this setup follow the Getting Started Guide, choose one (or more if you are using a dual-chip solution) boards that support Bluetooth and then run the application).

Embedded HCI tracing

When running both Host and Controller in actual Integrated Circuits, you will only see normal log messages on the console by default, without any way of accessing the HCI traffic between the Host and the Controller. However, there is a special Bluetooth logging mode that converts the console to use a binary protocol that interleaves both normal log messages as well as the HCI traffic. Set the following Kconfig options to enable this protocol before building your application:

CONFIG_BT_DEBUG_MONITOR=y
CONFIG_UART_CONSOLE=n

Setting CONFIG_BT_DEBUG_MONITOR to y replaces the CONFIG_BT_DEBUG_LOG option, and setting CONFIG_UART_CONSOLE to n disables the default printk/printf hooks.

To decode the binary protocol that will now be sent to the console UART you need to use the btmon tool from BlueZ:

$ btmon --tty <console TTY> --tty-speed 115200

Host on Linux with an external Controller

Note

This is currently only available on GNU/Linux

This setup relies on a “dual-chip” configuration which is comprised of the following devices:

  1. A Host-only application running in the QEMU emulator or the native_posix native port of Zephyr

  2. A Controller, which can be one of two types:

Warning

Certain external Controllers are either unable to accept the Host to Controller flow control parameters that Zephyr sets by default (Qualcomm), or do not transmit any data from the Controller to the Host (Realtek). If you see a message similar to:

<wrn> bt_hci_core: opcode 0x0c33 status 0x12

when booting your sample of choice (make sure you have enabled CONFIG_BT_DEBUG_LOG in your prj.conf before running the sample), or if there is no data flowing from the Controller to the Host, then you need to disable Host to Controller flow control. To do so, set CONFIG_BT_HCI_ACL_FLOW_CONTROL=n in your prj.conf.

QEMU

You can run the Zephyr Host on the QEMU emulator and have it interact with a physical external Bluetooth Controller. Refer to Running on QEMU and Native POSIX for full instructions on how to build and run an application in this setup.

Native POSIX

Note

This is currently only available on GNU/Linux

The Native POSIX target builds your Zephyr application with the Zephyr kernel, and some minimal HW emulation as a native Linux executable. This executable is a normal Linux program, which can be debugged and instrumented like any other, and it communicates with a physical external Controller.

Refer to Running on QEMU and Native POSIX for full instructions on how to build and run an application in this setup.

Simulated nRF52 with BabbleSim

Note

This is currently only available on GNU/Linux

The nrf52_bsim board, is a simulated target board which emulates the necessary peripherals of a nrf52 SOC to be able to develop and test BLE applications. This board, uses:

Just like with the native_posix target, the build result is a normal Linux executable. You can find more information on how to run simulations with one or several devices in this board’s documentation

Currently, only Combined builds are possible, as this board does not yet have any models of a UART, or USB which could be used for an HCI interface towards another real or simulated device.

Initialization

The Bluetooth subsystem is initialized using the bt_enable() function. The caller should ensure that function succeeds by checking the return code for errors. If a function pointer is passed to bt_enable(), the initialization happens asynchronously, and the completion is notified through the given function.

Bluetooth Application Example

A simple Bluetooth beacon application is shown below. The application initializes the Bluetooth Subsystem and enables non-connectable advertising, effectively acting as a Bluetooth Low Energy broadcaster.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

/*
 * Set Advertisement data. Based on the Eddystone specification:
 * https://github.com/google/eddystone/blob/master/protocol-specification.md
 * https://github.com/google/eddystone/tree/master/eddystone-url
 */
static const struct bt_data ad[] = {
	BT_DATA_BYTES(BT_DATA_FLAGS, BT_LE_AD_NO_BREDR),
	BT_DATA_BYTES(BT_DATA_UUID16_ALL, 0xaa, 0xfe),
	BT_DATA_BYTES(BT_DATA_SVC_DATA16,
		      0xaa, 0xfe, /* Eddystone UUID */
		      0x10, /* Eddystone-URL frame type */
		      0x00, /* Calibrated Tx power at 0m */
		      0x00, /* URL Scheme Prefix http://www. */
		      'z', 'e', 'p', 'h', 'y', 'r',
		      'p', 'r', 'o', 'j', 'e', 'c', 't',
		      0x08) /* .org */
};

/* Set Scan Response data */
static const struct bt_data sd[] = {
	BT_DATA(BT_DATA_NAME_COMPLETE, DEVICE_NAME, DEVICE_NAME_LEN),
};

static void bt_ready(int err)
{
	char addr_s[BT_ADDR_LE_STR_LEN];
	bt_addr_le_t addr = {0};
	size_t count = 1;

	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
		return;
	}

	printk("Bluetooth initialized\n");

	/* Start advertising */
	err = bt_le_adv_start(BT_LE_ADV_NCONN_IDENTITY, ad, ARRAY_SIZE(ad),
			      sd, ARRAY_SIZE(sd));
	if (err) {
		printk("Advertising failed to start (err %d)\n", err);
		return;
	}


	/* For connectable advertising you would use
	 * bt_le_oob_get_local().  For non-connectable non-identity
	 * advertising an non-resolvable private address is used;
	 * there is no API to retrieve that.
	 */

	bt_id_get(&addr, &count);
	bt_addr_le_to_str(&addr, addr_s, sizeof(addr_s));

	printk("Beacon started, advertising as %s\n", addr_s);
}

void main(void)
{
	int err;

	printk("Starting Beacon Demo\n");

	/* Initialize the Bluetooth Subsystem */
	err = bt_enable(bt_ready);
	if (err) {
		printk("Bluetooth init failed (err %d)\n", err);
	}
}

The key APIs employed by the beacon sample are bt_enable() that’s used to initialize Bluetooth and then bt_le_adv_start() that’s used to start advertising a specific combination of advertising and scan response data.