第1步:打开EVT\EXAM\BLE\Peripheral\Profile下的gattprofile.c和gattprofile.h文件,
// Key Pressed UUID #define SIMPLEPROFILE_CHAR1_UUID 0xFFE1 #define SIMPLEPROFILE_CHAR2_UUID 0xFFE2 #define SIMPLEPROFILE_CHAR3_UUID 0xFFE3 #define SIMPLEPROFILE_CHAR4_UUID 0xFFE4 #define SIMPLEPROFILE_CHAR5_UUID 0xFFE5
官方当前GARR属性表只有5张,0xFFE1 -- 0xFFE5,我们参考方法,实现0xFFE6.
最终效果如:

只配了读取和写入权限,接下来通过手机写入0x0F关掉 0x0E打开,原理图上的PB6外接的LED灯.
在开始之前,先使用下面代码,测试LED是不是能正常点亮:
#include "CH58x_common.h" // muzi测试led任务 static void MuZiLedTask(void) // led函数TASK { static uint8_t init_1=1; if(init_1) // 只初始化一次 { init_1=0; GPIOB_ModeCfg(GPIO_Pin_6, GPIO_ModeOut_PP_20mA); // 推挽输出最大5mA GPIOB_InverseBits(GPIO_Pin_6); // PB6电平翻转,点亮LED } GPIOB_InverseBits(GPIO_Pin_6); // PB6电平翻转,点亮LED }
如果裸机要在GPIOB_InverseBits(GPIO_Pin_6); 后面多加一段延时,才能看清LED灯闪.我是直接用的TMOS系统的周期性EV事件进行测试OK.
时间2026-08-26:
经过阅读及测试发现,用户名首先实现gattprofile.c, gattprofile.h就可以。
以下是gattprofile.h
/********************************** (C) COPYRIGHT ******************************* * File Name : gattprofile.h * Author : WCH * Version : V1.0 * Date : 2018/12/11 * Description : Simple GATT Profile 头文件,定义 MuZi 协议特征值、UUID 及 API 接口 ********************************************************************************* * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. * Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics. *******************************************************************************/ #ifndef GATTPROFILE_H #define GATTPROFILE_H #ifdef __cplusplus extern "C" { #endif /********************************************************************* * INCLUDES */ /********************************************************************* * CONSTANTS */ // Profile Parameters - 特征值参数 ID 定义(需与 gattprofile.c 中 switch-case 一一对应) #define SIMPLEPROFILE_CHAR1 0 // RW uint8_t - 通用特征值 1(读写) #define SIMPLEPROFILE_CHAR2 1 // RW uint8_t - 通用特征值 2(读写) #define SIMPLEPROFILE_CHAR3 2 // RW uint8_t - 通用特征值 3(读写) #define SIMPLEPROFILE_CHAR4 3 // RW uint8_t - 通用特征值 4(读写),MuZi 协议复用为 ACK/响应通知通道 #define SIMPLEPROFILE_CHAR5 4 // RW uint8_t[5] - 通用特征值 5(读写) #define MuZi_CHAR6 5 // MuZi 命令特征值(Write Only),主机→从机:下发控制指令(采集/增益/模式切换等) #define MuZi_CHAR7 6 // MuZi 数据特征值(Notify Only),从机→主机:上报 ADC 分片数据等 // Simple Profile Service UUID - 自定义服务 UUID #define SIMPLEPROFILE_SERV_UUID 0xFFE0 // Characteristic UUIDs - 各特征值 UUID #define SIMPLEPROFILE_CHAR1_UUID 0xFFE1 // 通用特征值 1 UUID #define SIMPLEPROFILE_CHAR2_UUID 0xFFE2 // 通用特征值 2 UUID #define SIMPLEPROFILE_CHAR3_UUID 0xFFE3 // 通用特征值 3 UUID #define SIMPLEPROFILE_CHAR4_UUID 0xFFE4 // 通用特征值 4 UUID,MuZi 协议复用为 ACK/响应通道 #define SIMPLEPROFILE_CHAR5_UUID 0xFFE5 // 通用特征值 5 UUID #define MuZi_CHAR6_UUID 0xFFE6 // MuZi 命令通道 UUID(主机写入指令) #define MuZi_CHAR7_UUID 0xFFE7 // MuZi 数据通道 UUID(从机通知 ADC 数据) // Simple Keys Profile Services bit fields - 服务注册位掩码 #define SIMPLEPROFILE_SERVICE 0x00000001 // SimpleProfile 服务使能位 // Length of characteristic in bytes (Default MTU is 23) - 各特征值最大字节长度 #define SIMPLEPROFILE_CHAR1_LEN 1 // CHAR1 长度:1 字节 #define SIMPLEPROFILE_CHAR2_LEN 1 // CHAR2 长度:1 字节 #define SIMPLEPROFILE_CHAR3_LEN 1 // CHAR3 长度:1 字节 #define SIMPLEPROFILE_CHAR4_LEN 1 // CHAR4 长度:1 字节(MuZi ACK 帧实际使用 4~6 字节,受 MTU 限制) #define SIMPLEPROFILE_CHAR5_LEN 5 // CHAR5 长度:5 字节 // 以上长度用于声明 GATT 属性表中特征值缓冲区大小 #define MuZi_CHAR6_LEN 12 // MuZi 命令帧最大长度:12 字节(帧头1 + 命令码1 + 载荷长度1 + 载荷最多8 + CRC1) #define MuZi_CHAR7_LEN 240 // MuZi 数据分片最大长度:240 字节(配合 BLE_BUFF_MAX_LEN=247,扣除 ATT 头3 + 协议开销4 = 240 有效载荷) /********************************************************************* * TYPEDEFS */ /********************************************************************* * MACROS */ /********************************************************************* * Profile Callbacks */ // Callback when a characteristic value has changed - 特征值写入回调函数类型 // paramID: 被写入的特征值参数 ID(SIMPLEPROFILE_CHAR1~MuZi_CHAR7) // pValue: 写入数据指针 // len: 写入数据长度 typedef void (*simpleProfileChange_t)(uint8_t paramID, uint8_t *pValue, uint16_t len); typedef struct { simpleProfileChange_t pfnSimpleProfileChange; // 特征值变更回调函数指针,由应用层注册 } simpleProfileCBs_t; /********************************************************************* * API FUNCTIONS - 对外 API 接口声明 */ /* * SimpleProfile_AddService - 初始化 Simple GATT Profile 服务,向 GATT Server 注册属性表 * * @param services - 要注册的服务位掩码,可包含多个服务的按位或组合 * @return bStatus_t - SUCCESS 或错误码 */ extern bStatus_t SimpleProfile_AddService(uint32_t services); /* * SimpleProfile_RegisterAppCBs - 注册应用层回调函数(全局仅调用一次) * * @param appCallbacks - 应用回调结构体指针,包含特征值变更回调 * @return bStatus_t - SUCCESS 或 bleAlreadyInRequestedMode(重复注册) */ extern bStatus_t SimpleProfile_RegisterAppCBs(simpleProfileCBs_t *appCallbacks); /* * SimpleProfile_SetParameter - 设置 Simple GATT Profile 特征值参数 * * @param param - 特征值参数 ID(SIMPLEPROFILE_CHAR1~MuZi_CHAR7) * @param len - 写入数据长度(必须与该特征值定义的 LEN 匹配) * @param value - 写入数据指针(根据 param ID 自动转换为对应数据类型) * @return bStatus_t - SUCCESS 或 bleInvalidRange(参数 ID 无效) */ extern bStatus_t SimpleProfile_SetParameter(uint8_t param, uint16_t len, void *value); /* * SimpleProfile_GetParameter - 读取 Simple GATT Profile 特征值参数 * * @param param - 特征值参数 ID(SIMPLEPROFILE_CHAR1~MuZi_CHAR7) * @param value - 读出数据指针(根据 param ID 自动转换为对应数据类型) * @return bStatus_t - SUCCESS 或 bleInvalidRange(参数 ID 无效) */ extern bStatus_t SimpleProfile_GetParameter(uint8_t param, void *value); /* * simpleProfile_Notify - 通过 CHAR4 发送通知(MuZi 协议复用为 ACK/响应通道) * * @param connHandle - 当前连接句柄 * @param pNoti - 通知数据结构体指针(含长度和数据指针,数据需由 GATT_bm_alloc 分配) * @return bStatus_t - SUCCESS 或发送失败错误码 */ extern bStatus_t simpleProfile_Notify(uint16_t connHandle, attHandleValueNoti_t *pNoti); /* * simpleProfile_Notify7 - 通过 MuZi CHAR7 (UUID=FFE7) 发送 ADC 数据通知 * * @param connHandle - 当前连接句柄 * @param pNoti - 通知数据结构体指针(含长度和数据指针) * @return bStatus_t - SUCCESS 或发送失败错误码 */ extern bStatus_t simpleProfile_Notify7(uint16_t connHandle, attHandleValueNoti_t *pNoti); /********************************************************************* *********************************************************************/ #ifdef __cplusplus } #endif #endif /* GATTPROFILE_H */
以下是gattprofile.c文件
/********************************** (C) COPYRIGHT ******************************* * File Name : gattprofile.C * Author : WCH * Version : V1.0 * Date : 2018/12/10 * Description : Customize services with five different attributes, * including readable, writable, notification, * readable and writable, and safe readable * (自定义 GATT 服务,包含可读、可写、通知、读写及安全读等属性, * 并扩展了 MuZi 协议的命令(CHAR6)和数据(CHAR7)通道) ********************************************************************************* * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. * Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics. *******************************************************************************/ /********************************************************************* * INCLUDES (头文件引用) */ #include "CONFIG.h" #include "gattprofile.h" /********************************************************************* * MACROS (宏定义) */ /********************************************************************* * CONSTANTS (常量定义) */ // Position of simpleProfilechar4 value in attribute array // CHAR4 值在属性表中的索引位置(用于 Notify 时快速定位句柄) #define SIMPLEPROFILE_CHAR4_VALUE_POS 11 // Position of MuZi char7 value in attribute array (after adding CCCD) // MuZi CHAR7 值在属性表中的索引位置(用于 Notify7 时快速定位句柄) // AttrTbl Index (属性表索引布局): // 0: Service (服务声明) // 1-3: Char1 (声明+值+描述) // 4-6: Char2 (声明+值+描述) // 7-9: Char3 (声明+值+描述) // 10-13: Char4 (10:Decl, 11:Val, 12:CCCD, 13:Desc) // 14-16: Char5 (声明+值+描述) // 17-19: Char6 (声明+值+描述) // 20-23: Char7 (20:Decl, 21:Val, 22:CCCD, 23:Desc) #define MuZi_CHAR7_VALUE_POS 21 /********************************************************************* * TYPEDEFS (类型定义) */ /********************************************************************* * GLOBAL VARIABLES (全局变量 - UUID 定义) */ // Simple GATT Profile Service UUID: 0xFFE0 (主服务 UUID,小端序存储) const uint8_t simpleProfileServUUID[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_SERV_UUID), HI_UINT16(SIMPLEPROFILE_SERV_UUID)}; // Characteristic 1 UUID: 0xFFE1 (通用特征值 1) const uint8_t simpleProfilechar1UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_CHAR1_UUID), HI_UINT16(SIMPLEPROFILE_CHAR1_UUID)}; // Characteristic 2 UUID: 0xFFE2 (通用特征值 2) const uint8_t simpleProfilechar2UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_CHAR2_UUID), HI_UINT16(SIMPLEPROFILE_CHAR2_UUID)}; // Characteristic 3 UUID: 0xFFE3 (通用特征值 3) const uint8_t simpleProfilechar3UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_CHAR3_UUID), HI_UINT16(SIMPLEPROFILE_CHAR3_UUID)}; // Characteristic 4 UUID: 0xFFE4 (MuZi 协议复用为 ACK/响应通知通道) const uint8_t simpleProfilechar4UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_CHAR4_UUID), HI_UINT16(SIMPLEPROFILE_CHAR4_UUID)}; // Characteristic 5 UUID: 0xFFE5 (通用特征值 5) const uint8_t simpleProfilechar5UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(SIMPLEPROFILE_CHAR5_UUID), HI_UINT16(SIMPLEPROFILE_CHAR5_UUID)}; // MuZi CHAR6 UUID: 0xFFE6 (命令下行通道,主机→从机写入控制指令) const uint8_t MuZi_char6UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(MuZi_CHAR6_UUID), HI_UINT16(MuZi_CHAR6_UUID)}; // MuZi CHAR7 UUID: 0xFFE7 (数据上行通道,从机→主机通知 ADC 分片数据) const uint8_t MuZi_char7UUID[ATT_BT_UUID_SIZE] = { LO_UINT16(MuZi_CHAR7_UUID), HI_UINT16(MuZi_CHAR7_UUID)}; /********************************************************************* * EXTERNAL VARIABLES (外部变量) */ /********************************************************************* * EXTERNAL FUNCTIONS (外部函数) */ /********************************************************************* * LOCAL VARIABLES (局部静态变量) */ // 应用层回调函数指针(特征值被写入时通过此回调通知应用层处理) static simpleProfileCBs_t *simpleProfile_AppCBs = NULL; /********************************************************************* * Profile Attributes - variables (配置文件属性变量) */ // Simple Profile Service attribute (服务声明属性,标识本服务的 UUID) static const gattAttrType_t simpleProfileService = {ATT_BT_UUID_SIZE, simpleProfileServUUID}; // ===== Characteristic 1: Read/Write (可读可写) ===== // 特征值 1 属性位掩码:支持读和写操作 static uint8_t simpleProfileChar1Props = GATT_PROP_READ | GATT_PROP_WRITE; // 特征值 1 本地数据缓冲区(存储当前值,供读取和 SetParameter 使用) static uint8_t simpleProfileChar1[SIMPLEPROFILE_CHAR1_LEN] = {0}; // 特征值 1 用户描述字符串(GATT 可选属性,便于调试工具识别) static uint8_t simpleProfileChar1UserDesp[] = "Characteristic 1\0"; // ===== Characteristic 2: Read Only (只读) ===== // 特征值 2 属性位掩码:仅支持读操作 static uint8_t simpleProfileChar2Props = GATT_PROP_READ; // 特征值 2 本地数据缓冲区 static uint8_t simpleProfileChar2[SIMPLEPROFILE_CHAR2_LEN] = {0}; // 特征值 2 用户描述字符串 static uint8_t simpleProfileChar2UserDesp[] = "Characteristic 2\0"; // ===== Characteristic 3: Write Only (只写) ===== // 特征值 3 属性位掩码:仅支持写操作 static uint8_t simpleProfileChar3Props = GATT_PROP_WRITE; // 特征值 3 本地数据缓冲区 static uint8_t simpleProfileChar3[SIMPLEPROFILE_CHAR3_LEN] = {0}; // 特征值 3 用户描述字符串 static uint8_t simpleProfileChar3UserDesp[] = "Characteristic 3\0"; // ===== Characteristic 4: Notify (通知,MuZi 复用为 ACK/响应通道) ===== // 特征值 4 属性位掩码:仅支持通知(不可直接读写) static uint8_t simpleProfileChar4Props = GATT_PROP_NOTIFY; // 特征值 4 本地数据缓冲区(Notify 不通过此缓冲发送,仅作占位) static uint8_t simpleProfileChar4[SIMPLEPROFILE_CHAR4_LEN] = {0}; // 特征值 4 CCCD 配置表(每个连接独立维护通知使能状态) // Each client has its own instantiation of the Client Characteristic Configuration. // Reads/writes only affect the configuration for that specific client. static gattCharCfg_t simpleProfileChar4Config[PERIPHERAL_MAX_CONNECTION]; // MuZi CHAR7 CCCD 配置表(每个连接独立维护数据通知使能状态) static gattCharCfg_t simpleProfileChar7Config[PERIPHERAL_MAX_CONNECTION]; // 特征值 4 用户描述字符串 static uint8_t simpleProfileChar4UserDesp[] = "Characteristic 4\0"; // ===== Characteristic 5: Authenticated Read (认证读) ===== // 特征值 5 属性位掩码:需配对认证后才能读取 static uint8_t simpleProfileChar5Props = GATT_PROP_READ; // 特征值 5 本地数据缓冲区 static uint8_t simpleProfileChar5[SIMPLEPROFILE_CHAR5_LEN] = {0}; // 特征值 5 用户描述字符串 static uint8_t simpleProfileChar5UserDesp[] = "Characteristic 5\0"; // ===== MuZi Characteristic 6: Write Only (命令下行通道) ===== // MuZi CHAR6 属性位掩码:仅支持写入(主机下发控制指令) static uint8_t MuZi_Char6Props = GATT_PROP_WRITE; // MuZi CHAR6 本地数据缓冲区(初始化为帧头 0xAA,便于调试识别) static uint8_t MuZi_Char6[MuZi_CHAR6_LEN] = {0xAA}; // MuZi CHAR6 用户描述字符串 static uint8_t MuZi_Char6UserDesp[] = "MuZi_Characteristic 6\0"; // ===== MuZi Characteristic 7: Notify (数据上行通道) ===== // MuZi CHAR7 属性位掩码:仅支持通知(从机上报 ADC 分片数据) static uint8_t MuZi_Char7Props = GATT_PROP_NOTIFY; // MuZi CHAR7 本地数据缓冲区(初始化为 SOF 0x55,便于调试识别) static uint8_t MuZi_Char7[MuZi_CHAR7_LEN] = {0x55}; // MuZi CHAR7 用户描述字符串 static uint8_t MuZi_Char7UserDesp[] = "MuZi_Characteristic 7\0"; /********************************************************************* * Profile Attributes - Table (GATT 属性表) * 格式: {UUID类型/权限/自动句柄/值指针} * 注意:属性表顺序必须与上方 POS 常量定义的索引严格对应 */ static gattAttribute_t simpleProfileAttrTbl[] = { // ===== Simple Profile Service Declaration (服务声明,索引 0) ===== { { ATT_BT_UUID_SIZE, primaryServiceUUID }, GATT_PERMIT_READ, 0, (uint8_t *)&simpleProfileService }, // ----- Characteristic 1: Read/Write + User Desc (索引 1-3) ----- { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &simpleProfileChar1Props }, // [1] 特征值声明(描述属性位掩码) { { ATT_BT_UUID_SIZE, simpleProfilechar1UUID}, GATT_PERMIT_READ | GATT_PERMIT_WRITE , 0, simpleProfileChar1 }, // [2] 特征值本体(可读可写) { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, simpleProfileChar1UserDesp }, // [3] 用户描述 // ----- Characteristic 2: Read Only + User Desc (索引 4-6) ----- { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &simpleProfileChar2Props }, // [4] 特征值声明 { { ATT_BT_UUID_SIZE, simpleProfilechar2UUID}, GATT_PERMIT_READ, 0, simpleProfileChar2 }, // [5] 特征值本体(只读) { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, simpleProfileChar2UserDesp }, // [6] 用户描述 // ----- Characteristic 3: Write Only + User Desc (索引 7-9) ----- { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &simpleProfileChar3Props }, // [7] 特征值声明(声明本身始终可读) { { ATT_BT_UUID_SIZE, simpleProfilechar3UUID}, GATT_PERMIT_WRITE, 0, simpleProfileChar3 }, // [8] 特征值本体(只写) { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, simpleProfileChar3UserDesp }, // [9] 用户描述 // ----- Characteristic 4: Notify + CCCD + User Desc (索引 10-13) ----- { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &simpleProfileChar4Props }, // [10] 特征值声明 { { ATT_BT_UUID_SIZE, simpleProfilechar4UUID}, 0, 0, simpleProfileChar4 }, // [11] 特征值本体(Notify 无直接读写权限,POS=11) { { ATT_BT_UUID_SIZE, clientCharCfgUUID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, (uint8_t *)simpleProfileChar4Config }, // [12] CCCD(客户端通知使能配置) { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, simpleProfileChar4UserDesp }, // [13] 用户描述 // ----- Characteristic 5: Authenticated Read + User Desc (索引 14-16) ----- { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &simpleProfileChar5Props }, // [14] 特征值声明 { { ATT_BT_UUID_SIZE, simpleProfilechar5UUID}, GATT_PERMIT_AUTHEN_READ, 0, simpleProfileChar5 }, // [15] 特征值本体(需配对认证后才可读) { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, simpleProfileChar5UserDesp }, // [16] 用户描述 // ----- MuZi CHAR6: Write Only (命令通道) + User Desc (索引 17-19) ----- { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &MuZi_Char6Props }, // [17] 特征值声明 { { ATT_BT_UUID_SIZE, MuZi_char6UUID }, GATT_PERMIT_WRITE, 0, MuZi_Char6 }, // [18] 特征值本体(主机写入 MuZi 命令帧) { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, MuZi_Char6UserDesp }, // [19] 用户描述 // ----- MuZi CHAR7: Notify + CCCD + User Desc (索引 20-23) ----- { { ATT_BT_UUID_SIZE, characterUUID }, GATT_PERMIT_READ, 0, &MuZi_Char7Props }, // [20] 特征值声明 { { ATT_BT_UUID_SIZE, MuZi_char7UUID }, 0, 0, MuZi_Char7 }, // [21] 特征值本体(Notify-only,POS=21) { { ATT_BT_UUID_SIZE, clientCharCfgUUID }, GATT_PERMIT_READ | GATT_PERMIT_WRITE, 0, (uint8_t *)simpleProfileChar7Config }, // [22] CCCD(MuZi 数据通知使能配置) { { ATT_BT_UUID_SIZE, charUserDescUUID }, GATT_PERMIT_READ, 0, MuZi_Char7UserDesp }, // [23] 用户描述 }; /********************************************************************* * LOCAL FUNCTIONS (局部函数原型) */ // GATT 读属性回调(客户端发起 Read Request 时由协议栈调用) static bStatus_t simpleProfile_ReadAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t *pLen, uint16_t offset, uint16_t maxLen, uint8_t method); // GATT 写属性回调(客户端发起 Write Request 时由协议栈调用) static bStatus_t simpleProfile_WriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method); // 链路连接状态变更回调(断开时重置 CCCD) static void simpleProfile_HandleConnStatusCB(uint16_t connHandle, uint8_t changeType); /********************************************************************* * PROFILE CALLBACKS (协议栈回调注册) */ // Simple Profile Service Callbacks (GATT 服务回调结构体) static gattServiceCBs_t simpleProfileCBs = { simpleProfile_ReadAttrCB, // Read callback function pointer (读属性回调) simpleProfile_WriteAttrCB, // Write callback function pointer (写属性回调) NULL // Authorization callback function pointer (授权回调,未使用) }; /********************************************************************* * PUBLIC FUNCTIONS (公开函数) */ /********************************************************************* * @fn SimpleProfile_AddService * * @brief Initializes the Simple Profile service by registering * GATT attributes with the GATT server. * (初始化 Simple Profile 服务,向 GATT 服务器注册属性表) * * @param services - services to add. This is a bit map and can * contain more than one service. * (要注册的服务位掩码,支持多服务按位或组合) * * @return Success or Failure */ bStatus_t SimpleProfile_AddService(uint32_t services) { bStatus_t status = SUCCESS; // Initialize CCCD to default (notifications disabled) // 初始化所有 CCCD 为默认状态(通知关闭),防止未使能就发送通知 GATTServApp_InitCharCfg(INVALID_CONNHANDLE, simpleProfileChar4Config); GATTServApp_InitCharCfg(INVALID_CONNHANDLE, simpleProfileChar7Config); // Register connection status callback with Link DB // 注册链路状态回调,用于连接断开时自动重置 CCCD linkDB_Register(simpleProfile_HandleConnStatusCB); // Register GATT service only if requested // 仅当请求了 SIMPLEPROFILE_SERVICE 时才注册属性表 if (services & SIMPLEPROFILE_SERVICE) { status = GATTServApp_RegisterService(simpleProfileAttrTbl, GATT_NUM_ATTRS(simpleProfileAttrTbl), // 属性表及条目数 GATT_MAX_ENCRYPT_KEY_SIZE, // 最大加密密钥长度 &simpleProfileCBs); // 服务回调结构体 } return status; } /********************************************************************* * @fn SimpleProfile_RegisterAppCBs * * @brief Registers the application callback function. Only call * this function once. * (注册应用层回调函数,特征值被写入时通知应用层。全局仅调用一次) * * @param callbacks - pointer to application callbacks. * (应用回调结构体指针) * * @return SUCCESS or bleAlreadyInRequestedMode */ bStatus_t SimpleProfile_RegisterAppCBs(simpleProfileCBs_t *appCallbacks) { if (appCallbacks != NULL) { simpleProfile_AppCBs = appCallbacks; return SUCCESS; } return bleAlreadyInRequestedMode; // 传入 NULL 视为重复/无效注册 } /********************************************************************* * @fn SimpleProfile_SetParameter (设置参数) * * @brief Set a Simple Profile parameter. * (设置指定特征值的本地数据副本,不影响 BLE 空中接口) * * @param param - Profile parameter ID (特征值参数 ID) * @param len - length of data to write (写入数据长度,必须匹配特征值定义长度) * @param value - pointer to data to write. (写入数据指针) * * @return bStatus_t - SUCCESS / bleInvalidRange / INVALIDPARAMETER */ bStatus_t SimpleProfile_SetParameter(uint8_t param, uint16_t len, void *value) { bStatus_t ret = SUCCESS; switch(param) { case SIMPLEPROFILE_CHAR1: if(len == SIMPLEPROFILE_CHAR1_LEN) tmos_memcpy(simpleProfileChar1, value, SIMPLEPROFILE_CHAR1_LEN); else ret = bleInvalidRange; // 长度不匹配 break; case SIMPLEPROFILE_CHAR2: if(len == SIMPLEPROFILE_CHAR2_LEN) tmos_memcpy(simpleProfileChar2, value, SIMPLEPROFILE_CHAR2_LEN); else ret = bleInvalidRange; break; case SIMPLEPROFILE_CHAR3: if(len == SIMPLEPROFILE_CHAR3_LEN) tmos_memcpy(simpleProfileChar3, value, SIMPLEPROFILE_CHAR3_LEN); else ret = bleInvalidRange; break; case SIMPLEPROFILE_CHAR4: if(len == SIMPLEPROFILE_CHAR4_LEN) tmos_memcpy(simpleProfileChar4, value, SIMPLEPROFILE_CHAR4_LEN); else ret = bleInvalidRange; break; case SIMPLEPROFILE_CHAR5: if(len == SIMPLEPROFILE_CHAR5_LEN) tmos_memcpy(simpleProfileChar5, value, SIMPLEPROFILE_CHAR5_LEN); else ret = bleInvalidRange; break; case MuZi_CHAR6: //-- MuZi 命令通道:设置本地命令缓冲副本 if(len == MuZi_CHAR6_LEN) tmos_memcpy(MuZi_Char6, value, MuZi_CHAR6_LEN); else ret = bleInvalidRange; break; default: ret = INVALIDPARAMETER; // 未知参数 ID break; } return (ret); } /********************************************************************* * @fn SimpleProfile_GetParameter (获取参数) * * @brief Get a Simple Profile parameter. * (获取指定特征值的本地数据副本) * * @param param - Profile parameter ID (特征值参数 ID) * @param value - pointer to data buffer to receive value. (读出数据缓冲指针) * * @return bStatus_t - SUCCESS / INVALIDPARAMETER */ bStatus_t SimpleProfile_GetParameter(uint8_t param, void *value) { bStatus_t ret = SUCCESS; switch(param) { case SIMPLEPROFILE_CHAR1: tmos_memcpy(value, simpleProfileChar1, SIMPLEPROFILE_CHAR1_LEN); break; case SIMPLEPROFILE_CHAR2: tmos_memcpy(value, simpleProfileChar2, SIMPLEPROFILE_CHAR2_LEN); break; case SIMPLEPROFILE_CHAR3: tmos_memcpy(value, simpleProfileChar3, SIMPLEPROFILE_CHAR3_LEN); break; case SIMPLEPROFILE_CHAR4: tmos_memcpy(value, simpleProfileChar4, SIMPLEPROFILE_CHAR4_LEN); break; case SIMPLEPROFILE_CHAR5: tmos_memcpy(value, simpleProfileChar5, SIMPLEPROFILE_CHAR5_LEN); break; case MuZi_CHAR6: //-- MuZi 命令通道:读取本地命令缓冲副本 tmos_memcpy(value, MuZi_Char6, MuZi_CHAR6_LEN); break; case MuZi_CHAR7: //-- MuZi 数据通道:读取本地数据缓冲副本 tmos_memcpy(value, MuZi_Char7, MuZi_CHAR7_LEN); break; default: ret = INVALIDPARAMETER; // 未知参数 ID break; } return (ret); } /********************************************************************* * @fn simpleProfile_Notify (CHAR4 通知功能) * * @brief Send a notification containing a heart rate measurement. * (通过 CHAR4 发送通知数据,MuZi 协议复用为 ACK/响应通道) * * @param connHandle - connection handle (当前连接句柄) * @param pNoti - pointer to notification structure (通知结构体,含数据指针和长度) * * @return Success or Failure (bleIncorrectMode 表示客户端未使能通知) */ bStatus_t simpleProfile_Notify(uint16_t connHandle, attHandleValueNoti_t *pNoti) { // 读取该连接的 CCCD 配置,检查客户端是否已使能通知 uint16_t value = GATTServApp_ReadCharCfg(connHandle, simpleProfileChar4Config); // If notifications enabled (如果已开启通知) if(value & GATT_CLIENT_CFG_NOTIFY) { // Set the handle (通过预定义索引获取 CHAR4 值属性的运行时句柄) pNoti->handle = simpleProfileAttrTbl[SIMPLEPROFILE_CHAR4_VALUE_POS].handle; // Send the notification (通过 BLE 协议栈发送通知,FALSE=不需要认证) return GATT_Notification(connHandle, pNoti, FALSE); } return bleIncorrectMode; // 客户端未使能 CCCD 通知 } /********************************************************************* * @fn simpleProfile_Notify7 (CHAR7 MuZi 数据通知功能) * * @brief Send a notification containing the MuZi CHAR7 data. * (通过 MuZi CHAR7 发送 ADC 分片数据通知) * * @param connHandle - connection handle (当前连接句柄) * @param pNoti - pointer to notification structure (通知结构体,含数据指针和长度) * * @return Success or Failure (bleIncorrectMode 表示客户端未使能通知) */ bStatus_t simpleProfile_Notify7(uint16_t connHandle, attHandleValueNoti_t *pNoti) { // 读取该连接的 CHAR7 CCCD 配置 uint16_t value = GATTServApp_ReadCharCfg(connHandle, simpleProfileChar7Config); // If notifications enabled (如果已开启 MuZi 数据通知) if(value & GATT_CLIENT_CFG_NOTIFY) { // Set the handle using the defined POS constant // 通过预定义索引获取 CHAR7 值属性的运行时句柄 pNoti->handle = simpleProfileAttrTbl[MuZi_CHAR7_VALUE_POS].handle; // Send the notification (通过 BLE 协议栈发送通知) return GATT_Notification(connHandle, pNoti, FALSE); } return bleIncorrectMode; // 客户端未使能 CCCD 通知 } /********************************************************************* * @fn simpleProfile_ReadAttrCB (用户手机读取回调函数) * * @brief Read an attribute. * (GATT 读操作回调,当客户端发起 Read Request 时由协议栈调用) * * @param connHandle - connection handle (当前连接句柄) * @param pAttr - pointer to attribute being read (被读的属性指针) * @param pValue - output buffer for read data (读出数据输出缓冲) * @param pLen - output: actual bytes read (实际读出字节数) * @param offset - read offset (supports Read Blob) (读偏移,支持 Read Blob 分包读取) * @param maxLen - maximum bytes client can accept (客户端 MTU 限制的最大可读字节数) * * @return Success or Failure */ static bStatus_t simpleProfile_ReadAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t *pLen, uint16_t offset, uint16_t maxLen, uint8_t method) { // Only 16-bit UUIDs are used in Simple Profile (本服务仅使用 16-bit UUID) if (pAttr->type.len != ATT_BT_UUID_SIZE) { *pLen = 0; return ATT_ERR_INVALID_HANDLE; } // 从属性中提取 16-bit UUID 并映射到对应的特征值 uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]); uint16_t attrLen = 0; uint8_t charIndex = 0; // 特征值索引(可用于扩展统计或访问控制) switch (uuid) { // Char3 has no read permission; handled by GATT Server App before reaching here // CHAR3 为只写,协议栈会在到达此回调前拦截非法读请求 case SIMPLEPROFILE_CHAR1_UUID: attrLen = SIMPLEPROFILE_CHAR1_LEN; charIndex = SIMPLEPROFILE_CHAR1; break; case SIMPLEPROFILE_CHAR2_UUID: attrLen = SIMPLEPROFILE_CHAR2_LEN; charIndex = SIMPLEPROFILE_CHAR2; break; // Char4 is not readable per spec, but included for notification state query // CHAR4 规范上不可读,但保留分支以兼容某些客户端的状态查询行为 case SIMPLEPROFILE_CHAR4_UUID: attrLen = SIMPLEPROFILE_CHAR4_LEN; charIndex = SIMPLEPROFILE_CHAR4; break; case SIMPLEPROFILE_CHAR5_UUID: attrLen = SIMPLEPROFILE_CHAR5_LEN; charIndex = SIMPLEPROFILE_CHAR5; break; case MuZi_CHAR6_UUID: // MuZi 命令通道(通常为只写,保留读分支用于调试回读) attrLen = MuZi_CHAR6_LEN; charIndex = MuZi_CHAR6; break; // MuZi_CHAR7 已改为 Notify-only,不再支持 Read,移除读分支 // 未匹配的 UUID 返回属性未找到错误 default: *pLen = 0; return ATT_ERR_ATTR_NOT_FOUND; } // 偏移校验:支持 Read Blob Request,offset 必须在有效范围内 // 规范要求 offset >= attrLen 时返回 Invalid Offset if (offset >= attrLen) { *pLen = 0; return ATT_ERR_INVALID_OFFSET; } // Clamp to maxLen and copy attribute value from offset // 计算剩余可读字节数,截取不超过 maxLen 的数据并从偏移处拷贝 uint16_t remaining = attrLen - offset; *pLen = (maxLen < remaining) ? maxLen : remaining; tmos_memcpy(pValue, pAttr->pValue + offset, *pLen); //-- 将特征值数据填入读出缓冲 return SUCCESS; } /********************************************************************* * @fn simpleProfile_WriteAttrCB (用户手机写入回调函数) * * @brief Validate attribute data prior to a write operation * (GATT 写操作回调,当客户端发起 Write Request 时由协议栈调用) * * @param connHandle - connection handle (当前连接句柄) * @param pAttr - pointer to attribute being written (被写的属性指针) * @param pValue - data to be written (写入数据) * @param len - length of data (写入数据长度) * @param offset - write offset (0 for normal write) (写偏移,普通写为 0) * * @return Success or Failure */ static bStatus_t simpleProfile_WriteAttrCB(uint16_t connHandle, gattAttribute_t *pAttr, uint8_t *pValue, uint16_t len, uint16_t offset, uint8_t method) { bStatus_t status = SUCCESS; uint8_t notifyApp = 0xFF; // 标记需要通知应用层的特征值 ID,0xFF 表示无需通知 // If attribute permissions require authorization to write, return error // 检查属性是否需要授权写入 if(gattPermitAuthorWrite(pAttr->permissions)) { return (ATT_ERR_INSUFFICIENT_AUTHOR); } if(pAttr->type.len == ATT_BT_UUID_SIZE) { // 16-bit UUID 分支处理 uint16_t uuid = BUILD_UINT16(pAttr->type.uuid[0], pAttr->type.uuid[1]); switch(uuid) { /////////////////////////////////////////////高频操作放最前面(优化分支预测) case MuZi_CHAR6_UUID: // MUZI 命令通道写入(最高频操作) // Validate: Make sure it's not a blob operation // 校验:不支持长写(Write Long),offset 必须为 0 if(offset == 0) { if(len > MuZi_CHAR6_LEN) status = ATT_ERR_INVALID_VALUE_SIZE; // 写入长度超限 } else { status = ATT_ERR_ATTR_NOT_LONG; // 不支持长写操作 } // Write the value (校验通过后拷贝数据到属性缓冲) if(status == SUCCESS) { tmos_memcpy(pAttr->pValue, pValue, len); notifyApp = MuZi_CHAR6; // 标记需通知应用层处理命令 } break; case GATT_CLIENT_CHAR_CFG_UUID: // CCCD 写入处理:协议栈自动更新对应的 Config 数组 // 根据 pAttr 指针自动区分是 CHAR4 还是 CHAR7 的 CCCD status = GATTServApp_ProcessCCCWriteReq(connHandle, pAttr, pValue, len, offset, GATT_CLIENT_CFG_NOTIFY); break; ////////////////////////////////////////////////////////////////// case SIMPLEPROFILE_CHAR1_UUID: if(offset == 0) { if(len > SIMPLEPROFILE_CHAR1_LEN) status = ATT_ERR_INVALID_VALUE_SIZE; } else { status = ATT_ERR_ATTR_NOT_LONG; } if(status == SUCCESS) { tmos_memcpy(pAttr->pValue, pValue, len); notifyApp = SIMPLEPROFILE_CHAR1; } break; case SIMPLEPROFILE_CHAR3_UUID: if(offset == 0) { if(len > SIMPLEPROFILE_CHAR3_LEN) status = ATT_ERR_INVALID_VALUE_SIZE; } else { status = ATT_ERR_ATTR_NOT_LONG; } if(status == SUCCESS) { tmos_memcpy(pAttr->pValue, pValue, len); notifyApp = SIMPLEPROFILE_CHAR3; } break; default: // 程序不应执行到此处!(特征值 2、4、5、7 没有写权限,协议栈应提前拦截) status = ATT_ERR_ATTR_NOT_FOUND; break; } } else { // 128-bit UUID not supported (本服务不支持 128-bit UUID) status = ATT_ERR_INVALID_HANDLE; } // If a characteristic value changed then callback to notify application // 如果特征值被成功写入且标记了通知,通过回调通知应用层处理 if((notifyApp != 0xFF) && simpleProfile_AppCBs && simpleProfile_AppCBs->pfnSimpleProfileChange) { simpleProfile_AppCBs->pfnSimpleProfileChange(notifyApp, pValue, len); } return (status); } /********************************************************************* * @fn simpleProfile_HandleConnStatusCB (链路连接状态变化回调) * * @brief Simple Profile link status change handler function. * (连接状态变更处理:断开时重置 CCCD,防止重连后误发通知) * * @param connHandle - connection handle (连接句柄) * @param changeType - type of change (变更类型:移除/状态标志更新) * * @return none */ static void simpleProfile_HandleConnStatusCB(uint16_t connHandle, uint8_t changeType) { // Ignore loopback connections (忽略环回测试连接) if (connHandle == LOOPBACK_CONNHANDLE) { return; } // Check if connection has been removed or transitioned to disconnected state // 判断连接是否已被移除或已断开 uint8_t connRemoved = (changeType == LINKDB_STATUS_UPDATE_REMOVED); uint8_t connDown = (changeType == LINKDB_STATUS_UPDATE_STATEFLAGS) && !linkDB_Up(connHandle); if (connRemoved || connDown) { // 断开连接时重置 CCCD,确保下次连接不会自动发送通知 // 必须由客户端重新写入 CCCD 使能通知后才能发送数据 GATTServApp_InitCharCfg(connHandle, simpleProfileChar4Config); // 重置 CHAR4 ACK 通知使能 GATTServApp_InitCharCfg(connHandle, simpleProfileChar7Config); // 重置 CHAR7 数据通知使能 } } /********************************************************************* *********************************************************************/
然后就要实现peripheral.h和peripheral.c,这样你的系统就可以跑起来。
以下是peripheral.h文件
/********************************** (C) COPYRIGHT ******************************* * File Name : peripheral.h * Author : WCH * Version : V1.0 * Date : 2018/12/11 * Description : ********************************************************************************* * Copyright (c) 2021 Nanjing Qinheng Microelectronics Co., Ltd. * Attention: This software (modified or not) and binary are used for * microcontroller manufactured by Nanjing Qinheng Microelectronics. *******************************************************************************/ #ifndef PERIPHERAL_H #define PERIPHERAL_H #ifdef __cplusplus extern "C" { #endif /********************************************************************* * INCLUDES */ /********************************************************************* * CONSTANTS */ // Peripheral Task Events 外围角色事件 #define SBP_START_DEVICE_EVT 0x0001 // 启动设备事件 #define SBP_PERIODIC_EVT 0x0002 // 周期性事件 #define SBP_READ_RSSI_EVT 0x0004 // 读取RSSI事件 #define SBP_PARAM_UPDATE_EVT 0x0008 // 参数更新事件 // (0x0010 原 PHY_UPDATE_EVT 已移除,保留占位避免事件位变更) #define SBP_MUZI_CMD_EVT 0x0020 // MUZI-CMD事件 #define SBP_MUZI_DATA_EVT 0x0040 // MUZI-Data事件 #define SBP_MTU_EXCHANGE_EVT 0x0080 // 从机主动发起 MTU 交换事件 /********************************************************************* * MACROS */ typedef struct { // 当前连接的连接句柄 uint16_t connHandle; // Connection handle of current connection uint16_t connInterval; uint16_t connSlaveLatency; uint16_t connTimeout; } peripheralConnItem_t; /********************************************************************* * FUNCTIONS */ /* * Task Initialization for the BLE Application */ extern void Peripheral_Init(void); /* * Task Event Processor for the BLE Application */ // 单独优化性能O4 extern uint16_t Peripheral_ProcessEvent(uint8_t task_id, uint16_t events) __attribute__ ((optimize("04")));; /********************************************************************* *********************************************************************/ #ifdef __cplusplus } #endif #endif
以下是gattprofile.c文件
/********************************** (C) COPYRIGHT ******************************* * File Name : peripheral.c * Author : WCH / Modified by Assistant * Version : V1.2 * Date : 2023/10/27 * Description : Peripheral slave multi-connection application with * custom MuZi protocol for ADC data transmission. * (外设从机多连接应用,集成自定义 MuZi 协议用于 ADC 数据传输) *********************************************************************************/ /********************************************************************* * INCLUDES (头文件引用) */ #include "CONFIG.h" // 全局配置宏定义 #include "devinfoservice.h" // 设备信息服务 #include "gattprofile.h" // GATT 配置文件接口 #include "peripheral.h" // 外设角色头文件 #include "muzi_crc.h" // MuZi 协议 CRC-8/MAXIM 校验 #include "muzi_mode.h" // MuZi 模式管理(快速/普通模式切换) #include "muzi_random.h" #include "HAL.h" // 抽像层接口 // Standard includes for standalone compilation check (标准库,用于独立编译检查) #include <stdint.h> #include <stddef.h> #include <stdbool.h> #include <string.h> /********************************************************************* * MACROS & CONSTANTS (宏与常量定义) */ // How often to perform periodic event 0.625ms x N (周期性事件间隔,单位 0.625ms) #define SBP_PERIODIC_EVT_PERIOD 1600 // 1600 * 0.625ms = 1s // [FIX] 命令处理不再使用周期轮询,改为立即触发,此宏保留但不再用于 CMD 事件启动 // #define SBP_MUZI_CMD_EVT_PERIOD 160 // 已废弃: 100ms 轮询延迟是ACK丢失主因 // MUZI-Data event period (MuZi 数据发送事件周期) #define SBP_MUZI_DATA_EVT_PERIOD 80 // 50ms // How often to perform read rssi event (RSSI 读取周期) #define SBP_READ_RSSI_EVT_PERIOD 3200 // 2s // Parameter update delay (连接参数更新延迟) #define SBP_PARAM_UPDATE_DELAY 6400 // 4s // MTU exchange delay (units of 625us, 160 = 100ms) (MTU 交换延迟) #define SBP_MTU_EXCHANGE_DELAY 160 // Advertising interval (units of 625us, 80=50ms) (广播间隔) #define DEFAULT_ADVERTISING_INTERVAL 80 // Discoverable mode (可发现模式) #define DEFAULT_DISCOVERABLE_MODE GAP_ADTYPE_FLAGS_GENERAL // Company Identifier: WCH (公司标识符) #define WCH_COMPANY_ID 0x07D7 // [FIX] 新增: 延迟 ACK 事件及延迟时间(避开 PHY/参数更新竞争窗口) #define SBP_MUZI_ACK_DELAY_EVT 0x0800 // 延迟ACK事件位(确保不与已有事件冲突) #define MUZI_ACK_DELAY_TICKS 32 // 20ms (32 * 0.625ms),等待PHY更新完成 // MuZi CHAR7 Data Fragmentation Constants (MuZi CHAR7 数据分片常量) #define MuZi_ADC_SOF 0x55 // 帧起始标志 #define MuZi_ADC_MAX_LEN 234 // 最大数据长度,按 3 字节对齐(24-bit 模式) #define MuZi_CHAR7_OVERHEAD 4 // 协议开销: SOF + FRAG_HDR + LEN + CRC #define MuZi_MAX_PAYLOAD (MuZi_CHAR7_LEN - MuZi_CHAR7_OVERHEAD) // 单包最大有效载荷 // MuZi CHAR6 Command Codes (MuZi CHAR6 命令码定义) #define MuZi_CMD_START_ACQ 0x01 // 开始采集 #define MuZi_CMD_STOP_ACQ 0x02 // 停止采集 #define MuZi_CMD_SET_GAIN 0x03 // 设置增益 #define MuZi_CMD_SET_RATE 0x04 // 设置采样率 #define MuZi_CMD_NEXT_FRAG 0x05 // 请求下一分片(流控) #define MuZi_CMD_REQ_FRAG 0x06 // 请求指定分片(重传) #define MuZi_CMD_FAST_MODE 0x07 // 切换到快速模式 #define MuZi_CMD_NORMAL_MODE 0x08 // 切换到普通模式 #define MuZi_CMD_GET_INFO 0x09 // 获取设备信息 // MuZi Acknowledgement Codes (MuZi 应答码) #define MuZi_ACK_SET_GAIN 0x43 // 增益设置确认 #define MuZi_ACK_SET_RATE 0x44 // 采样率设置确认 // GET_INFO Response Payload Definition (GET_INFO 响应载荷定义) #define MUZI_INFO_VER_MAJOR 1 // 主版本号 #define MUZI_INFO_VER_MINOR 0 // 次版本号 #define MUZI_INFO_MAX_MTU_LO LO_UINT16(BLE_BUFF_MAX_LEN) // 最大 MTU 低字节 #define MUZI_INFO_MAX_MTU_HI HI_UINT16(BLE_BUFF_MAX_LEN) // 最大 MTU 高字节 #define MUZI_FEATURE_FAST_MODE 0x01 // 特性位: 支持快速模式 #define MUZI_FEATURE_24BIT_ADC 0x02 // 特性位: 支持 24-bit ADC #define MUZI_FEATURE_8BIT_ADC 0x04 // 特性位: 支持 8-bit ADC #define MUZI_FEATURE_NOTIFY 0x08 // 特性位: 支持通知 /********************************************************************* * TYPEDEFS (类型定义) */ // ADC Data Sample Format (ADC 数据采样格式枚举) typedef enum { MuZi_ADC_FMT_8BIT = 1, // 8-bit 模式,每样本 1 字节 MuZi_ADC_FMT_24BIT = 3 // 24-bit 模式,每样本 3 字节 } MuZi_AdcFmt_t; // Data Transmission State Machine (数据传输状态机枚举) typedef enum { MuZi_STATE_IDLE = 0, // 空闲态 MuZi_STATE_TX, // 传输态 MuZi_STATE_END // 结束态(等待确认) } MuZi_TxState_t; // Command Buffer Structure (命令缓冲区结构体,用于异步解耦 BLE 回调与命令解析) typedef struct { uint8_t data[MuZi_CHAR6_LEN]; // 原始命令数据 uint16_t len; // 数据长度 _Bool valid; // 有效性标志(CRC 校验通过后置 true) } MuziCmdBuf_t; // MuZi Data Transmission Context (MuZi 数据传输上下文,维护传输会话状态) typedef struct { uint8_t tx_buf[MuZi_ADC_MAX_LEN]; // 待发送 ADC 数据缓冲 uint16_t tx_len; // 当前数据包总长度 uint16_t tx_offset; // 当前发送偏移量 uint8_t pkg_id; // 包 ID (0-15 循环) uint8_t frag_idx; // 当前分片索引 uint8_t frag_total; // 总分片数 _Bool next_requested; // 主机是否已请求下一片(流控标志) MuZi_TxState_t state; // 传输状态 MuZi_AdcFmt_t fmt; // 当前 ADC 格式 uint8_t gain; // 当前增益值 uint16_t rate; // 当前采样率 } MuZi_AdcCtx_t; /********************************************************************* * GLOBAL VARIABLES (全局变量) */ // Task ID is assigned during initialization (任务 ID,初始化时由 TMOS 分配) static uint8_t Peripheral_TaskID = INVALID_TASK_ID; /********************************************************************* * LOCAL VARIABLES (局部静态变量) */ // Instruction Cache (指令缓存,避免在 BLE 回调中直接处理耗时操作) static MuziCmdBuf_t g_muziCmdBuf = {0}; // Connection Information (连接信息,记录当前活动连接的参数) static peripheralConnItem_t peripheralConnList; // MTU Variable (协商后的 MTU 值) static uint16_t peripheralMTU = ATT_MTU_SIZE; // ADC Context (ADC 传输上下文实例) static MuZi_AdcCtx_t g_muziAdcCtx = { .state = MuZi_STATE_IDLE, .fmt = MuZi_ADC_FMT_24BIT, .gain = 1, .rate = 100 }; // [FIX] 新增: 延迟 ACK 缓存(用于模式切换后延迟发送确认) static uint8_t s_pendingAckCmd = 0; // GAP - SCAN RSP data (扫描响应数据) static uint8_t scanRspData[] = { 0x12, GAP_ADTYPE_LOCAL_NAME_COMPLETE, // 完整本地名称长度+类型 'S','i','m','p','l','e',' ','P','e','r','i','p','h','e','r','3','L', 0x05, GAP_ADTYPE_SLAVE_CONN_INTERVAL_RANGE, // 从机连接间隔范围 LO_UINT16(MUZI_MODE_NORMAL_MIN_CONN_INTERVAL), HI_UINT16(MUZI_MODE_NORMAL_MIN_CONN_INTERVAL), LO_UINT16(MUZI_MODE_NORMAL_MAX_CONN_INTERVAL), HI_UINT16(MUZI_MODE_NORMAL_MAX_CONN_INTERVAL), 0x02, GAP_ADTYPE_POWER_LEVEL, // 发射功率等级 0 }; // GAP - Advertisement data (广播数据) static uint8_t advertData[] = { 0x02, GAP_ADTYPE_FLAGS, // 标志字段 DEFAULT_DISCOVERABLE_MODE | GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED, 0x03, GAP_ADTYPE_16BIT_MORE, // 16-bit UUID (更多服务) LO_UINT16(SIMPLEPROFILE_SERV_UUID), HI_UINT16(SIMPLEPROFILE_SERV_UUID) }; // Device Name (设备名称) static uint8_t attDeviceName[GAP_DEVICE_NAME_LEN] = "Simple Peripheral"; // Performance Optimization: Static buffers for notifications to avoid alloc/free overhead in high speed mode // (性能优化: 静态通知缓冲区,避免高速模式下频繁 malloc/free 造成的开销和碎片) static uint8_t s_staticNotifyBuf[MuZi_CHAR7_LEN]; static _Bool s_isNotifyBusy = false; // 静态缓冲区忙标志 /********************************************************************* * LOCAL FUNCTIONS PROTOTYPES (局部函数原型声明) */ static void Peripheral_ProcessTMOSMsg(tmos_event_hdr_t *pMsg); static void peripheralStateNotificationCB(gapRole_States_t newState, gapRoleEvent_t *pEvent); static void performPeriodicTask(void); static void MuZiCmdTask(void); static void MuZiDataTask(void); static void simpleProfileChangeCB(uint8_t paramID, uint8_t *pValue, uint16_t len); static void peripheralParamUpdateCB(uint16_t connHandle, uint16_t connInterval, uint16_t connSlaveLatency, uint16_t connTimeout); static void peripheralInitConnItem(peripheralConnItem_t *peripheralConnList); static void peripheralRssiCB(uint16_t connHandle, int8_t rssi); static void Peripheral_LinkEstablished(gapRoleEvent_t *pEvent); static void Peripheral_LinkTerminated(gapRoleEvent_t *pEvent); static void Peripheral_ProcessGAPMsg(gapRoleEvent_t *pEvent); // MuZi Specific Functions (MuZi 协议专用函数) static bStatus_t MuZiSendFragNotify(const uint8_t *frag, uint8_t len); static void MuZiConnectEventCB(uint32_t timeUs); static void peripheralChar4Notify(uint8_t *pValue, uint16_t len); static void MuZiSendAck(uint8_t ack_cmd); static void MuZiSendErrRsp(uint8_t cmd, uint8_t err_code); static void MuZiSendRsp(uint8_t cmd, const uint8_t *payload, uint8_t plen); static uint16_t MuZiLoadAdcData(uint8_t *buf, uint16_t max_len, MuZi_AdcFmt_t fmt); // 优化级别O4 static uint8_t MuZiPayloadPerFrag(void) __attribute__ ((optimize("04")));; static bStatus_t MuZiPrepareDataFrag(void); static bStatus_t MuZiPrepareEndFrag(void); static void MuZiStartDataTx(void); static void ParseAndExecute(const uint8_t *buf, uint8_t len); // Command Handlers (命令处理函数) static void Cmd_START_ACQ(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_STOP_ACQ(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_SET_GAIN(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_SET_RATE(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_NEXT_FRAG(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_REQ_FRAG(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_FAST_MODE(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_NORMAL_MODE(uint8_t cmd, const uint8_t *payload, uint8_t plen); static void Cmd_GET_INFO(uint8_t cmd, const uint8_t *payload, uint8_t plen); /********************************************************************* * PROFILE CALLBACKS (协议栈回调函数注册表) */ // GAP 角色回调 static gapRolesCBs_t Peripheral_PeripheralCBs = { peripheralStateNotificationCB, // 状态变更通知 peripheralRssiCB, // RSSI 读取完成 peripheralParamUpdateCB // 连接参数更新完成 }; // 广播者回调 static gapRolesBroadcasterCBs_t Broadcaster_BroadcasterCBs = { NULL, NULL }; // 绑定管理器回调 static gapBondCBs_t Peripheral_BondMgrCBs = { NULL, NULL, NULL }; // SimpleProfile 特征值变更回调( 写入回调 ) static simpleProfileCBs_t Peripheral_SimpleProfileCBs = { simpleProfileChangeCB }; /********************************************************************* * PUBLIC FUNCTIONS (公开函数) */ /********************************************************************* * @fn Peripheral_Init * @brief Initialization function for the Peripheral App Task. * (外设应用任务初始化入口) */ void Peripheral_Init() { // 注册任务事件处理函数 Peripheral_TaskID = TMOS_ProcessEventRegister(Peripheral_ProcessEvent); // Initialize MuZi Mode Manager (初始化 MuZi 模式管理器) MuZiMode_Init(); // Setup GAP Peripheral Role Profile (配置 GAP 外设角色) { uint8_t initial_advertising_enable = TRUE; uint16_t desired_min_interval = MUZI_MODE_NORMAL_MIN_CONN_INTERVAL; uint16_t desired_max_interval = MUZI_MODE_NORMAL_MAX_CONN_INTERVAL; GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &initial_advertising_enable); GAPRole_SetParameter(GAPROLE_SCAN_RSP_DATA, sizeof(scanRspData), scanRspData); GAPRole_SetParameter(GAPROLE_ADVERT_DATA, sizeof(advertData), advertData); GAPRole_SetParameter(GAPROLE_MIN_CONN_INTERVAL, sizeof(uint16_t), &desired_min_interval); GAPRole_SetParameter(GAPROLE_MAX_CONN_INTERVAL, sizeof(uint16_t), &desired_max_interval); } // Set advertising interval (设置广播间隔) { uint16_t advInt = DEFAULT_ADVERTISING_INTERVAL; GAP_SetParamValue(TGAP_DISC_ADV_INT_MIN, advInt); GAP_SetParamValue(TGAP_DISC_ADV_INT_MAX, advInt); GAP_SetParamValue(TGAP_ADV_SCAN_REQ_NOTIFY, ENABLE); // 使能扫描请求通知 } // Setup GAP Bond Manager (配置绑定管理器) { uint32_t passkey = 0; uint8_t pairMode = GAPBOND_PAIRING_MODE_WAIT_FOR_REQ; uint8_t mitm = TRUE; // 启用中间人保护 uint8_t bonding = TRUE; // 启用绑定 uint8_t ioCap = GAPBOND_IO_CAP_DISPLAY_ONLY; // IO 能力: 仅显示 GAPBondMgr_SetParameter(GAPBOND_PERI_DEFAULT_PASSCODE, sizeof(uint32_t), &passkey); GAPBondMgr_SetParameter(GAPBOND_PERI_PAIRING_MODE, sizeof(uint8_t), &pairMode); GAPBondMgr_SetParameter(GAPBOND_PERI_MITM_PROTECTION, sizeof(uint8_t), &mitm); GAPBondMgr_SetParameter(GAPBOND_PERI_IO_CAPABILITIES, sizeof(uint8_t), &ioCap); GAPBondMgr_SetParameter(GAPBOND_PERI_BONDING_ENABLED, sizeof(uint8_t), &bonding); } // Initialize GATT attributes (初始化 GATT 属性表) GGS_AddService(GATT_ALL_SERVICES); GATTServApp_AddService(GATT_ALL_SERVICES); DevInfo_AddService(); SimpleProfile_AddService(GATT_ALL_SERVICES); // Set GAP Characteristics (设置 GAP 特征值) GGS_SetParameter(GGS_DEVICE_NAME_ATT, sizeof(attDeviceName), attDeviceName); GATT_InitClient(); // Setup SimpleProfile Characteristic Values (初始化 SimpleProfile 特征值默认值) { uint8_t charValue1[SIMPLEPROFILE_CHAR1_LEN] = {1}; uint8_t charValue2[SIMPLEPROFILE_CHAR2_LEN] = {2}; uint8_t charValue3[SIMPLEPROFILE_CHAR3_LEN] = {3}; uint8_t charValue4[SIMPLEPROFILE_CHAR4_LEN] = {4}; uint8_t charValue5[SIMPLEPROFILE_CHAR5_LEN] = {1, 2, 3, 4, 5}; uint8_t charValue6[MuZi_CHAR6_LEN] = {6}; uint8_t charValue7[MuZi_CHAR7_LEN] = {7}; SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR1, SIMPLEPROFILE_CHAR1_LEN, charValue1); SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR2, SIMPLEPROFILE_CHAR2_LEN, charValue2); SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR3, SIMPLEPROFILE_CHAR3_LEN, charValue3); SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR4, SIMPLEPROFILE_CHAR4_LEN, charValue4); SimpleProfile_SetParameter(SIMPLEPROFILE_CHAR5, SIMPLEPROFILE_CHAR5_LEN, charValue5); SimpleProfile_SetParameter(MuZi_CHAR6, MuZi_CHAR6_LEN, charValue6); SimpleProfile_SetParameter(MuZi_CHAR7, MuZi_CHAR7_LEN, charValue7); } // Init Connection Item (初始化连接信息结构体) peripheralInitConnItem(&peripheralConnList); // Register callbacks (注册各类回调) SimpleProfile_RegisterAppCBs(&Peripheral_SimpleProfileCBs); GAPRole_BroadcasterSetCB(&Broadcaster_BroadcasterCBs); LL_ConnectEventRegister(MuZiConnectEventCB); // 注册底层连接事件回调(用于快速模式) // Start device (触发设备启动事件) tmos_set_event(Peripheral_TaskID, SBP_START_DEVICE_EVT); } /********************************************************************* * @fn peripheralInitConnItem * @brief Initialize connection parameters structure. * (初始化连接参数结构体为无效/零值状态) */ static void peripheralInitConnItem(peripheralConnItem_t *peripheralConnList) { peripheralConnList->connHandle = GAP_CONNHANDLE_INIT; peripheralConnList->connInterval = 0; peripheralConnList->connSlaveLatency = 0; peripheralConnList->connTimeout = 0; } /********************************************************************* * @fn Peripheral_ProcessEvent * @brief Main event processor for the Peripheral task. * (外设任务主事件处理器,TMOS 调度核心) */ uint16_t Peripheral_ProcessEvent(uint8_t task_id, uint16_t events) { // 处理系统消息队列 if(events & SYS_EVENT_MSG) { uint8_t *pMsg; if((pMsg = tmos_msg_receive(Peripheral_TaskID)) != NULL) { Peripheral_ProcessTMOSMsg((tmos_event_hdr_t *)pMsg); tmos_msg_deallocate(pMsg); // 释放消息内存 } return (events ^ SYS_EVENT_MSG); } // 设备启动事件 if(events & SBP_START_DEVICE_EVT) { GAPRole_PeripheralStartDevice(Peripheral_TaskID, &Peripheral_BondMgrCBs, &Peripheral_PeripheralCBs); return (events ^ SBP_START_DEVICE_EVT); } // 周期性任务事件 if(events & SBP_PERIODIC_EVT) { if(SBP_PERIODIC_EVT_PERIOD) { tmos_start_task(Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD); } performPeriodicTask(); return (events ^ SBP_PERIODIC_EVT); } // MuZi 命令处理事件(从缓冲区取出并解析) // [FIX] 此事件现在由 simpleProfileChangeCB 立即触发,不再周期轮询 if(events & SBP_MUZI_CMD_EVT) { MuZiCmdTask(); return (events ^ SBP_MUZI_CMD_EVT); } // [FIX] 新增: 延迟 ACK 事件处理(模式切换后延迟发送确认,避开 PHY 更新窗口) if(events & SBP_MUZI_ACK_DELAY_EVT) { if (s_pendingAckCmd != 0) { MuZiSendAck(s_pendingAckCmd); s_pendingAckCmd = 0; } return (events ^ SBP_MUZI_ACK_DELAY_EVT); } // MuZi 数据发送事件 if(events & SBP_MUZI_DATA_EVT) { // In FAST_MODE, data is pushed via ConnectEventCB, so we skip processing here to reduce latency // unless we are in normal mode or need to restart a stream // (快速模式下数据通过 ConnectEventCB 推送以降低延迟,此处仅处理普通模式) if (!MuZiMode_IsFast()) { MuZiDataTask(); } return (events ^ SBP_MUZI_DATA_EVT); } // MTU 交换事件 if(events & SBP_MTU_EXCHANGE_EVT) { if(peripheralConnList.connHandle != GAP_CONNHANDLE_INIT) { attExchangeMTUReq_t req = {0}; req.clientRxMTU = BLE_BUFF_MAX_LEN; // MTU = 247 PRINT("Peripheral request MTU exchange: %d\n", req.clientRxMTU); GATT_ExchangeMTU(peripheralConnList.connHandle, &req, Peripheral_TaskID); } return (events ^ SBP_MTU_EXCHANGE_EVT); } // 连接参数更新请求事件 if(events & SBP_PARAM_UPDATE_EVT) { GAPRole_PeripheralConnParamUpdateReq(peripheralConnList.connHandle, MUZI_MODE_NORMAL_MIN_CONN_INTERVAL, MUZI_MODE_NORMAL_MAX_CONN_INTERVAL, MUZI_MODE_SLAVE_LATENCY, MUZI_MODE_CONN_TIMEOUT, Peripheral_TaskID); return (events ^ SBP_PARAM_UPDATE_EVT); } // RSSI 读取事件 if(events & SBP_READ_RSSI_EVT) { GAPRole_ReadRssiCmd(peripheralConnList.connHandle); tmos_start_task(Peripheral_TaskID, SBP_READ_RSSI_EVT, SBP_READ_RSSI_EVT_PERIOD); return (events ^ SBP_READ_RSSI_EVT); } return 0; } /********************************************************************* * @fn Peripheral_ProcessGAPMsg * @brief Process GAP messages. * (处理 GAP 层消息,如 PHY 更新等) */ static void Peripheral_ProcessGAPMsg(gapRoleEvent_t *pEvent) { switch(pEvent->gap.opcode) { case GAP_SCAN_REQUEST_EVENT: break; case GAP_PHY_UPDATE_EVENT: PRINT("PHY updated: Rx=%d, Tx=%d, status=%d\n", pEvent->linkPhyUpdate.connRxPHYS, pEvent->linkPhyUpdate.connTxPHYS, pEvent->gap.hdr.status); break; default: break; } } /********************************************************************* * @fn Peripheral_ProcessTMOSMsg * @brief Process TMOS system messages. * (处理 TMOS 系统消息,包括 GAP 和 GATT 消息分发) */ static void Peripheral_ProcessTMOSMsg(tmos_event_hdr_t *pMsg) { switch(pMsg->event) { case GAP_MSG_EVENT: Peripheral_ProcessGAPMsg((gapRoleEvent_t *)pMsg); break; case GATT_MSG_EVENT: { gattMsgEvent_t *pMsgEvent = (gattMsgEvent_t *)pMsg; // 处理 MTU 更新完成事件 if(pMsgEvent->method == ATT_MTU_UPDATED_EVENT) { uint16_t negotiatedMTU = pMsgEvent->msg.exchangeMTUReq.clientRxMTU; // 钳制 MTU 到合法范围 if(negotiatedMTU > BLE_BUFF_MAX_LEN) negotiatedMTU = BLE_BUFF_MAX_LEN; if(negotiatedMTU < ATT_MTU_SIZE) negotiatedMTU = ATT_MTU_SIZE; peripheralMTU = negotiatedMTU; PRINT("mtu exchange: %d (cap %d)\n", peripheralMTU, BLE_BUFF_MAX_LEN); } break; } default: break; } } /********************************************************************* * @fn Peripheral_LinkEstablished * @brief Handle link establishment. * (处理链路建立事件,初始化连接参数并启动各定时任务) */ static void Peripheral_LinkEstablished(gapRoleEvent_t *pEvent) { gapEstLinkReqEvent_t *event = (gapEstLinkReqEvent_t *)pEvent; // 如果已有连接,断开新连接(单连接限制) if(peripheralConnList.connHandle != GAP_CONNHANDLE_INIT) { GAPRole_TerminateLink(pEvent->linkCmpl.connectionHandle); PRINT("Connection max...\n"); } else { // 保存连接参数 peripheralConnList.connHandle = event->connectionHandle; peripheralConnList.connInterval = event->connInterval; peripheralConnList.connSlaveLatency = event->connLatency; peripheralConnList.connTimeout = event->connTimeout; peripheralMTU = ATT_MTU_SIZE; // 重置 MTU 为默认值 // [FIX] 移除 CMD 事件的周期启动,命令处理改为立即触发 // 启动所有周期性任务 tmos_start_task(Peripheral_TaskID, SBP_PERIODIC_EVT, SBP_PERIODIC_EVT_PERIOD); tmos_start_task(Peripheral_TaskID, SBP_PARAM_UPDATE_EVT, SBP_PARAM_UPDATE_DELAY); tmos_start_task(Peripheral_TaskID, SBP_READ_RSSI_EVT, SBP_READ_RSSI_EVT_PERIOD); // [FIX] 已删除: tmos_start_task(Peripheral_TaskID, SBP_MUZI_CMD_EVT, SBP_MUZI_CMD_EVT_PERIOD); tmos_start_task(Peripheral_TaskID, SBP_MUZI_DATA_EVT, SBP_MUZI_DATA_EVT_PERIOD); // 蓝牙芯片从机不主动发更新MTU,让手机APP主机协议为准。 //tmos_start_task(Peripheral_TaskID, SBP_MTU_EXCHANGE_EVT, SBP_MTU_EXCHANGE_DELAY); PRINT("Conn %x - Int %x \n", event->connectionHandle, event->connInterval); } } /********************************************************************* * @fn Peripheral_LinkTerminated * @brief Handle link termination. * (处理链路断开事件,清理状态并恢复广播) */ static void Peripheral_LinkTerminated(gapRoleEvent_t *pEvent) { gapTerminateLinkEvent_t *event = (gapTerminateLinkEvent_t *)pEvent; if(event->connectionHandle == peripheralConnList.connHandle) { // 清空连接信息 peripheralConnList.connHandle = GAP_CONNHANDLE_INIT; peripheralConnList.connInterval = 0; peripheralConnList.connSlaveLatency = 0; peripheralConnList.connTimeout = 0; // 停止所有周期性任务 tmos_stop_task(Peripheral_TaskID, SBP_PERIODIC_EVT); tmos_stop_task(Peripheral_TaskID, SBP_MUZI_CMD_EVT); tmos_stop_task(Peripheral_TaskID, SBP_MUZI_DATA_EVT); tmos_stop_task(Peripheral_TaskID, SBP_READ_RSSI_EVT); tmos_stop_task(Peripheral_TaskID, SBP_MTU_EXCHANGE_EVT); // [FIX] 停止延迟 ACK 事件并清除缓存 tmos_stop_task(Peripheral_TaskID, SBP_MUZI_ACK_DELAY_EVT); s_pendingAckCmd = 0; // 重置 ADC 传输上下文 g_muziAdcCtx.state = MuZi_STATE_IDLE; g_muziAdcCtx.next_requested = false; g_muziAdcCtx.tx_len = 0; g_muziAdcCtx.tx_offset = 0; g_muziAdcCtx.frag_idx = 0; g_muziAdcCtx.frag_total = 0; // 重置 MuZi 模式 MuZiMode_Reset(); // 重新开启广播 { uint8_t advertising_enable = TRUE; GAPRole_SetParameter(GAPROLE_ADVERT_ENABLED, sizeof(uint8_t), &advertising_enable); } } else { PRINT("ERR..\n"); } } /********************************************************************* * @fn peripheralRssiCB * @brief RSSI callback. * (RSSI 读取完成回调) */ static void peripheralRssiCB(uint16_t connHandle, int8_t rssi) { PRINT("RSSI -%d dB Conn %x \n", -rssi, connHandle); } /********************************************************************* * @fn peripheralParamUpdateCB * @brief Parameter update complete callback. * (连接参数更新完成回调) */ static void peripheralParamUpdateCB(uint16_t connHandle, uint16_t connInterval, uint16_t connSlaveLatency, uint16_t connTimeout) { if(connHandle == peripheralConnList.connHandle) { peripheralConnList.connInterval = connInterval; peripheralConnList.connSlaveLatency = connSlaveLatency; peripheralConnList.connTimeout = connTimeout; PRINT("Update %x - Int %x \n", connHandle, connInterval); } else { PRINT("ERR..\n"); } } /********************************************************************* * @fn peripheralStateNotificationCB * @brief Notification from the profile of a state change. * (GAP 角色状态变更通知回调,处理设备连接/断开/广播等状态转换) */ static void peripheralStateNotificationCB(gapRole_States_t newState, gapRoleEvent_t *pEvent) { switch(newState & GAPROLE_STATE_ADV_MASK) { case GAPROLE_STARTED: PRINT("Initialized..\n"); break; case GAPROLE_ADVERTISING: if(pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT) { Peripheral_LinkTerminated(pEvent); PRINT("Disconnected.. Reason:%x\n", pEvent->linkTerminate.reason); PRINT("Advertising..\n"); } else if(pEvent->gap.opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT) { PRINT("Advertising..\n"); } break; case GAPROLE_CONNECTED: if(pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT) { Peripheral_LinkEstablished(pEvent); PRINT("Connected..\n"); } break; case GAPROLE_CONNECTED_ADV: if(pEvent->gap.opcode == GAP_MAKE_DISCOVERABLE_DONE_EVENT) { PRINT("Connected Advertising..\n"); } break; case GAPROLE_WAITING: if(pEvent->gap.opcode == GAP_END_DISCOVERABLE_DONE_EVENT) { PRINT("Waiting for advertising..\n"); } else if(pEvent->gap.opcode == GAP_LINK_TERMINATED_EVENT) { Peripheral_LinkTerminated(pEvent); PRINT("Disconnected.. Reason:%x\n", pEvent->linkTerminate.reason); } else if(pEvent->gap.opcode == GAP_LINK_ESTABLISHED_EVENT) { if(pEvent->gap.hdr.status != SUCCESS) { PRINT("Waiting for advertising..\n"); } else { PRINT("Error..\n"); } } break; case GAPROLE_ERROR: PRINT("Error..\n"); break; default: break; } } /********************************************************************* * @fn performPeriodicTask * @brief Perform periodic application task. * (执行周期性应用任务,非快速模式下通过 CHAR4 发送随机测试数据) */ static void performPeriodicTask(void) { if (MuZiMode_IsFast()) return; // 快速模式下跳过此任务 uint8_t notiData[SIMPLEPROFILE_CHAR4_LEN] = {random_u8()}; peripheralChar4Notify(notiData, 1); } /********************************************************************* * @fn MuZiCmdTask * @brief Process incoming commands from host. * (处理来自主机的命令,从缓冲区拷贝后解析执行,实现回调与处理的解耦) */ static void MuZiCmdTask(void) { //HalLedUpdate(); 测试LED灯的 if (g_muziCmdBuf.valid) { uint8_t local_buf[MuZi_CHAR6_LEN]; uint8_t local_len = g_muziCmdBuf.len; if (local_len > MuZi_CHAR6_LEN) local_len = MuZi_CHAR6_LEN; // 拷贝到局部变量后立即清除有效标志,防止重复处理 tmos_memcpy(local_buf, g_muziCmdBuf.data, local_len); g_muziCmdBuf.valid = false; ParseAndExecute(local_buf, local_len); // 解析并执行 MuZi 命令帧 } } /********************************************************************* * @fn MuZiDataTask * @brief Process data transmission tasks. * (处理数据传输任务,仅在普通模式且收到主机流控请求时发送下一分片) */ static void MuZiDataTask(void) { if (g_muziAdcCtx.state == MuZi_STATE_IDLE) return; if (MuZiMode_IsFast()) return; // Handled by ConnectEventCB in fast mode if (!g_muziAdcCtx.next_requested) return; // 等待主机 NEXT_FRAG 命令 g_muziAdcCtx.next_requested = false; if (g_muziAdcCtx.state == MuZi_STATE_END) { MuZiStartDataTx(); // 上一轮传输结束,开始新一轮 return; } MuZiPrepareDataFrag(); // 准备并发送下一个数据分片 } /********************************************************************* * @fn MuZiConnectEventCB * @brief Connection event callback for FAST_MODE. * Optimized to push data directly during connection events for minimal latency. * (快速模式下的连接事件回调,直接在连接事件中推送数据以实现最低延迟) */ static void MuZiConnectEventCB(uint32_t timeUs) { (void)timeUs; if (!MuZiMode_IsFast()) return; if (peripheralConnList.connHandle == GAP_CONNHANDLE_INIT) return; if (g_muziAdcCtx.state == MuZi_STATE_IDLE) return; // Try to send as many fragments as possible within this connection event // Note: Actual limit depends on BLE stack implementation, usually 1 packet per event per handle // But we call Prepare which handles the state machine. // (尝试在当前连接事件中尽可能多地发送分片,实际受协议栈限制) if (g_muziAdcCtx.state == MuZi_STATE_TX) { MuZiPrepareDataFrag(); } if (g_muziAdcCtx.state == MuZi_STATE_END && g_muziAdcCtx.next_requested) { g_muziAdcCtx.next_requested = false; MuZiStartDataTx(); } } /********************************************************************* * @fn peripheralChar4Notify * @brief Send notification via CHAR4. * (通过 CHAR4 发送通知,带 MTU 检查和动态内存分配) */ static void peripheralChar4Notify(uint8_t *pValue, uint16_t len) { if(len > (peripheralMTU - 3)) { PRINT("Too large noti\n"); return; } attHandleValueNoti_t noti; noti.len = len; // 从 GATT 缓冲区管理器分配内存 noti.pValue = GATT_bm_alloc(peripheralConnList.connHandle, ATT_HANDLE_VALUE_NOTI, noti.len, NULL, 0); if(noti.pValue) { tmos_memcpy(noti.pValue, pValue, noti.len); // [FIX] 捕获发送返回值并打印失败日志,便于排查静默丢包 bStatus_t status = simpleProfile_Notify(peripheralConnList.connHandle, ¬i); if(status != SUCCESS) { PRINT("CHAR4 Noti FAIL: 0x%02X\n", status); GATT_bm_free((gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI); // 发送失败则释放 } } else { // [FIX] 新增: 内存分配失败日志 PRINT("CHAR4 bm_alloc FAIL! len=%d\n", len); } } /********************************************************************* * @fn simpleProfileChangeCB * @brief Callback when characteristic value changes. * (特征值写入回调,MuZi CHAR6 命令在此接收并进行 CRC 校验) */ static void simpleProfileChangeCB(uint8_t paramID, uint8_t *pValue, uint16_t len) { switch(paramID) { case SIMPLEPROFILE_CHAR1: { uint8_t newValue[SIMPLEPROFILE_CHAR1_LEN]; tmos_memcpy(newValue, pValue, len); PRINT("profile ChangeCB CHAR1.. \n"); break; } case SIMPLEPROFILE_CHAR3: { uint8_t newValue[SIMPLEPROFILE_CHAR3_LEN]; tmos_memcpy(newValue, pValue, len); PRINT("profile ChangeCB CHAR3..\n"); break; } case MuZi_CHAR6: // MuZi 命令通道 { // 基本长度和帧头校验 if(len > MuZi_CHAR6_LEN || len < 3) return; if (pValue[0] != 0xAA) return; uint8_t payload_len = pValue[2]; if (len != (4 + payload_len)) return; // 拷贝到命令缓冲区 tmos_memcpy(g_muziCmdBuf.data, pValue, len); g_muziCmdBuf.len = len; // CRC 校验(使用优化后的单函数版本) bool is_valid = CheckCRC(g_muziCmdBuf.data, len); if (is_valid) { g_muziCmdBuf.valid = true; // [FIX] 立即触发命令处理(延迟=0),替代原来的 100ms 周期轮询 tmos_start_task(Peripheral_TaskID, SBP_MUZI_CMD_EVT, 0); } else { g_muziCmdBuf.valid = false; return; } break; } default: break; } } /********************************************************************* * MuZi Protocol Implementation (MuZi 协议实现) *********************************************************************/ /** * @brief 发送数据分片通知(带静态缓冲区优化) * @param frag 分片数据指针 * @param len 分片长度 * @return bStatus_t 发送状态 */ static bStatus_t MuZiSendFragNotify(const uint8_t *frag, uint8_t len) { if (peripheralConnList.connHandle == GAP_CONNHANDLE_INIT) return bleIncorrectMode; if (len > (peripheralMTU - 3)) return bleInvalidRange; // Optimization: Use static buffer if not busy, otherwise fall back to alloc // This reduces heap fragmentation and allocation time in high-speed modes // (优化: 优先使用静态缓冲区以减少堆碎片和分配时间,繁忙时回退到动态分配) attHandleValueNoti_t noti; noti.len = len; if (!s_isNotifyBusy) { // Use static buffer for zero-latency preparation noti.pValue = s_staticNotifyBuf; tmos_memcpy(noti.pValue, frag, noti.len); s_isNotifyBusy = true; bStatus_t status = simpleProfile_Notify7(peripheralConnList.connHandle, ¬i); if (status != SUCCESS) { s_isNotifyBusy = false; } // Note: We do NOT free static buffer. The stack should have copied it or sent it. // If the stack requires ownership (async send), this needs to be reverted to bm_alloc. // For WCH CH58x/CH59x, simpleProfile_Notify usually copies data internally or sends immediately. // If you experience corruption, revert to bm_alloc below. // (注意: 静态缓冲区不释放。WCH 协议栈通常会内部拷贝或立即发送。若出现数据损坏请回退到 bm_alloc) return status; } else { // Fallback to dynamic allocation if previous packet is still being processed // (回退路径: 上一个包仍在处理中时使用动态分配) noti.pValue = GATT_bm_alloc(peripheralConnList.connHandle, ATT_HANDLE_VALUE_NOTI, noti.len, NULL, 0); if (noti.pValue == NULL) return bleMemAllocError; tmos_memcpy(noti.pValue, frag, noti.len); bStatus_t status = simpleProfile_Notify7(peripheralConnList.connHandle, ¬i); if (status != SUCCESS) GATT_bm_free((gattMsg_t *)¬i, ATT_HANDLE_VALUE_NOTI); return status; } } /** * @brief 发送 MuZi ACK 应答帧 * @param ack_cmd 应答命令码 */ static void MuZiSendAck(uint8_t ack_cmd) { uint8_t rsp[4] = {0xAA, ack_cmd, 0x00, 0x00}; rsp[3] = Crc8Maxim(rsp, 3); // 计算 CRC peripheralChar4Notify(rsp, 4); } /** * @brief 发送 MuZi 错误响应帧 * @param cmd 原始命令码 * @param err_code 错误码 */ static void MuZiSendErrRsp(uint8_t cmd, uint8_t err_code) { uint8_t rsp[6] = {0xAA, 0xFF, 0x02, cmd, err_code, 0x00}; rsp[5] = Crc8Maxim(rsp, 5); peripheralChar4Notify(rsp, 6); } /** * @brief 发送 MuZi 通用响应帧 * @param cmd 命令码 * @param payload 载荷数据 * @param plen 载荷长度 */ static void MuZiSendRsp(uint8_t cmd, const uint8_t *payload, uint8_t plen) { uint8_t rsp[32] = {0}; rsp[0] = 0xAA; rsp[1] = cmd; rsp[2] = plen; if (plen > 0 && payload != NULL) { tmos_memcpy(&rsp[3], payload, plen); } rsp[3 + plen] = Crc8Maxim(rsp, 3 + plen); peripheralChar4Notify(rsp, 4 + plen); } /** * @brief 加载 ADC 模拟数据到缓冲区(测试用) * @param buf 目标缓冲区 * @param max_len 最大长度 * @param fmt ADC 格式 * @return 实际加载的字节数 */ static uint16_t MuZiLoadAdcData(uint8_t *buf, uint16_t max_len, MuZi_AdcFmt_t fmt) { // 按采样宽度对齐 uint16_t aligned = (max_len / fmt) * fmt; uint16_t len = 0; for (uint16_t i = 0; i < aligned; i += fmt) { // 生成递增值作为模拟 ADC 数据 uint32_t val = (uint32_t)(i + (g_muziAdcCtx.pkg_id * 256)); if (fmt == MuZi_ADC_FMT_24BIT) { buf[len++] = val & 0xFF; buf[len++] = (val >> 8) & 0xFF; buf[len++] = (val >> 16) & 0xFF; } else { buf[len++] = val & 0xFF; } } return len; } /** * @brief 计算每个分片的有效载荷大小(根据 MTU 和 ADC 格式动态调整) * @return 单分片最大载荷字节数 */ static inline uint8_t MuZiPayloadPerFrag(void) { // 扣除 ATT 头(3字节)和 MuZi 协议开销 uint16_t max_payload = (peripheralMTU > MuZi_CHAR7_OVERHEAD + 3) ? (peripheralMTU - MuZi_CHAR7_OVERHEAD - 3) : 0; if (max_payload > MuZi_MAX_PAYLOAD) max_payload = MuZi_MAX_PAYLOAD; // 24-bit 模式需按 3 字节对齐 if (g_muziAdcCtx.fmt == MuZi_ADC_FMT_24BIT) { return (uint8_t)((max_payload / 3) * 3); } return (uint8_t)max_payload; } /** * @brief 准备并发送一个数据分片 * @return bStatus_t 发送状态 */ static bStatus_t MuZiPrepareDataFrag(void) { static uint8_t frag[MuZi_CHAR7_LEN] = {0}; if (g_muziAdcCtx.state == MuZi_STATE_IDLE) return SUCCESS; if (g_muziAdcCtx.state == MuZi_STATE_END) return bleIncorrectMode; uint16_t remaining = g_muziAdcCtx.tx_len - g_muziAdcCtx.tx_offset; uint8_t ppl = MuZiPayloadPerFrag(); uint8_t plen = (remaining > ppl) ? ppl : (uint8_t)remaining; // 构建分片帧: [SOF][HDR][LEN][PAYLOAD...][CRC] frag[0] = MuZi_ADC_SOF; frag[1] = ((g_muziAdcCtx.pkg_id & 0x0F) << 4) | (g_muziAdcCtx.frag_idx & 0x0F); frag[2] = plen; tmos_memcpy(&frag[3], &g_muziAdcCtx.tx_buf[g_muziAdcCtx.tx_offset], plen); frag[3 + plen] = Crc8Maxim(frag, 3 + plen); // 清零剩余空间(可选,便于调试抓包) if ((uint16_t)(4 + plen) < MuZi_CHAR7_LEN) tmos_memset(&frag[4 + plen], 0, MuZi_CHAR7_LEN - 4 - plen); bStatus_t status = MuZiSendFragNotify(frag, 4 + plen); if (status != SUCCESS) return status; // 保存当前状态用于失败回滚 uint16_t prev_offset = g_muziAdcCtx.tx_offset; uint8_t prev_idx = g_muziAdcCtx.frag_idx; // 更新偏移和分片索引 g_muziAdcCtx.tx_offset += plen; g_muziAdcCtx.frag_idx++; // 检查是否为最后一个数据分片 if (g_muziAdcCtx.frag_idx >= g_muziAdcCtx.frag_total) { if (MuZiPrepareEndFrag() == SUCCESS) g_muziAdcCtx.state = MuZi_STATE_END; // 进入结束态等待确认 else { // 结束帧发送失败,回滚状态以便重试 g_muziAdcCtx.tx_offset = prev_offset; g_muziAdcCtx.frag_idx = prev_idx; } } return SUCCESS; } /** * @brief 准备并发送结束帧(空载荷,仅含 SOF+HDR+CRC) * @return bStatus_t 发送状态 */ static bStatus_t MuZiPrepareEndFrag(void) { static uint8_t end[MuZi_CHAR7_LEN] = {0}; end[0] = MuZi_ADC_SOF; end[1] = (g_muziAdcCtx.pkg_id & 0x0F) << 4; // frag_idx = 0 表示结束帧 end[2] = 0x00; // 长度为 0 end[3] = Crc8Maxim(end, 3); return MuZiSendFragNotify(end, 4); } /** * @brief 启动新一轮数据传输(加载数据、计算分片数、发送首片) */ static void MuZiStartDataTx(void) { // 加载 ADC 数据 g_muziAdcCtx.tx_len = MuZiLoadAdcData(g_muziAdcCtx.tx_buf, MuZi_ADC_MAX_LEN, g_muziAdcCtx.fmt); if (g_muziAdcCtx.tx_len == 0) { g_muziAdcCtx.state = MuZi_STATE_IDLE; return; } // 计算总分片数 uint8_t ppl = MuZiPayloadPerFrag(); g_muziAdcCtx.frag_total = (uint8_t)((g_muziAdcCtx.tx_len + ppl - 1) / ppl); if (g_muziAdcCtx.frag_total == 0) g_muziAdcCtx.frag_total = 1; // 限制最大分片数为 16(4-bit frag_idx 上限) if (g_muziAdcCtx.frag_total > 16) { g_muziAdcCtx.frag_total = 16; g_muziAdcCtx.tx_len = 16 * ppl; } // 初始化传输状态 g_muziAdcCtx.tx_offset = 0; g_muziAdcCtx.frag_idx = 0; g_muziAdcCtx.pkg_id = (g_muziAdcCtx.pkg_id + 1) & 0x0F; // 包 ID 递增 g_muziAdcCtx.state = MuZi_STATE_TX; g_muziAdcCtx.next_requested = false; // 立即发送第一个分片 MuZiPrepareDataFrag(); } // ==================== Command Handlers Implementation (命令处理函数实现) ==================== static void Cmd_START_ACQ(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)cmd; (void)payload; (void)plen; MuZiStartDataTx(); } static void Cmd_STOP_ACQ(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)cmd; (void)payload; (void)plen; g_muziAdcCtx.state = MuZi_STATE_IDLE; } static void Cmd_SET_GAIN(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)cmd; (void)plen; g_muziAdcCtx.gain = payload[1]; MuZiSendAck(MuZi_ACK_SET_GAIN); } static void Cmd_SET_RATE(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)cmd; (void)plen; g_muziAdcCtx.rate = ((uint16_t)payload[0] << 8) | payload[1]; MuZiSendAck(MuZi_ACK_SET_RATE); } static void Cmd_NEXT_FRAG(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)payload; (void)plen; if (g_muziAdcCtx.state == MuZi_STATE_TX || g_muziAdcCtx.state == MuZi_STATE_END) { g_muziAdcCtx.next_requested = true; // 设置流控标志 // In fast mode, trigger immediate processing if possible if (MuZiMode_IsFast()) { // The connect event callback will pick this up // (快速模式下由 ConnectEventCB 自动拾取) } else { tmos_set_event(Peripheral_TaskID, SBP_MUZI_DATA_EVT); } } else { MuZiSendErrRsp(cmd, 0x03); // 状态错误 } } static void Cmd_REQ_FRAG(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)plen; if (g_muziAdcCtx.state != MuZi_STATE_TX) { MuZiSendErrRsp(cmd, 0x03); return; } uint8_t req_idx = payload[0] & 0x0F; if (req_idx >= g_muziAdcCtx.frag_total) { MuZiSendErrRsp(cmd, 0x04); // 索引越界 return; } // 跳转到指定分片并重发 uint8_t ppl = MuZiPayloadPerFrag(); g_muziAdcCtx.frag_idx = req_idx; g_muziAdcCtx.tx_offset = req_idx * ppl; MuZiPrepareDataFrag(); } /** * @brief 切换到快速模式 * [FIX] ACK 延迟发送,避开 MuZiMode_Set 触发的 PHY/参数更新竞争窗口 */ static void Cmd_FAST_MODE(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)payload; (void)plen; MuZiModeResult_t r = MuZiMode_Set(MUZI_MODE_FAST, peripheralConnList.connHandle, Peripheral_TaskID); switch (r) { case MUZI_MODE_RESULT_OK: // [FIX] 延迟发送 ACK,等待 PHY 更新完成后再回复 s_pendingAckCmd = cmd; tmos_start_task(Peripheral_TaskID, SBP_MUZI_ACK_DELAY_EVT, MUZI_ACK_DELAY_TICKS); break; case MUZI_MODE_RESULT_ALREADY: MuZiSendAck(cmd); // 无 PHY 变更,可立即回复 break; case MUZI_MODE_RESULT_NO_CONN: case MUZI_MODE_RESULT_PHY_FAIL: MuZiSendErrRsp(cmd, 0x03); break; } } /** * @brief 切换到普通模式 * [FIX] ACK 延迟发送,避开 MuZiMode_Set 触发的 PHY/参数更新竞争窗口 */ static void Cmd_NORMAL_MODE(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)payload; (void)plen; MuZiModeResult_t r = MuZiMode_Set(MUZI_MODE_NORMAL, peripheralConnList.connHandle, Peripheral_TaskID); switch (r) { case MUZI_MODE_RESULT_OK: // [FIX] 延迟发送 ACK,等待 PHY 更新完成后再回复 s_pendingAckCmd = cmd; tmos_start_task(Peripheral_TaskID, SBP_MUZI_ACK_DELAY_EVT, MUZI_ACK_DELAY_TICKS); break; case MUZI_MODE_RESULT_ALREADY: MuZiSendAck(cmd); // 无 PHY 变更,可立即回复 break; case MUZI_MODE_RESULT_NO_CONN: case MUZI_MODE_RESULT_PHY_FAIL: MuZiSendErrRsp(cmd, 0x03); break; } } static void Cmd_GET_INFO(uint8_t cmd, const uint8_t *payload, uint8_t plen) { (void)payload; (void)plen; uint8_t info[6] = { MUZI_INFO_VER_MAJOR, MUZI_INFO_VER_MINOR, MUZI_INFO_MAX_MTU_LO, MUZI_INFO_MAX_MTU_HI, (MUZI_FEATURE_FAST_MODE | MUZI_FEATURE_24BIT_ADC | MUZI_FEATURE_8BIT_ADC | MUZI_FEATURE_NOTIFY), (uint8_t)MuZiMode_Get(), }; MuZiSendRsp(cmd, info, sizeof(info)); } // ==================== Command Table (命令查找表) ==================== // 命令表条目结构 typedef struct { uint8_t cmd; // 命令码 uint8_t plen; // 期望的载荷长度 void (*handler)(uint8_t, const uint8_t *, uint8_t); // 处理函数指针 } MuZi_CmdEntry_t; // 命令映射表(使用表驱动法替代 if-else/switch,易于扩展) static const MuZi_CmdEntry_t s_muziCmdTable[] = { {MuZi_CMD_START_ACQ, 0, Cmd_START_ACQ}, {MuZi_CMD_STOP_ACQ, 0, Cmd_STOP_ACQ}, {MuZi_CMD_SET_GAIN, 2, Cmd_SET_GAIN}, {MuZi_CMD_SET_RATE, 2, Cmd_SET_RATE}, {MuZi_CMD_NEXT_FRAG, 0, Cmd_NEXT_FRAG}, {MuZi_CMD_REQ_FRAG, 1, Cmd_REQ_FRAG}, {MuZi_CMD_FAST_MODE, 0, Cmd_FAST_MODE}, {MuZi_CMD_NORMAL_MODE,0, Cmd_NORMAL_MODE}, {MuZi_CMD_GET_INFO, 0, Cmd_GET_INFO}, }; /** * @brief 在命令表中查找指定命令码对应的条目 * @param cmd 命令码 * @return 匹配条目指针,未找到返回 NULL */ static const MuZi_CmdEntry_t *MuZiCmd_Find(uint8_t cmd) { for (uint8_t i = 0; i < (sizeof(s_muziCmdTable) / sizeof(s_muziCmdTable[0])); i++) { if (s_muziCmdTable[i].cmd == cmd) return &s_muziCmdTable[i]; } return NULL; } /** * @brief 解析并执行 MuZi 命令帧 * @param buf 命令帧数据(已通过 CRC 校验) * @param len 帧长度 */ static void ParseAndExecute(const uint8_t *buf, uint8_t len) { if (len < 4) return; uint8_t cmd = buf[1]; uint8_t plen = buf[2]; // 校验帧长度一致性 if (len != (4 + plen)) { MuZiSendErrRsp(cmd, 0x01); // 长度不匹配 return; } // 查找命令处理函数 const MuZi_CmdEntry_t *entry = MuZiCmd_Find(cmd); if (entry == NULL) { MuZiSendErrRsp(cmd, 0x05); // 未知命令 return; } // 校验载荷长度 if (plen != entry->plen) { MuZiSendErrRsp(cmd, 0x02); // 载荷长度错误 return; } // 执行命令处理函数 entry->handler(cmd, &buf[3], plen); }
四份文件:peripheral.h peripheral.c gattprofile.h gattprofile.c基本是AI生成的,自己修修补补后,经测试稳定性还可以,暂时没有发现问题。与理论值相差有点多,实测如下:

=== END ===
浙公网安备 33010602011771号