返回

CH32 BLE 协议栈应用笔记

目录

CH32 BLE 协议栈应用参考笔记

适用于 CH32 BLE MCU + WCH BLE/TMOS 协议栈的应用开发。 核心思路:Attribute Table → Handle → Callback → Value → 手机


1. BLE 软件层次

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Application
    │
    ├── LED / Battery / RTC / Sensor
    │
    ▼
GATT Service
    │
    ├── Service
    ├── Characteristic
    └── Descriptor / CCCD
    │
    ▼
ATT
    │
    ▼
GAP / Link Layer / PHY

常用概念

概念 作用
GAP 广播、连接、角色、连接参数
GATT Service / Characteristic 的组织
ATT Attribute 的读写、发现、Notify
Service 一组相关功能
Characteristic 真正承载业务数据
UUID 标识“这是什么”
Handle 标识“操作哪个 Attribute”
Value 实际数据
CCCD Notify/Indicate 的订阅配置

2. 最重要:Attribute Table

一个 Characteristic 通常至少有两个 Attribute:

1
2
3
4
5
6
7
8
9
Characteristic
    │
    ├── Declaration
    │      ├── Properties
    │      └── Value Handle
    │
    └── Value
           ├── UUID
           └── Data

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
static uint8_t valPower;

static gattAttribute_t lightAttrTbl[] =
{
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &propPower
    },

    {
        { ATT_BT_UUID_SIZE, powerUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        &valPower
    },
};

3. gattAttribute_t 怎么看

1
2
3
4
5
6
7
typedef struct
{
    gattAttrType_t type;
    uint8_t permissions;
    uint16_t handle;
    uint8_t *pValue;
} gattAttribute_t;

理解成:

1
2
3
4
5
6
pAttr
 │
 ├── type.uuid   → UUID:我是谁?
 ├── permissions → 能做什么?
 ├── handle      → 我的编号是什么?
 └── pValue      → 我的数据在哪里?

Handle 为什么初始化为 0?

1
handle = 0;

只是表示:

当前还没有分配最终 ATT Handle。

Service 注册到协议栈后,由协议栈分配真正 Handle。

因此:

1
lightAttrTbl[x].handle

运行时才是实际可以用于 ATT 操作的 Handle。


4. UUID

16-bit UUID 例如:

1
#define BSP_BLE_UUID_POWER 0xFFF1

定义数组:

1
2
3
4
5
static const uint8_t powerUUID[2] =
{
    LO_UINT16(BSP_BLE_UUID_POWER),
    HI_UINT16(BSP_BLE_UUID_POWER)
};

结果:

1
2
3
4
5
UUID 数值:
0xFFF1

内存字节:
F1 FF

原因:

BLE 使用 Little Endian 的字节顺序。

重新组合:

1
2
3
4
5
6
uint16_t uuid;

uuid = BUILD_UINT16(
    pAttr->type.uuid[0],
    pAttr->type.uuid[1]
);

等价于:

1
uuid = low_byte | (high_byte << 8);

5. UUID、Handle、Value 的关系

牢记:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
UUID
 ↓
“这个 Attribute 是什么?”

Handle
 ↓
“我要操作哪个 Attribute?”

Value
 ↓
“这个 Attribute 里面的数据是什么?”

例如:

1
2
3
4
Power
UUID   = 0xFFF1
Handle = 0x0025
Value  = 0x01

6. 手机如何找到 Characteristic?

手机第一次连接后,会进行 GATT Discovery。

大致过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
手机
 │
 │ Discover Services / Characteristics
 ▼
CH32 BLE Stack
 │
 ▼
Attribute Table
 │
 ├── UUID
 └── Handle
 │
 ▼
手机建立映射

例如:

1
2
3
Power       → UUID 0xFFF1 → Handle 0x0025
Brightness  → UUID 0xFFF2 → Handle 0x0028
Mode        → UUID 0xFFF3 → Handle 0x002B

之后手机操作时主要使用:

1
Handle + Value

而不是每次重新发送 UUID。


7. Write:手机 → MCU

手机:

1
Write Power = 1

本质上类似:

1
2
Handle = 0x0025
Value  = 0x01

BLE Stack:

1
2
3
4
5
6
7
Handle
  ↓
