Skip to main content

使用 Seeed Studio XIAO MG24 的蓝牙功能

Seeed Studio XIAO MG24Seeed Studio XIAO MG24 Sense

Seeed Studio XIAO MG24 是一款强大的开发板,支持蓝牙 LE 5.3 和蓝牙网格,使其成为需要无线连接的各种物联网应用的理想选择。凭借其卓越的射频性能,XIAO MG24 可在各种距离上提供可靠的高速无线通信,使其成为短距离和长距离应用的多功能解决方案。在本教程中,我们将探索 XIAO MG24 蓝牙功能的基本特性,包括如何扫描附近的蓝牙设备、建立蓝牙连接以及通过该连接传输和接收数据。

天线切换方法

Seeed Studio XIAO MG24 有两种天线选项:内置天线和外置天线。为了方便起见,您可以选择使用内置天线,为了增强信号强度,您可以选择外置天线。以下是在两种天线之间切换的方法。

PB04 用于选择使用内置天线还是外置天线。在此之前,您需要将 PB05 设置为高电平以开启此功能。如果 PB04 设置为低电平,则使用内置天线;如果设置为高电平,则使用外置天线。默认为低电平。如果您想将其设置为高电平,可以参考下面的代码。

#define RF_SW_PW_PIN PB5
#define RF_SW_PIN PB4

