目录
APP (Application / 应用层)存放用户应用程序代码。所有的业务逻辑、蓝牙的主从机行为控制都在这里。
1、通用访问配置 (GAP) 角色参数设置,这段代码设置了设备在广播和连接参数的设置
11、任务事件处理器:Peripheral_ProcessEvent 详解
12、WCH 蓝牙协议栈中状态通知回调函数peripheralStateNotificationCB 详解
13、蓝牙断开连接事件函数Peripheral_LinkTerminated
14、蓝牙建立连接事件函数Peripheral_LinkEstablished
15、蓝牙从机任务连接参数更新回调函数peripheralParamUpdateCB
在最新的CH585芯片的EVT程序中,包含很多BLE的示例工程,其中有一个CentPeri工程文件,是主从一体例程,整合了主机例程和从机例程的功能同时运行。

1、在CentPeri程序文件结构如下:

-
APP (Application / 应用层)存放用户应用程序代码。所有的业务逻辑、蓝牙的主从机行为控制都在这里。
-
include: 存放应用层所需的头文件(
.h)。 -
centPeri_main.c: 工程的主入口文件。包含了
main()函数,负责系统初始化、协议栈初始化,以及主循环(Main_Circulation) -
central.c: 主机(Central)角色 的相关代码。包含了扫描设备、发起连接、发现服务、读写数据等逻辑。
-
peripheral.c: 从机(Peripheral)角色 的相关代码。包含了广播配置、被连接后的参数更新、发送通知(Notify)等逻辑。
-
-
Profile (配置文件 / 蓝牙服务定义)这个文件夹存放的是 GATT(通用属性配置文件) 相关的代码。
-
在蓝牙 BLE 开发中,"Profile" 定义了设备对外提供的“服务”和“特征”。通常包含
gattprofile.c和gattprofile.h。 -
作用: 它定义了您的蓝牙设备有哪些“能力”。比如,它定义了 UUID 为
0xFFF0的服务,以及该服务下 UUID 为0xFFF1的读写特征值。协议栈会调用这里的代码来处理主机与从机之间的读写请求。
-
-
HAL (硬件抽象层)这个文件夹存放的是对底层硬件的封装代码,专门为 BLE 任务服务。
-
区别:
StdPeriphDriver提供的是原子的硬件操作(如“把 GPIO 拉高”),而HAL提供的是面向功能的接口(如“初始化 LED”、“检测按键”、“管理系统睡眠”)。 -
内容: 通常包含
MCU.c(系统与蓝牙库初始化,硬件初始化)、RTC.c(实时时钟/睡眠管理)、KEY.c(按键扫描)、LED.c(LED 闪烁控制) 等。 -
作用: 将硬件操作抽象成 Task(任务)或简单的接口,供 TMOS 系统调用,把硬件细节和应用逻辑隔离开。
-
-
LIB (蓝牙库文件)这个文件夹存放的是 BLE 协议栈的核心库。
-
内容: 通常是一个
.a文件(如CH58xBLE_LIB.a)。 -
作用: 这里面是沁恒编译好的二进制代码,包含了蓝牙协议栈的底层实现(链路层、L2CAP、SMP 安全管理等)。无法看到里面的源码,只能通过 API(头文件)来调用它。
-
-
StdPeriphDriver (标准外设驱动)这是芯片厂商提供的底层驱动库。
-
内容: 包含了芯片所有外设的驱动源码,如
CH58x_gpio.c(GPIO控制),CH58x_uart.c(串口),CH58x_timer.c(定时器),CH58x_adc.c(模数转换) 等。 -
作用: 操作硬件寄存器时,调用这些驱动提供的函数(如
GPIO_SetBits(GPIO_Pin_0))
-
-
Startup (启动代码)这是单片机上电后运行的第一段代码。
-
内容: 一个汇编文件(
.S文件)。 -
作用: 初始化堆栈指针(SP)、初始化数据段(把有初值的全局变量从 Flash 搬运到 RAM)。清零 BSS 段(把没初值的全局变量清零)。最后跳转到
main()函数。
-
-
RVMSIS 这是针对 RISC-V 内核 的系统接口文件。
-
内容: 类似于 ARM 的 CMSIS。它包含了 RISC-V 内核核心寄存器的定义、系统时钟(SysTick)配置、中断控制器(PFIC)配置等。处理与 CPU 内核紧密相关的底层操作,比如开启全局中断、设置中断优先级等。
-
-
Ld (Linker Script / 链接脚本)这个文件夹存放的是链接脚本(
.ld文件)。-
作用: 它告诉编译器和链接器,芯片的 Flash 有多大、RAM 有多大,以及代码应该放在 Flash 的哪个地址,变量应该放在 RAM 的哪个地址。它是生成最终固件的关键配置文件。
-
-
obj (Object / 目标文件)这是一个编译输出目录。存放编译过程中生成的中间文件(
.o文件,.d依赖文件等)。每次编译时它会自动更新。
2、主程序入口文件和初始化配置
CentPeri_main.c 里面包含 main函数所在的文件,配置芯片底层硬件,初始化蓝牙协议栈,启动蓝牙的主机和从机任务,进入主循环操作

