关于树莓派 Pico W
Raspberry Pi Pico W 在 Pico 的基础上,增加了 Wi-Fi 和蓝牙模块。Pico W 配备英飞凌 CYW43439 无线电芯片组,该芯片组 支持 802.11n Wi-Fi 和蓝牙 5.2。在发布之初,树莓派 Pico W 虽然具备蓝牙功能,但官方 SDK 并未加入蓝牙和 Wi-Fi 功能的支持。从 C/C++ SDK 1.5.1 版本开始,可以很方便的使用 BTStack 库来开发蓝牙相关功能。树莓派 Pico W 系列包含 Pico W 和 WH ,WH 是增加了引脚焊接的 Pico W,功能和 Pico W 一致。 树莓派 Pico 没有蓝牙和 Wi-Fi 模块,所以如果你需要使用蓝牙功能,必须再购买一个 HC-05 之类的外置蓝牙模块。
蓝牙协议栈
在开始之前,我们还需要了解一下蓝牙协议相关的一些基础知识。
蓝牙分为经典蓝牙和低功耗蓝牙(BLE),目前主流是更新的低功耗蓝牙。
GAP 协议
GAP(Generic Access Profile)主要用于广播数据、设备发现以及设备间建立安全连接。
在发送接收数据之前,需要使用 GAP 协议扫描发现蓝牙设备,并通过 GAP 建立安全连接。
GAP 定义了 4 种角色:广播者(Broadcaster)、观察者(Observer)、中心设备(Central)、外围设备(peripheral),每一个设备都可以同时有一个或多个角色。
GAP层有4种不同类型的广播:通用的、定向的、不可连接的以及可发现的。设备每次广播时,会在3个广播信道上发送相同的报文。这些报文被称为一个广播事件。
例如 Beacon 设备只是向外广播,不支持连接,而小米手环等设备就可以与中心设备连接。
大部分情况下,外设通过广播自己来让中心设备发现自己,并建立 GATT 连接,从而进行更多的数据交换。
GATT 协议
GATT(Generic Attribute Profile)构建在 ATT(Attribute Protocol) 协议基础之上,为属性协议传输和存储数据建立了一些通用操作和框架。 GATT 使用属性协议作为自身传输协议来交换设备间的数据,并且定义了如何使用这些属性进行读、写、通知和指示操作。GATT 包含了许多服务和特征,每一个服务可以包含多个特征,而每一个特征也可以有多个描述符。 在 GATT 协议中,有两种设备角色:服务器(Server)和客户端(Client)。服务器保存数据,并响应来自客户端的请求,客户端则发起请求并接收响应。以小米手环为例,小米手环作为 GATT 服务器,保存着心率、步数等数据,而手机作为 GATT 客户端,读取这些数据并进行处理。 GATT 连接建立后,客户端可以通过发现服务和特征,来了解服务器上提供的所有服务。服务和特征都是通过唯一的 UUID(Universally Unique Identifier)来标识的。标准服务和特征由蓝牙 SIG(Special Interest Group)定义,如心率服务和电池电量服务;也可以定义自定义服务和特征,以满足特定应用的需求。 GATT的这两种角色,和GAP的central/peripheral角色是相互独立的,它们都能作为Client或者Server,甚至能同时作为Client和Server。在 Pico W 上使用蓝牙
下面我们通过一个简单蓝牙收发数据示例项目来学习 Pico W 的蓝牙功能。创建工程
首先,我们创建工程文件,并且拷贝cmake 文件。
mdkir pico_ble
cd pico_ble
touch pico_ble.c CMakeLists.txt
cp $PICO_SDK_PATH/external/pico_sdk_import.cmake .
配置 BTStack
树莓派 Pico C/C++ SDK 基于 BTStack 封装了蓝牙操作库 pico_btstack,相关接口可查看官方文档。
使用 BTstack 需要在源码根目录中提供一个 btstack_config.h 文件,并配置 CMakeLists.txt 将其位置添加到你的包含路径中。
btstack_config.h 如下:
#ifndef _PICO_BTSTACK_BTSTACK_CONFIG_H
#define _PICO_BTSTACK_BTSTACK_CONFIG_H
#ifndef ENABLE_BLE
#error Please link to pico_btstack_ble
#endif
// BTstack features that can be enabled
#define ENABLE_LE_PERIPHERAL
#define ENABLE_LOG_INFO
#define ENABLE_LOG_ERROR
#define ENABLE_PRINTF_HEXDUMP
// for the client
#if RUNNING_AS_CLIENT
#define ENABLE_LE_CENTRAL
#define MAX_NR_GATT_CLIENTS 1
#else
#define MAX_NR_GATT_CLIENTS 0
#endif
// BTstack configuration. buffers, sizes, ...
#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
// Limit number of ACL/SCO Buffer to use by stack to avoid cyw43 shared bus overrun
#define MAX_NR_CONTROLLER_ACL_BUFFERS 3
#define MAX_NR_CONTROLLER_SCO_PACKETS 3
// Enable and configure HCI Controller to Host Flow Control to avoid cyw43 shared bus overrun
#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
// Link Key DB and LE Device DB using TLV on top of Flash Sector interface
#define NVM_NUM_DEVICE_DB_ENTRIES 16
#define NVM_NUM_LINK_KEYS 16
// We don't give btstack a malloc, so use a fixed-size ATT DB.
#define MAX_ATT_DB_SIZE 512
// BTstack HAL configuration
#define HAVE_EMBEDDED_TIME_MS
// map btstack_assert onto Pico SDK assert()
#define HAVE_ASSERT
// Some USB dongles take longer to respond to HCI reset (e.g. BCM20702A).
#define HCI_RESET_RESEND_TIMEOUT_MS 1000
#define ENABLE_SOFTWARE_AES128
#define ENABLE_MICRO_ECC_FOR_LE_SECURE_CONNECTIONS
#endif // MICROPY_INCLUDED_EXTMOD_BTSTACK_BTSTACK_CONFIG_H
定义 GATT 服务和特征
使用 BTStack 库时,我们需要定义一个 .gatt 文件来描述 GATT 服务和特征。以下是一个读写数据服务的 gatt 文件,定义了一个三个服务,分别为设备名称读取服务、GATT 数据库哈希、读写数据服务。
有关 gatt 描述文件的详细配置请查阅 BTStack 文档。
PRIMARY_SERVICE, GAP_SERVICE
CHARACTERISTIC, GAP_DEVICE_NAME, READ, "picow_ble"
PRIMARY_SERVICE, GATT_SERVICE
CHARACTERISTIC, GATT_DATABASE_HASH, READ,
// ReadWrite Service
PRIMARY_SERVICE, AABB
// ReadWrite Characteristic, with read, write, write_without_response and notify
CHARACTERISTIC, 0000FF11-0000-1000-8000-00805F9B34FB, READ | NOTIFY | WRITE | WRITE_WITHOUT_RESPONSE | DYNAMIC,
配置 CMakeLists.txt
CMakeLists.txt 文件是项目的构建脚本,用于定义如何编译和链接项目。在使用 BTStack 库时,需要在 CMakeLists.txt 文件中添加相关配置。
# 设置Cmake 最小依赖版本
cmake_minimum_required(VERSION 3.17)
# 设置c/c++ 编译版本
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# 导入 pico sdk cmake
include(pico_sdk_import.cmake)
# ====================================================================================
# 设置开发板为 pico w (如果你的开发板是 pico 可以注释这行)
set(PICO_BOARD pico_w CACHE STRING "Board type")
# 设置项目名
project(pico_ble C CXX ASM)
# 初始化sdk
pico_sdk_init()
# 添加执行文件
add_executable(pico_ble
pico_ble.c
)
# 设置项目名称字符串
pico_set_program_name(pico_ble "pico_ble")
# 设置项目版本号
pico_set_program_version(pico_ble "0.1")
# Modify the below lines to enable/disable output over UART/USB
# 是否打开 UART 串口
pico_enable_stdio_uart(pico_ble 0)
# 是否打开USB 串口
pico_enable_stdio_usb(pico_ble 1)
# 添加依赖库 (pico w 必须加入 pico_cyw43_arch_none )
target_link_libraries(pico_ble
pico_stdlib # for core functionality
pico_btstack_ble # 蓝牙ble 支持库
pico_btstack_cyw43 # cyw43 芯片蓝牙支持库
pico_cyw43_arch_none
)
# Add the standard include files to the build
target_include_directories(pico_ble PRIVATE
${CMAKE_CURRENT_LIST_DIR} # 编译包含 btstack_config.h 文件
${CMAKE_CURRENT_LIST_DIR}/.. # for our common lwipopts or any other standard includes, if required
)
pico_btstack_make_gatt_header(pico_ble PRIVATE "${CMAKE_CURRENT_LIST_DIR}/picow_ble.gatt")
# 设置输出文件
pico_add_extra_outputs(pico_ble)
我们在 CMakeLists.txt 文件中,添加了 pico_btstack_ble 和 pico_btstack_cyw43 链接库。如果你要使用经典蓝牙则需要将 pico_btstack_ble 替换为 pico_btstack_classic。
使用 CMake 函数 pico_btstack_make_gatt_header 以运行 BTstack 的 compile_gatt 工具,从 BTstack GATT 文件生成 GATT 头文件。运行 cmake .. 命令时,会自动在 build/generated 目录下生成 .gatt 文件同名的 .h 文件。
蓝牙连接和读写数据操作
以下是一个示例程序,演示如何在 Pico W 上使用 BTStack 库进行蓝牙连接和数据读写操作。
#include <stdio.h>
#include "btstack.h"
#include "pico/cyw43_arch.h"
#include "pico/btstack_cyw43.h"
#include "pico/stdlib.h"
#include "picow_ble.h"
/*
* @section Advertisements
*
* @text The Flags attribute in the Advertisement Data indicates if a device is dual-mode or le-only.
*/
/* LISTING_START(advertisements): Advertisement data: Flag 0x02 indicates dual-mode device */
// 广播数据格式: length , type, data
const uint8_t adv_data[] = {
// Flags general discoverable
0x02, BLUETOOTH_DATA_TYPE_FLAGS, 0x02,
// Name
0x0a, BLUETOOTH_DATA_TYPE_COMPLETE_LOCAL_NAME, 'P', 'i', 'c', 'o', 'W', '-', 'B', 'L', 'E',
// Incomplete List of 16-bit Service Class UUIDs
0x03, BLUETOOTH_DATA_TYPE_INCOMPLETE_LIST_OF_16_BIT_SERVICE_CLASS_UUIDS, 0xbb, 0xaa,
};
/* LISTING_END */
uint8_t adv_data_len = sizeof(adv_data);
static btstack_timer_source_t heartbeat;
static btstack_packet_callback_registration_t hci_event_callback_registration;
// 蓝牙堆栈事件回调
static void packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
UNUSED(size);
UNUSED(channel);
bd_addr_t local_addr;
if (packet_type != HCI_EVENT_PACKET)
return;
uint8_t event_type = hci_event_packet_get_type(packet);
printf("event_type: %d\n", event_type);
switch (event_type)
{
case BTSTACK_EVENT_STATE:
if (btstack_event_state_get_state(packet) != HCI_STATE_WORKING)
return;
gap_local_bd_addr(local_addr);
printf("BTstack up and running on %s.\n", bd_addr_to_str(local_addr));
// 设置广播数据
uint16_t adv_int_min = 800;
uint16_t adv_int_max = 800;
uint8_t adv_type = 0;
bd_addr_t null_addr;
memset(null_addr, 0, 6);
gap_advertisements_set_params(adv_int_min, adv_int_max, adv_type, 0, null_addr, 0x07, 0x00);
assert(adv_data_len <= 31); // ble limitation
gap_advertisements_set_data(adv_data_len, (uint8_t *)adv_data);
// 开始广播
gap_advertisements_enable(1);
break;
case HCI_EVENT_DISCONNECTION_COMPLETE:
break;
case ATT_EVENT_CAN_SEND_NOW:
break;
case ATT_WRITE_REQUEST:
break;
case ATT_WRITE_COMMAND:
printf("ATT_WRITE_COMMAND\n");
break;
case GATT_WRITE_VALUE_OF_CHARACTERISTIC_WITHOUT_RESPONSE:
printf("GATT_WRITE_VALUE_OF_CHARACTERISTIC_WITHOUT_RESPONSE\n");
break;
case HCI_EVENT_TRANSPORT_PACKET_SENT:
printf("HCI_EVENT_TRANSPORT_PACKET_SENT\n");
break;
default:
break;
}
}
uint16_t att_read_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t offset, uint8_t * buffer, uint16_t buffer_size) {
UNUSED(connection_handle);
printf("Read callback for handle 0x%04x\n", att_handle);
return 0;
}
int att_write_callback(hci_con_handle_t connection_handle, uint16_t att_handle, uint16_t transaction_mode, uint16_t offset, uint8_t *buffer, uint16_t buffer_size) {
UNUSED(transaction_mode);
UNUSED(offset);
UNUSED(buffer_size);
printf("att_write_callback\n");
// 读取客户端写入的数据
printf("Handle: 0x%04X\n", att_handle);
printf("Data: ");
for (int i = 0; i < buffer_size; i++)
{
printf("%02X ", buffer[i]);
}
printf("\n");
return 0;
}
int main() {
// 初始化标准输入输出
stdio_init_all();
// initialize CYW43 driver architecture (will enable BT if/because CYW43_ENABLE_BLUETOOTH == 1)
if (cyw43_arch_init()) {
printf("failed to initialise cyw43_arch\n");
return -1;
}
// 初始化 BTStack
l2cap_init();
sm_init();
att_server_init(profile_data, att_read_callback, att_write_callback);
// inform about BTstack state
hci_event_callback_registration.callback = &packet_handler;
hci_add_event_handler(&hci_event_callback_registration);
// register for ATT event
att_server_register_packet_handler(packet_handler);
// 打开蓝牙
hci_power_control(HCI_POWER_ON);
// 进入主循环
while (true)
{
// sleep_ms(1000);
}
return 0;
}
这个程序初始化 BLE 并使用 .gatt 生成的 profile_data 初始化 att 服务 和设置 att 读写回调,然后使用 hci_add_event_handler 添加 BTstack state 事件回调监听,当收到 HCI_STATE_WORKING 蓝牙设备打开事件时,打开设备 GAP 可连接广播。
在 att_write_callback 回调中,会将对端发送的数据打印出来。
调试验证
编译项目,并且将 uf2 文件拖拽烧录到 树莓派 Pico W 中。
使用 Android 手机 打开 BLE调试助手搜索蓝牙设备,可以看到名称为 “PicoW-BLE” 的设备,点击”Connect”连接。选择最后一个服务并发送数据 “123”。
在 CoolTerm 上可以看到调试打印信息 Data: 31 32 33 (对应 123 Unicode 码)
如果你看到上面的打印信息,恭喜🎉,你成功使用 Pico W 完成了蓝牙读写功能的开发。
总结
通过本文,我们学习了 使用 C/C++ SDK 在 Pico W 上开发蓝牙功能。首先,我们需要导入 btstack_config.h 和编写 gatt 配置文件 ,并且在 CMakeLists.txt 中做相应的配置。接下来,我们在 pico_ble.c 文件中开发蓝牙核心功能。最后,通过调试,我们验证了蓝牙收发数据功能。
源码在 GitHub开源: https://github.com/xtcel/Raspberry_Pi_Pico_C_Tutorial/tree/master/pico_ble, 有需要的请自行取用。
参考
蓝牙协议: https://github.com/Eronwu/Getting-Started-with-Bluetooth-Low-Energy-in-Chinese
低功耗蓝牙开发手册: https://ingchips.github.io/application-notes/pg_ble_stack_cn/