void setup() {
// turn on this antenna function
pinMode(RF_SW_PW_PIN, OUTPUT);
digitalWrite(RF_SW_PW_PIN, HIGH);

delay(100);

// HIGH -> Use external antenna / LOW -> Use built-in antenna
pinMode(RF_SW_PIN, OUTPUT);
digitalWrite(RF_SW_PIN, HIGH);

蓝牙低功耗 (BLE) 使用

蓝牙低功耗,简称 BLE,是蓝牙的一种节能变体。BLE 的主要应用是小数据量的短距离传输(低带宽)。与始终开启的蓝牙不同,BLE 除了在建立连接时,其余时间都保持在睡眠模式。

由于其特性,BLE 适用于需要定期交换少量数据并使用纽扣电池运行的应用。例如,BLE 在医疗保健、健身、跟踪、信标、安全和家庭自动化行业中非常有用。

这使得它的功耗非常低。BLE 的功耗大约比蓝牙低 100 倍(取决于使用情况)。

关于 XIAO MG24 的 BLE 部分,我们将在以下章节中介绍其使用方法。

  • 一些基本概念 -- 我们将首先了解一些在 BLE 中可能经常使用的概念,以帮助我们理解 BLE 程序的执行过程和思路。
  • BLE 扫描器 -- 本节将解释如何搜索附近的蓝牙设备并在串行监视器中打印出来。
  • BLE 服务器/客户端 -- 本节将解释如何使用 XIAO MG24 作为服务器和客户端来发送和接收指定的数据消息。它还将用于从手机接收或向 XIAO 发送消息。

一些基本概念

服务器和客户端

在蓝牙低功耗中,有两种类型的设备:服务器和客户端。XIAO MG24 可以充当客户端或服务器。

服务器广播其存在,以便其他设备可以找到它,并包含客户端可以读取的数据。客户端扫描附近的设备,当它找到正在寻找的服务器时,它建立连接并监听传入的数据。这称为点对点通信。

属性

属性实际上是一段数据。每个蓝牙设备都用于提供服务,服务是数据的集合,该集合可以称为数据库,数据库中的每个条目都是一个属性,所以这里我将属性翻译为数据条目。您可以将蓝牙设备想象为一个表格,表格内的每一行都是一个属性。

GATT

当两个蓝牙设备建立连接时,它们需要一个协议来确定如何通信。GATT(通用属性配置文件)就是这样一个协议,它定义了蓝牙设备之间如何传输数据。

在 GATT 协议中,设备的功能和属性被组织成称为服务、特征和描述符的结构。服务代表设备提供的一组相关功能和特性。每个服务可以包含多个特征,这些特征定义了服务的某种属性或行为,例如传感器数据或控制命令。每个特征都有一个唯一标识符和一个值,可以读取或写入以进行通信。描述符用于描述特征的元数据,例如特征值的格式和访问权限。

通过使用 GATT 协议,蓝牙设备可以在不同的应用场景中进行通信,例如传输传感器数据或控制远程设备。

BLE 特征

ATT,全名属性协议。最终,ATT 由一组 ATT 命令组成,即请求和响应命令,ATT 也是蓝牙空包的最上层,即 ATT 是我们分析蓝牙包最多的地方。

ATT 命令,正式名称为 ATT PDU(协议数据单元)。它包括 4 个类别:读取、写入、通知和指示。这些命令可以分为两种类型:如果需要响应,则后面会跟一个请求;相反,如果只需要 ACK 而不需要响应,则后面不会跟请求。

服务和特征在 GATT 层中定义。服务端提供服务,服务就是数据,数据就是属性,服务和特征是数据的逻辑表示,或者说用户可以看到的数据最终都转换为服务和特征。

让我们从移动设备的角度来看看服务和特征是什么样子的。nRF Connect 是一个应用程序,它非常直观地向我们展示了每个数据包应该是什么样子。

如您所见,在蓝牙规范中,每个特定的蓝牙应用都由多个服务组成,每个服务由多个特征组成。特征由 UUID、属性和值组成。

属性用于描述对特征进行操作的类型和权限,例如是否支持读取、写入、通知等。这类似于 ATT PDU 中包含的四个类别。

UUID

每个服务、特征和描述符都有一个 UUID(通用唯一标识符)。UUID 是一个唯一的 128 位(16 字节)数字。例如:

ea094cbd-3695-4205-b32d-70c1dea93c35

对于SIG(蓝牙特殊兴趣小组)中指定的所有类型、服务和配置文件,都有缩短的UUID。但如果您的应用程序需要自己的UUID,您可以使用这个UUID生成器网站来生成它。

BLE扫描器

创建一个XIAO MG24 BLE扫描器很简单。以下是创建扫描器的示例程序。

/*
BLE scan example

The example scans for other BLE devices and prints out the address, RSSI, channel and name for each found device.

Find out more on the Silabs BLE API usage at: https://docs.silabs.com/bluetooth/latest/bluetooth-stack-api/

This example only works with the 'BLE (Silabs)' protocol stack variant.

Compatible boards:
- Arduino Nano Matter
- SparkFun Thing Plus MGM240P
- xG27 DevKit
- xG24 Explorer Kit
- xG24 Dev Kit
- BGM220 Explorer Kit
- Ezurio Lyra 24P 20dBm Dev Kit
- Seeed Studio XIAO MG24 (Sense)

Author: Tamas Jozsi (Silicon Labs)
*/
#define RF_SW_PW_PIN PB5
#define RF_SW_PIN PB4

void setup() {
Serial.begin(115200);
}

void loop() {

}

static String get_complete_local_name_from_ble_advertisement(sl_bt_evt_scanner_legacy_advertisement_report_t* response);

/**************************************************************************/ /**
* Bluetooth stack event handler
* Called when an event happens on BLE the stack
*
* @param[in] evt Event coming from the Bluetooth stack
*****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t* evt) {
static uint32_t scan_report_num = 0u;
sl_status_t sc;

switch (SL_BT_MSG_ID(evt->header)) {
// This event is received when the BLE device has successfully booted
case sl_bt_evt_system_boot_id:
// Print a welcome message
Serial.begin(115200);

// turn on this antenna function
pinMode(RF_SW_PW_PIN, OUTPUT);
digitalWrite(RF_SW_PW_PIN, HIGH);

delay(100);
// HIGH -> Use external antenna / LOW -> Use built-in antenna
pinMode(RF_SW_PIN, OUTPUT);
digitalWrite(RF_SW_PIN, HIGH);

Serial.println();
Serial.println("Silicon Labs BLE scan example");
Serial.println("BLE stack booted");
// Start scanning for other BLE devices
sc = sl_bt_scanner_set_parameters(sl_bt_scanner_scan_mode_active, // mode
16, // interval (value * 0.625 ms)
16); // window (value * 0.625 ms)
app_assert_status(sc);
sc = sl_bt_scanner_start(sl_bt_scanner_scan_phy_1m,
sl_bt_scanner_discover_generic);
app_assert_status(sc);
Serial.println("Started scanning...");
break;

// This event is received when we scan the advertisement of another BLE device
case sl_bt_evt_scanner_legacy_advertisement_report_id:
scan_report_num++;
Serial.print(" -> #");
Serial.print(scan_report_num);
Serial.print(" | Address: ");
for (int i = 5; i >= 0; i--) {
Serial.printf("%02x", evt->data.evt_scanner_legacy_advertisement_report.address.addr[i]);
if (i > 0) {
Serial.print(":");
}
}
Serial.print(" | RSSI: ");
Serial.print(evt->data.evt_scanner_legacy_advertisement_report.rssi);
Serial.print(" dBm");
Serial.print(" | Channel: ");
Serial.print(evt->data.evt_scanner_legacy_advertisement_report.channel);
Serial.print(" | Name: ");
Serial.println(get_complete_local_name_from_ble_advertisement(&(evt->data.evt_scanner_legacy_advertisement_report)));
break;

// Default event handler
default:
Serial.print("BLE event: 0x");
Serial.println(SL_BT_MSG_ID(evt->header), HEX);
break;
}
}

/**************************************************************************/ /**
* Finds the complete local name in BLE advertisements
*
* @param[in] response BLE response event received from scanning
*
* @return The complete local name if found, "N/A" otherwise
*****************************************************************************/
static String get_complete_local_name_from_ble_advertisement(sl_bt_evt_scanner_legacy_advertisement_report_t* response) {
int i = 0;
// Go through the response data
while (i < (response->data.len - 1)) {
uint8_t advertisement_length = response->data.data[i];
uint8_t advertisement_type = response->data.data[i + 1];

// If the length exceeds the maximum possible device name length
if (advertisement_length > 29) {
continue;
}

// Type 0x09 = Complete Local Name, 0x08 Shortened Name
// If the field type matches the Complete Local Name
if (advertisement_type == 0x09) {
// Copy the device name
char device_name[advertisement_length + 1];
memcpy(device_name, response->data.data + i + 2, advertisement_length);
device_name[advertisement_length] = '\0';
return String(device_name);
}
// Jump to next advertisement record
i = i + advertisement_length + 1;
}
return "N/A";
}

#ifndef BLE_STACK_SILABS
#error "This example is only compatible with the Silicon Labs BLE stack. Please select 'BLE (Silabs)' in 'Tools > Protocol stack'."
#endif
tip

需要注意的是,在编译之前需要在"工具 > 协议栈"中选择"BLE (Silabs)"。

现在您可以选择 XIAO MG24 主板并上传程序。如果程序运行顺利,打开串口监视器并将波特率设置为 115200,您可以看到以下结果。

该程序打印出扫描到的蓝牙设备的名称、MAC 地址、信道和信号强度。

程序注释

此示例演示了如何使用 Silicon Labs BLE 协议栈扫描附近的低功耗蓝牙 (BLE) 设备,打印每个发现设备的地址、RSSI(接收信号强度指示器)、信道和名称。

代码首先定义了一个事件处理函数 sl_bt_on_event,该函数处理 BLE 协议栈生成的各种低功耗蓝牙 (BLE) 事件。此函数使用 switch 语句来区分事件类型,例如 BLE 设备启动时和从附近设备接收广播报告时。收到启动事件后,它初始化串行通信,配置用于天线控制的 GPIO 引脚,并开始使用指定参数扫描 BLE 设备。

当扫描过程检测到来自 BLE 设备的广播报告时,会触发 sl_bt_evt_scanner_legacy_advertisement_report_id 分支。在这种情况下,函数为每个检测到的设备递增计数器,并提取关键信息,包括设备的地址、RSSI、信道和本地名称。它利用辅助函数 get_complete_local_name_from_ble_advertisement 从广播数据中检索设备的完整名称,然后将其打印到串行输出。

辅助函数 get_complete_local_name_from_ble_advertisement 遍历广播数据以定位完整本地名称字段。它检查每个广播记录的类型是否对应于完整本地名称,并将其作为字符串返回。如果未找到完整名称,函数返回"N/A"。这种系统化方法允许应用程序有效地发现和识别附近的 BLE 设备,在扫描过程中提供有价值的信息。

BLE 服务器/客户端

如前所述,XIAO MG24 可以同时充当服务器和客户端。让我们看看作为服务器的程序以及如何使用它。将以下程序上传到 XIAO 后,它将充当服务器并向连接到 XIAO 的所有蓝牙设备发送"Hello World"消息。

//Server Code
#define RF_SW_PW_PIN PB5
#define RF_SW_PIN PB4

bool notification_enabled = false;

void setup() {
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
Serial.begin(115200);
Serial.println("Silicon Labs BLE send hello world example");

// turn on the antenna function
pinMode(RF_SW_PW_PIN, OUTPUT);
digitalWrite(RF_SW_PW_PIN, HIGH);

delay(100);

// HIGH -> Use external antenna / LOW -> Use built-in antenna
pinMode(RF_SW_PIN, OUTPUT);
digitalWrite(RF_SW_PIN, LOW);
}

void loop() {
if (notification_enabled) {
// Send a notification every two seconds with the message 'hello world'
send_helloworld_notification();
}
delay(2000);
}

static void ble_initialize_gatt_db();
static void ble_start_advertising();

static const uint8_t advertised_name[] = "XIAO_MG24 Server"; // Name of your BLE device
static uint16_t gattdb_session_id;
static uint16_t generic_access_service_handle;
static uint16_t name_characteristic_handle;
static uint16_t my_service_handle;
static uint16_t led_control_characteristic_handle;
static uint16_t notify_characteristic_handle;

/**************************************************************************/ /**
* Bluetooth stack event handler
* Called when an event happens on BLE the stack
*
* @param[in] evt Event coming from the Bluetooth stack
*****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t *evt) {
switch (SL_BT_MSG_ID(evt->header)) {
// -------------------------------
// This event indicates the device has started and the radio is ready.
// Do not call any stack command before receiving this boot event!
case sl_bt_evt_system_boot_id:
{
Serial.println("BLE stack booted");

// Initialize the application specific GATT table
ble_initialize_gatt_db();

// Start advertising
ble_start_advertising();
Serial.println("BLE advertisement started");
}
break;

// -------------------------------
// This event indicates that a new connection was opened
case sl_bt_evt_connection_opened_id:
Serial.println("BLE connection opened");
break;

// -------------------------------
// This event indicates that a connection was closed
case sl_bt_evt_connection_closed_id:
Serial.println("BLE connection closed");
// Restart the advertisement
ble_start_advertising();
Serial.println("BLE advertisement restarted");
break;

// -------------------------------
// This event indicates that the value of an attribute in the local GATT
// database was changed by a remote GATT client
case sl_bt_evt_gatt_server_attribute_value_id:
// Check if the changed characteristic is the LED control
if (led_control_characteristic_handle == evt->data.evt_gatt_server_attribute_value.attribute) {
Serial.println("LED control characteristic data received");
// Check the length of the received data
if (evt->data.evt_gatt_server_attribute_value.value.len == 0) {
break;
}
// Get the received byte
uint8_t received_data = evt->data.evt_gatt_server_attribute_value.value.data[0];
// Turn the LED on/off according to the received data
// If we receive a '0' - turn the LED off
// If we receive a '1' - turn the LED on
if (received_data == 0x00) {
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
Serial.println("LED off");
} else if (received_data == 0x01) {
Serial.println("LED on");
digitalWrite(LED_BUILTIN, LED_BUILTIN_ACTIVE);
}
}
break;

// -------------------------------
// This event is received when a GATT characteristic status changes
case sl_bt_evt_gatt_server_characteristic_status_id:
// If the 'Notify' characteristic has been changed
if (evt->data.evt_gatt_server_characteristic_status.characteristic == notify_characteristic_handle) {
// The client just enabled the notification - send notification of the current state
if (evt->data.evt_gatt_server_characteristic_status.client_config_flags & sl_bt_gatt_notification) {
Serial.println("change notification enabled");
notification_enabled = true;
} else {
Serial.println("change notification disabled");
notification_enabled = false;
}
}
break;

// -------------------------------
// Default event handler
default:
break;
}
}

/**************************************************************************/ /**
* Sends a BLE notification the the client if notifications are enabled
*****************************************************************************/
static void send_helloworld_notification() {
uint8_t str[12] = "Hello World";
sl_status_t sc = sl_bt_gatt_server_notify_all(notify_characteristic_handle,
sizeof(str),
(const uint8_t *)&str);
if (sc == SL_STATUS_OK) {
Serial.println("Send notification!");
}
}

/**************************************************************************/ /**
* Starts BLE advertisement
* Initializes advertising if it's called for the first time
*****************************************************************************/
static void ble_start_advertising() {
static uint8_t advertising_set_handle = 0xff;
static bool init = true;
sl_status_t sc;

if (init) {
// Create an advertising set
sc = sl_bt_advertiser_create_set(&advertising_set_handle);
app_assert_status(sc);

// Set advertising interval to 100ms
sc = sl_bt_advertiser_set_timing(
advertising_set_handle,
160, // minimum advertisement interval (milliseconds * 1.6)
160, // maximum advertisement interval (milliseconds * 1.6)
0, // advertisement duration
0); // maximum number of advertisement events
app_assert_status(sc);

init = false;
}

// Generate data for advertising
sc = sl_bt_legacy_advertiser_generate_data(advertising_set_handle, sl_bt_advertiser_general_discoverable);
app_assert_status(sc);

// Start advertising and enable connections
sc = sl_bt_legacy_advertiser_start(advertising_set_handle, sl_bt_advertiser_connectable_scannable);
app_assert_status(sc);
}

/**************************************************************************/ /**
* Initializes the GATT database
* Creates a new GATT session and adds certain services and characteristics
*****************************************************************************/
static void ble_initialize_gatt_db() {
sl_status_t sc;
// Create a new GATT database
sc = sl_bt_gattdb_new_session(&gattdb_session_id);
app_assert_status(sc);

// Add the Generic Access service to the GATT DB
const uint8_t generic_access_service_uuid[] = { 0x00, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
sizeof(generic_access_service_uuid),
generic_access_service_uuid,
&generic_access_service_handle);
app_assert_status(sc);

// Add the Device Name characteristic to the Generic Access service
// The value of the Device Name characteristic will be advertised
const sl_bt_uuid_16_t device_name_characteristic_uuid = { .data = { 0x00, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
generic_access_service_handle,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0x00,
0x00,
device_name_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(advertised_name) - 1,
sizeof(advertised_name) - 1,
advertised_name,
&name_characteristic_handle);
app_assert_status(sc);

// Start the Generic Access service
sc = sl_bt_gattdb_start_service(gattdb_session_id, generic_access_service_handle);
app_assert_status(sc);

// Add my BLE service to the GATT DB
// UUID: de8a5aac-a99b-c315-0c80-60d4cbb51224
const uuid_128 my_service_uuid = {
.data = { 0x24, 0x12, 0xb5, 0xcb, 0xd4, 0x60, 0x80, 0x0c, 0x15, 0xc3, 0x9b, 0xa9, 0xac, 0x5a, 0x8a, 0xde }
};
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
sizeof(my_service_uuid),
my_service_uuid.data,
&my_service_handle);
app_assert_status(sc);

// Add the 'LED Control' characteristic to the Blinky service
// UUID: 5b026510-4088-c297-46d8-be6c736a087a
const uuid_128 led_control_characteristic_uuid = {
.data = { 0x7a, 0x08, 0x6a, 0x73, 0x6c, 0xbe, 0xd8, 0x46, 0x97, 0xc2, 0x88, 0x40, 0x10, 0x65, 0x02, 0x5b }
};
uint8_t led_char_init_value = 0;
sc = sl_bt_gattdb_add_uuid128_characteristic(gattdb_session_id,
my_service_handle,
SL_BT_GATTDB_CHARACTERISTIC_READ | SL_BT_GATTDB_CHARACTERISTIC_WRITE,
0x00,
0x00,
led_control_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
1, // max length
sizeof(led_char_init_value), // initial value length
&led_char_init_value, // initial value
&led_control_characteristic_handle);

// Start the Blinky service
sc = sl_bt_gattdb_start_service(gattdb_session_id, my_service_handle);
app_assert_status(sc);

// Add the 'Notify' characteristic to my BLE service
// UUID: 61a885a4-41c3-60d0-9a53-6d652a70d29c
const uuid_128 btn_report_characteristic_uuid = {
.data = { 0x9c, 0xd2, 0x70, 0x2a, 0x65, 0x6d, 0x53, 0x9a, 0xd0, 0x60, 0xc3, 0x41, 0xa4, 0x85, 0xa8, 0x61 }
};
uint8_t notify_char_init_value = 0;
sc = sl_bt_gattdb_add_uuid128_characteristic(gattdb_session_id,
my_service_handle,
SL_BT_GATTDB_CHARACTERISTIC_READ | SL_BT_GATTDB_CHARACTERISTIC_NOTIFY,
0x00,
0x00,
btn_report_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
1, // max length
sizeof(notify_char_init_value), // initial value length
&notify_char_init_value, // initial value
&notify_characteristic_handle);

// Start my BLE service
sc = sl_bt_gattdb_start_service(gattdb_session_id, my_service_handle);
app_assert_status(sc);

// Commit the GATT DB changes
sc = sl_bt_gattdb_commit(gattdb_session_id);
app_assert_status(sc);
}

#ifndef BLE_STACK_SILABS
#error "This example is only compatible with the Silicon Labs BLE stack. Please select 'BLE (Silabs)' in 'Tools > Protocol stack'."
#endif

同时,您可以在主要的移动应用商店中搜索并下载 nRF Connect 应用,该应用允许您的手机搜索并连接到蓝牙设备。

下载软件后,按照下面显示的步骤搜索并连接 XIAO_MG24,您将看到广播的"Hello World"。

如果您想使用另一个 XIAO MG24 作为客户端来接收来自服务器的消息,那么您可以为客户端 XIAO 使用以下程序。

// Client Code
#define RF_SW_PW_PIN PB5
#define RF_SW_PIN PB4

// Connection states
enum conn_state_t {
ST_BOOT,
ST_SCAN,
ST_CONNECT,
ST_SERVICE_DISCOVER,
ST_CHAR_DISCOVER,
ST_READY
};

conn_state_t connection_state = ST_BOOT;
uint8_t connection_handle = __UINT8_MAX__;
uint32_t blinky_service_handle = __UINT32_MAX__;
uint16_t led_control_char_handle = __UINT16_MAX__;
bool gatt_procedure_in_progress = false;

// If there's no built-in button set a pin where a button is connected
#ifndef BTN_BUILTIN
#define BTN_BUILTIN D0
#endif

void setup() {
// Set the built-in LED as output
pinMode(LED_BUILTIN, OUTPUT);
// Turn the built-in LED off
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
// Set the built-in button as input
pinMode(BTN_BUILTIN, INPUT);
// Start Serial
Serial.begin(115200);

// turn on the antenna function
pinMode(RF_SW_PW_PIN, OUTPUT);
digitalWrite(RF_SW_PW_PIN, HIGH);

delay(100);

// HIGH -> Use external antenna / LOW -> Use built-in antenna
pinMode(RF_SW_PIN, OUTPUT);
digitalWrite(RF_SW_PIN, LOW);
}

void loop() {
// Static variable for remembering the previous state of the button
static uint8_t btn_state_prev = LOW;
// If the connection is fully established and we don't have any ongoing GATT procedures
if (connection_state == ST_READY && !gatt_procedure_in_progress) {
// Read the current state of the button
uint8_t btn_state = digitalRead(BTN_BUILTIN);
// If the current state is different than the previous state
if (btn_state_prev != btn_state) {
// Update the previous state
btn_state_prev = btn_state;
// Invert the state (the SL board buttons give a 0 when pressed and 1 when released)
uint8_t btn_state_inv = !btn_state;
// Log the state change
Serial.print("Sending button state: ");
Serial.println(btn_state_inv);
// Send the new state over BLE by writing the other device's LED Control characteristic
sl_status_t sc = sl_bt_gatt_write_characteristic_value(connection_handle, led_control_char_handle, sizeof(uint8_t), &btn_state_inv);
app_assert_status(sc);
gatt_procedure_in_progress = true;
}
}
}

// Blinky service
// UUID: de8a5aac-a99b-c315-0c80-60d4cbb51224
const uuid_128 blinky_service_uuid = {
.data = { 0x24, 0x12, 0xb5, 0xcb, 0xd4, 0x60, 0x80, 0x0c, 0x15, 0xc3, 0x9b, 0xa9, 0xac, 0x5a, 0x8a, 0xde }
};

// LED Control characteristic
// UUID: 5b026510-4088-c297-46d8-be6c736a087a
const uuid_128 led_control_characteristic_uuid = {
.data = { 0x7a, 0x08, 0x6a, 0x73, 0x6c, 0xbe, 0xd8, 0x46, 0x97, 0xc2, 0x88, 0x40, 0x10, 0x65, 0x02, 0x5b }
};
const uint8_t advertised_name[] = "XIAO_MG24 Server";

static bool find_complete_local_name_in_advertisement(sl_bt_evt_scanner_legacy_advertisement_report_t* response);

/**************************************************************************/ /**
* Bluetooth stack event handler
* Called when an event happens on BLE the stack
*
* @param[in] evt Event coming from the Bluetooth stack
*****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t* evt) {
static uint32_t scan_report_num = 0u;
sl_status_t sc;

switch (SL_BT_MSG_ID(evt->header)) {
// This event is received when the BLE device has successfully booted
case sl_bt_evt_system_boot_id:
// Print a welcome message
Serial.println();
Serial.println("Silicon Labs BLE light switch client example");
Serial.println("BLE stack booted");
// Start scanning for other BLE devices
sc = sl_bt_scanner_set_parameters(sl_bt_scanner_scan_mode_active, 16, 16);
app_assert_status(sc);
sc = sl_bt_scanner_start(sl_bt_scanner_scan_phy_1m,
sl_bt_scanner_discover_generic);
app_assert_status(sc);
Serial.println("Started scanning...");
connection_state = ST_SCAN;
break;

// This event is received when we scan the advertisement of another BLE device
case sl_bt_evt_scanner_legacy_advertisement_report_id:
scan_report_num++;
Serial.print(" -> #");
Serial.print(scan_report_num);
Serial.print(" | Address: ");
for (int i = 5; i >= 0; i--) {
Serial.printf("%02x", evt->data.evt_scanner_legacy_advertisement_report.address.addr[i]);
if (i > 0) {
Serial.print(":");
}
}
Serial.print(" | RSSI: ");
Serial.print(evt->data.evt_scanner_legacy_advertisement_report.rssi);
Serial.print(" dBm");
Serial.print(" | Channel: ");
Serial.print(evt->data.evt_scanner_legacy_advertisement_report.channel);
Serial.print(" | Name: ");
Serial.println(find_complete_local_name_in_advertisement(&(evt->data.evt_scanner_legacy_advertisement_report)));

// If we find the other devices's name
if (find_complete_local_name_in_advertisement(&(evt->data.evt_scanner_legacy_advertisement_report))) {
Serial.println("Target device found!");
Serial.print("Forming a connection to ");
for (int i = 5; i >= 0; i--) {
Serial.printf("%02x", evt->data.evt_scanner_legacy_advertisement_report.address.addr[i]);
if (i > 0) {
Serial.print(":");
}
}
Serial.println(" ");

// Stop scanning
sc = sl_bt_scanner_stop();
app_assert_status(sc);
// Connect to the device
sc = sl_bt_connection_open(evt->data.evt_scanner_legacy_advertisement_report.address,
evt->data.evt_scanner_legacy_advertisement_report.address_type,
sl_bt_gap_phy_1m,
NULL);
// app_assert_status(sc);
connection_state = ST_CONNECT;

Serial.println("We are now connected to the BLE Server");
}
break;

// This event is received when a BLE connection has been opened
case sl_bt_evt_connection_opened_id:
Serial.println("Connection opened");
digitalWrite(LED_BUILTIN, LED_BUILTIN_ACTIVE);
connection_handle = evt->data.evt_connection_opened.connection;
// Discover Health Thermometer service on the connected device
sc = sl_bt_gatt_discover_primary_services_by_uuid(connection_handle,
sizeof(blinky_service_uuid),
blinky_service_uuid.data);
app_assert_status(sc);
gatt_procedure_in_progress = true;
connection_state = ST_SERVICE_DISCOVER;
break;

// This event is received when a BLE connection has been closed
case sl_bt_evt_connection_closed_id:
Serial.println("Connection closed");
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
connection_handle = __UINT8_MAX__;
// Restart scanning
sc = sl_bt_scanner_start(sl_bt_scanner_scan_phy_1m,
sl_bt_scanner_discover_generic);
app_assert_status(sc);
Serial.println("Restarted scanning...");
connection_state = ST_SCAN;
break;

// This event is generated when a new service is discovered
case sl_bt_evt_gatt_service_id:
Serial.println("GATT service discovered");
// Store the handle of the discovered Thermometer Service
blinky_service_handle = evt->data.evt_gatt_service.service;
break;

// This event is generated when a new characteristic is discovered
case sl_bt_evt_gatt_characteristic_id:
Serial.println("GATT charactersitic discovered");
// Store the handle of the discovered Temperature Measurement characteristic
led_control_char_handle = evt->data.evt_gatt_characteristic.characteristic;
break;

// This event is received when a GATT procedure completes
case sl_bt_evt_gatt_procedure_completed_id:
Serial.println("GATT procedure completed");
gatt_procedure_in_progress = false;

if (connection_state == ST_SERVICE_DISCOVER) {
Serial.println("GATT service discovery finished");
// Discover thermometer characteristic on the connected device
sc = sl_bt_gatt_discover_characteristics_by_uuid(evt->data.evt_gatt_procedure_completed.connection,
blinky_service_handle,
sizeof(led_control_characteristic_uuid.data),
led_control_characteristic_uuid.data);
app_assert_status(sc);
gatt_procedure_in_progress = true;
connection_state = ST_CHAR_DISCOVER;
break;
}

if (connection_state == ST_CHAR_DISCOVER) {
Serial.println("GATT characteristic discovery finished");
connection_state = ST_READY;
break;
}
break;

// Default event handler
default:
Serial.print("BLE event: 0x");
Serial.println(SL_BT_MSG_ID(evt->header), HEX);
break;
}
}

/**************************************************************************/ /**
* Finds a configured name in BLE advertisements
*
* @param[in] response BLE response event received from scanning
*
* @return true if found, false otherwise
*****************************************************************************/
static bool find_complete_local_name_in_advertisement(sl_bt_evt_scanner_legacy_advertisement_report_t* response) {
int i = 0;
bool found = false;

// Go through the response data
while (i < (response->data.len - 1)) {
uint8_t advertisement_length = response->data.data[i];
uint8_t advertisement_type = response->data.data[i + 1];

// Type 0x09 = Complete Local Name, 0x08 Shortened Name
// If the field type matches the Complete Local Name
if (advertisement_type == 0x09) {
// Check if device name matches
if (memcmp(response->data.data + i + 2, advertised_name, strlen((const char*)advertised_name)) == 0) {
found = true;
break;
}
}
// Jump to next advertisement record
i = i + advertisement_length + 1;
}
return found;
}

#ifndef BLE_STACK_SILABS
#error "This example is only compatible with the Silicon Labs BLE stack. Please select 'BLE (Silabs)' in 'Tools > Protocol stack'."
#endif

上述程序将把 XIAO 变成一个客户端,搜索附近的蓝牙设备。当蓝牙设备的 UUID 与您提供的 UUID 匹配时,它将连接到该设备并获取其特征值。

程序注释

让我们快速了解一下 BLE 服务器示例代码的工作原理。它首先导入蓝牙功能所需的库。然后,您需要为服务和特征定义一个 UUID。

// Add my BLE service to the GATT DB
// UUID: de8a5aac-a99b-c315-0c80-60d4cbb51224
const uuid_128 my_service_uuid = {
.data = { 0x24, 0x12, 0xb5, 0xcb, 0xd4, 0x60, 0x80, 0x0c, 0x15, 0xc3, 0x9b, 0xa9, 0xac, 0x5a, 0x8a, 0xde }
};

// Add the 'Notify' characteristic to my BLE service
// UUID: 61a885a4-41c3-60d0-9a53-6d652a70d29c
const uuid_128 btn_report_characteristic_uuid = {
.data = { 0x9c, 0xd2, 0x70, 0x2a, 0x65, 0x6d, 0x53, 0x9a, 0xd0, 0x60, 0xc3, 0x41, 0xa4, 0x85, 0xa8, 0x61 }
};

您可以保留默认的UUID,或者可以访问 uuidgenerator.net 为您的服务和特征创建随机UUID。

然后,您创建一个名为"XIAO_MG24 Server"的BLE设备。您可以将此名称更改为任何您喜欢的名称。在下一行中,您将BLE设备设置为服务器。之后,您使用之前定义的UUID为BLE服务器创建一个服务。

sl_status_t sc;
// Create a new GATT database
sc = sl_bt_gattdb_new_session(&gattdb_session_id);
app_assert_status(sc);

// Add the Generic Access service to the GATT DB
const uint8_t generic_access_service_uuid[] = { 0x00, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
sizeof(generic_access_service_uuid),
generic_access_service_uuid,
&generic_access_service_handle);
app_assert_status(sc);

// Add the Device Name characteristic to the Generic Access service
// The value of the Device Name characteristic will be advertised
const sl_bt_uuid_16_t device_name_characteristic_uuid = { .data = { 0x00, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
generic_access_service_handle,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0x00,
0x00,
device_name_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(advertised_name) - 1,
sizeof(advertised_name) - 1,
advertised_name,
&name_characteristic_handle);
app_assert_status(sc);

// Start the Generic Access service
sc = sl_bt_gattdb_start_service(gattdb_session_id, generic_access_service_handle);
app_assert_status(sc);

// Add my BLE service to the GATT DB
// UUID: de8a5aac-a99b-c315-0c80-60d4cbb51224
const uuid_128 my_service_uuid = {
.data = { 0x24, 0x12, 0xb5, 0xcb, 0xd4, 0x60, 0x80, 0x0c, 0x15, 0xc3, 0x9b, 0xa9, 0xac, 0x5a, 0x8a, 0xde }
};
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
sizeof(my_service_uuid),
my_service_uuid.data,
&my_service_handle);
app_assert_status(sc);

然后,您为该服务设置特征。如您所见,您还使用了之前定义的 UUID,并且需要将特征的属性作为参数传递。在这种情况下,它们是:READ 和 NOTIFY。

// Add the 'Notify' characteristic to my BLE service
// UUID: 61a885a4-41c3-60d0-9a53-6d652a70d29c
const uuid_128 btn_report_characteristic_uuid = {
.data = { 0x9c, 0xd2, 0x70, 0x2a, 0x65, 0x6d, 0x53, 0x9a, 0xd0, 0x60, 0xc3, 0x41, 0xa4, 0x85, 0xa8, 0x61 }
};
uint8_t notify_char_init_value = 0;
sc = sl_bt_gattdb_add_uuid128_characteristic(gattdb_session_id,
my_service_handle,
SL_BT_GATTDB_CHARACTERISTIC_READ | SL_BT_GATTDB_CHARACTERISTIC_NOTIFY,
0x00,
0x00,
btn_report_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
1, // max length
sizeof(notify_char_init_value), // initial value length
&notify_char_init_value, // initial value
&notify_characteristic_handle);

// Start my BLE service
sc = sl_bt_gattdb_start_service(gattdb_session_id, my_service_handle);
app_assert_status(sc);

// Commit the GATT DB changes
sc = sl_bt_gattdb_commit(gattdb_session_id);
app_assert_status(sc);

创建特征后,您可以使用 sl_bt_gatt_server_notify_all() 方法设置其值。在这种情况下,我们将值设置为文本"Hello World"。您可以将此文本更改为任何您喜欢的内容。在未来的项目中,此文本可以是传感器读数或灯的状态等。

最后,您可以启动服务和广播,以便其他 BLE 设备可以扫描并找到此 BLE 设备。

// Start advertising
ble_start_advertising();

这只是一个如何创建BLE服务器的简单示例。该程序的功能是每两秒发送一次通知,内容为"Hello World"。

BLE传感器数据交换

接下来,我们将进入实际应用来完成一个案例。在这个案例中,我们将使用XIAO MG24的getCPUTemp()函数来测量当前MCU的温度,然后通过蓝牙将MCU的温度值发送到另一个XIAO MG24,以模拟健康温度计。

我们需要准备两个XIAO,一个作为服务器,一个作为客户端。以下是作为服务器的示例程序。作为服务器的XIAO具有以下主要任务:

  • 首先,使用getCPUTemp()函数获取MCU的当前温度;
  • 其次,创建蓝牙服务器;
  • 第三,通过蓝牙广播温度值;
  • 第四,显示实时温度。
// server

/*
BLE health thermometer example

The example implements a minimal BLE Health Thermometer profile to provide temperature measurements over BLE

On startup the sketch will start a BLE advertisement with the configured name, then
it will accept any incoming connection. When a device is connected and enables indications for the
health thermometer characteristic, then the device will send it's CPU temperature readings as thermometer data.
With the EFR Connect app you can test this functionality by going to the "Demo" tab and selecting "Health Thermometer".
Alternatively, you can test this example by flashing an other BLE board with the 'ble_health_thermometer_client' demo
and have the two boards exchange the temperature measurements over BLE.

Find out more on the API usage at: https://docs.silabs.com/bluetooth/latest/bluetooth-stack-api/

This example only works with the 'BLE (Silabs)' protocol stack variant.

You can test the thermometer device with the EFR Connect app:
- https://play.google.com/store/apps/details?id=com.siliconlabs.bledemo
- https://apps.apple.com/us/app/efr-connect-ble-mobile-app/id1030932759

Compatible boards:
- Arduino Nano Matter
- SparkFun Thing Plus MGM240P
- xG27 DevKit
- xG24 Explorer Kit
- xG24 Dev Kit
- BGM220 Explorer Kit
- Ezurio Lyra 24P 20dBm Dev Kit
- Seeed Studio XIAO MG24 (Sense)

Author: Tamas Jozsi (Silicon Labs)
*/

#define RF_SW_PW_PIN PB5
#define RF_SW_PIN PB4

static void handle_temperature_indication();
static void ble_initialize_gatt_db();
static void ble_start_advertising();

const uint8_t advertised_name[] = "XIAOMG24_BLE";
uint8_t connection_handle = 0u;
uint16_t temp_measurement_characteristic_handle = 0u;
bool indication_enabled = false;

void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
Serial.begin(115200);

// turn on this antenna function
pinMode(RF_SW_PW_PIN, OUTPUT);
digitalWrite(RF_SW_PW_PIN, HIGH);

delay(100);

// HIGH -> Use external antenna / LOW -> Use built-in antenna
pinMode(RF_SW_PIN, OUTPUT);
digitalWrite(RF_SW_PIN, LOW);
}

