跳转至

蓝牙

一、简介

蓝牙低功耗(Bluetooth Low Energy,简称 BLE)是一种专为低功耗设备设计的无线通信技术,广泛应用于物联网、智能家居、可穿戴设备等领域。相较于传统蓝牙,BLE 具备更低的功耗和更快的连接速度,但传输数据量较小。它使用广播模式进行设备间的通信,支持定向和非定向广告。通过 BLE,设备可以长时间保持待机状态,并在需要时迅速建立连接,因此非常适合对能耗敏感的应用场景。

二、本教程实现的功能概述

本教程将教你如何使用 LUA 脚本快速上手 BLE 开发,并掌握 BLE 广播,客户端,服务端功能。

本教程实现的功能定义是:

三、硬件环境

3.1 Air 724UG 开发板

724UG 开发板购买链接(https://item.taobao.com/item.htm?id=614125604268)

此开发板的详细使用说明参考:Air724UG 产品手册 中的《EVB_Air724UG_AXX 开发板使用说明》,写这篇文章时最新版本的使用说明为:《EVB_Air724UG_A14 开发板使用说明》;开发板使用过程中遇到任何问题,可以直接参考这份使用说明文档。

3.2 数据通信线

Micro-B USB 数据线

3.3 PC 电脑

Windows7 及以上系统。

注意:电脑有 USB 口,并且可以正常上网!!!

四、准备软件环境

4.1 LuaTools 安装

使用说明参考:Luatools 下载和详细使用

4.2 底层 core 下载

下载底层固件,并解压

链接:https://docs.openluat.com/air724ug/luatos/firmware/

如下图所示,红框的是我们要使用到的

4.3 手机端 nRF connect 下载

nRF connect 软件下载链接

nRF connect 使用教程下载链接

五、ibeacon 基本用法

1.ibeacon 报文结构

首先我们来看看 BLE 广播报文结构体如下,需要注意的是,一个 BLE 广播包必须存在 0x01 类型的广播数据,该广播主要是用于指示当前设备能进行的一些行为。因此 AD Structure 1 必须是 0x01 类型广播。

接下来的 AD Structure 2 就是 ibeacon 的数据包格式。从下图我们即可看出,我们正在要关心和修改的地方只有如下四个。对于一部分人而言,Company ID 甚至也不需要管。

  • Company ID : 设备公司名
  • UUID : 用于唯一标识应用场景
  • Major : 用户自定义的主要值,用于分组或区域标识
  • Minor : 用户自定义的次要值,用于更精细的分组或区域标识

2.API 介绍

本文用到的接口函数不做详细介绍,可通过点击右侧链接查看具体介绍:btcore API

3.源码和工具

4.BLE 广播实现 ibeacon

烧录 demo 后,BLE 广播实现 ibeacon。

核心代码

local function advertising()

    log.info("bt", "advertising")
    --btcore.setadvparam(0x80,0xa0,0,0,0x07,0,0,"11:22:33:44:55:66") --广播参数设置 (最小广播间隔,最大广播间隔,广播类型,广播本地地址类型,广播channel map,广播过滤策略,定向地址类型,定向地址)
    btcore.setbeacondata("AB8190D5D11E4941ACC442F30510B408",10107,50179) --beacon设置  (uuid,major,minor),默认公司为 Microsof
    -- btcore.setadvdata(string.fromHex("0201061AFF06000215AB8190D5D11E4941ACC442F30510B408277BC403C5")) --通过设置广播包接口设置 beacon, Microsof 公司
    -- btcore.setadvdata(string.fromHex("0201061AFF4C000215AB8190D5D11E4941ACC442F30510B408277BC403C5")) -- 通过设置广播包接口设置 beacon,公司修改为 apple
    btcore.advertising(1)-- 打开广播
end

效果展示。下图中的的是 nRF 中的效果,我们可以看到 UUID 、Major、Minor 即为我们设置的值。

当蓝牙初始化成功后,会打印 [bt] init,并且每秒打印一次 “hello world”表示程序正在运行。

六、服务端基本用法

1.服务端介绍

BLE(蓝牙低能耗)服务端是指提供特定服务和特性的设备,它通过 GATT(通用属性配置文件)架构与客户端(如智能手机或其他 BLE 设备)进行通信。服务端定义了一组特征,每个特征包含一个或多个数据点,并可以设置属性(如读、写、通知等)。常见的应用场景包括健康监测设备、智能家居和工业自动化。BLE 服务端的实现通常涉及初始化 BLE 堆栈、创建服务和特征、设置回调函数以处理客户端请求,并通过广播进行连接。通过这种方式,服务端能够有效地与多个客户端进行数据交互,实现实时监控和控制。

2.API 介绍

本文用到的接口函数不做详细介绍,可通过点击右侧链接查看具体介绍:btcore API

3.源码和工具

4.服务端实现

烧录 demo 后,BLE 即可实现。实现服务端的步骤如下:

  1. 使用 rtos.on() 函数创建外部消息的处理函数
  2. 启动 BLE 服务端模式
  3. 设置广播数据、 GATT 服务、广播参数,启动广播
  4. 等待连接成功后轮询收到的蓝牙数据,将所有收到的数据拼接起来直至没有数据退出
  5. 将收到的数据打印出来,并且返回到 UUID 0xfee2 的特征中进行显示。如果收到的数据为 "close" 那么断开连接

核心代码

--自定义服务
local function service(uuid,struct)
    btcore.addservice(uuid) --添加服务
    for i = 1, #struct do
        btcore.addcharacteristic(struct[i][1],struct[i][2],struct[i][3]) --添加特征
        if(type(struct[i][4]) == "table") then
            for j = 1,#struct[i][4] do
                btcore.adddescriptor(struct[i][4][j][1],struct[i][4][j][2])  --添加描述
            end
        end
    end
end

local function advertising()
    local struct1 = {{0xfee1, 0x08, 0x0002},
       {0xfee2, 0x10,0x0001, {{0x2902,0x0001},{0x2901,"123456"}}}}--{特征uuid,特征属性,特征权限,{特征描述uuid,描述属性}}
    local struct2 = {{"9ecadc240ee5a9e093f3a3b50200406e",0x10,0x0001,{{0x2902,0x0001}}},
                 {"9ecadc240ee5a9e093f3a3b50300406e",0x0c, 0x0002}}

    log.info("bt", "advertising")
    btcore.setname("Luat_Air724UG")-- 设置广播名称
    btcore.setadvdata(string.fromHex("02010604ff000203")) -- 设置 BLE 广播包数
    --btcore.setscanrspdata(string.fromHex("04ff000203")) -- 设置 BLE 扫描响应包数据
    service(0xfee0, struct1) -- 添加 16bit 自定义服务 uuid,该服务服务默认存在
    service("9ecadc240ee5a9e093f3a3b50100406e",struct2) -- 添加服务128bit uuid 自定义服务
    --btcore.addwhitelist("11:22:33:44:55:66",0) -- 增加白名单
    --btcore.setadvparam(0x80,0xa0,0,0,0x07,2,0,"11:22:33:44:55:66") -- 广播参数设置 (最小广播间隔,最大广播间隔,广播类型,广播本地地址类型,广播channel map,广播过滤策略,定向地址类型,定向地址)
    btcore.advertising(1) -- 打开广播
end

local function data_trans()

    advertising() -- 启动广播功能
    _, bt_connect = sys.waitUntil("BT_CONNECT_IND") -- 等待连接成功
    if bt_connect.result ~= 0 then
        return false    
    end
    -- 连接成功
    log.info("bt.connect_handle",bt_connect.handle) -- 连接句柄
    log.info("bt.send", "Hello I'm Luat BLE")
    while true do
        _, bt_recv = sys.waitUntil("BT_DATA_IND") -- 等待接收到数据
        local data = ""
        local len = 0
        local uuid = ""
        while true do
            local recvuuid, recvdata, recvlen = btcore.recv(3) -- 每次最大读取 3 字节蓝牙数据
            if recvlen == 0 then -- 如果没有读取到数据,直接返回
                break
            end
            uuid = recvuuid -- 数据来源的 UUID
            len = len + recvlen -- 实际读到的数据内容长度递增
            data = data .. recvdata -- 将实际读到的数据内容进行拼接
        end
        if len ~= 0 then -- 如果实际读到的数据内容长度不为 0,则打印出数据内容、数据长度和来源 UUID
            log.info("bt.recv_data", data) -- 将读取到的数据打印出来
            log.info("bt.recv_data len", len) -- 将读取到的数据长度打印出来
            log.info("bt.recv_uuid", string.toHex(uuid)) -- 将读取到的数据来源 UUID 以 16 进制形式打印出来
            if data == "close" then -- 如果收到的数据为 "close",则断开连接
                btcore.disconnect() -- 主动断开连接
            end
            btcore.send(data, 0xfee2, bt_connect.handle)-- 将数据发送到 0xfee2 特征中
            --btcore.send(data, "9ecadc240ee5a9e093f3a3b50200406e", bt_connect.handle)--发送数据(数据 对应特征uuid 连接句柄)
        end
    end
end

效果展示

打开 nRF connect 点击扫描能够出现名为 Luat_Air724UG 的设备。

在 nRF connect 中点击 CONNECT 之后,我们可以在 LuaTools 中看到如下打印信息。

并且在 nRF connect 出现如下界面。

按照从左到右的顺序进行操作,目的是让客户端给 Air724UG 发送 0x112233 这个 3 byte 数据。

之后我们在 LuaTools 中可以看到如下图中的打印信息

  • “hello world”是固件中设置的每秒 1s 的打印。
  • bt.recv_data recvlen 表示每次读取了多少字节数据
  • bt.recv_data recvdata 表示每次接收到的数据,因为 0x11 和 0x22 从 ascii 码中可知是特殊字符,0x33 是字符 3,因此打印内容如下。
  • bt.recv_data 是总共接收到的数据内容。
  • bt.recv_data len 表示总共接收到的字节数据长度。
  • bt.recv_uuid 表示对端 UUID 号

同时可以在 nRF connect 中可以看到如下图的信息。0xFEE2 是 btcore.send(data, 0xfee2, bt_connect.handle) 的回包数据。而 0xFEE1 表示你成功发送出来的数据。

当我们在 nRF connect 中输入 close 字符。

此时我们可以在 LuaTools 中看到如下的打印的信息。

并且在 nRF connect 看到断开连接的信息。

七、客户端基本用法

1.客户端介绍

蓝牙低功耗(BLE)客户端是一种在 BLE 通信中用于扫描和连接 BLE 服务器(外设)的设备。客户端可以发现附近的 BLE 设备,建立连接,并与之交换数据。它通常用于需要低功耗、短距离数据传输的应用,如健康监测设备、智能家居传感器或穿戴设备。通过 BLE 客户端,应用程序能够读取或写入服务器上的特性,实现设备间的实时通信和控制。

2.API 介绍

本文用到的接口函数不做详细介绍,可通过点击右侧链接查看具体介绍:btcore API

3.源码和工具

4.客户端实现

烧录 demo 后,BLE 即可实现。实现客户端的步骤如下:

  1. 使用 rtos.on() 函数创建外部消息的处理函数
  2. btcore.open(1) 启动 BLE 客户端模式
  3. 设置扫描参数,以及要扫描的目标设备名称
  4. 如果扫描到目标设备,打印设备名称,对端设备 MAC 地址,信号强度等信息,并进行连接。

核心代码

local function scan()
    log.info("bt", "scan")
    -- 主动扫描,扫描间隔 48*0.625ms=30ms,扫描窗口 6*0.625ms=3.75ms,接收所有广播包和扫描回应包(除定向广播),公共地址
    btcore.setscanparam(1,48,6,0,0)-- 扫描参数设置(扫描类型,扫描间隔,扫描窗口,扫描过滤策略,本地地址类型)
    btcore.scan(1) -- 开启扫描
    _, result = sys.waitUntil("BT_SCAN_OPEN_CNF", 50000) -- 等待扫描打开成功
    if result ~= 0 then
        return false
    end
    sys.timerStart(
        function()
            sys.publish("BT_SCAN_IND", nil)
        end,
        10000)  
    while true do
        _, bt_device = sys.waitUntil("BT_SCAN_IND") -- 等待扫描回复数据
        if not bt_device then
            -- 超时结束
            btcore.scan(0) --停止扫描
            return false
        else
            log.info("bt", "scan result")
            log.info("bt.scan_name", bt_device.name)  -- 对端蓝牙设备名称
            log.info("bt.rssi", bt_device.rssi)  -- 对端蓝牙设备信号强度
            log.info("bt.addr_type", bt_device.addr_type) -- 对端蓝牙设备地址类型
            log.info("bt.scan_addr", bt_device.addr) -- 对端蓝牙设备地址
            if bt_device.manu_data ~= nil then
                log.info("bt.manu_data", string.toHex(bt_device.manu_data)) -- 如果有厂商数据,那么打印厂商数据
            end
            log.info("bt.raw_len", bt_device.raw_len) -- 扫描到的原始广播包数据长度
            if bt_device.raw_data ~= nil then
                log.info("bt.raw_data", string.toHex(bt_device.raw_data)) -- 广播包原始数据
            end

            --蓝牙连接   根据设备蓝牙广播数据协议解析广播原始数据(bt_device.raw_data)
            if (bt_device.name == "Luat_Air724UG") then   -- 如果扫描到的蓝牙设备名称为"Luat_Air724UG"则连接
                name = bt_device.name
                addr_type = bt_device.addr_type
                addr = bt_device.addr
                manu_data = bt_device.manu_data
                adv_data = bt_device.raw_data -- 广播包数据 根据蓝牙广播包协议解析

                scanrsp_data = bt_device.raw_data -- 响应包数据 根据蓝牙广播包协议解析
                btcore.scan(0)  -- 停止扫描
                btcore.connect(bt_device.addr,bt_device.addr_type) -- 建立连接
                log.info("bt.connect_name", name)
                log.info("bt.connect_addr_type", addr_type)
                log.info("bt.connect_addr", addr)
                if manu_data ~= nil then
                    log.info("bt.connect_manu_data", manu_data)
                end
                if adv_data ~= nil then
                    log.info("bt.connect_adv_data", adv_data)
                end
                if scanrsp_data ~= nil then
                    log.info("bt.connect_scanrsp_data", scanrsp_data)
                end
                return true
            end

        end
    end
    return true
end

local function data_trans()

    _, bt_connect = sys.waitUntil("BT_CONNECT_IND") --等待连接成功
    if bt_connect.result ~= 0 then
        return false
    end
    -- 连接成功
    log.info("bt.connect_handle", bt_connect.handle) -- 蓝牙连接句柄
    log.info("bt", "find all service uuid")
    btcore.findservice() -- 发现所有服务
    _, result = sys.waitUntil("BT_FIND_SERVICE_IND") -- 等待发现服务 uuid 完成
    if not result then
        return false
    end

    btcore.findcharacteristic(0xfee0) -- 查找 uuid 0xfee0 的服务内所有特征
    _, result = sys.waitUntil("BT_FIND_CHARACTERISTIC_IND") -- 等待发现服务包含的特征成功
    if not result then
        return false
    end
    btcore.opennotification(0xfee2); -- 打开对应特征 uuid 的 notify 功能

    --btcore.findcharacteristic("9ecadc240ee5a9e093f3a3b50100406e")--服务uuid
    --_, result = sys.waitUntil("BT_FIND_CHARACTERISTIC_IND") --等待发现服务包含的特征成功
    --if not result then
    --    return false
    --end
    --btcore.opennotification("9ecadc240ee5a9e093f3a3b50200406e"); --打开通知 对应特征uuid  

    log.info("bt.send", "Hello I'm Luat BLE")
    sys.wait(1000)
    while true do
        local data = "123456"
        -- 向特征 uuid 0xfee1 发送数据
        btcore.send(data,0xfee1, bt_connect.handle) -- 发送数据(数据 对应特征uuid 连接句柄)
        --btcore.send(bt_recv.data,"9ecadc240ee5a9e093f3a3b50300406e",bt_connect.handle)
        _, bt_recv = sys.waitUntil("BT_DATA_IND") --等待接收到数据
        local data = ""
        local len = 0
        local uuid = ""
        while true do
            local recvuuid, recvdata, recvlen = btcore.recv(3)
            if recvlen == 0 then
                break
            end
            uuid = recvuuid
            len = len + recvlen
            data = data .. recvdata
        end
        if len ~= 0 then
        log.info("bt.recv_data", data)
        log.info("bt.recv_data len", len)
        log.info("bt.recv_uuid", uuid)
        end
    end
end

服务端效果展示。这里需要注意,不同手机的蓝牙设置规则可能不一样,我当前使用的手机是 OPPO K11 进行的演示。

首先我们使用服务端的例程,使用手机连接开发板。参考下图点击右上角三个点

然后根据右图点击 clone device's services。

我们点击图中左上角的设置选项。

参考下间这张图选择 configure GATT server。

最后我们可以看到把对应的服务信息给复制下来了。

回到主界面,点击 ADVERTISER 中的加号。

之后参考如下两张图进行设置。

最后参考下图修改设备名称。

打开广播功能。

开发板上电,之后就会扫描附近的设备并打印扫描到的设备信息。如果找到的指定的设备名后,将会出现如下的打印内容。

八、总结

在本教程中,我们详细介绍了 iBeacon 的工作原理及其在 BLE 应用中的实现。通过讲解客户端和服务端的交互方式,读者可以掌握如何使用 BLE 广播和扫描机制来实现设备间的通信。同时,教程涵盖了 iBeacon 配置、数据格式以及客户端的监听和解析过程。无论是使用 BLE 技术进行位置追踪还是设备交互,本教程提供了全面的实践指导,帮助开发者快速入门并深入理解 BLE 应用的核心技术。