这是单片机启动后执行的第一个 C 语言函数。系统上电后,首先需要配置电源和时钟,HSECFG_Capacitance 和 SetSysClock 用于配置外部32M高频晶振的负载电容,并设置系统主频。因为这个CH585芯片内置带晶振匹配电容,就无需再外接电容。
DEBUG是 调试串口初始化,初始化 UART0 串口,可以使用 PRINT() 函数向电脑发送调试信息,
蓝牙与应用初始化中,这是启动蓝牙功能的关键步骤,顺序很重要
-
CH58x_BLEInit: 初始化蓝牙协议栈底层,类似启动最底层的蓝牙协议“黑盒子” -
HAL_Init: 硬件参数初始化 -
GAPRole_PeripheralInit(): 初始化GAP层从机角色逻辑,由蓝牙库自动完成 -
GAPRole_CentralInit(): 初始化GAP层主机角色逻辑,由蓝牙库自动完成 -
Peripheral_Init/Central_Init: 调用peripheral.c和central.c中完成对从机和主机应用程序初始化函数,分别注册从机任务 ID和注册主机任务 ID。
当 main 函数执行完所有初始化代码后,会调用 Main_Circulation(),程序进入死循环,永不返回。

进入主循环后,一直在调用 TMOS_SystemProcess(),开始任务调度。TMOS 实际上就是OSAL的简化版本,这是一个轮询实现的系统,不支持抢占任务,这是为了方便其协议栈自身的管理,以及用户的使用,它不断检查各个任务(HAL、Peripheral、Central)是否有事件标志位被置位,或者定时器是否到期。一旦发现待处理事件,它会调用对应任务的回调函数(如 Peripheral_ProcessEvent)。
更详细的使用这里就不展开介绍,具体蓝牙的TMOS系统使用可以参考这位博主的博客:https://www.cnblogs.com/iot-fan/p/13460082.html
注1:
在上述程序中,可以看到有两段程序初始化时,是没有被定义,是HAL_SLEEP和DCDC_ENABLE,这个两处共同的作用就是开启低功耗睡眠,降低电流功耗。
那如何开启这两处程序,进行初始化呐?可以右击工程属性,或者也可以点击下图中的MRS编译器中的图标进去工程属性配置。在宏定义处,设置DCDC_ENABLE=1和HAL_SLEEP=1。
上述讲解的DUBUG串口调试的串口初始化,如果修改了其他的串口初始化,就得将DEBUG=0修改为对应的串口号。
记住这个工程属性配置方式,后续很多参数配置的数据的需求,都需要在这个地方添加参数配置。

3、Periphera从机设备程序
在peripheral.C 是 WCH 蓝牙协议栈中 Peripheral (从机) 应用层 的核心代码。它负责配置设备进行广播,管理与主机的连接,处理连接建立后的参数更新,并实现自定义 GATT 服务的数据交互。
可以先看一下Peripheral_Init函数中,

TMOS_ProcessEventRegister(): 向 TMOS注册任务的事件处理器函数 (Peripheral_ProcessEvent)。
Peripheral_TaskID: 注册成功后,TMOS 会返回一个唯一的任务 ID。这个 ID 是任务的“身份牌”,后续所有发送给该任务的事件,都必须使用这个 ID。

在默认定义中,uint8_t Peripheral_TaskID = INVALID_TASK_ID;用于存储 TMOS 任务调度器分配给从机应用任务的唯一 ID
1、通用访问配置 (GAP) 角色参数设置,这段代码设置了设备在广播和连接参数的设置