void loop()
{
handle_temperature_indication();
}

/**************************************************************************//**
* Sends a BLE indication with the current temperature to the connected device
* if enabled, then waits for a second
*****************************************************************************/
static void handle_temperature_indication()
{
// Return immediately if indications are not enabled
if (!indication_enabled) {
return;
}

// Get the current CPU temperature
float temperature = getCPUTemp();

// Convert the temperature to an IEEE 11073 float value
int32_t millicelsius = (int32_t)(temperature * 1000);
uint8_t buffer[5];
uint32_t tmp_value = ((uint32_t)millicelsius & 0x00ffffffu) | ((uint32_t)(-3) << 24);
buffer[0] = 0;
buffer[1] = tmp_value & 0xff;
buffer[2] = (tmp_value >> 8) & 0xff;
buffer[3] = (tmp_value >> 16) & 0xff;
buffer[4] = (tmp_value >> 24) & 0xff;

// Send the indication
sl_bt_gatt_server_send_indication(connection_handle, temp_measurement_characteristic_handle, sizeof(buffer), buffer);

// Log the temperature
Serial.print("Temperature indication sent - current temperature: ");
Serial.print(temperature);
Serial.println(" C");

// Wait for a second
delay(1000);
}

/**************************************************************************//**
* Bluetooth stack event handler
* Called when an event happens on BLE the stack
*
* @param[in] evt Event coming from the Bluetooth stack
*****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t *evt)
{
switch (SL_BT_MSG_ID(evt->header)) {
// This event is received when the BLE device has successfully booted
case sl_bt_evt_system_boot_id:
{
// Print a welcome message
Serial.begin(115200);
Serial.println();
Serial.println("Silicon Labs BLE health thermometer example");
Serial.println("BLE stack booted");
// Initialize the application specific GATT DB
ble_initialize_gatt_db();
// Start advertising
ble_start_advertising();
}
break;

// This event is received when a BLE connection has been opened
case sl_bt_evt_connection_opened_id:
// Store the connection handle which will be needed for sending indications
connection_handle = evt->data.evt_connection_opened.connection;
Serial.println("Connection opened");
digitalWrite(LED_BUILTIN, LED_BUILTIN_ACTIVE);
break;

// This event is received when a BLE connection has been closed
case sl_bt_evt_connection_closed_id:
// Reset stored values
connection_handle = 0u;
indication_enabled = false;
Serial.println("Connection closed");
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
// Restart the advertisement
ble_start_advertising();
break;

// This event is received when a GATT characteristic status changes
case sl_bt_evt_gatt_server_characteristic_status_id:
{
// If the temperature measurement characteristic has been changed
if (evt->data.evt_gatt_server_characteristic_status.characteristic == temp_measurement_characteristic_handle) {
uint16_t client_config_flags = evt->data.evt_gatt_server_characteristic_status.client_config_flags;
uint8_t status_flags = evt->data.evt_gatt_server_characteristic_status.status_flags;
if ((client_config_flags == 0x02) && (status_flags == 0x01)) {
// If indication was enabled (0x02) in the client config flags, and the status flag shows that it's a change
Serial.println("Temperature indication enabled");
indication_enabled = true;
} else if ((client_config_flags == 0x00) && (status_flags == 0x01)) {
// If indication was disabled (0x00) in the client config flags, and the status flag shows that it's a change
Serial.println("Temperature indication disabled");
indication_enabled = false;
}
}
}
break;

// Default event handler
default:
Serial.print("BLE event: 0x");
Serial.println(SL_BT_MSG_ID(evt->header), HEX);
break;
}
}

/**************************************************************************//**
* Starts BLE advertisement
* Initializes advertising if it's called for the first time
*****************************************************************************/
static void ble_start_advertising()
{
static uint8_t advertising_set_handle = 0xff;
static bool init = true;
sl_status_t sc;

if (init) {
// Create an advertising set
sc = sl_bt_advertiser_create_set(&advertising_set_handle);
app_assert_status(sc);

// Set advertising interval to 100ms
sc = sl_bt_advertiser_set_timing(
advertising_set_handle,
160, // minimum advertisement interval (milliseconds * 1.6)
160, // maximum advertisement interval (milliseconds * 1.6)
0, // advertisement duration
0); // maximum number of advertisement events
app_assert_status(sc);

init = false;
}

// Generate data for advertising
sc = sl_bt_legacy_advertiser_generate_data(advertising_set_handle, sl_bt_advertiser_general_discoverable);
app_assert_status(sc);

// Start advertising and enable connections
sc = sl_bt_legacy_advertiser_start(advertising_set_handle, sl_bt_advertiser_connectable_scannable);
app_assert_status(sc);

Serial.print("Started advertising as '");
Serial.print((const char*)advertised_name);
Serial.println("'...");
}