查 Attribute Table
  ↓
找到 pAttr
  ↓
调用 Write Callback

Write Callback 中:

1
2
3
4
5
6
7
pAttr
    ↓
本地 Attribute 信息

pValue
    ↓
手机本次发送的数据

例如:

1
valPower = pValue[0];

数据流:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
手机
 │
 │ Value = 0x01
 ▼
Write Callback
 │
 │ pValue[0]
 ▼
valPower
 │
 ▼
LED 打开

8. Write Callback 的核心判断

如果一个 Callback 负责多个 Characteristic:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
uint16_t uuid;

uuid = BUILD_UINT16(
    pAttr->type.uuid[0],
    pAttr->type.uuid[1]
);

if(uuid == BSP_BLE_UUID_POWER)
{
    valPower = pValue[0];
}
else if(uuid == BSP_BLE_UUID_BRIGHTNESS)
{
    valBrightness = pValue[0];
}

这里:

1
pAttr->type.uuid

来自本地 Attribute Table。

而:

1
pValue

来自手机发送的数据。


9. Read:MCU → 手机

手机:

1
Read Battery

本质:

1
Read Handle = Battery Value Handle

BLE Stack 根据 Handle 找到:

1
pAttr

然后调用 Read Callback。

本地数据:

1
static uint8_t valBattery = 85;

通常:

1
2
3
pAttr->pValue
        ↓
    &valBattery

Read Callback:

1
2
3
4
5
tmos_memcpy(
    *pValue,
    pAttr->pValue,
    *pLen
);

数据流:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
MCU 本地

valBattery = 85
      ↓
pAttr->pValue
      ↓
memcpy
      ↓
*pValue
      ↓
BLE Stack
      ↓
手机

注意三个东西

1
2
3
4
5
6
7
8
pAttr->pValue
    → MCU 本地数据地址

Write Callback 的 pValue
    → 手机发送给 MCU 的数据

Read Callback 的 *pValue
    → BLE Stack 提供的返回数据缓冲区

不要混淆。


10. 为什么 Read 不能直接给指针?

错误思路:

1
*pValue = pAttr->pValue;

这是:

修改指针地址。

正确:

1
2
3
4
5
memcpy(
    *pValue,
    pAttr->pValue,
    *pLen
);

这是:

复制数据。

简单记:

1
2
指针赋值 → 改地址
memcpy   → 复制内容

11. Notify

Notify 与 Read 最大的区别:

1
2
3
4
5
Read:
手机 → “给我数据”

Notify:
手机 → “以后数据变化时主动告诉我”

例如 Battery:

1
2
3
4
5
6
7
8
手机
 │
 │ 开启 Notify
 ▼
CCCD
 │
 ▼
batteryCharCfg

以后:

1
2
3
4
5
Battery 100%
     ↓
Battery 99%
     ↓
Battery 98%

MCU 可以主动:

1
2
3
4
5
MCU → 手机
Battery = 99

MCU → 手机
Battery = 98

不需要手机不断 Read。


12. CCCD

CCCD:

1
Client Characteristic Configuration Descriptor

标准 UUID:

1
0x2902

它不是 Battery 数据。

它是:

客户端对 Notify / Indicate 的配置。

例如:

1
2
3
4
5
6
7
8
CCCD = 0x0000
→ Notify OFF

CCCD = 0x0001
→ Notify ON

CCCD = 0x0002
→ Indicate ON

13. GATTServApp_ProcessCCCWriteReq

这个函数可以理解为:

处理手机对 CCCD 的 Write 请求。

流程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
手机
 │
 │ Write CCCD = 0x0001
 ▼
GATTServApp_ProcessCCCWriteReq()
 │
 ▼
保存客户端配置
 │
 ▼
batteryCharCfg[]

所以它解决的是:

“手机有没有订阅 Notify?”

而不是:

“手机发送了多少电量数据?”


14. GATTServApp_ReadCharCfg

1
2
3
4
cfg = GATTServApp_ReadCharCfg(
    connItem.connHandle,
    batteryCharCfg
);

作用:

查询当前连接是否开启了 Notify。

通常:

1
2
if(!(cfg & GATT_CLIENT_CFG_NOTIFY))
    return;