| 参数 (GAPRole_SetParameter 的第一个参数) | 作用 | 对应值 (来自 peripheral.C 定义) |
|---|---|---|
GAPROLE_ADVERT_ENABLED | 初始是否开启广播。 | TRUE (开启) |
GAPROLE_SCAN_RSP_DATA | 设置扫描响应包内容,包含设备完整名称 ("Simple Peripheral") 和连接间隔范围。 | scanRspData 数组 |
GAPROLE_ADVERT_DATA | 设置广播包内容,包含可发现模式标志 (General) 和服务 UUID (0xFFE0)。 | advertData 数组 |
GAPROLE_MIN_CONN_INTERVAL | 期望的最小连接间隔 | DEFAULT_DESIRED_MIN_CONN_INTERVAL |
GAPROLE_MAX_CONN_INTERVAL | 期望的最大连接间隔 | DEFAULT_DESIRED_MAX_CONN_INTERVAL |
2、在这段程序中,可以针对广播包和扫描应答包展开讲解一下,
这是主机设备(如手机或另一个BLE设备)在扫描时能直接获取到的关于从机设备的信息。遵循 GAP 标准格式(长度-类型-值)。
scanRspData[] (扫描响应数据):扫描响应包用于在主机发出扫描请求后,给主机响应的数据包。包含设备完整的本地名称 ("Simple Peripheral") 和它支持的连接间隔范围 (25ms 到 125ms)。和发射功率

广播包是在空中周期性发送的,用于通知周围设备从机的存在,以确保所有 31 字节的广播数据包都能发送成功。advertData[] (广播数据):包含设备可发现标志 (DEFAULT_DISCOVERABLE_MODE) 和设备包含的服务 UUID (SIMPLEPROFILE_SERV_UUID = 0xFFE0)。

注:广义上的广播分为:广播数据,扫描回复数据,无论是扫描应答包还是广播包,包内容总长度最大数据是31个字节,对于广播的内容格式, 蓝牙有定义 按照 |1byte长度|1byte类型|n字节内容|循环放。至于针对广播包和扫描应答包的修改,比如更新广播内容,或者存放厂商自定义数据信息等一些操作,可以但是拉出来讲解,也可以先参考这些链接:
1、https://www.cnblogs.com/iot-fan/p/13623201.html
2、https://www.cnblogs.com/risc5-ble/p/16731694.html
3、https://www.cnblogs.com/risc5-ble/p/16619902.html
3、广播间隔配置
配置实际的广播间隔。这里将最小和最大间隔都设置为 80 (即 50ms),这意味着从机将以固定的 50ms 频率发送广播包。

4、绑定管理 (Bond Manager) 安全配置

GAPBOND_PERI_DEFAULT_PASSCODE: 默认的配对码。设置为 0 对应数字 "000000"。
GAPBOND_PERI_PAIRING_MODE: 配对模式,设置为 WAIT_FOR_REQ (等待请求)。
GAPBOND_PERI_MITM_PROTECTION: 中间人攻击保护 。设置为 TRUE,表示要求进行密钥交换或配对码认证。
GAPBOND_PERI_BONDING_ENABLED: 绑定功能。设置为 TRUE,表示配对成功后会将密钥信息存储起来 (绑定)。
GAPBOND_PERI_IO_CAPABILITIES: 输入/输出能力。设置为 DISPLAY_ONLY (只显示),意味着设备可以显示一个配对码,但不能接收用户输入。
注2:这里针对配对绑定的功能,先不展开说明,下次可以单独出一期针对配对绑定的说明,可以先参考这个链接:https://www.cnblogs.com/gscw/collections/19924 (包含原理-连接过程)
5、通用属性配置 (GATT) 服务初始化
这段代码构建了从机的数据结构,即定义了主机能看到和操作哪些服务和特征值。

调用这些函数后,蓝牙协议栈的 GATT 数据库中就添加了以下服务:
-
GGS (Generic Access Service):包含设备名称、外观等基本信息。
-
GATTServApp (Generic Attribute Service):包含 GATT Service 特征。
-
DevInfo (Device Information Service):包含制造商名称、型号、固件版本等信息。
-
SimpleProfile (自定义服务):本应用的核心数据传输服务 (UUID: 0xFFE0),包含 Char1 到 Char5 五个自定义特征。