/**************************************************************************//**
* Initializes the GATT database
* Creates a new GATT session and adds certain services and characteristics
*****************************************************************************/
static void ble_initialize_gatt_db()
{
sl_status_t sc;
uint16_t gattdb_session_id;
uint16_t service_handle;
uint16_t device_name_characteristic_handle;
uint16_t temp_type_characteristic_handle;

// Create a new GATT database
sc = sl_bt_gattdb_new_session(&gattdb_session_id);
app_assert_status(sc);

// Generic Access service
const uint8_t generic_access_service_uuid[] = { 0x00, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
sizeof(generic_access_service_uuid),
generic_access_service_uuid,
&service_handle);
app_assert_status(sc);

// Device Name characteristic
const sl_bt_uuid_16_t device_name_characteristic_uuid = { .data = { 0x00, 0x2A } };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service_handle,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0x00,
0x00,
device_name_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
sizeof(advertised_name) - 1,
sizeof(advertised_name) - 1,
advertised_name,
&device_name_characteristic_handle);
app_assert_status(sc);

sc = sl_bt_gattdb_start_service(gattdb_session_id, service_handle);
app_assert_status(sc);

// Health Thermometer service
const uint8_t thermometer_service_uuid[] = { 0x09, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
sizeof(thermometer_service_uuid),
thermometer_service_uuid,
&service_handle);
app_assert_status(sc);