意思:

1
2
3
Notify 没开
   ↓
不要发送

15. GATT_Notification

真正发送 Notify:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
attHandleValueNoti_t noti;

noti.handle = batteryHandle;
noti.len = 1;
noti.pValue = &valBattery;

GATT_Notification(
    connHandle,
    &noti,
    FALSE
);

数据流:

1
2
3
4
5
6
7
8
9
valBattery
     ↓
noti.pValue
     ↓
GATT_Notification()
     ↓
BLE Stack
     ↓
手机

16. Notify 完整流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
① 手机开启 Notify
        ↓
ProcessCCCWriteReq()
        ↓
batteryCharCfg = Notify ON
        ↓
② MCU 查询配置
        ↓
ReadCharCfg()
        ↓
Notify ON?
        ↓
③ 获取最新 Battery
        ↓
valBattery = BSP_BAT_GetLevel()
        ↓
④ 构造 Notification
        ↓
GATT_Notification()
        ↓
⑤ 手机收到 Battery

17. Service / Characteristic / Value 的关系

例如夜灯:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
Light Service
│
├── Power Characteristic
│     └── Value = valPower
│
├── Brightness Characteristic
│     └── Value = valBrightness
│
├── Color Characteristic
│     └── Value = valColor[3]
│
├── Battery Characteristic
│     ├── Value = valBattery
│     └── CCCD = batteryCharCfg[]
│
└── RTC Characteristic
      └── Value = valRtcTime[7]

18. 广播

典型:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
static uint8_t advertData[] =
{
    0x02,
    GAP_ADTYPE_FLAGS,
    GAP_ADTYPE_FLAGS_GENERAL |
    GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED,

    0x03,
    GAP_ADTYPE_16BIT_MORE,
    LO_UINT16(BSP_BLE_SERVICE_UUID),
    HI_UINT16(BSP_BLE_SERVICE_UUID)
};

作用:

1
2
3
4
5
广播
 │
 ├── Flags
 │
 └── Service UUID

扫描响应:

1
2
3
4
5
6
static uint8_t scanRspData[] =
{
    18,
    GAP_ADTYPE_LOCAL_NAME_COMPLETE,
    'C','H','5','9','1','D','_','N','i','g','h','t','L','i','g','h','t'
};

作用:

1
2
3
扫描响应
 │
 └── 设备名称

19. GAP 与 GATT 初始化

典型流程:

1
2
3
4
5
6
7
8
9
GAPRole_SetParameter(...);
GAP_SetParamValue(...);

GGS_AddService(...);
GATTServApp_AddService(...);

BSP_Ble_AddLightService();

GGS_SetParameter(...);

理解:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
GAP
 ↓
广播 / 连接 / Peripheral

GATT Server
 ↓
注册 Attribute

自定义 Service
 ↓
注册夜灯功能

GGS
 ↓
设备名称等 GAP 相关属性

20. Connection Parameter

例如:

1
2
uint16_t desired_min_interval = 6;
uint16_t desired_max_interval = 1000;

BLE Connection Interval 单位:

1
1.25 ms

因此:

1
2
3
4
5
6 × 1.25 ms
= 7.5 ms

1000 × 1.25 ms
= 1250 ms

这是:

期望的连接参数范围。

不是保证实际一定使用这个值。


21. Callback 的职责

常见:

1
BSP_Ble_StateCB()

负责:

1
2
3
4
5
BLE 状态变化
├── Started
├── Advertising
├── Connected
└── Terminated

1
BSP_Ble_LinkEstablished()

负责:

1
2
3
4
5
连接建立
├── 保存 connection handle
├── 保存 interval
├── 保存 latency
└── 保存 timeout

1
BSP_Ble_LinkTerminated()

负责:

1
2
3
断开连接
├── 清理连接信息
└── 重新开始 Advertising

1
BSP_Ble_ParamUpdateCB()

负责:

1
2
3
连接参数发生变化
        ↓
更新 connItem

它不是 Tick 主动调用,而是协议栈发生参数更新后回调。


22. TMOS Task

典型:

1
2
3
4
BSP_BleTaskID =
    TMOS_ProcessEventRegister(
        BSP_Ble_ProcessEvent
    );

然后:

1
2
3
4
tmos_set_event(
    BSP_BleTaskID,
    BSP_BLE_START_DEVICE_EVT
);

周期任务:

1
2
3
4
5
tmos_start_task(
    BSP_BleTaskID,
    BSP_BLE_TICK_EVT,
    ...
);

可以理解成:

1
2
3
4
5
6
7
TMOS
 │
 └── BLE Task
       │
       ├── START_DEVICE_EVT
       ├── TICK_EVT
       └── BATTERY_EVT

BSP_Ble_ProcessEvent() 就是这个 Task 的事件分发器。


23. ISR 与 Stack

CH32 的 ISR 和普通函数共用 CPU Stack。

如果:

1
Stack = 512 Bytes

那么 ISR 中应该避免:

1
uint8_t buffer[500];

以及:

1
2
printf();
sprintf();

和过深的函数调用。

适合:

1
2
3
uint8_t flag;
uint16_t value;
uint32_t counter;

static 局部变量不占用运行时 Stack:

1
static uint32_t counter;

24. BLE 开发排错顺序

当手机无法控制设备时,按这个顺序查:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
① 能不能广播?
        ↓
② 手机能不能发现 Service?
        ↓
③ 能不能发现 Characteristic?
        ↓
④ UUID 是否正确?
        ↓
⑤ Handle 是否正确?
        ↓
⑥ Permission 是否允许?
        ↓
⑦ Callback 是否被调用?
        ↓
⑧ pValue 是否正确?
        ↓
⑨ 本地 valXXX 是否更新?
        ↓
⑩ 硬件是否执行?

Notify 问题:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
① 是否建立连接?
        ↓
② 手机是否写 CCCD?
        ↓
③ ProcessCCCWriteReq 是否成功?
        ↓
④ batteryCharCfg 是否更新?
        ↓
⑤ ReadCharCfg 是否返回 Notify?
        ↓
⑥ GATT_Notification 是否执行?
        ↓
⑦ Handle 是否正确?
        ↓
⑧ len / pValue 是否正确?

25. 一张图总结整个 BLE 数据流

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
                    BLE Attribute Table
                           │
                           ▼
                       ┌────────┐
                       │ pAttr  │
                       └───┬────┘
                           │
             ┌─────────────┼─────────────┐
             ▼             ▼             ▼
           UUID          Handle        pValue
          “是谁”         “编号”        “数据”
             │             │             │
          0xFFF1        0x0025       &valPower
                                           │
                                           ▼
                                        valPower


手机 Write
────────────────────────────────

Handle = 0x0025
Value  = 0x01
        │
        ▼
    BLE Stack
        │
        ▼
找到本地 pAttr
        │
        ▼
Write Callback
        │
        ├── pAttr → 本地 Attribute
        │
        └── pValue → 手机发送的数据
                         │
                         ▼
                     valPower


手机 Read
────────────────────────────────

Handle = 0x0025
        │
        ▼
    BLE Stack
        │
        ▼
找到本地 pAttr
        │
        ▼
pAttr->pValue
        │
        ▼
   valPower
        │
        ▼
Read Callback
        │
        ▼
*pValue
        │
        ▼
      手机


手机 Notify
────────────────────────────────

手机 Write CCCD
        │
        ▼
ProcessCCCWriteReq()
        │
        ▼
batteryCharCfg
        │
        ▼
ReadCharCfg()
        │
        ▼
Notify ON?
        │
       YES
        │
        ▼
GATT_Notification()
        │
        ▼
      手机

26. 最终记忆口诀

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
UUID   → 我是谁
Handle → 找谁操作
Value  → 数据是什么
pAttr  → 本地 Attribute 描述
Write pValue → 手机给我的数据
Read *pValue → 我要返回给手机的数据
CCCD   → 手机是否订阅 Notify
Notify → MCU 主动推数据
Read   → 手机主动要数据
Write  → 手机主动给数据

BLE 应用开发最核心的不是记 API,而是搞清楚:

手机的 Handle → 协议栈找到本地 pAttr → Callback → 根据操作类型处理 pValue / pAttr->pValue。

27. CH32 BLE 应用开发代码骨架

27.1 文件结构

