Zephyr OS 入门与实战

第13课:蓝牙协议栈(Bluetooth LE)实战

13.1 实现 GATT 服务端(自定义特征值)

GATT (Generic Attribute Profile) 是蓝牙低功耗(BLE)中用于数据传输的核心协议。本节将学习如何在Zephyr中创建自定义GATT服务。

GATT 基础概念

创建自定义服务

定义UUID

首先需要为你的服务定义唯一的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)应该能看到你的设备,并能发现自定义服务。

13.2 手机端数据交互实战

使用nRF Connect进行测试

安装nRF Connect

在手机应用商店搜索并安装"nRF Connect for Mobile"

扫描并连接设备

  1. 打开nRF Connect应用
  2. 点击"SCAN"按钮开始扫描
  3. 找到你的设备(名称通常是"Zephyr"或你在代码中设置的名称)
  4. 点击"CONNECT"按钮

与服务交互

  1. 连接后,应用会显示所有服务
  2. 找到你的自定义服务(128位UUID)
  3. 点击特征值进行读写操作

Android应用开发示例

以下是一个简单的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应用开发示例

以下是一个简单的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在广告数据中<