// Temperature Measurement characteristic
const sl_bt_uuid_16_t temp_measurement_characteristic_uuid = { .data = { 0x1C, 0x2A } };
uint8_t temp_initial_value[5] = { 0, 0, 0, 0, 0 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service_handle,
SL_BT_GATTDB_CHARACTERISTIC_INDICATE,
0x00,
0x00,
temp_measurement_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
5,
5,
temp_initial_value,
&temp_measurement_characteristic_handle);
app_assert_status(sc);

// Temperature Type characteristic
const sl_bt_uuid_16_t temp_type_characteristic_uuid = { .data = { 0x1D, 0x2A } };
// Temperature type: body (2)
uint8_t temp_type_initial_value = 2;
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service_handle,
SL_BT_GATTDB_CHARACTERISTIC_READ,
0x00,
0x00,
temp_type_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
1,
1,
&temp_type_initial_value,
&temp_type_characteristic_handle);
app_assert_status(sc);

// Start the Health Thermometer service
sc = sl_bt_gattdb_start_service(gattdb_session_id, service_handle);
app_assert_status(sc);

// Commit the GATT DB changes
sc = sl_bt_gattdb_commit(gattdb_session_id);
app_assert_status(sc);
}

#ifndef BLE_STACK_SILABS
#error "This example is only compatible with the Silicon Labs BLE stack. Please select 'BLE (Silabs)' in 'Tools > Protocol stack'."
#endif