通过 GGS_SetParameter 和 SimpleProfile_SetParameter,为每个可读/可写特征值设置初始数据。例如,设备的广播名称被设置为 "Simple Peripheral",SimpleProfile 的 Char1 初始值为 {1}。
6、连接项初始化与回调注册

1、调用本地函数 peripheralInitConnItem,将存储连接信息的结构体 peripheralConnList 初始化。连接句柄 (connHandle) 被设置为 GAP_CONNHANDLE_INIT,表示当前处于断开状态。
2、注册 SimpleProfile 的应用层回调结构体。其中最重要的回调函数是 simpleProfileChangeCB,它使得应用层可以在主机写入数据到自定义特征值时被及时通知并处理。
3、tmos_set_event(Peripheral_TaskID, SBP_START_DEVICE_EVT);启动事件 (Startup Event) 触发,这是初始化的最后一步,将控制权从初始化函数交给 TMOS 任务处理器。SBP_START_DEVICE_EVT: 这是一个自定义的事件标志,通知任务处理器该开始工作了。

然后调用 Peripheral_ProcessEvent 函数。在 Peripheral_ProcessEvent 中,这个事件将最终触发 GAPRole_PeripheralStartDevice(),从而使从机设备开始广播
7、定义 Peripheral (从机) 设备的配置参数
接下来,来看一下定义 Peripheral (从机) 设备的配置参数相关数据时间,这部分定义了从机在运行过程中使用的各种时间间隔和连接参数。所有时间相关的参数都是以协议栈的基础时间单位为准
| 宏定义 | 原始值 | 实际时间计算 (单位: 100ms, 1.25ms, 或 625µs) | 作用 |
|---|---|---|---|
SBP_PERIODIC_EVT_PERIOD | 1600 | 1600 \times 1.25 \text{ms} = 2.0 \text{s} | 周期事件的间隔时间,用于执行重复的应用层任务(如发送通知)。 |
SBP_READ_RSSI_EVT_PERIOD | 3200 | 3200 \times 1.25 \text{ms} = 4.0 \text{s} | 读取 RSSI(信号强度)事件的间隔时间。 |
SBP_PARAM_UPDATE_DELAY | 6400 | 6400 \times 1.25 \text{ms} = 8.0 \text{s} | 连接参数更新请求的延迟时间。连接建立后延迟 8 秒再发起请求。 |
DEFAULT_ADVERTISING_INTERVAL | 80 | 80 \times 0.625 \text{ms} = 50 \text{ms} | 广播间隔。设备每隔 50 毫秒发送一次广播包。 |
DEFAULT_DESIRED_MIN_CONN_INTERVAL | 20 | 20 \times 1.25 \text{ms} = 25 \text{ms} | 期望的最小连接间隔。 |
DEFAULT_DESIRED_MAX_CONN_INTERVAL | 100 | 100 \times 1.25 \text{ms} = 125 \text{ms} | 期望的最大连接间隔。 |
DEFAULT_DESIRED_SLAVE_LATENCY | 0 | - | 期望的 Slave Latency (从机延迟),为 0 表示每次连接事件从机都必须醒来接收数据。 |
DEFAULT_DESIRED_CONN_TIMEOUT | 100 | 100 \times 10 \text{ms} = 1.0 \text{s} | 期望的连接监管超时。如果在 1 秒内没有收到数据,则认为连接断开。 |
DEFAULT_DISCOVERABLE_MODE | GAP_ADTYPE_FLAGS_GENERAL | - | 通用可发现模式 (General Discoverable Mode),设备将无限期地进行广播。 |
8、 GATT 属性名称

设置 GATT 服务中 设备名称 属性的初始值。这个地方的初始值,如果是用IOS系统手机进行扫描时,需要将这个地方的初始值与扫描应答包中设置的蓝牙名字一一对应,否者会出现扫描的蓝牙名字不对应。
9、连接状态结构体