1
2
3
4
5
6
7
User/
├── app_ble.c
├── app_ble.h
├── ble_light_service.c
├── ble_light_service.h
├── bsp_led.c
└── bsp_led.h

27.2 Service Header

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
#ifndef __BLE_LIGHT_SERVICE_H
#define __BLE_LIGHT_SERVICE_H

#include "CH59x_common.h"
#include "gatt.h"
#include "att.h"
#include "gatt_profile.h"

#define LIGHT_SERVICE_UUID       0xFFF0
#define LIGHT_POWER_UUID         0xFFF1
#define LIGHT_BRIGHTNESS_UUID    0xFFF2
#define LIGHT_BATTERY_UUID       0xFFF8

void LightService_AddService(void);

void LightService_SetBattery(uint8_t level);
void LightService_NotifyBattery(void);

#endif

27.3 UUID 定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static const uint8_t serviceUUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(LIGHT_SERVICE_UUID),
    HI_UINT16(LIGHT_SERVICE_UUID)
};

static const uint8_t powerUUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(LIGHT_POWER_UUID),
    HI_UINT16(LIGHT_POWER_UUID)
};

static const uint8_t brightnessUUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(LIGHT_BRIGHTNESS_UUID),
    HI_UINT16(LIGHT_BRIGHTNESS_UUID)
};

static const uint8_t batteryUUID[ATT_BT_UUID_SIZE] =
{
    LO_UINT16(LIGHT_BATTERY_UUID),
    HI_UINT16(LIGHT_BATTERY_UUID)
};

27.4 Characteristic Value

1
2
3
static uint8_t valPower = 0;
static uint8_t valBrightness = 50;
static uint8_t valBattery = 100;
1
2
3
4
5
6
7
8
valPower
    → Power 数据

valBrightness
    → Brightness 数据

valBattery
    → Battery 数据

27.5 Characteristic Properties

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static uint8_t propPower =
    GATT_PROP_READ |
    GATT_PROP_WRITE;

static uint8_t propBrightness =
    GATT_PROP_READ |
    GATT_PROP_WRITE;

static uint8_t propBattery =
    GATT_PROP_READ |
    GATT_PROP_NOTIFY;

27.6 CCCD

1
2
3
static gattCharCfg_t batteryCharCfg[
    PERIPHERAL_MAX_CONNECTION
];

作用:

1
记录每个连接是否开启 Battery Notify

27.7 Attribute Table

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
static gattAttribute_t lightAttrTbl[] =
{
    /* Service */
    {
        { ATT_BT_UUID_SIZE, primaryServiceUUID },
        GATT_PERMIT_READ,
        0,
        (uint8_t *)&lightServiceType
    },

    /* Power Declaration */
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &propPower
    },

    /* Power Value */
    {
        { ATT_BT_UUID_SIZE, powerUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        &valPower
    },

    /* Brightness Declaration */
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &propBrightness
    },

    /* Brightness Value */
    {
        { ATT_BT_UUID_SIZE, brightnessUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        &valBrightness
    },

    /* Battery Declaration */
    {
        { ATT_BT_UUID_SIZE, characterUUID },
        GATT_PERMIT_READ,
        0,
        &propBattery
    },

    /* Battery Value */
    {
        { ATT_BT_UUID_SIZE, batteryUUID },
        GATT_PERMIT_READ,
        0,
        &valBattery
    },

    /* Battery CCCD */
    {
        { ATT_BT_UUID_SIZE, clientCharCfgUUID },
        GATT_PERMIT_READ | GATT_PERMIT_WRITE,
        0,
        (uint8_t *)batteryCharCfg
    },
};

27.8 Attribute Index

避免直接使用魔法数字:

1
2
3
4
#define IDX_POWER_VALUE       2
#define IDX_BRIGHTNESS_VALUE  4
#define IDX_BATTERY_VALUE     6
#define IDX_BATTERY_CCCD      7