为其中一个 XIAO 上传程序后,如果程序运行顺利,那么您可以拿出手机并使用 nRF Connect APP 搜索名为 XIAOMG24_BLE 的蓝牙设备,连接它,然后点击下面显示的按钮,您将收到温度数据信息。

接下来,我们需要拿出另一个 XIAO,它充当客户端来收集和显示我们的数据。

// client

/*
BLE health thermometer client example

The example connects to another board running the 'BLE Health Thermometer' example and reads the temperature through BLE

On startup the sketch will start a scanning for the other board running the 'ble_health_thermometer' example and
advertising as "Thermometer Example". Once the other board is found, it establishes a connection,
discovers it's services and characteristics, then subscribes to the temperature measurements.
After the subscription the example starts receiving the temperature data from the other board periodically,
and prints it to Serial.

Find out more on the API usage at: https://docs.silabs.com/bluetooth/latest/bluetooth-stack-api/

This example only works with the 'BLE (Silabs)' protocol stack variant.

Compatible boards:
- Arduino Nano Matter
- SparkFun Thing Plus MGM240P
- xG27 DevKit
- xG24 Explorer Kit
- xG24 Dev Kit
- BGM220 Explorer Kit
- Ezurio Lyra 24P 20dBm Dev Kit
- Seeed Studio XIAO MG24 (Sense)

Author: Tamas Jozsi (Silicon Labs)
*/