结构体 peripheralConnItem_t 用于保存当前连接的实时信息,包括:
-
connHandle:连接句柄(ID)。 -
connInterval:当前的实际连接间隔。 -
connSlaveLatency:当前的从机延迟。 -
connTimeout:当前的连接超时时间。
peripheralConnList: 这是一个指向 peripheralConnItem_t 结构体的指针。这个结构体是用来保存单个连接的所有关键参数的
该函数的唯一目的就是将用于存储当前蓝牙连接参数的结构体初始化为一个未连接状态的默认值。
在 BLE 协议栈中,一个设备可以处于广播状态、连接状态或断开状态等。当设备处于非连接状态时,其连接参数应该是无效的或零值的,以确保程序逻辑在处理连接相关操作时不会误用旧的或随机的内存数据。简而言之peripheralInitConnItem 函数就是重置或清空一个连接记录,确保在连接建立之前,程序不会误认为自己已经处于连接状态,或使用任何无效的连接参数。
10、关键回调函数结构体 (Callbacks)
回调函数是应用层与协议栈进行交互的关键接口。当协议栈发生特定事件时(如连接状态变化、收到主机写入的数据),它会通过这些结构体中注册的函数来通知应用层。

10.1 GAP 角色回调 (Peripheral_PeripheralCBs) 这些回调函数用于处理与连接和角色相关的事件。
10.2 绑定管理回调 (Peripheral_BondMgrCBs) 该结构体用于处理安全和配对相关的事件。它们被设置为 NULL,表示不处理复杂的配对流程。
10.3 Simple GATT 服务回调 (Peripheral_SimpleProfileCBs) 这个回调函数是应用层处理数据接收的入口。当主机通过 BLE 写入数据到 Char1 或 Char3 时,simpleProfileChangeCB 会被调用,应用层可以对接收数据的处理逻辑。
11、任务事件处理器:Peripheral_ProcessEvent 详解
这段程序是基于WCH 蓝牙中的 TMOS任务系统与Peripheral (从机) 任务** 的核心函数。它的作用是作为任务的事件循环处理器,负责接收和分发所有发送给该从机任务的事件,包括系统消息、定时器事件以及用户自定义事件。
A. 栈层消息处理 (SYS_EVENT_MSG)

-
标志位:
SYS_EVENT_MSG是系统级的事件,表示协议栈(GAP、GATT、L2CAP 等)有消息要发送给应用层。 -
执行逻辑:
-
tmos_msg_receive(Peripheral_TaskID):从任务的消息队列中接收消息。 -
Peripheral_ProcessTMOSMsg():调用另一个本地函数来解析和处理消息内容。这些消息通常是连接状态变化通知、GATT 读写请求、配对请求等。 -
tmos_msg_deallocate(pMsg):处理完毕后,释放该消息占用的内存。
-
-
返回: 清除
SYS_EVENT_MSG标志。

其中,Peripheral_ProcessTMOSMsg该函数的唯一目的是接收和分发来自协议栈 或其他 TMOS 任务发送给 Peripheral 任务的消息。
当底层协议栈(如 GAP、GATT、L2CAP)或硬件抽象层 (HAL) 发生事件需要通知应用层时,它们不是通过简单的函数回调,而是通过消息队列发送一个结构化的消息。通过读取 pMsg->event,程序就能知道消息的来源和内容类型,从而决定如何处理它。但是当前的程序中,它不处理任何特定类型的消息,所有接收到的消息都会被简单地忽略。
B. 设备启动事件 (SBP_START_DEVICE_EVT)

-
标志位:
SBP_START_DEVICE_EVT是在Peripheral_Init()函数中手动设置的第一个事件。 -
执行逻辑:
-
调用
GAPRole_PeripheralStartDevice():这是启动从机 BLE 功能的关键函数。它会让设备根据在Peripheral_Init()中设置的参数(广播数据、连接间隔等)开始发送广播,进入可发现状态。 -
同时注册了绑定管理器回调 (
Peripheral_BondMgrCBs) 和 GAP 角色回调 (Peripheral_PeripheralCBs)。
-
-
返回: 清除
SBP_START_DEVICE_EVT标志。
C. 周期性任务事件 (SBP_PERIODIC_EVT)

-
标志位:
SBP_PERIODIC_EVT是一个应用层定义的定时器事件,用于执行周期性的数据发送或状态检查。 -
执行逻辑:
-
重启定时器: 检查周期是否大于 0,如果成立,则调用
tmos_start_task()重新设置一个定时器。这实现了周期性事件的循环触发。 -
performPeriodicTask():调用应用层的实际工作函数,
-
-
返回: 清除
SBP_PERIODIC_EVT标志。
在这个performPeriodicTask中可以看倒,这个在周期性定时器事件触发时,向已连接的主机发送一个 BLE 通知(Notification)数据包。

