第13课:蓝牙协议栈(Bluetooth LE)实战
GATT (Generic Attribute Profile) 是蓝牙低功耗(BLE)中用于数据传输的核心协议。本节将学习如何在Zephyr中创建自定义GATT服务。
首先需要为你的服务定义唯一的UUID。可以使用在线UUID生成器或遵循蓝牙SIG规范。
#define BT_UUID_CUSTOM_SERVICE_VAL \
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef0)
#define BT_UUID_CUSTOM_CHAR_VAL \
BT_UUID_128_ENCODE(0x12345678, 0x1234, 0x5678, 0x1234, 0x56789abcdef1)
使用Zephyr的蓝牙API注册你的服务:
static struct bt_uuid_128 custom_service_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_SERVICE_VAL);
static struct bt_uuid_128 custom_char_uuid = BT_UUID_INIT_128(BT_UUID_CUSTOM_CHAR_VAL);
static ssize_t read_custom_char(struct bt_conn *conn, const struct bt_gatt_attr *attr,
void *buf, uint16_t len, uint16_t offset)
{
const char *value = "Hello from Zephyr!";
return bt_gatt_attr_read(conn, attr, buf, len, offset, value, strlen(value));
}
static ssize_t write_custom_char(struct bt_conn *conn, const struct bt_gatt_attr *attr,
const void *buf, uint16_t len, uint16_t offset,
uint8_t flags)
{
printk("Received data: %s\n", (char *)buf);
return len;
}
BT_GATT_SERVICE_DEFINE(custom_service,
BT_GATT_PRIMARY_SERVICE(&custom_service_uuid),
BT_GATT_CHARACTERISTIC(&custom_char_uuid.uuid,
BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
read_custom_char, write_custom_char, NULL)
);
在应用初始化时启动蓝牙协议栈:
void main(void)
{
int err;
err = bt_enable(NULL);
if (err) {
printk("Bluetooth init failed (err %d)\n", err);
return;
}
printk("Bluetooth initialized\n");
/* 广告配置 */
struct bt_le_adv_param *adv_param = BT_LE_ADV_PARAM(
BT_LE_ADV_OPT_CONNECTABLE | BT_LE_ADV_OPT_USE_NAME,
800, /* 最小广告间隔(0.625ms单位) */
801, /* 最大广告间隔 */
NULL /* 对等设备地址 */
);
err = bt_le_adv_start(adv_param, NULL, 0, NULL, 0);
if (err) {
printk("Advertising failed to start (err %d)\n", err);
return;
}
printk("Advertising successfully started\n");
}
成功提示: 编译并烧录程序后,使用手机蓝牙扫描工具(如nRF Connect)应该能看到你的设备,并能发现自定义服务。
在手机应用商店搜索并安装"nRF Connect for Mobile"
以下是一个简单的Android应用代码片段,演示如何与Zephyr设备交互:
// 在AndroidManifest.xml中添加蓝牙权限
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> // Android 6.0+需要
// Kotlin代码片段
private val gattCallback = object : BluetoothGattCallback() {
override fun onConnectionStateChange(gatt: BluetoothGatt, status: Int, newState: Int) {
if (newState == BluetoothProfile.STATE_CONNECTED) {
gatt.discoverServices()
}
}
override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) {
if (status == BluetoothGatt.GATT_SUCCESS) {
val service = gatt.getService(UUID.fromString("12345678-1234-5678-1234-56789abcdef0"))
val characteristic = service.getCharacteristic(
UUID.fromString("12345678-1234-5678-1234-56789abcdef1"))
// 读取特征值
gatt.readCharacteristic(characteristic)
// 写入特征值
characteristic.value = "Hello from Android".toByteArray()
gatt.writeCharacteristic(characteristic)
}
}
override fun onCharacteristicRead(gatt: BluetoothGatt,
characteristic: BluetoothGattCharacteristic,
status: Int) {
val value = characteristic.getStringValue(0)
Log.d("BLE", "Received: $value")
}
}
// 连接设备
val bluetoothAdapter: BluetoothAdapter? = BluetoothAdapter.getDefaultAdapter()
val device = bluetoothAdapter?.getRemoteDevice("DEVICE_MAC_ADDRESS")
device?.connectGatt(context, false, gattCallback)
以下是一个简单的iOS应用代码片段,演示如何与Zephyr设备交互:
// 在Info.plist中添加隐私描述
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要蓝牙权限来连接设备</string>
// Swift代码片段
import CoreBluetooth
class BLEManager: NSObject, CBCentralManagerDelegate, CBPeripheralDelegate {
var centralManager: CBCentralManager!
var peripheral: CBPeripheral!
override init() {
super.init()
centralManager = CBCentralManager(delegate: self, queue: nil)
}
func centralManagerDidUpdateState(_ central: CBCentralManager) {
if central.state == .poweredOn {
central.scanForPeripherals(withServices: nil, options: nil)
}
}
func centralManager(_ central: CBCentralManager,
didDiscover peripheral: CBPeripheral,
advertisementData: [String : Any],
rssi RSSI: NSNumber) {
if peripheral.name == "Zephyr" {
self.peripheral = peripheral
central.stopScan()
central.connect(peripheral, options: nil)
}
}
func centralManager(_ central: CBCentralManager,
didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
peripheral.discoverServices(nil)
}
func peripheral(_ peripheral: CBPeripheral,
didDiscoverServices error: Error?) {
guard let services = peripheral.services else { return }
for service in services {
if service.uuid == CBUUID(string: "12345678-1234-5678-1234-56789abcdef0") {
peripheral.discoverCharacteristics(nil, for: service)
}
}
}
func peripheral(_ peripheral: CBPeripheral,
didDiscoverCharacteristicsFor service: CBService,
error: Error?) {
guard let characteristics = service.characteristics else { return }
for characteristic in characteristics {
if characteristic.uuid == CBUUID(string: "12345678-1234-5678-1234-56789abcdef1") {
// 读取特征值
peripheral.readValue(for: characteristic)
// 写入特征值
let data = "Hello from iOS".data(using: .utf8)!
peripheral.writeValue(data, for: characteristic, type: .withResponse)
}
}
}
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic,
error: Error?) {
if let value = characteristic.value {
let string = String(data: value, encoding: .utf8)
print("Received: \(string ?? "")")
}
}
}
提示: 在实际应用中,你需要处理更多的错误情况和状态变化,以上代码仅为基本示例。
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 设备不可见 | 广告未启动或参数错误 | 检查bt_le_adv_start返回值,确保蓝牙已初始化 |
| 连接后服务不可见 | UUID定义错误或服务未注册 | 检查UUID定义和服务注册代码 |
| 写入失败 | 权限设置不正确 | 确保特征值有BT_GATT_CHRC_WRITE属性和BT_GATT_PERM_WRITE权限 |
| 数据接收不完整 | MTU大小限制 | 协商更大的MTU (bt_gatt_exchange_mtu) |
| iOS设备无法发现 | 广告数据格式问题 | 确保包含完整的128位UUID在广告数据中< |