#define RF_SW_PW_PIN PB5
#define RF_SW_PIN PB4

void setup()
{
pinMode(LED_BUILTIN, OUTPUT);
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
Serial.begin(115200);

// turn on this antenna function
pinMode(RF_SW_PW_PIN, OUTPUT);
digitalWrite(RF_SW_PW_PIN, HIGH);

delay(100);

// HIGH -> Use external antenna / LOW -> Use built-in antenna
pinMode(RF_SW_PIN, OUTPUT);
digitalWrite(RF_SW_PIN, LOW);
}

void loop()
{
}

// Connection states
enum conn_state_t {
ST_BOOT,
ST_SCAN,
ST_CONNECT,
ST_SERVICE_DISCOVER,
ST_CHAR_DISCOVER,
ST_REQUEST_INDICATION,
ST_RECEIVE_DATA
};

// IEEE 11073 float structure
typedef struct {
uint8_t mantissa_l;
uint8_t mantissa_m;
int8_t mantissa_h;
int8_t exponent;
} IEEE_11073_float;

static bool find_complete_local_name_in_advertisement(sl_bt_evt_scanner_legacy_advertisement_report_t *response);
static float translate_IEEE_11073_temperature_to_float(IEEE_11073_float const *IEEE_11073_value);

const uint8_t thermometer_service_uuid[] = { 0x09, 0x18 };
const sl_bt_uuid_16_t temp_measurement_characteristic_uuid = { .data = { 0x1C, 0x2A } };
const uint8_t advertised_name[] = "XIAOMG24_BLE";

uint32_t thermometer_service_handle = __UINT32_MAX__;
uint16_t temp_measurement_char_handle = __UINT16_MAX__;
conn_state_t connection_state = ST_BOOT;

/**************************************************************************//**
* Bluetooth stack event handler
* Called when an event happens on BLE the stack
*
* @param[in] evt Event coming from the Bluetooth stack
*****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t *evt)
{
sl_status_t sc;

switch (SL_BT_MSG_ID(evt->header)) {
// This event is received when the BLE device has successfully booted
case sl_bt_evt_system_boot_id:
// Print a welcome message
Serial.println();
Serial.println("Silicon Labs BLE health thermometer client example");
Serial.println("BLE stack booted");
// Start scanning for other BLE devices
sc = sl_bt_scanner_set_parameters(sl_bt_scanner_scan_mode_active, 16, 16);
app_assert_status(sc);
sc = sl_bt_scanner_start(sl_bt_scanner_scan_phy_1m,
sl_bt_scanner_discover_generic);
app_assert_status(sc);
Serial.println("Started scanning...");
connection_state = ST_SCAN;
break;

// This event is received when we scan the advertisement of another BLE device
case sl_bt_evt_scanner_legacy_advertisement_report_id:
Serial.println("BLE scan report received");
// If we find the other devices's name
if (find_complete_local_name_in_advertisement(&(evt->data.evt_scanner_legacy_advertisement_report))) {
Serial.println("Target device found");
// Stop scanning
sc = sl_bt_scanner_stop();
app_assert_status(sc);
// Connect to the device
sc = sl_bt_connection_open(evt->data.evt_scanner_legacy_advertisement_report.address,
evt->data.evt_scanner_legacy_advertisement_report.address_type,
sl_bt_gap_phy_1m,
NULL);
app_assert_status(sc);
connection_state = ST_CONNECT;
}
break;

// This event is received when a BLE connection has been opened
case sl_bt_evt_connection_opened_id:
Serial.println("Connection opened");
digitalWrite(LED_BUILTIN, LED_BUILTIN_ACTIVE);
// Discover Health Thermometer service on the connected device
sc = sl_bt_gatt_discover_primary_services_by_uuid(evt->data.evt_connection_opened.connection,
sizeof(thermometer_service_uuid),
thermometer_service_uuid);
app_assert_status(sc);
connection_state = ST_SERVICE_DISCOVER;
break;

// This event is received when a BLE connection has been closed
case sl_bt_evt_connection_closed_id:
Serial.println("Connection closed");
digitalWrite(LED_BUILTIN, LED_BUILTIN_INACTIVE);
// Restart scanning
sc = sl_bt_scanner_start(sl_bt_scanner_scan_phy_1m,
sl_bt_scanner_discover_generic);
app_assert_status(sc);
Serial.println("Restarted scanning...");
connection_state = ST_SCAN;
break;

// This event is generated when a new service is discovered
case sl_bt_evt_gatt_service_id:
Serial.println("GATT service discovered");
// Store the handle of the discovered Thermometer Service
thermometer_service_handle = evt->data.evt_gatt_service.service;
break;

// This event is generated when a new characteristic is discovered
case sl_bt_evt_gatt_characteristic_id:
Serial.println("GATT charactersitic discovered");
// Store the handle of the discovered Temperature Measurement characteristic
temp_measurement_char_handle = evt->data.evt_gatt_characteristic.characteristic;
break;

// This event is received when a GATT procedure completes
case sl_bt_evt_gatt_procedure_completed_id:
Serial.println("GATT procedure completed");

if (connection_state == ST_SERVICE_DISCOVER) {
Serial.println("GATT service discovery finished");
// Discover thermometer characteristic on the connected device
sc = sl_bt_gatt_discover_characteristics_by_uuid(evt->data.evt_gatt_procedure_completed.connection,
thermometer_service_handle,
sizeof(temp_measurement_characteristic_uuid.data),
temp_measurement_characteristic_uuid.data);
app_assert_status(sc);
connection_state = ST_CHAR_DISCOVER;
break;
}

if (connection_state == ST_CHAR_DISCOVER) {
Serial.println("GATT characteristic discovery finished");
// Enable temperature measurement indications
sc = sl_bt_gatt_set_characteristic_notification(evt->data.evt_gatt_procedure_completed.connection,
temp_measurement_char_handle,
sl_bt_gatt_indication);
app_assert_status(sc);
connection_state = ST_REQUEST_INDICATION;
break;
}

if (connection_state == ST_REQUEST_INDICATION) {
Serial.println("Temperature measurement indications enabled");
connection_state = ST_RECEIVE_DATA;
}
break;

// This event is received when a characteristic value was received (like an indication)
case sl_bt_evt_gatt_characteristic_value_id:
{
Serial.println("GATT data received");
// Get the received data from the event
uint8_t* char_value = &(evt->data.evt_gatt_characteristic_value.value.data[0]);
// Convert it back to float
float temperature = translate_IEEE_11073_temperature_to_float((IEEE_11073_float *)(char_value + 1));
// Print to Serial
Serial.print("Received temperature: ");
Serial.print(temperature);
Serial.println(" C");

sc = sl_bt_gatt_send_characteristic_confirmation(evt->data.evt_gatt_characteristic_value.connection);
app_assert_status(sc);
}
break;

// Default event handler
default:
Serial.print("BLE event: 0x");
Serial.println(SL_BT_MSG_ID(evt->header), HEX);
break;
}
}

/**************************************************************************//**
* Finds a configured name in BLE advertisements
*
* @param[in] response BLE response event received from scanning
*
* @return true if found, false otherwise
*****************************************************************************/
static bool find_complete_local_name_in_advertisement(sl_bt_evt_scanner_legacy_advertisement_report_t *response)
{
int i = 0;
bool found = false;

// Go through the response data
while (i < (response->data.len - 1)) {
uint8_t advertisement_length = response->data.data[i];
uint8_t advertisement_type = response->data.data[i + 1];

// Type 0x09 = Complete Local Name, 0x08 Shortened Name
// If the field type matches the Complete Local Name
if (advertisement_type == 0x09) {
// Check if device name matches
if (memcmp(response->data.data + i + 2, advertised_name, strlen((const char*)advertised_name)) == 0) {
found = true;
break;
}
}
// Jump to next advertisement record
i = i + advertisement_length + 1;
}
return found;
}