调用时机: 该函数由任务事件处理器 Peripheral_ProcessEvent 调用,响应 SBP_PERIODIC_EVT 事件。
触发通知: 调用 peripheralChar4Notify 函数,将准备好的数据 (notiData) 及其长度传递给它,请求发送 BLE 通知给主机

主要是调用这个peripheralChar4Notify发送函数,它负责处理发送 BLE 通知所必需的底层内存分配和协议栈调用。
先进行内存分配,这是发送 BLE 消息的关键一步,必须从协议栈的消息缓冲池中分配内存。GATT_bm_alloc():协议栈专用的内存分配函数。
-
第一个参数 (
peripheralConnList.connHandle): 当前连接句柄,用于确认通知是发往哪个已连接的主机。 -
第二个参数 (
ATT_HANDLE_VALUE_NOTI): 指示要分配的内存是用于 通知 (Notification) 消息。 -
第三个参数 (
noti.len): 消息内容的长度。
分配成功后,返回的指针(内存地址)存储在 noti.pValue 中。
if(noti.pValue){
tmos_memcpy(noti.pValue, pValue, noti.len);
if(simpleProfile_Notify(peripheralConnList.connHandle, ¬i) != SUCCESS){
GATT_bm_free((gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI);}
}
检查分配: 只有当 noti.pValue 不为空(即内存分配成功)时,才继续。
复制数据: tmos_memcpy() 将应用层准备好的数据 (pValue,即 0x88) 复制到协议栈分配的缓冲区 (noti.pValue) 中。
发送通知: 调用 simpleProfile_Notify() 函数,这是将 ATT 通知消息推送到 L2CAP/Link Layer 的核心函数。这个函数可以后面单独将讲解蓝牙服务时,详细讲解一下。
失败处理: 如果 simpleProfile_Notify() 返回不成功 (!= SUCCESS),说明消息发送失败(例如,主机可能已断开连接,或者主机没有开启 Notification通知)。此时,需要调用 GATT_bm_free() 释放之前分配的内存,避免内存泄漏。
D. 连接参数更新请求事件 (SBP_PARAM_UPDATE_EVT)

-
标志位:
SBP_PARAM_UPDATE_EVT通常是在设备连接成功一段时间后触发的事件。 -
执行逻辑:
-
调用
GAPRole_PeripheralConnParamUpdateReq():向中央设备请求更新连接参数,以优化连接的功耗和吞吐量。它使用了当前连接句柄 (peripheralConnList.connHandle) 和初始化时定义的期望连接参数(如DEFAULT_DESIRED_MIN_CONN_INTERVAL)。 -
请求发出后,等待主机的响应,结果将通过回调函数
peripheralParamUpdateCB处理。
-
-
返回: 清除
SBP_PARAM_UPDATE_EVT标志。
E. 读取 RSSI 事件 (SBP_READ_RSSI_EVT)

-
标志位:
SBP_READ_RSSI_EVT是一个周期性事件,用于读取连接的信号强度。 -
执行逻辑:
-
GAPRole_ReadRssiCmd():向底层协议栈发送命令,要求读取当前连接 (peripheralConnList.connHandle) 的 RSSI 接收信号强度指示。 -
tmos_start_task():重新设置定时器,在下一个SBP_READ_RSSI_EVT_PERIOD(3200*0.625=2秒) 后再次触发此事件。
-
-
返回: 清除
SBP_READ_RSSI_EVT标志。
12、WCH 蓝牙协议栈中状态通知回调函数peripheralStateNotificationCB 详解
它是应用层监控和响应底层蓝牙连接状态变化的核心枢纽,peripheralStateNotificationCB 函数在从机设备的蓝牙状态发生变化时(例如开始广播、连接成功、连接断开)被协议栈调用。

newState: 新的 GAP 角色状态。表示设备当前进入了哪种蓝牙状态(例如:初始化完成、广播中、已连接等)。
newState & GAPROLE_STATE_ADV_MASK:代码中使用位掩码 (GAPROLE_STATE_ADV_MASK) 来获取核心状态,忽略其他可能的状态标志。
pEvent: 一个指向 gapRoleEvent_t 的指针,包含了关于导致状态变化的具体事件的详细信息(例如:连接句柄、断开原因码等)。
A. 状态机分发 (switch 结构)
函数的主体是一个 switch 语句,根据传入的 newState 确定设备当前处于的状态,并执行相应的应用层逻辑。
| case (新状态) | 状态描述 | 处理逻辑 |
|---|---|---|
GAPROLE_STARTED | 初始化完成。设备已完成协议栈和 GAP 角色的初始化。 | 打印 Initialized..。这是设备开始广播前的最后一个初始化步骤。 |
GAPROLE_ADVERTISING | 广播中。设备正在向外发送广播包。 | 打印 Advertising..。这里包含一个特殊的检查:如果事件类型是 GAP_LINK_TERMINATED_EVENT(连接断开事件),说明设备是从连接状态转回了广播状态。此时调用 Peripheral_LinkTerminated(pEvent) 断开连接设备,执行清理和状态重置。 |
GAPROLE_CONNECTED | 已连接。设备已与一个中央设备建立链路。 | 打印 Connected..。这里检查事件类型是否为 GAP_LINK_ESTABLISHED_EVENT(连接建立事件)。如果是,则调用 Peripheral_LinkEstablished(pEvent),建立连接,执行保存连接参数、启动周期任务等连接后操作。 |
GAPROLE_CONNECTED_ADV | 已连接且广播中。设备可能支持同时连接和广播(例如,可连接一个设备,同时对其他设备进行故广播)。 | 打印 Connected Advertising..。 |
GAPROLE_WAITING | 等待状态。通常在有限广播结束后(GAP_END_DISCOVERABLE_DONE_EVENT)或链路断开后、但重新广播前出现。 | 处理 GAP_END_DISCOVERABLE_DONE_EVENT,打印等待广播信息。也处理 GAP_LINK_TERMINATED_EVENT,调用 Peripheral_LinkTerminated(pEvent) 来清理连接并准备重新广播。 |
GAPROLE_ERROR | 错误状态。协议栈或 GAP 角色遇到不可恢复的错误。 | 打印 Error..。应用层可能需要进行重置或特殊处理。 |
default | 其他状态。 | 忽略。 |
13、蓝牙断开连接事件函数Peripheral_LinkTerminated
其中,看一下Peripheral_LinkTerminated函数,用于处理链路断开 事件的核心函数。它的主要职责是在与主机连接断开时,清理所有连接状态,并确保设备能够重新开始广播,准备接受新的连接。

1、pEvent: 这是一个泛型事件结构体指针,由协议栈传递给回调函数。函数将 pEvent 安全地转型为 gapTerminateLinkEvent_t *event。这个特定结构体包含了断开连接的句柄 (connectionHandle) 和断开的原因代码 (reason)。
2、连接句柄校验与错误处理:
检查事件报告的断开连接句柄 (event->connectionHandle) 是否与应用层当前正在跟踪的连接句柄 (peripheralConnList.connHandle) 相匹配。确保程序处理的是正确的、已记录的连接。通常从机设备支持一个连接,这是关键的有效性检查。如果句柄不匹配,打印 ERR..,表示可能出现了意外的连接状态错误。
3、重置连接参数:
将全局保存的连接信息 (peripheralConnList) 清零或重置。
将 connHandle 设置为特殊值 GAP_CONNHANDLE_INIT,这是应用程序判断设备处于断开状态的关键标志。
调用 tmos_stop_task 停止所有依赖于连接才能运行的周期性定时任务
4、重新启动广播:
在连接断开后,设备必须重新进入广播状态才能被其他主机发现和连接。
GAPRole_SetParameter: 这是一个设置 GAP 角色参数的 API。GAPROLE_ADVERT_ENABLED: 指定要设置的参数是广播使能。TRUE: 将广播使能设置为真 (开启)。
总结来说:Peripheral_LinkTerminated 是一个状态机转换函数。它的作用是:
-
确认断开连接事件的有效性。
-
清理连接相关的数据和状态。
-
停止所有连接依赖的事件任务。
-
重启广播,使设备能够被重新发现和连接,
14、蓝牙建立连接事件函数Peripheral_LinkEstablished
用于处理连接建立成功事件的核心函数。它在底层协议栈与主机成功建立物理连接后被调用,负责执行所有连接后的初始化、参数记录和定时任务的调度。

1、pEvent: 指向由 GAP 角色回调 (peripheralStateNotificationCB) 传递的通用事件结构体。将通用指针转型为 gapEstLinkReqEvent_t *event。这个特定结构体包含了连接建立成功后,由协议栈协商确定的所有关键连接参数,例如连接句柄、连接间隔等。
2、应用程序检查全局连接状态变量 peripheralConnList.connHandle。
-
GAP_CONNHANDLE_INIT是一个特殊值(通常是0xFFFF),表示当前没有有效的连接。
如果 connHandle 不等于 GAP_CONNHANDLE_INIT,说明设备当前已经连接了一个主机。
-
调用
GAPRole_TerminateLink()断开刚刚建立的新连接。 -
打印
Connection max...警告信息。
3、连接成功处理:
如果设备处于未连接状态,将连接建立事件报告的实际连接参数保存到全局的 peripheralConnList 结构体中。
connHandle (连接句柄) 是最重要的参数,是所有数据传输、RSSI 读取和连接管理操作的唯一标识符。
4、启动周期性应用任务
通过 TMOS 任务调度系统启动三个重要的周期性事件:周期性数据发送事件、连接参数更新延迟事件、周期性读取 RSSI 事件
5、PRINT("Conn %x - Int %x \n", event->connectionHandle, event->connInterval);打印连接句柄和协商出的连接间隔,这一点可以有助于调试中查看。
15、蓝牙从机任务连接参数更新回调函数peripheralParamUpdateCB

在连接参数(如间隔、延迟和超时)发生变化并被主机和从机确认更新后,将新的参数值记录到应用层变量中。在底层协议栈完成连接参数更新流程后被调用。它接收四个参数,这些参数代表成功协商并生效的新连接参数:
| 参数 | 含义 |
|---|---|
connHandle | 发生参数变化的连接句柄。 |
connInterval | 新的连接间隔(Connection Interval)。 |
connSlaveLatency | 新的从机延迟(Slave Latency)。 |
connTimeout | 新的连接监管超时(Supervision Timeout)。 |
确认协议栈通知的参数变化是针对应用层正在跟踪的当前连接。比较传入的 connHandle 与全局保存的 peripheralConnList.connHandle。
-
如果匹配,程序将协议栈返回的新连接参数值(
connInterval,connSlaveLatency,connTimeout)覆盖全局变量peripheralConnList中保存的旧值。 -
如果不匹配,打印
ERR..错误信息,
16、蓝牙接收数据接口和发送数据接口
蓝牙最主要的功能就进行无线收发功能,在蓝牙从机中,之前讲解过peripheralChar4Notify函数,就是当主机成功连接从机之后,通过NOTIFY通知属性服务,通过从机发送数据至主机设备(手机APP或者BLE芯片主机设备),
针对于接收数据接口上,是利用simpleProfileChangeCB接口函数,这是BLE 从机应用程序中处理 GATT 特征值 (Characteristic Value) 变化 的回调函数。

1、当主机写入: 主机设备(如手机)通过 BLE 链路向从机的某个具有写权限的特征值(例如 Char1 或 Char3)发送了 Write Request 或 Write Command。
2、paramID: 标识发生变化的特征值。它对应于 gattprofile.h 中定义的宏, SIMPLEPROFILE_CHAR1、SIMPLEPROFILE_CHAR3 ,函数使用 switch 结构体根据 paramID 来确定是哪个特征值发生了变化,来接收哪个CHAR通道的数据。

如:当 paramID 为 SIMPLEPROFILE_CHAR1 时执行。
获取新值: 声明一个局部缓冲区 newValue,然后调用 SimpleProfile_GetParameter() 从 GATT 数据库中读取 Char1 特征值的最新值。
应用处理: 打印日志。应用程序会在这里读取 newValue,后续就可以通过这个值判断,来做其他逻辑上的处理。
| 函数 | 类型 | 方向 | 触发原因 | simpleProfileChangeCB 的角色 |
|---|---|---|---|---|
simpleProfileChangeCB | 写/通知 | 主机 \rightarrow 从机 | 主机向从机具有写权限的特征值 (Char1/Char3) 写入数据。 | 接收数据 |
peripheralChar4Notify | 通知 (Notification) | 从机 \rightarrow 主机 | 从机周期性定时器 (SBP_PERIODIC_EVT) 到期。 | 发送数据。 |
simpleProfileChangeCB :处理从主机写入的数据。用于从机接收控制命令或配置参数。
peripheralChar4Notify:处理向主机发送的数据。用于从机发送传感器读数或状态报告**。
整个从机的应用程序就讲解到这个地方,后续在讲解针对不同服务的UUID的操作不同的读写,通知等不一样的服务属性。




1519

被折叠的 条评论
为什么被折叠?