27.9 Write Callback

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static bStatus_t
LightService_WriteAttrCB(
    uint8_t *connHandle,
    gattAttribute_t *pAttr,
    uint8_t *pValue,
    uint16_t len,
    uint16_t offset,
    uint8_t method
)
{
    uint16_t uuid;

    if(offset != 0)
        return ATT_ERR_ATTR_NOT_LONG;

    if(pAttr == NULL)
        return ATT_ERR_INVALID_HANDLE;

    uuid = BUILD_UINT16(
        pAttr->type.uuid[0],
        pAttr->type.uuid[1]
    );

    if(uuid == LIGHT_POWER_UUID)
    {
        if(len != 1)
            return ATT_ERR_INVALID_VALUE_SIZE;

        valPower = pValue[0];

        if(valPower)
            LED_On();
        else
            LED_Off();
    }
    else if(uuid == LIGHT_BRIGHTNESS_UUID)
    {
        if(len != 1)
            return ATT_ERR_INVALID_VALUE_SIZE;

        valBrightness = pValue[0];

        LED_SetBrightness(valBrightness);
    }
    else if(uuid == GATT_CLIENT_CHAR_CFG_UUID)
    {
        return GATTServApp_ProcessCCCWriteReq(
            *connHandle,
            pAttr,
            pValue,
            len,
            offset,
            GATT_CLIENT_CFG_NOTIFY
        );
    }
    else
    {
        return ATT_ERR_ATTR_NOT_FOUND;
    }

    return SUCCESS;
}

核心:

1
2
3
4
5
6
7
8
pAttr
    → 本地 Attribute

pAttr->type.uuid
    → 判断操作的是哪个 Characteristic

pValue
    → 手机发送的数据

27.10 Read Callback

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
static bStatus_t
LightService_ReadAttrCB(
    uint8_t *connHandle,
    gattAttribute_t *pAttr,
    uint8_t *pValue,
    uint16_t *pLen,
    uint16_t offset,
    uint8_t maxLen,
    uint8_t method
)
{
    uint16_t uuid;
    uint16_t len;

    if(pAttr == NULL)
        return ATT_ERR_INVALID_HANDLE;

    uuid = BUILD_UINT16(
        pAttr->type.uuid[0],
        pAttr->type.uuid[1]
    );

    if(uuid == LIGHT_POWER_UUID)
    {
        len = 1;
        pValue[0] = valPower;
    }
    else if(uuid == LIGHT_BRIGHTNESS_UUID)
    {
        len = 1;
        pValue[0] = valBrightness;
    }
    else if(uuid == LIGHT_BATTERY_UUID)
    {
        len = 1;
        pValue[0] = valBattery;
    }
    else
    {
        return ATT_ERR_ATTR_NOT_FOUND;
    }

    if(len > maxLen)
        len = maxLen;

    *pLen = len;

    return SUCCESS;
}

注意:

1
2
3
4
5
Write Callback:
pValue → 手机发送给 MCU 的数据

Read Callback:
pValue → BLE Stack 提供的返回缓冲区

27.11 Service Callback

1
2
3
4
5
6
static gattServiceCBs_t LightServiceCBs =
{
    LightService_ReadAttrCB,
    LightService_WriteAttrCB,
    NULL
};

具体结构体字段顺序以当前 SDK 定义为准。


27.12 注册 Service

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
void LightService_AddService(void)
{
    GATTServApp_RegisterService(
        lightAttrTbl,
        sizeof(lightAttrTbl) /
        sizeof(lightAttrTbl[0]),
        GATT_MAX_ENCRYPT_KEY_SIZE,
        &LightServiceCBs
    );
}

注册后:

1
2
3
4
5
6
7
Attribute Table
       ↓
BLE Stack
       ↓
分配 Handle
       ↓
手机 Discovery

27.13 Battery Notify

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void LightService_NotifyBattery(void)
{
    attHandleValueNoti_t noti;
    uint16_t cfg;

    if(g_connHandle == GAP_CONNHANDLE_INIT)
        return;

    cfg = GATTServApp_ReadCharCfg(
        g_connHandle,
        batteryCharCfg
    );

    if(!(cfg & GATT_CLIENT_CFG_NOTIFY))
        return;

    noti.handle =
        lightAttrTbl[IDX_BATTERY_VALUE].handle;

    noti.len = 1;
    noti.pValue = &valBattery;

    GATT_Notification(
        g_connHandle,
        &noti,
        FALSE
    );
}

流程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CCCD
 ↓
检查 Notify 是否开启
 ↓
获取 Battery
 ↓
获取 Battery Value Handle
 ↓