/**************************************************************************//**
* Translates a IEEE-11073 temperature value to float
*
* @param[in] IEEE_11073_value the IEEE 11073 float value to convert
*
* @return the converted value in float, NAN on failure
*****************************************************************************/
static float translate_IEEE_11073_temperature_to_float(IEEE_11073_float const *IEEE_11073_value)
{
int32_t mantissa = 0;
uint8_t mantissa_l;
uint8_t mantissa_m;
int8_t mantissa_h;
int8_t exponent;

// Wrong Argument: NULL pointer is passed
if (!IEEE_11073_value) {
return NAN;
}

// Caching Fields
mantissa_l = IEEE_11073_value->mantissa_l;
mantissa_m = IEEE_11073_value->mantissa_m;
mantissa_h = IEEE_11073_value->mantissa_h;
exponent = IEEE_11073_value->exponent;

// IEEE-11073 Standard NaN Value Passed
if ((mantissa_l == 0xFF) && (mantissa_m == 0xFF) && (mantissa_h == 0x7F) && (exponent == 0x00)) {
return NAN;
}

// Converting a 24bit Signed Value to a 32bit Signed Value
mantissa |= mantissa_h;
mantissa <<= 8;
mantissa |= mantissa_m;
mantissa <<= 8;
mantissa |= mantissa_l;
mantissa <<= 8;
mantissa >>= 8;

return ((float)mantissa) * pow(10.0f, (float)exponent);
}

#ifndef BLE_STACK_SILABS
#error "This example is only compatible with the Silicon Labs BLE stack. Please select 'BLE (Silabs)' in 'Tools > Protocol stack'."
#endif

最后,如果服务器和客户端程序运行顺利,您可以通过串口看到客户端打印的以下信息。

程序注释

对于上述程序,我们将挑选更重要的部分进行解释。我们将从服务器程序开始。

在程序开始时,我们定义了蓝牙服务器的名称,这个名称可以是您设置的名称,但您需要记住它,因为您需要依靠这个名称来搜索这个蓝牙设备。

const uint8_t advertised_name[] = "XIAOMG24_BLE";

在本教程的前面部分,我们已经讨论过在服务器下会有特征值,在特征值下会有数值和其余内容。所以我们在创建广告时需要遵循这一原则。

// Health Thermometer service
const uint8_t thermometer_service_uuid[] = { 0x09, 0x18 };
sc = sl_bt_gattdb_add_service(gattdb_session_id,
sl_bt_gattdb_primary_service,
SL_BT_GATTDB_ADVERTISED_SERVICE,
sizeof(thermometer_service_uuid),
thermometer_service_uuid,
&service_handle);
app_assert_status(sc);

// Temperature Measurement characteristic
const sl_bt_uuid_16_t temp_measurement_characteristic_uuid = { .data = { 0x1C, 0x2A } };
uint8_t temp_initial_value[5] = { 0, 0, 0, 0, 0 };
sc = sl_bt_gattdb_add_uuid16_characteristic(gattdb_session_id,
service_handle,
SL_BT_GATTDB_CHARACTERISTIC_INDICATE,
0x00,
0x00,
temp_measurement_characteristic_uuid,
sl_bt_gattdb_fixed_length_value,
5,
5,
temp_initial_value,
&temp_measurement_characteristic_handle);
app_assert_status(sc);

在上述程序中,您可以看到 sl_bt_gattdb_add_service() 用于创建服务器。参数是一个特定的 UUID:0x1809。在 GATT 规则中,0x1809 表示温度计类型数据,相同特征的 UUID:0x2A1C 也有特殊含义。在 GATT 中,它表示温度测量。这符合我们温度值的情况,所以我在这里将其定义为这样。您可以在这里阅读 GATT 为我们准备的一些特定 UUID 的含义。

当然,您也可以不遵循 GATT 标准来设置 UUID,您只需要确保这两个值是唯一的,并且不会影响您的客户端通过识别这些 UUID 来找到值的能力。您可以访问 uuidgenerator.net 为您的服务和特征创建随机 UUID。

最后,我们在 loop 中每秒测量并广播一次 MCU 的温度值。

下一步是客户端程序,这看起来会复杂得多。

在程序开始时,仍然是非常熟悉的内容。您需要确保此内容与您在服务器端配置的内容一致。

const uint8_t thermometer_service_uuid[] = { 0x09, 0x18 };
const sl_bt_uuid_16_t temp_measurement_characteristic_uuid = { .data = { 0x1C, 0x2A } };
const uint8_t advertised_name[] = "XIAOMG24_BLE";

接下来,我们将编写一个蓝牙协议栈事件处理函数,主要处理各种蓝牙事件触发的回调任务,包括蓝牙设备的初始化、蓝牙的连接和断开连接,以及搜索附近的蓝牙设备。

/**************************************************************************//**
* Bluetooth stack event handler
* Called when an event happens on BLE the stack
*
* @param[in] evt Event coming from the Bluetooth stack
*****************************************************************************/
void sl_bt_on_event(sl_bt_msg_t *evt)

以下过程是在服务器中查找温度值的关键。首先,在我们成功定位服务器UUID并找到服务器下的特征UUID后,我们将处理获得的数据,如下面的代码片段所示。最后,通过串口打印出处理后的数据。这种解析方法与蓝牙的数据结构是一一对应的。

void sl_bt_on_event(sl_bt_msg_t *evt)
{
sl_status_t sc;

switch (SL_BT_MSG_ID(evt->header)) {

...

// This event is received when a characteristic value was received (like an indication)
case sl_bt_evt_gatt_characteristic_value_id:
{
Serial.println("GATT data received");
// Get the received data from the event
uint8_t* char_value = &(evt->data.evt_gatt_characteristic_value.value.data[0]);
// Convert it back to float
float temperature = translate_IEEE_11073_temperature_to_float((IEEE_11073_float *)(char_value + 1));
// Print to Serial
Serial.print("Received temperature: ");
Serial.print(temperature);
Serial.println(" C");

sc = sl_bt_gatt_send_characteristic_confirmation(evt->data.evt_gatt_characteristic_value.connection);
app_assert_status(sc);
}
break;

...

}
}
tip

上面的示例给出了来自Silicon Labs的单个传感器单个值的最简单示例。如果您想更深入地了解SiliconLabs BLE API的使用方法,我们建议您阅读这里的教程。

技术支持与产品讨论

感谢您选择我们的产品!我们在这里为您提供不同的支持,以确保您使用我们产品的体验尽可能顺畅。我们提供多种沟通渠道,以满足不同的偏好和需求。

Loading Comments...