Scanning for BLE Devices with Coral Dev Board Micro + Wireless Add-ons
Coral Dev Board Micro is the newest addition to the Coral product family. The difference from its predecessor is that this board integrates a dual-core microcontroller with the Edge TPU module. A perfect development board for TinyML use cases.
This board has additional connectivity add-ons: wired and wireless (WiFi + Bluetooth). For this purpose, I will use the wireless add-on to scan for BLE devices around me.
Prerequisites
I assume you have already set up a development environment following the getting started guide.
Project Setup
We will create an out-of-tree (OOT) project. We can use the sample repository for that.
git clone https://github.com/google-coral/coralmicro-out-of-tree-sample ble-scan
cd ble-scan
git submodule update --init
Update the CMakeLists.txt on the project’s top-level directory.
cmake_minimum_required(VERSION 3.13)
# Toolchain must be set before project() call.
if (NOT DEFINED CMAKE_TOOLCHAIN_FILE)
set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_LIST_DIR}/coralmicro/cmake/toolchain-arm-none-eabi-gcc.cmake)
endif()
project(ble-scan)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)
include_directories(coralmicro)
add_subdirectory(coralmicro)
add_executable_m7(ble-scan
main.cc
)
target_link_libraries(ble-scan
libs_base-m7_freertos
libs_nxp_rt1176-sdk_wiced
edgefast_bluetooth
)
We change the project name to ble-scan then we link the required libraries to our project’s output binary.
Now, let’s jump to the code.
The Code
Replace the entire main.cc file with the following content.
#include <cstdio>
#include "libs/base/led.h"
#include "libs/base/tasks.h"
#include "libs/nxp/rt1176-sdk/edgefast_bluetooth/edgefast_bluetooth.h"
#include "third_party/freertos_kernel/include/FreeRTOS.h"
#include "third_party/freertos_kernel/include/event_groups.h"
#include "third_party/freertos_kernel/include/task.h"
static EventGroupHandle_t s_event_group;
static constexpr uint32_t EVENT_BLE_READY = BIT(0);
static void bleReadyCallback(int err);
static void bleScanCallback(const bt_addr_le_t *addr, int8_t rssi,
uint8_t adv_type, struct net_buf_simple *buf);
extern "C" void app_main(void *param) {
(void)param;
vTaskDelay(pdMS_TO_TICKS(3000));
s_event_group = xEventGroupCreate();
InitEdgefastBluetooth(bleReadyCallback);
xEventGroupWaitBits(s_event_group, EVENT_BLE_READY, pdFALSE, pdTRUE,
portMAX_DELAY);
auto btReady = BluetoothReady();
printf("BLE ready? %d \n", static_cast<int>(btReady));
bt_le_scan_param scan_params{};
scan_params.interval = 100;
scan_params.window = 100;
scan_params.type = BT_LE_SCAN_TYPE_ACTIVE;
scan_params.timeout = 0;
int err = bt_le_scan_start(&scan_params, bleScanCallback);
if (err) {
printf("Failed to start scan: %d\n", err);
vTaskSuspend(nullptr);
}
printf("Started scan\n");
vTaskSuspend(nullptr);
}
static void bleReadyCallback(int err) {
if (err) {
printf("Failed initializing BLE: %d\n", err);
return;
}
printf("BLE initialization completed\n");
xEventGroupSetBits(s_event_group, EVENT_BLE_READY);
}
void bleScanCallback(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type,
struct net_buf_simple *buf) {
char addr_s[BT_ADDR_LE_STR_LEN] = {0};
bt_addr_le_to_str(addr, addr_s, sizeof(addr_s));
printf("=========================================\n");
printf("Received advertisement from %s\n", addr_s);
printf("RSSI: %d\n", rssi);
printf("Type: %d\n", adv_type);
printf("Length: %d\n", buf->len);
printf("=========================================\n");
}
Here’s the explanation:
First, we include all the necessary header files, especially for Bluetooth.
#include <cstdio>
#include "libs/base/led.h"
#include "libs/base/tasks.h"
#include "libs/nxp/rt1176-sdk/edgefast_bluetooth/edgefast_bluetooth.h"
#include "third_party/freertos_kernel/include/FreeRTOS.h"
#include "third_party/freertos_kernel/include/event_groups.h"
#include "third_party/freertos_kernel/include/task.h"
Then, we declare the global variables and a couple of callback functions. The event group is used to signal whether the Bluetooth stack is ready to use or not. We can use a semaphore as an alternative.
static EventGroupHandle_t s_event_group;
static constexpr uint32_t EVENT_BLE_READY = BIT(0);
static void bleReadyCallback(int err);
static void bleScanCallback(const bt_addr_le_t *addr, int8_t rssi,
uint8_t adv_type, struct net_buf_simple *buf);
The first callback function will signal the main thread when the Bluetooth is ready to use by flipping the first bit on the event group. The second one will print the result of the BLE scanning process when they’re available.
Then, in the app_main function, we wait three seconds before initializing the Bluetooth stack. Then, we use the helper function to initialize the Bluetooth stack and wait for the readiness status.
vTaskDelay(pdMS_TO_TICKS(3000));
s_event_group = xEventGroupCreate();
InitEdgefastBluetooth(bleReadyCallback);
xEventGroupWaitBits(s_event_group, EVENT_BLE_READY, pdFALSE, pdTRUE,
portMAX_DELAY);
The InitEdgefastBluetooth
function takes a function pointer as its only argument. In our case, the function pointer points to the bleReadyCallback
callback function.
Then, the call xEventGroupWaitBits
will wait indefinitely until the EVENT_BLE_READY
bit is set. This call blocks the main thread and allows other tasks (if any) to run.
We can confirm that our Bluetooth stack is ready to use by calling the BluetoothReady
function. We can also poll the readiness status using a while-loop instead of calling the callback function to set a bit in FreeRTOS’s event group.
auto btReady = BluetoothReady();
printf("BLE ready? %d \n", static_cast<int>(btReady));
Then, we configure the scan parameters. Here, we use 99 as the window and 100 as the interval. Those values will be multiplied by 0.625, resulting in 61.85 and 62.5 milliseconds. Next, we use the active scan as the type parameter. Also, we use 0 as the timeout value. This allows the Bluetooth stack to scan for advertisement packets and scan responses (if using active scan) indefinitely, or at least until we tell it to stop.
bt_le_scan_param scan_params{};
scan_params.interval = 100;
scan_params.window = 99;
scan_params.type = BT_LE_SCAN_TYPE_ACTIVE;
scan_params.timeout = 0;
Next, we start the scan and print the result. Once the scan has begun, we will see results written by the scan result callback function to the serial monitor.
int err = bt_le_scan_start(&scan_params, bleScanCallback);
if (err) {
printf("Failed to start scan: %d\n", err);
vTaskSuspend(nullptr);
}
printf("Started scan\n");
vTaskSuspend(nullptr);
Then, the following callback will signal the main thread to continue its task by setting a bit in an event group if the Bluetooth stack is running, indicated by the 0 result provided in the function argument.
static void bleReadyCallback(int err) {
if (err) {
printf("Failed initializing BLE: %d\n", err);
return;
}
printf("BLE initialization completed\n");
xEventGroupSetBits(s_event_group, EVENT_BLE_READY);
}
Then, the following callback will write the scan result, including:
- The device address
- RSSI
- Advertisement type
- Data length
static void bleScanCallback(const bt_addr_le_t *addr, int8_t rssi, uint8_t adv_type,
struct net_buf_simple *buf) {
char addr_s[BT_ADDR_LE_STR_LEN] = {0};
bt_addr_le_to_str(addr, addr_s, sizeof(addr_s));
printf("=========================================\n");
printf("Received advertisement from %s\n", addr_s);
printf("RSSI: %d\n", rssi);
printf("Type: %d\n", adv_type);
printf("Length: %d\n", buf->len);
printf("=========================================\n");
}
Compile and Run the Code
Next, we compile the code using cmake and flash firmware binaries to the Coral Dev Board Micro using a flash tool.
The following commands will create a build directory named build, then it will also tell cmake to produce the compile commands file that we can use in our IDEs.
cmake -B build -S . -DCMAKE_EXPORT_COMPILE_COMMANDS=1
cmake --build build -j8
Next, we can flash firmware binaries (including the firmware for the wireless add-on) using the following command.
python3 coralmicro/scripts/flashtool.py --build_dir build --elf_path build/ble-scan
Next, we can see the result using a serial monitor. Here’s an example of the output of our code.
WLAN MAC Address : 24:CD:8D:6F:91:16
WLAN Firmware : wl0: May 2 2019 02:46:20 version 7.45.189 (r714228 CY) FWID 01-105ab14e
WLAN CLM : API: 12.2 Data: 9.10.136 Compiler: 1.29.4 ClmImport: 1.36.3 Creation: 2019-05-02 02:31:24
BLE initialization completed
BLE ready? 1
Started scan
=========================================
Received advertisement from 08:A2:A9:83:FD:F2 (random)
RSSI: -54
Type: 3
Length: 15
=========================================
=========================================
Received advertisement from EE:83:ED:73:CE:01 (random)
RSSI: -61
Type: 3
Length: 8
=========================================