GATT_Notification()
 ↓
手机

27.14 TMOS Task

1
2
3
4
5
#define BLE_EVT_START_DEVICE   0x0001
#define BLE_EVT_TICK           0x0002
#define BLE_EVT_BATTERY        0x0004

static uint8_t g_bleTaskID;
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
static uint16_t
BLE_ProcessEvent(
    uint8_t *task_id,
    uint16_t *events
)
{
    if(*events & SYS_EVENT_MSG)
    {
        uint8_t *msg;

        msg = tmos_msg_receive(g_bleTaskID);

        if(msg)
        {
            /* 处理 BLE 消息 */

            tmos_msg_deallocate(msg);
        }

        return *events ^ SYS_EVENT_MSG;
    }

    if(*events & BLE_EVT_START_DEVICE)
    {
        /* 启动 Peripheral */

        return *events ^ BLE_EVT_START_DEVICE;
    }

    if(*events & BLE_EVT_TICK)
    {
        /* 周期任务 */

        tmos_start_task(
            g_bleTaskID,
            BLE_EVT_TICK,
            MS1_TO_SYSTEM_TIME(100)
        );

        return *events ^ BLE_EVT_TICK;
    }

    if(*events & BLE_EVT_BATTERY)
    {
        valBattery = BSP_BAT_GetLevel();

        LightService_NotifyBattery();

        tmos_start_task(
            g_bleTaskID,
            BLE_EVT_BATTERY,
            MS1_TO_SYSTEM_TIME(5000)
        );

        return *events ^ BLE_EVT_BATTERY;
    }

    return 0;
}

27.15 BLE 初始化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
void BLE_AppInit(void)
{
    uint8_t advertising_enable = TRUE;

    g_connHandle = GAP_CONNHANDLE_INIT;

    g_bleTaskID =
        TMOS_ProcessEventRegister(
            BLE_ProcessEvent
        );

    GAPRole_SetParameter(
        GAPROLE_ADVERT_ENABLED,
        sizeof(uint8_t),
        &advertising_enable
    );

    GGS_AddService(GATT_ALL_SERVICES);

    GATTServApp_AddService(
        GATT_ALL_SERVICES
    );

    LightService_AddService();

    tmos_set_event(
        g_bleTaskID,
        BLE_EVT_START_DEVICE
    );

    tmos_start_task(
        g_bleTaskID,
        BLE_EVT_BATTERY,
        MS1_TO_SYSTEM_TIME(5000)
    );
}

27.16 Main

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
int main(void)
{
    SetSysClock(CLK_SOURCE_PLL_60MHz);

    LED_Init();
    BSP_BAT_Init();

    BLE_AppInit();

    while(1)
    {
        TMOS_SystemProcess();
    }
}

27.17 三条核心数据流

Write

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
手机
 ↓
Handle + Value
 ↓
BLE Stack
 ↓
pAttr
 ↓
Write Callback
 ↓
pValue
 ↓
valXXX
 ↓
硬件

Read

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
手机
 ↓
Handle
 ↓
BLE Stack
 ↓
pAttr
 ↓
Read Callback
 ↓
valXXX
 ↓
pValue[]
 ↓
BLE Stack
 ↓
手机

Notify

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
手机
 ↓
Write CCCD
 ↓
GATTServApp_ProcessCCCWriteReq()
 ↓
batteryCharCfg
 ↓
GATTServApp_ReadCharCfg()
 ↓
确认 Notify
 ↓
GATT_Notification()
 ↓
手机

27.18 开发时最重要的对应关系

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
UUID
→ 我是谁

Handle
→ 我要操作哪个 Attribute

pAttr
→ 本地 Attribute 描述

pAttr->pValue
→ 本地数据地址

Write Callback 的 pValue
→ 手机发送给 MCU 的数据

Read Callback 的 pValue
→ BLE Stack 提供的返回缓冲区

CCCD
→ 手机是否订阅 Notify

GATT_Notification()
→ MCU 主动发送数据

注意:上述函数名、结构体字段、错误码和注册函数原型可能因 CH32/CH59x SDK 版本不同而略有差异;实际工程以当前 SDK 的头文件定义为准。

Licensed under CC BY-NC-SA 4.0