发帖
3 0 0

【PB-03F-kit】全网最详细的奉加PHY6252教程

KeNengBu
金牌会员

7

主题

13

回帖

1507

积分

金牌会员

积分
1507
PB&TG系列 103 3 6 天前

置顶链接

  1. PB-03F-kit规格书
    PB-03F-kit规格书
  2. PB-03F-kit相关使用工具
    https://docs.ai-thinker.com/blue_tooth_pb
  3. PB-03F-kit环境搭建、烧录教程参考
    【蓝牙5.2 PB-03F教程】二次开发环境搭建
    https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45385&extra=page%3D1&_dsign=7c8fe8cb
    【蓝牙5.2 PB-03F教程】烧录流程
    https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45392&extra=page%3D1&_dsign=5f5f2ec8
    【蓝牙5.2 PB-03F教程】蓝牙基础+主从机指令的使用
    https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45396&extra=page%3D1&_dsign=63f9575e
    【蓝牙5.2 PB-03教程】SDK二次开发入门,认识架构,开始点亮一盏LED
    https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45412&extra=page%3D5&_dsign=7c8fe8cb
    低功耗实战(电池供电必学)
    https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=46152&extra=page%3D1
    【蓝牙5.2 PB-03F教程】GPIO中断
    https://bbs.ai-thinker.com/forum.php?mod=viewthread&tid=45397&extra=page%3D6
  4. 非官方教程
    收发广播包
    OTA代码解析
    OSAL
    OSAL2
    蓝牙协议栈
    SPI
    PWM
    WS2812
    DMA
    OTA过程
    FLASH

PHY 蓝牙系列(PHY6202/6212/6222/6252)
封装 QFN32 :PHY6202/6212/6222
QFN48 :PHY6202/6212
SSOP24:PHY6252
外部电路
有 DCDC :PHY6202/6212/6222(功耗低)
无 DCDC : PHY6202/6212/6222 /PHY6252(功耗高)
无 DCDC 情况下发射功率可以达到最大,例:6222 带 DCDC 最大功率为 5dBm, 不带 DCDC 最大发射功率可达到 10dBm

芯片不支持 J-link 烧录,但是支持 J-link 仿真,仅支持串口烧录。
(6202/6212 支持 JTAG 接口,6222/6252 支持 SWD 接口)

PHY6252是奉加微的产品,ST17H66是伦茨科技的产品。
但是所有的东西都一样的,SDK、文档、下载工具。
还和CC254x差不多,培训视频、文档资料相当多,拿来做参考有助于理解。BLE-CC254x-1.5.2.0.EXE

参数

支持低功耗蓝牙:
Bluetooth5.2, Bluetoothmesh;
蓝牙速率支持:125Kbps, 500Kbps,1Mbps, 2Mbps;支持广播扩展,
多广播,信道选择。
64KB SRAM,256KB flash, 96KB ROM, 256bit efuse;

Flash 存储区域映射表

256KB Flash ota_single_bank存储区域映射表

分区名称 起始地址 结束地址 大小 说明
Reserved By PhyPlus 0x0000 0x1FFF 8KB 不可修改
1st Boot Info 0x2000 0x2FFF 4KB BootloaderInfo
2nd Boot Info 0x3000 0x3FFF 4KB App Info
FCDS 0x4000 0x4FFF 4KB FCDS(工厂配置数据存储区)
OTA Bootloader 0x5000 0x10FFF 48KB Bootloader
App Bank 0x11000 0x1FFFF 60KB App Sram
XIP 0x20000 0x3BFFF 112KB eXecute In Place(原地执行)
FS (UCDS) 0x3C000 0x3DFFF 8KB (文件系统 / 用户配置数据区)
Resource 0x3E000 0x3FFFF 8KB FlashNVM
FW Storage 0x40000 0x3FFFF 0 FW Storage

APP bank和XIP共用172KB空间.除app bank起始地址做限制外。app bank size及xip不做限制

256KB Flash ota存储区域映射表

分区名称 起始地址 结束地址 大小 说明
Reserved By PhyPlus 0x0000 0x1FFF 8KB 不可修改
1st Boot Info 0x2000 0x2FFF 4KB BootloaderInfo
2nd Boot Info 0x3000 0x3FFF 4KB App Info
FCDS 0x4000 0x4FFF 4KB FCDS(工厂配置数据存储区)
SLB Bootloader 0x5000 0x8FFF 16KB 单 Bank Bootloader 代码
Resource 0x9000 0x9FFF 8KB 资源区(如字体、图标等)
App (Map to Sram) 0x11000 0x1FFFF 60KB App Sram
XIP 0x20000 0x2BFFF 48KB eXecute In Place(原地执行)
FS (UCDS) 0x2C000 0x2DFFF 8KB (文件系统 / 用户配置数据区)
Resource 0x2E000 0x2DFFF 0KB FlashNVM
FW Storage 0x2E000 0x3FFFF 72KB FW Storage

二次开发

SDK网址

image.png
下载PHY6252芯片的SDK,解压后打开SDK下面的example\peripheral\gpio例程

image.png

关闭低功耗模式

因为芯片休眠了,LED的输出也会关闭。

image.png

CFG_SLEEP_MODE=PWR_MODE_SLEEP中插入 _NO即可:CFG_SLEEP_MODE=PWR_MODE_NO_SLEEP
Options for Target选项卡中的C/C++里面的Preprocessor Symbols中的Define处定义了宏定义会参与工程的编译。
定义DEBUG_INFO=1,会打开调试日记。
定义DEBUG_INFO=0,会关闭调试日记。

加载分散文件

在Linker选项卡内Scatter File处可设置工程的分散加载文件。
image.png

点亮蓝色LED(GPIO_18 输出高电平)

修改gpio_demo.c

注释 void Key_Demo_Init(uint8 task_id)整个函数,并修改为

void Key_Demo_Init(uint8 task_id)
{
key_TaskID = task_id;// 任务id,先暂时不用管。

// 此写函数默认会调用hal_gpio_pin_init(pin,GPIO_OUTPUT);
    hal_gpio_write(GPIO_P18,HAL_HIGH_IDLE); // GPIO18 输出高电平,点亮LED

}

编译

After Build - User command #2: fromelf.exe .\Objects\gpio_demo.axf --i32combined --output .\bin\gpio_demo.hex

注意这一行的 --output .\bin\gpio_demo.hex告诉了我们输出的gpio_demo.hex固件在bin目录下

烧录

image.png

image.png

输入自己想要使用的MAC地址。

出现 UART TX ASCII: UXTDWU时说明开发板连接成功。
此时长按复位按键,大概2s左右后,松开复位键。再进行第9和第10步烧录成功后再按RST复位运行。
奉加微电子PhyPlusKit使用视频教程

PhyPlusKit软件支持6种烧录模式
仅有应用固件的APP烧录
附带OTA功能的烧录
附带OTA和CHIPID模式的烧录
附带OTA和ENCIV模式的烧录
ENC_IV:加密初始向量(Initialization Vector ),是加密算法的 “随机数种子”,让每次加密结果不同,增强安全性。
含义:固件包含 OTA 功能 + 加密相关逻辑(依赖 ENC_IV ),用于:
OTA 升级时,加密传输固件包(防止被篡改、窃取 );
设备通信时,加密敏感数据(如配网密钥、用户数据 )。
Preserve模式
Preserve:“保留、维持”→ 烧录时保留设备原有部分数据 / 配置(如已存储的用户参数、校准数据、密钥 )。
含义:更新固件时,不覆盖特定分区 / 数据,避免重置设备已有信息。
场景:
升级应用层逻辑,但想保留用户设置(如蓝牙配对信息 );
修复 Bug 时,维持设备已存储的敏感参数(如加密密钥 )。
FCT烧录模式
FCT:Factory Config Table(工厂配置表),是一种用于存储设备出厂配置信息的表格。
含义:固件包含 FCT 表格,用于:
存储设备出厂配置信息(如 MAC 地址、产品型号、版本号等 );
提供设备配置接口(如通过 APP 配置设备参数 )。

Keil烧录

image.png

依次点击HexF,Erase,Write烧录固件

SWD调试

P2 GPIO2/SWD debug data inout
P3 GPIO3/SWD debug clock
6222/6252 支持 SWD 接口

未关闭SWD调试的情况下可以通过Jlink读出来固件的,可以把SWD的io口拉低屏蔽。

hal_gpio_pin2pin3_control(GPIO_P02,0);
hal gpio pin2pin3 control(GPIO_P03,0);

OSAL(Operating System Abstraction Layer)即操作系统抽象层

一个单片机通用的软件调度器框架 --- 基于任务和事件的OSAL调度器。OSAL最初的概念是由德州仪器TI在ZigBee的协议栈Z-Stack上引入的,严格意义上来说,它并不是一个传统意义的操作系统,但可以实现部分类似操作系统的功能。简单来说就是初始化任务池以及轮询任务池。
默认情况下,OSAL最大任务数为9,最大事件数为16
image.png

任务调度:采用任务数组tasks_events来管理各层对应的任务事件,tasks_arr是任务事件的处理数组,使用osal_set_event()函数,可以为对应的任务设置事件,使用osal_clear_event()函数,可以清除对应的任务事件。
时间管理:OSAL使用STM32的滴答时钟systick作为时间基准,可以虚拟出多个软件定时器,使用这些软件定时器,可以设置延时任务事件,当延时时间到达后,会为对应的任务事件设置触发标志位,然后执行延时事件。
OSAL调度器主要涉及以下源文件:
image.png

osal.c和osal.h:主要是任务的注册和调度实现,以及提供任务事件函数设置函数和任务事件清除函数。

osal_clock.c和osal_clock.h:主要是OSAL调度器使用STM32的滴答时钟systick作为时基,更新系统的软件定时器计数值,以及查找定时任务是否到达定时时间。

osal_timers.c和osal_timers.h:主要是通过链表的方式管理系统的软件定时器,对外提供软件定时任务的启动函数,软件定时任务的停止函数。
image.png

以上流程图,板级初始化和OSAL任务初始化,在main函数中完成,具体的调用方式可以查看GitHub里面的源代码文件。run_system()函数主要在while(1)大循环中调用,不断更新软件定时器并查找是否有定时任务,如果有立即执行的任务或定时任务,则执行任务事件函数,事件处理函数执行完成后,清除事件标志位。

int app_main(void)
{
    /* 初始化OSAL,包括初始化任务池osalInitTasks() */
    osal_init_system();
    osal_pwrmgr_device( PWRMGR_BATTERY );
    /* Start OSAL */
    osal_start_system(); // 轮询任务池
    return 0;
}

其中osal_init_system()会调用osalInitTasks(),做OSAL初始化。
osal_init_system没有公开源码。
osal_start_system()会调用osal_run_system(),进行osal轮询。
但是osal_start_system、osal_run_system都没有公开源码。
用户能看到的是:
jump_table_base和tasksArr

run_system()函数主要在osal.c文件中实现,函数的实现方式如下所示:

image.png
举个例子:例如我们需要添加一个看门狗的任务来监测系统是否正在运行,并定时进行喂狗操作。那么可以新建任务源文件iwdg_task.c和头文件iwdg_task.h,然后在osal.h文件中定义一个任务ID:IWDG_TASK_ID,这个ID就代表看门狗任务,看门狗任务的一系列事件,可以在头文件iwdg_task.h中进行定义。
源文件iwdg_task.c提供看门狗任务的初始化函数和任务执行函数,每个任务都需要提供任务的初始化函数和任务执行函数,格式如下图所示。

image.png

在函数iwdg_task_init()中,先通过register_task_array()函数,绑定IWDG_TASK_ID任务ID和iwdg_task()任务处理函数,然后再初始化STM32的看门狗外设,最后通过osal_start_timer()函数启动一个定时器,不断定时进行喂狗操作。
喂狗事件IWDG_FEED_EVENT和喂狗周期IWDG_FEED_PERIOD是在头文件iwdg_task.h中进行定义的,这里需要注意的是,每个任务事件都是按位操作的,目前每个任务下面最多定义16个事件如下图所示。
image.png

对于OSAL调度器,以下函数接口使用得比较多:
image.png

函数osal_set_event()和函数osal_clear_event()主要用来设置和清除立即执行的任务事件,当任务事件设置后,在run_system()函数中,该事件会立即执行。
函数osal_start_timer()和osal_stop_timer()主要用来设置和停止定时器任务事件,当定时时间到达后事件才会执行。osal_start_timer()的第三个参数timeout_value表示首次执行的超时时间,第四个参数reload_timeout_value表示下一次执行的重装载时间。如果要定时任务事件只触发一次,只需要把第四个参数reload_timeout_value置为0即可。
对于德州仪器TI提供的原生OSAL调度器,还有消息通信机制,不同的任务之间通过消息队列来进行数据传输,以实现任务间的数据访问。但这里为了使调度器简单易用,并没有把消息通信机制一并整合进来,使用这个调度器,多个任务间的数据仍然以共享内存的方式进行访问。
osal_msg_send()函数用来发送消息,osal_msg_recv()函数用来接收消息。

image.png

大致的流程如下:事件发生后-->被打包为消息-->存放到消息队列-->事件处理函数取出消息并进行相应操作

任务池初始化

每一个层次都有一个对应的任务来处理本层次的事务,例如MAC层对应一个MAC层的任务、网络层对应一个网络层的任务、HAL对应一个HAL的任务,以及应用层对应一个应用层的任务等,这些各个层次的任务构成一个任务池,这个任务池也就是上节课讲到的tasksEvents数组,如图所示。
image.png

这个过程跟《OSAL的任务调度原理》章节中创建和初始化任务池是类似的,它们的主要差别在于:

首先,任务池存储的数据结构不同,这里的tasksEvents是一个uint16类型的数组。
其次,tasksEvents支持存放多种类型的任务,例如MAC层的任务、网络层的任务和应用层的任务等。
最后,这里的每一个任务中都可能会包含多个待处理事件。

前面章节中曾经讲到,tasksEvents中的每个任务可以包含0个或者多个待处理的事件,而这个数组类型变量pTaskEventHandlerFn是一个函数指针类型变量,用于指向事件对应的处理函数,因此这段代码定义了一个事件处理函数数组,这个数组中的每一个元素均表示某一个层次任务的事件处理函数,例如:

  • MAC层任务对应的事件处理函数是macEventLoop(),它专门处理MAC层任务中的事件。
  • 网络层任务对应的事件处理函数是nwk_event_loop(),它专门处理网络层任务中的事件。
  • 应用层任务对应的事件处理函数是zclSampleSw_event_loop(),它专门处理应用层任中的事件。

OTA

image.png
启用 OTA 功能:再次改造 simpleBlePeripheral 项目,在 C/C++ 选项卡下添加预处理器定义 PHY_OTA_ENABLE=1,启用 OTA 功能,然后重新编译生成 hex 文件。
PHY_OTA_ENABLE=1其实就是帮你把一些OTA相关代码启用了。
烧写 OTA 相关固件:打开烧写软件,app 选择新编译的 hex 文件,BOOT 选择 SDK 中 example\OTA\OTA_internal_flash\bin 下预先编译的 ota 的 boot hex。OTA 模式选择 Single No FCT(意思是,升级的时候蓝牙app会暂停程序且覆盖写入),进入烧写模式,先 erase 擦除,再 write 烧写。烧写完成后按 reset,开发板即具备 OTA 功能。

首先用户程序要添加OTA服务,如果在运行过程中检测到OTA升级,程序就会跳转到指定地址,运行完全独立的OTA程序。OTA程序会将接收到的数据写入原来用户程序的位置,写入完成后OTA程序会跳转执行下载好的程序。在profile里面添加sbpProfile_ota.c,ota_app_service.c.直接编译下载就可以。

image.png

烧录要内存空间要选择SLB OTA模式

APP OTA

以看到下面的界面,app首先会进行连接,然后会显示 特性Enable成功SBH App已准备好,就说明我们的APP已经准备好要对Bumble进行OTA升级了,接下来我们准备升级所需要的hex16文件

image.png

我们准备一个新版本的simpleBlePeripheral,只需在代码里加一个log以区分新旧版本,我们修改 simpleBLEPeripheral.c,在main函数里添加一个log

SimpleBLEPeripheral_Init函数的最后加了一行

LOG("======================THIS IS NEW OTA VERSION====================\n");

重新编译,生成hex文件,然后我们在烧写工具当中使用HEX Merge选项卡,app选择新编译出来的hex文件,而BOOT选择SDK当中预先编译的ota的boot hex,然后在OTA模式选择 Single No FCT
点击右侧绿色的Hex16按钮,生成hex16文件
image.png

代码解析

添加ota_app_service.c文件。将其ota_app_AddService();在app应用程序的初始化函数bleuart_Init如下代码段添加,即完成了对该工程demo的OTA支持。

void bleuart_Init( uint8 task_id )
{
...
  // Initialize GATT attributes
  GGS_AddService( GATT_ALL_SERVICES );            // GAP  0xFFFFFFFF
  GATTServApp_AddService( GATT_ALL_SERVICES );    // GATT attributes
  DevInfo_AddService();                           // Device Information Service
  bleuart_AddService(on_bleuartServiceEvt);
  ota_app_AddService();                     //添加ota服务
  at_Init(); // initial uart for AT cmd first.
...
  osal_set_event( bleuart_TaskID, BUP_OSAL_EVT_START_DEVICE ); 
}

当 BLE从机设备和支持BLE OTA的手机APP建立连接之后,就是可以实现BLE设备的OTA升级。
其过程分为三个阶段:
1、启动OTA升级 命令OTA_CMD_START_OTA,可以启动OTA过程。
2、应用参数传递(此步骤为可选步骤) OTA_CMD_START_OTA命令的参数如果param_size字段不为0,那么自动进入参数传递状态,进行参数的传递。
3、应用固件传输以及烧写 如果之前的OTA_CMD_START_OTA命令param_size字段为0或者参数传递已经完成,就可以通过OTA_CMD_PARTITION_INFO命令开始块数据的传输。
通常一个应用固件由2~3个partition组成。目前OTA最多支持16个partition。
实现原理可以参考ota_app_service.c里的代码。
第一次点击OTA后,手机APP会跟BLE设备断开,BLE设备会从运行应用程序跳转运行OTA bootloder程序,所以其广播的蓝牙名称为PPlusOTA。如下图,我们再次使用手机APP对其连接:

NVM的使用

NVM就是用来存储设备参数的一个功能,把程序运行过程中的一些需要断电保存的数据,存储到flash里面,例如存储音量、亮度、设备号等等。

本SDK里面用到的一个NVM组件,是由系统组件osal_snv.c 调用 flash文件系统fs.c构建的,存储位置为FS区 #define NVM_BASE_ADDR 0x1103C000

  1. 添加源文件:
    image.png

image.png

  1. 指定代码链接空间(不指定会出现空间不足的故障) :
    在“Options for Target 'Target 1' -> Linker -> Edit”里面的添加“fs.o(+RO)”和“osal_snv.o(+RO)”
    image.png
  2. 例子:
    初始化nvm,并读取参数
osal_snv_init();
osal_snv_read(123, 2, &derver_num);//参数1:位置,参数2:长度,参数3:源

参数被修改后,进行写入保存

osal_snv_write(123, 2, &derver_num);

PHY6252 BLE

学习奉加的蓝牙工程建议先看最简单的simplebleperipheral.uvprojx工程。看这个工程可以结合PHY蓝牙连接流程.docx文档一起看。其他如bleuart_at,multi工程也有对应的文档,可以先结合文档一起看。
image.png

蓝牙名称默认是BUMBLE-FFFFFFFF

主机连接流程

  1. 在SimpleBLECentral_Init配置各种参数,启动START_DEVICE_EVT 事件osal_set_event(simpleBLETaskId, START_DEVICE_EVT);
  2. 收到START_DEVICE_EVT事件:a:GAPCentralRole_StartDevice(初始化central)b: GAPBondMgr_Register(注册配对绑定回调函数)。
  3. 收到GAP_DEVICE_INIT_DONE_EVENT:central初始化完成后gap层会发送此事件到应用层回调函数simpleBLECentralEventCB,然后发送CENTRAL_INIT_DONE_EVT事件 osal_set_event(simpleBLETaskId,CENTRAL_INIT_DONE_EVT);
  4. 收到CENTRAL_INIT_DONE_EVT:调用simpleBLECentral_DiscoverDevice函数,开始扫描广播。
  5. gap层每次扫描到一个广播就发送GAP_DEVICE_INFO_EVENT到应用层回调函数 simpleBLECentralEventCB,并调用simpleBLEAddDeviceInfo函数将扫描到的设备加到设备列表。
  6. 收到GAP_DEVICE_DISCOVERY_EVENT:central扫描结束收到此事件,可以在此事件做相应处理,比如打印扫描到设备地址,地址类型等。然后调用osal_set_event(simpleBLETaskId,CENTRAL_DISCOVER_DEVDONE_EVT);,发送CENTRAL_DISCOVER_DEVDONE_EVT事件。
  7. 收到CENTRAL_DISCOVER_DEVDONE_EVT:收到此事件后调用simpleBLECentral_LinkDevice GAPCentralRole_EstablishLink连接设备。
  8. 收到GAP_LINK_ESTABLISHED_EVENT:收到此事件时代表连接成功。可以做后续的处理,比如更新MTU,设置PHY mode等,这些根据具体需求来做调用。然后调用osal_start_timerEx( simpleBLETaskId, START_DISCOVERY_SERVICE_EVT,DEFAULT_SVC_DISCOVERY_DELAY );
  9. 收到DEFAULT_SVC_DISCOVERY_DELAY:收到此事件后调用simpleBLECentralStartDiscoveryService GATT_DiscAllPrimaryServices函数来发现peripheral设备的服务和特征。
  10. 收到gatt层发送的SYS_EVENT_MSG事件 simpleBLECentral_ProcessOSALMsg GATT_MSG_EVENT simpleBLECentralProcessGATTMsg simpleBLEGATTDiscoveryEvent,最后到simpleBLEGATTDiscoveryEvent函数,此函数会将发现到服务和特征保存到SimpleClientInfo变量中。

到此为一个基本的连接过程,后面的配对绑定在连接后面处理,原理也是一样的:
首先在SimpleBLECentral_Init函数配置初始化函数时设置pairMode 为GAPBOND_PAIRING_MODE_INITIATE;
然后在START_DEVICE_EVT事件处向gapbondmgr层注册配对绑定回调函数GAPBondMgr_Register((gapBondCBs_t*)&simpleBLEBondCB);
在连接成功后central会自动发起配对。配对过程大部分流程都由gapbondmgr完成,如果在初始化时设置为不需要密钥输入,则不需要应用层参与,如果设置为需要密钥输入,则gapbondmgr会回调simpleBLECentralPasscodeCB函数让用户输入密钥和显示密钥。在配对过程中会在发送事件到simpleBLECentralPairStateCB,如GAPBOND_PAIRING_STATE_STARTED,GAPBOND_PAIRING_STATE_COMPLETE,GAPBOND_PAIRING_STATE_BONDED,让用户做相应处理。

从机连接流程

  1. 在SimpleBLEPeripheral_Init配置各种参数,启动START_DEVICE_EVT 事件osal_set_event(simpleBLETaskId, START_DEVICE_EVT);
  2. 收到START_DEVICE_EVT事件:a: GAPRole_StartDevice(初始化peripheral)b: GAPBondMgr_Register(注册配对绑定回调函数)。
  3. 在gapRole_ProcessGAPMsg收到GAP_DEVICE_INIT_DONE_EVENT:收到此事件说明初始化成功,然后调用GAP_UpdateAdvertisingData更新广播数据。
  4. 收到GAP_ADV_DATA_UPDATE_DONE_EVENT事件:继续调用GAP_UpdateAdvertisingData更新扫描回复数据。
  5. 收到GAP_ADV_DATA_UPDATE_DONE_EVENT事件:调用osal_set_event( gapRole_TaskID, START_ADVERTISING_EVT );
  6. 在GAPRole_ProcessEvent收到START_ADVERTISING_EVT事件:然后调用GAP_MakeDiscoverable函数开始广播。
  7. 在gapRole_ProcessGAPMsg收到GAP_MAKE_DISCOVERABLE_DONE_EVENT事件:在此事件下更新相关状态。并等待连接请求,连接过程会由gap层完成,连接成功后会发送相应事件。
  8. 收到GAP_LINK_ESTABLISHED_EVENT事件:说明连接成功。连接成功后可以做后续处理,如连接参数更新,读rssi,开始配对绑定流程等,具体根据需求来调用相关函数。

关于配对绑定流程涉及到的事件参照上文主机配对绑定流程。
image.png

SimpleBlePeripheral工程解析

【PHY6222】simpleBLEPeripheral剖析-CSDN
st17h66低功耗蓝牙soc开发(5)——simplebleperipheral私有服务修改-爱代码爱编程
这个工程是一个基于Phyplus PHY6222(52)芯片的蓝牙低功耗(BLE)外设示例项目。

蓝牙广播数据的基本格式是 LTV,由 Length(1 字节 )、Type(1 字节 )、Value(值 )三部分构成 ,用于规范蓝牙广播数据包里有效数据的组织形式 。

和我们应用最贴近的设置是添加服务的函数:
image.png

不同服务有各自的添加函数。而不同服务实质上是一个个属性数组。

1. 工程概述

这是一个典型的BLE外设应用程序,实现了基本的BLE广播、连接和数据交换功能。工程基于OSAL(操作系统抽象层)构建,采用事件驱动的编程模型。

2. 文件结构

主要源代码文件位于source目录下:

  • main.c: 系统入口点,初始化硬件和OSAL系统
  • SimpleBLEPeripheral_Main.c: 应用程序主入口,包含app_main函数
  • OSAL_SimpleBLEPeripheral.c: OSAL任务初始化,定义了任务优先级和事件处理函数
  • simpleBLEPeripheral.c: 主应用程序逻辑,包含初始化和事件处理函数
  • simpleBLEPeripheral.h: 应用程序相关的宏定义和函数声明
  • sbpProfile_ota.c/h: 自定义GATT服务和特性的实现
  • halPeripheral.c/h: 硬件抽象层,处理GPIO、按键和LED等外设

3. 系统架构

OSAL任务结构

系统基于OSAL任务调度,在OSAL_SimpleBLEPeripheral.c中定义了以下任务(按优先级排序):

  1. LL_ProcessEvent - 链路层事件处理
  2. HCI_ProcessEvent - 主机控制器接口事件处理
  3. L2CAP_ProcessEvent - 逻辑链路控制与适配协议事件处理
  4. SM_ProcessEvent - 安全管理器事件处理
  5. GAP_ProcessEvent - 通用访问配置文件事件处理
  6. GATT_ProcessEvent - 通用属性配置文件事件处理
  7. GAPRole_ProcessEvent - GAP角色事件处理
  8. GAPBondMgr_ProcessEvent - 绑定管理器事件处理(可选)
  9. GATTServApp_ProcessEvent - GATT服务应用事件处理
  10. SimpleBLEPeripheral_ProcessEvent - 应用程序事件处理

应用程序初始化流程

  1. 系统启动时,main.c中的app_main()函数被调用
  2. 初始化操作系统(osal_init_system)
  3. 设置电源管理模式(osal_pwrmgr_device)
  4. 启动OSAL系统(osal_start_system)
  5. OSAL初始化各个任务(osalInitTasks)
  6. 应用程序初始化(SimpleBLEPeripheral_Init)

4. BLE功能实现

广播实现

工程实现了标准的BLE广播功能:

  • 在simpleBLEPeripheral.c中定义了广播数据(advertData)和扫描响应数据(scanRspData)
  • 支持配置广播间隔、广播类型和广播通道
  • 支持iBeacon格式的广播数据

GATT服务

工程实现了一个自定义的GATT服务(sbpProfile_ota.c),包含以下特性:

  • 特性4(SIMPLEPROFILE_CHAR4): 用于电源控制,支持读写
  • 特性5(SIMPLEPROFILE_CHAR5): 用于复位,支持读写和无响应写
  • 特性6(SIMPLEPROFILE_CHAR6): 用于通知,支持读和通知
  • 特性7(SIMPLEPROFILE_CHAR7): 支持读和无响应写

连接参数更新

工程支持BLE连接参数更新:

  • 最小连接间隔: 30ms (DEFAULT_DESIRED_MIN_CONN_INTERVAL)
  • 最大连接间隔: 1000ms (DEFAULT_DESIRED_MAX_CONN_INTERVAL)
  • 从设备延迟: 0 (DEFAULT_DESIRED_SLAVE_LATENCY)
  • 监督超时: 5000ms (DEFAULT_DESIRED_CONN_TIMEOUT)

5. 硬件抽象层

halPeripheral.c/h文件实现了硬件抽象层,包括:

  • GPIO控制
  • 按键处理
  • LED控制
  • UART通信
  • 命令行接口(CLI)

6. 高级功能

工程还包含一些高级功能和测试选项:

  • OTA固件升级支持(PHY_OTA_ENABLE)
  • 可解析私有地址测试(APP_CFG_RPA_TEST)
  • 延迟测试(LATENCY_TEST)
  • 动态时钟变更测试(DYNAMIC_CLK_CHG_TEST)
  • Flash测试(DBG_SPIF_TEST)
  • RTC测试(DBG_RTC_TEST)

7. 事件处理

应用程序通过SimpleBLEPeripheral_ProcessEvent函数处理各种事件:

  • SBP_START_DEVICE_EVT: 启动设备
  • SBP_PERIODIC_EVT: 周期性事件,用于发送通知
  • SBP_RESET_ADV_EVT: 重置广播
  • 其他测试和调试事件

总结

这个SimpleBlePeripheral工程是一个完整的BLE外设示例,展示了PHY6222(52)芯片的BLE功能,包括广播、连接、GATT服务和各种高级功能。它采用OSAL事件驱动架构,提供了良好的代码结构和功能模块化,可以作为开发自定义BLE应用的良好起点。

PHY6252 BLE MESH

PhyMesh 使用文档
PHY6252-SDK-blemesh

PHY6222 BLE Mesh Light 工程功能详细介绍

这是一个基于PHY6222芯片的BLE Mesh智能灯光控制系统工程,实现了完整的Mesh网络节点功能。

🏗️ 工程架构

核心组件:

  • 主控芯片:PHY6222 (ARM Cortex-M0内核)
  • 协议栈:基于Ethermind的BLE Mesh协议栈
  • 开发环境:支持Keil MDK和GCC编译
  • 内存配置:7KB大堆内存,2KB Mesh专用堆内存

🌐 BLE Mesh网络功能

1. Mesh协议栈支持

  • 完整的BLE Mesh 1.0协议栈实现
  • 支持7层Mesh协议架构(承载层→网络层→传输层→访问层→模型层)
  • 自动网络配置和设备入网(Provisioning)
  • 支持Relay、Proxy、Friend、Low Power Node功能

2. 网络管理

  • 自动设备发现和配网
  • 网络密钥和应用密钥管理
  • 心跳机制和网络健康监控
  • 支持多子网和密钥更新

💡 智能灯光控制功能

1. 多种灯光模型支持

  • Generic OnOff Model:基础开关控制
  • Light HSL Model:色相、饱和度、亮度控制
  • Light CTL Model:色温和亮度控制
  • Scene Model:场景模式存储和切换
  • Vendor Model:自定义厂商功能

2. 硬件控制

  • GPIO引脚定义
    • QFN32封装:红(P31)、绿(P32)、蓝(P33)
    • 其他封装:红(P2)、绿(P3)、蓝(P7)
  • PWM控制:支持精确的亮度和颜色调节
  • LED状态指示:支持多种颜色和闪烁模式

3. 灯光效果

  • 8种预定义颜色:关闭、红、绿、蓝、青、黄、品红、白
  • 多种闪烁模式:极快、快速、慢速、极慢
  • 平滑过渡和渐变效果
  • 场景记忆和恢复功能

🔧 设备管理功能

1. 配网和绑定

  • 支持PB-ADV和PB-GATT配网方式
  • 自动应用密钥绑定
  • 设备信息服务(Device Information Service)
  • OTA固件升级支持

2. 状态管理

  • 设备状态持久化存储
  • 网络配置信息保存
  • 断电恢复功能
  • 工厂重置支持

sdk代码参考

应用流程

● 定义功能+注册模块(客户端注册客户端的,服务端注册服务端的)
○ 注册回调(用来处理接收响应)
● 填目标地址,密码appkey
● 调用api发送

API位置

● MS_config_api.h
● MS_generic_onoff_api.h
● vendormodel_client.h
● vendormodel_server.h

客户端(元素和模型的注册):

retval = MS_access_create_node(&node_id);
    /* Register Element */
    /**
        TBD: Define GATT Namespace Descriptions from
        https://www.bluetooth.com/specifications/assigned-numbers/gatt-namespace-descriptors


        Using 'main' (0x0106) as Location temporarily.
    */
    element.loc = 0x0106;
    retval = MS_access_register_element
             (
                 node_id,
                 &element,
                 &element_handle
             );


    if (API_SUCCESS == retval)
    {
        /* Register Configuration model client */
        retval = UI_register_config_model_client(element_handle);
    }


    if (API_SUCCESS == retval)
    {
        /* Register Generic OnOff model client */
        retval = UI_register_generic_onoff_model_client(element_handle);
    }


    #ifdef  USE_VENDORMODEL


    if (API_SUCCESS == retval)
    {
        /* Register Vendor Defined model server */
        retval = UI_register_vendor_defined_model_client(element_handle);
    }

服务端(元素和模型的注册):

etval = MS_access_create_node(&node_id);
/* Register Element */
/**
TBD: Define GATT Namespace Descriptions from
https://www.bluetooth.com/specifications/assigned-numbers/gatt-namespace-descriptors


Using 'main' (0x0106) as Location temporarily.
*/
element.loc = 0x0106;
retval = MS_access_register_element
    (
    node_id,
    &element,
    &element_handle
    );


if (API_SUCCESS == retval)
{
    /* Register foundation model servers */
    retval = UI_register_foundation_model_servers(element_handle);
}


if (API_SUCCESS == retval)
{
/* Register Generic OnOff model server */
retval = UI_register_generic_onoff_model_server(element_handle);
}


#ifdef  USE_HSL


if (API_SUCCESS == retval)
{
/* Register Light Lightness model server */
retval = UI_register_light_hsl_model_server(element_handle);
}


#endif
#ifdef  USE_CTL


if (API_SUCCESS == retval)
{
/* Register Light Lightness model server */
retval = UI_register_light_ctl_model_server(element_handle);
}


#endif
#ifdef  USE_SCENE


if (API_SUCCESS == retval)
{
/* Register Light Scene model server */
retval = UI_register_scene_model_server(element_handle);
}


#endif
#ifdef  USE_VENDORMODEL


if (API_SUCCESS == retval)
{
    /* Register Vendor Defined model server */
    retval = UI_register_vendor_defined_model_server(element_handle);
}

地址的分配(gateway节点):

获取地址列表,增加地址后再发送到配网中的设备

case PROV_EVT_PROVDATA_INFO_REQ:
CONSOLE_OUT ("Recvd PROV_EVT_PROVDATA_INFO_REQ\n");
CONSOLE_OUT ("Status - 0x%04X\n", event_result);
/* Send Provisioning Data */
CONSOLE_OUT ("Send Provisioning Data...\n");
/* Update the next device address if provisioned devices are present in database */
retval = MS_access_cm_get_prov_devices_list
(
&prov_dev_list[0],
    &num_entries,
    &pointer_entries
);


if ((API_SUCCESS == retval) /*&&
(0 != num_entries)*/)
{
    //                UI_prov_data.uaddr = prov_dev_list[num_entries - 1].uaddr +
    //                                     prov_dev_list[num_entries - 1].num_elements;
    UI_prov_data.uaddr = pointer_entries;//地址分配
}


printf("Updating Provisioning Start Addr to 0x%04X\n", UI_prov_data.uaddr);
//Get network key
MS_access_cm_get_netkey_at_offset(0,0,UI_prov_data.netkey);
//Get key refresh state
MS_access_cm_get_key_refresh_phase(0,&key_refresh_state);
key_refresh_state = (MS_ACCESS_KEY_REFRESH_PHASE_2 == key_refresh_state) ? 0x01 : 0x00;
UI_prov_data.ivindex = ms_iv_index.iv_index;
UI_prov_data.flags = ((ms_iv_index.iv_update_state & 0x01) << 1) | key_refresh_state;
blebrr_scan_pl(FALSE);  //by hq
retval = MS_prov_data (phandle, &UI_prov_data);//发送给配网的设备
CONSOLE_OUT ("Retval - 0x%04X\n", retval);
break;

Publish和Subscribe

Publish(gateway节点):

UI_prov_data.uaddr = 0xCFFF;
UI_set_publish_address(UI_prov_data.uaddr, UI_generic_onoff_client_model_handle,MS_FALSE);
UI_set_publish_address(UI_prov_data.uaddr, UI_vendor_defined_client_model_handle,MS_FALSE);

Subscribe(light节点):

void vm_subscriptiong_delete (MS_NET_ADDR addr)
{
    MS_ACCESS_ADDRESS       sub_addr;
    sub_addr.use_label=0;
    sub_addr.addr=addr;
    MS_access_ps_store_disable(MS_TRUE);
    MS_access_cm_delete_model_subscription(UI_generic_onoff_server_model_handle,&sub_addr);
    #ifdef  USE_LIGHTNESS
    MS_access_cm_delete_model_subscription(UI_light_lightness_server_model_handle,&sub_addr);
    #endif
    #ifdef  USE_CTL
    MS_access_cm_delete_model_subscription(UI_light_ctl_server_model_handle,&sub_addr);
    MS_access_cm_delete_model_subscription(UI_light_ctl_setup_server_model_handle,&sub_addr);
    #endif
    #ifdef  USE_HSL
    MS_access_cm_delete_model_subscription(UI_light_hsl_server_model_handle,&sub_addr);
    MS_access_cm_delete_model_subscription(UI_light_hsl_setup_server_model_handle,&sub_addr);
    #endif
    #ifdef  USE_SCENE
    MS_access_cm_delete_model_subscription(UI_scene_server_model_handle,&sub_addr);
    MS_access_cm_delete_model_subscription(UI_scene_setup_server_model_handle,&sub_addr);
    #endif
    MS_access_ps_store_disable(MS_FALSE);
    MS_access_ps_store_all_record();
}

设备密钥devicekey

是在配网的过程中,由配网器和入网设备共同生成的,每个节点只有1个,专门给config模型使用的(配置用的)!服务端和客户端一致,才能入网。

流程:

  1. 客户端(gateway节点)
    a. 生成devicekey
    b. 获取devicekey(根据需要配置的节点“地址”来获取)
    c. 在config模型的publish绑定中使用
    d. 发送config数据
  2. 服务端(light节点)
    a. 生成devicekey
    b. 默认绑定config模型,可以查看
    c. 根据客户端发来的命令,回调处理

以下代码是一些代码片段,需要了解完整内容的话,可以在工程里面进行全局搜索!
客户端(gateway节点):
1.定义生成

#define UI_DEVICE_UUID      {0x25, 0x22, 0x42, 0x54, 0x50, 0x02, 0x10, 0x03, 0x22, 0x14, 0x52, 0x81, 0x11, 0x14, 0x90, 0x22}

2.获取devicekey

retval = MS_access_cm_get_device_key_handle
                 (
                     publish_info.addr.addr,
                     &dev_key_handle
                 );

3.在设置publish时,用devicekey绑定定config模型

retval = MS_access_cm_set_model_publication
             (
                 model_handle,
                 &publish_info
             );

整体参考:

//void UI_set_publish_address(UINT16 addr, MS_ACCESS_MODEL_HANDLE model_handle,UINT8 config_mode)里面的片段
if(config_mode)
{
    publish_info.remote = MS_FALSE;
    retval = MS_access_cm_get_device_key_handle
        (
        publish_info.addr.addr,
        &dev_key_handle//重点
    );

    if (API_SUCCESS == retval)
    {
        publish_info.appkey_index = MS_CONFIG_LIMITS(MS_MAX_APPS) + dev_key_handle;//重点
        CONSOLE_OUT("DevKey -> AppKey Index: 0x%04X\n", publish_info.appkey_index);
    }
}

retval = MS_access_cm_set_model_publication
(
model_handle,
    &publish_info
);

4.发送config数据(在publish中配置好了目标地址和devicekey,调用MS_config_api.h里面的API直接发送命令即可)

/* ----------------------------------------- Functions */
/* Model Client - Configuration Models */
/* Send Config Composition Data Get */
void UI_config_client_get_composition_data(UCHAR page)
{
    API_RESULT retval;
    ACCESS_CONFIG_COMPDATA_GET_PARAM  param;
    CONSOLE_OUT
    ("Send Config Composition Data Get\n");
    param.page = page;
    retval = MS_config_client_composition_data_get(&param);
    CONSOLE_OUT
    ("Retval - 0x%04X\n", retval);
}

服务端(light节点):
1.定义生成

/** Unprovisioned device identifier */
//DECL_STATIC PROV_DEVICE_S UI_lprov_device =
PROV_DEVICE_S UI_lprov_device =
{
    /** UUID */
    {0x25, 0x22, 0x42, 0x54, 0x50, 0x02, 0x10, 0x03, 0x22, 0x14, 0x52, 0x81, 0x11, 0x14, 0x90, 0x22},
    /** OOB Flag */
    0x00,
    /**
        Encoded URI Information
        For example, to give a web address, "https://www.abc.com"
        the URI encoded data would be -
        0x17 0x2F 0x2F 0x77 0x77 0x77 0x2E 0x61 0x62 0x63 0x2E 0x63 0x6F 0x6D
        where 0x17 is the URI encoding for https:
    */
    NULL
};

2.绑定(默认绑定config模型)
3.回调处理

API_RESULT UI_app_config_server_callback

应用密钥appkey

流程

  1. 客户端(gateway节点)
    a. 存储appkey
    b. 获取appkey
    c. 自身模型绑定appkey,用于认证“对端设备”的appkey(这里的绑定类似于给功能模型加上密码)
    d. 在publish绑定里面使用appkey,用于给对端设备作认证(相当于填密码)
    e. 发送publish。
  2. 服务端(light节点)
    a. 获取appkey
    b. 绑定到模型上
    c. 根据客户端发来的命令,回调处理

客户端(gateway节点):

1.添加appkey到本地数据库

MS_access_cm_add_appkey
    (
        0, /* subnet_handle */
        param.appkey_index, /* appkey_index */
        &param.appkey[0] /* app_key */
    );

2.通过句柄handle获取appkey数据(这里只是用来打印显示)

retval = MS_access_cm_get_app_key
             (
                 handle,
                 &key,
                 &aid
             );

CONSOLE_OUT("App Key[0x%02X]: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\r\n",
                    handle, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7],
                    key[8], key[9], key[10], key[11], key[12], key[13], key[14], key[15]);

3.通过句柄handle来绑定第1步存储本地数据库里面的那个appkey(服务端的模型也要绑定)

retval=MS_access_bind_model_app(UI_generic_onoff_client_model_handle, handle);
#ifdef  USE_VENDORMODEL
retval=MS_access_bind_model_app(UI_vendor_defined_client_model_handle, handle);
CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_vendor_defined_client_model_handle,handle);
#endif

4.在注册publish时,用appkey绑定应用模型(只需调用索引即可)

/* Set the Publish address for onoff and vendor model Client */
    UI_prov_data.uaddr = 0xCFFF;
    UI_set_publish_address(UI_prov_data.uaddr, UI_generic_onoff_client_model_handle,MS_FALSE);
    UI_set_publish_address(UI_prov_data.uaddr, UI_vendor_defined_client_model_handle,MS_FALSE);

if(config_mode)
    {
        publish_info.remote = MS_FALSE;
        retval = MS_access_cm_get_device_key_handle
                 (
                     publish_info.addr.addr,
                     &dev_key_handle
                 );


        if (API_SUCCESS == retval)
        {
            publish_info.appkey_index = MS_CONFIG_LIMITS(MS_MAX_APPS) + dev_key_handle;
            CONSOLE_OUT("DevKey -> AppKey Index: 0x%04X\n", publish_info.appkey_index);
        }
    }
    else
    {
        publish_info.remote = MS_TRUE;
        publish_info.appkey_index = 0;//重点
        CONSOLE_OUT("AppKey Index: 0x%04X\n", publish_info.appkey_index);
    }

	//重点
    retval = MS_access_cm_set_model_publication
             (
                 model_handle,
                 &publish_info
             );

5.发送publish

void UI_generic_onoff_set(UCHAR state)
{
    API_RESULT retval;
    MS_GENERIC_ONOFF_SET_STRUCT  param;
    CONSOLE_OUT
    ("Send Generic Onoff Set\n");
    param.onoff = state;
    param.tid = 0;
    param.optional_fields_present = 0x00;
    retval = MS_generic_onoff_set(&param);//重点
    CONSOLE_OUT
    ("Retval - 0x%04X\n", retval);
}

#define MS_generic_onoff_set(param) \
    MS_generic_onoff_client_send_reliable_pdu \
    (\
     MS_ACCESS_GENERIC_ONOFF_SET_OPCODE,\
     param,\
     MS_ACCESS_GENERIC_ONOFF_STATUS_OPCODE\
    )

服务端(light节点):

1.获取appkey

retval = MS_access_cm_get_app_key
             (
                 handle,
                 &key,
                 &aid
             );

2.绑定appkey

retval=MS_access_bind_model_app(UI_generic_onoff_server_model_handle, handle);
            #ifdef  USE_LIGHTNESS
retval=MS_access_bind_model_app(UI_light_lightness_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x               %04x)\n",retval,UI_light_lightness_server_model_handle,handle);
            #endif
            #ifdef  USE_CTL
retval=MS_access_bind_model_app(UI_light_ctl_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_light_ctl_server_model_handle,handle);
            #endif
            #ifdef  USE_HSL
retval=MS_access_bind_model_app(UI_light_hsl_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_light_hsl_server_model_handle,handle);
            #endif
            #ifdef  USE_SCENE
retval=MS_access_bind_model_app(UI_scene_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_scene_server_model_handle,handle);
            #endif
            #ifdef  USE_VENDORMODEL
retval=MS_access_bind_model_app(UI_vendor_defined_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_vendor_defined_server_model_handle,handle);
            #endif

3.接收回调处理

static API_RESULT UI_generic_onoff_server_cb
(
    /* IN */ MS_ACCESS_MODEL_REQ_MSG_CONTEXT*     ctx,
    /* IN */ MS_ACCESS_MODEL_REQ_MSG_RAW*         msg_raw,
    /* IN */ MS_ACCESS_MODEL_REQ_MSG_T*           req_type,
    /* IN */ MS_ACCESS_MODEL_STATE_PARAMS*        state_params,
    /* IN */ MS_ACCESS_MODEL_EXT_PARAMS*          ext_params
)
{
    MS_STATE_GENERIC_ONOFF_STRUCT    param;
    MS_ACCESS_MODEL_STATE_PARAMS     current_state_params;
    API_RESULT                       retval;
    retval = API_SUCCESS;


    /* Check message type */
    if (MS_ACCESS_MODEL_REQ_MSG_T_GET == req_type->type)
    {
        CONSOLE_OUT("[GENERIC_ONOFF] GET Request.\n");
        UI_generic_onoff_model_state_get(state_params->state_type, 0, &param, 0);
        current_state_params.state_type = state_params->state_type;
        current_state_params.state = &param;
        /* Using same as target state and remaining time as 0 */
    }
    else if (MS_ACCESS_MODEL_REQ_MSG_T_SET == req_type->type)
    {
        CONSOLE_OUT("[GENERIC_ONOFF] SET Request.\n");
        retval = UI_generic_onoff_model_state_set(state_params->state_type, 0, (MS_STATE_GENERIC_ONOFF_STRUCT*)state_params->state, 0);
        current_state_params.state_type = state_params->state_type;
        current_state_params.state = (MS_STATE_GENERIC_ONOFF_STRUCT*)state_params->state;
    }


    /* See if to be acknowledged */
    if (0x01 == req_type->to_be_acked)
    {
        CONSOLE_OUT("[GENERIC_ONOFF] Sending Response.\n");
        /* Parameters: Request Context, Current State, Target State (NULL: to be ignored), Remaining Time (0: to be ignored), Additional Parameters (NULL: to be ignored) */
        retval = MS_generic_onoff_server_state_update(ctx, &current_state_params, NULL, 0, NULL);
    }


    return retval;
}

config模型的应用(配置应用)

config模型,专门用来给节点做“配置”用的,例如,订阅、发布、绑定appkey等配置。
image.png

客户端:
初始化

注册config client模型
/* Register Configuration model client */
retval = UI_register_config_model_client(element_handle);

设置config client发布地址和devicekey

/* Set the Publish address for Config Client */
UI_set_publish_address(UI_prov_data.uaddr, UI_config_client_model_handle,MS_TRUE);

发送获取Composition数据命令

/* Get the Composition data */
UI_config_client_get_composition_data(0x00);

服务端:
初始化

注册config server模型
/* Register foundation model servers */
retval = UI_register_foundation_model_servers(element_handle);

APP_config_server_CB_init(UI_app_config_server_callback);


获取devicekey?
UI_sample_get_device_key();

不处理Composition命令,返回Composition状态

null

客户端:
发送添加appkey命令

/* Add Appkey */
UI_config_client_appkey_add(0, 0, UI_appkey);

服务端:
绑定appkey到各个模型

case MS_ACCESS_CONFIG_APPKEY_ADD_OPCODE:
        #ifdef EASY_BOUNDING
        blebrr_scan_pl(FALSE);
        ms_provisioner_addr = saddr;
        vm_subscriptiong_binding_cb();//重要
//        ms_provisioner_addr = saddr;
        #if (CFG_HEARTBEAT_MODE)
        UI_trn_set_heartbeat_subscription(saddr);
        #endif
        #else
//        CONSOLE_OUT("Stop timer\n");
        ms_provisioner_addr = saddr;
//        EM_stop_timer(&procfg_timer_handle);
        blebrr_prov_started = MS_FALSE;
        #endif
        break;

客户端:
处理appkey添加完成响应回调

case MS_ACCESS_CONFIG_APPKEY_STATUS_OPCODE:
    {
        CONSOLE_OUT(
            "MS_ACCESS_CONFIG_APPKEY_STATUS_OPCODE\n");
        #ifdef EASY_BOUNDING
        UI_SET_RAW_DATA_DST_ADDR(UI_prov_data.uaddr);
        MS_access_cm_set_transmit_state(MS_NETWORK_TX_STATE, (0<<3)|0);
        /* Set provision started */
        blebrr_prov_started = MS_FALSE;
        EM_stop_timer(&procfg_timer_handle);
        CONSOLE_OUT(
            "PROVISION AND CONFIG DONE!!!\n");
//        UI_config_proxy_set(0);
        /* Send a Generic ON */
        #else
        /* Bind the model to Appkey */
        UI_config_client_model_app_bind(UI_prov_data.uaddr, 0, MS_ACCESS_MODEL_TYPE_SIG, MS_MODEL_ID_GENERIC_ONOFF_SERVER);
        #endif
    }

generic模型的应用示例(订阅、发布)

generic模型,就是sig mesh协议里面规定的一套通用功能模型,各个厂家只要按照协议去设计设备功能,设备之间即可兼容使用!
下面就用元素0x0106里面的“通用开关模型(generic_onoff_model)”来举例,了解并记录SDK里面,具体是如何应用generic模型的!
订阅、发布模式:

image.png

单播模式:
image.png

客户端(gateway节点):
初始化

if (API_SUCCESS == retval)
    {
        /* Register Configuration model client */
        retval = UI_register_config_model_client(element_handle);
    }


    if (API_SUCCESS == retval)
    {
        /* Register Generic OnOff model client */
        retval = UI_register_generic_onoff_model_client(element_handle);
    }


    #ifdef  USE_VENDORMODEL


    if (API_SUCCESS == retval)
    {
        /* Register Vendor Defined model server */
        retval = UI_register_vendor_defined_model_client(element_handle);
    }


    #endif
    UI_config_client_appkey_binding(0,0,UI_appkey);//重点
    printf("0x%08X 0x%08X 0x%02X\n",ms_iv_index.iv_index,ms_iv_index.iv_expire_time,ms_iv_index.iv_update_state);
    UI_prov_data.uaddr = 0x0001;
    //Get network key
    retval = MS_access_cm_get_netkey_at_offset(0,0,UI_prov_data.netkey);


    if(retval == ACCESS_NO_MATCH)     //if can't find netkey,generate it
    {
        UI_netkey_generate(UI_prov_data.netkey);
    }


    //Get key refresh state
    MS_access_cm_get_key_refresh_phase(0,&key_refresh_state);
    key_refresh_state = (MS_ACCESS_KEY_REFRESH_PHASE_2 == key_refresh_state) ? 0x01 : 0x00;
    UI_prov_data.ivindex = ms_iv_index.iv_index;
    UI_prov_data.flags = ((ms_iv_index.iv_update_state & 0x01) << 1) | key_refresh_state;
    retval = UI_set_provision_data(UI_prov_data.uaddr);
    /* Set the Publish address for onoff and vendor model Client */
    UI_prov_data.uaddr = 0xCFFF;//重点
    UI_set_publish_address(UI_prov_data.uaddr, UI_generic_onoff_client_model_handle,MS_FALSE);//重点
    UI_set_publish_address(UI_prov_data.uaddr, UI_vendor_defined_client_model_handle,MS_FALSE);

发送“generic_onoff_set”命令

void UI_generic_onoff_set(UCHAR state)
{
    API_RESULT retval;
    MS_GENERIC_ONOFF_SET_STRUCT  param;
    CONSOLE_OUT
    ("Send Generic Onoff Set\n");
    param.onoff = state;
    param.tid = 0;
    param.optional_fields_present = 0x00;
    retval = MS_generic_onoff_set(&param);//重点
    CONSOLE_OUT
    ("Retval - 0x%04X\n", retval);
}

#define MS_generic_onoff_set(param) \
    MS_generic_onoff_client_send_reliable_pdu \
    (\
     MS_ACCESS_GENERIC_ONOFF_SET_OPCODE,\//重点
     param,\
     MS_ACCESS_GENERIC_ONOFF_STATUS_OPCODE\
    )

处理generic_onoff回调

API_RESULT UI_generic_onoff_client_cb
(
    /* IN */ MS_ACCESS_MODEL_HANDLE* handle,
    /* IN */ UINT32                   opcode,
    /* IN */ UCHAR*                   data_param,
    /* IN */ UINT16                   data_len
)
{
    API_RESULT retval;
    retval = API_SUCCESS;
    CONSOLE_OUT (
        "[GENERIC_ONOFF_CLIENT] Callback. Opcode 0x%04X\n", opcode);
    appl_dump_bytes(data_param, data_len);


    switch(opcode)
    {
    case MS_ACCESS_GENERIC_ONOFF_STATUS_OPCODE://重点
    {
        CONSOLE_OUT(
            "MS_ACCESS_GENERIC_ONOFF_STATUS_OPCODE\n");
    }
    break;
    }


    return retval;
}

服务端(light节点):
初始化

if (API_SUCCESS == retval)
    {
        /* Register Generic OnOff model server */
        retval = UI_register_generic_onoff_model_server(element_handle);
    }

绑定appkey

API_RESULT UI_sample_binding_app_key(void)
{
    MS_APPKEY_HANDLE  handle;
    UINT8*              key;
    UINT8             aid;
    DECL_CONST UINT8  t_key[16] = {0};
    API_RESULT retval;
    CONSOLE_OUT("Fetching App Key for Handle 0x0000\n");
    handle = 0x0000;
    retval = MS_access_cm_get_app_key
             (
                 handle,
                 &key,
                 &aid
             );


    /* Check Retval. Print App Key */
    if (API_SUCCESS == retval)
    {
        CONSOLE_OUT("App Key[0x%02X]: %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X\r\n",
                    handle, key[0], key[1], key[2], key[3], key[4], key[5], key[6], key[7],
                    key[8], key[9], key[10], key[11], key[12], key[13], key[14], key[15]);


        if (0 == EM_mem_cmp(key, t_key, 16))
        {
            /* NO AppKey Bound */
            retval = API_FAILURE;
        }
        else
        {
            /* Found a Valid App Key */
            /* Keeping the retval as API_SUCCESS */
          
            retval=MS_access_bind_model_app(UI_generic_onoff_server_model_handle, handle);//重点
            #ifdef  USE_LIGHTNESS
            retval=MS_access_bind_model_app(UI_light_lightness_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_light_lightness_server_model_handle,handle);
            #endif
            #ifdef  USE_CTL
            retval=MS_access_bind_model_app(UI_light_ctl_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_light_ctl_server_model_handle,handle);
            #endif
            #ifdef  USE_HSL
            retval=MS_access_bind_model_app(UI_light_hsl_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_light_hsl_server_model_handle,handle);
            #endif
            #ifdef  USE_SCENE
            retval=MS_access_bind_model_app(UI_scene_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_scene_server_model_handle,handle);
            #endif
            #ifdef  USE_VENDORMODEL
            retval=MS_access_bind_model_app(UI_vendor_defined_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_vendor_defined_server_model_handle,handle);
            #endif
        }
    }


    #ifdef EASY_BOUNDING
    //Provision ok,stop provision/config timeout handler  by hq
    CONSOLE_OUT("Stop timer\n");
    EM_stop_timer(&procfg_timer_handle);
    blebrr_prov_started = MS_FALSE;
    #endif
    return retval;
}

订阅0xCFFF

vm_subscriptiong_add (0xCFFF);//重点

void vm_subscriptiong_add (MS_NET_ADDR addr)
{
    CONSOLE_OUT("vm_subscriptiong_add:%x\n",addr);
    MS_ACCESS_ADDRESS       sub_addr;
    sub_addr.use_label=0;
    sub_addr.addr=addr;
    MS_access_ps_store_disable(MS_TRUE);
    MS_access_cm_add_model_subscription(UI_generic_onoff_server_model_handle,&sub_addr);
    #ifdef  USE_LIGHTNESS
    MS_access_cm_add_model_subscription(UI_light_lightness_server_model_handle,&sub_addr);
    #endif
    #ifdef  USE_CTL
    MS_access_cm_add_model_subscription(UI_light_ctl_server_model_handle,&sub_addr);
    MS_access_cm_add_model_subscription(UI_light_ctl_setup_server_model_handle,&sub_addr);
    #endif
    #ifdef  USE_HSL
    MS_access_cm_add_model_subscription(UI_light_hsl_server_model_handle,&sub_addr);
    MS_access_cm_add_model_subscription(UI_light_hsl_setup_server_model_handle,&sub_addr);
    #endif
    #ifdef  USE_SCENE
    MS_access_cm_add_model_subscription(UI_scene_server_model_handle,&sub_addr);
    MS_access_cm_add_model_subscription(UI_scene_setup_server_model_handle,&sub_addr);
    #endif
    MS_access_ps_store_disable(MS_FALSE);
    MS_access_ps_store_all_record();
}

回调处理

static API_RESULT UI_generic_onoff_server_cb
(
    /* IN */ MS_ACCESS_MODEL_REQ_MSG_CONTEXT*     ctx,
    /* IN */ MS_ACCESS_MODEL_REQ_MSG_RAW*         msg_raw,
    /* IN */ MS_ACCESS_MODEL_REQ_MSG_T*           req_type,
    /* IN */ MS_ACCESS_MODEL_STATE_PARAMS*        state_params,
    /* IN */ MS_ACCESS_MODEL_EXT_PARAMS*          ext_params
)
{
    MS_STATE_GENERIC_ONOFF_STRUCT    param;
    MS_ACCESS_MODEL_STATE_PARAMS     current_state_params;
    API_RESULT                       retval;
    retval = API_SUCCESS;


    /* Check message type */
    if (MS_ACCESS_MODEL_REQ_MSG_T_GET == req_type->type)
    {
        CONSOLE_OUT("[GENERIC_ONOFF] GET Request.\n");
        UI_generic_onoff_model_state_get(state_params->state_type, 0, &param, 0);
        current_state_params.state_type = state_params->state_type;
        current_state_params.state = &param;
        /* Using same as target state and remaining time as 0 */
    }
    else if (MS_ACCESS_MODEL_REQ_MSG_T_SET == req_type->type)//重点
    {
        CONSOLE_OUT("[GENERIC_ONOFF] SET Request.\n");
        retval = UI_generic_onoff_model_state_set(state_params->state_type, 0, (MS_STATE_GENERIC_ONOFF_STRUCT*)state_params->state, 0);//重点
        current_state_params.state_type = state_params->state_type;
        current_state_params.state = (MS_STATE_GENERIC_ONOFF_STRUCT*)state_params->state;
    }


    /* See if to be acknowledged */
    if (0x01 == req_type->to_be_acked)
    {
        CONSOLE_OUT("[GENERIC_ONOFF] Sending Response.\n");
        /* Parameters: Request Context, Current State, Target State (NULL: to be ignored), Remaining Time (0: to be ignored), Additional Parameters (NULL: to be ignored) */
        retval = MS_generic_onoff_server_state_update(ctx, &current_state_params, NULL, 0, NULL);
    }


    return retval;
}



static API_RESULT UI_generic_onoff_model_state_set(UINT16 state_t, UINT16 state_inst, void* param, UINT8 direction)
{
    API_RESULT retval;
    UCHAR  proxy_state;
    retval = API_SUCCESS;


    switch (state_t)
    {
    case MS_STATE_GENERIC_ONOFF_T:
    {
        MS_STATE_GENERIC_ONOFF_STRUCT* param_p;
        param_p = (MS_STATE_GENERIC_ONOFF_STRUCT*)param;
        /* Instantaneous Change */
        UI_generic_onoff.onoff = param_p->onoff;
        *param_p = UI_generic_onoff;
        CONSOLE_OUT("[state] current: 0x%02X\n", UI_generic_onoff.onoff);
        CONSOLE_OUT("[state] target: 0x%02X\n", UI_generic_onoff.target_onoff);
        CONSOLE_OUT("[state] remaining_time: 0x%02X\n", UI_generic_onoff.transition_time);
        proxy_state = UI_proxy_state_get();


        if(proxy_state==MS_PROXY_CONNECTED)
        {
            EM_start_timer (&proxy_dly_thandle,    (EM_TIMEOUT_MILLISEC | 100), proxy_dly_generic_onoff, NULL, 0);
        }
        else
        {
            generic_onoff_set_pl(param_p->onoff);//重点
        }


        /* Ignoring Instance and direction right now */
    }
    break;


    default:
        break;
    }


    return retval;
}

vendor模型的应用示例(透传+publish)

蓝牙mesh组网实践(厂商透传模型介绍)
vendor模型,即厂家自定义模型,在通用模型中没有的功能,可以在vendor模型中通过自定义的方式来添加厂家特有的功能!
vendor在SDK里面,有两种应用方法:
1.以raw的方式(透传),该方式需要填写目标地址和应用密钥
2.以publish的方式,该方式只需填写目标地址

应用层流程(raw透传的方式)

image.png

vendor模型底层传输处理

vendormodel_client.c
vendormodel_server.c

客户端接收处理:

初始化时,MS_vendormodel_client_init把应用层的UI_phy_model_client_cb注册到vendormodel_client_UI_cb上!
收到信息后,先进入vendormodel_client_cb处理,然后再通过vendormodel_client_UI_cb调用应用层的UI_phy_model_client_cb!

服务端接收处理:

初始化时,MS_vendormodel_server_init把应用层的UI_phy_model_server_cb注册到vendormodel_server_UI_cb上!
收到信息后,先进入vendormodel_server_cb处理,然后再通过vendormodel_server_UI_cb调用应用层的UI_phy_model_server_c

vendor模型应用层处理
客户端:

注册vendor client 模型
/* Register Vendor Defined model server */
retval = UI_register_vendor_defined_model_client(element_handle);

绑定appkey到vendor client模型
UI_config_client_appkey_binding(0,0,UI_appkey);

#ifdef  USE_VENDORMODEL
            retval=MS_access_bind_model_app(UI_vendor_defined_client_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_vendor_defined_client_model_handle,handle);
            #endif

设置目标地址(与publish的区别就是需要在发送时,填写目标地址)

UI_vendor_model_set_raw_addr();//

填充发送数据

#if 1
            UINT8      ttl;
            MS_STATE_VENDOR_EXAMPLE_TEST_STRUCT  param;
            ACCESS_CM_GET_DEFAULT_TTL(ttl);
            param.total_len = test_len+7;
            MS_PACK_LE_2_BYTE_VAL(&buffer[marker], param.total_len);
            marker += 2;
            param.is_to_be_ack = ack_en;
            MS_PACK_LE_1_BYTE_VAL(&buffer[marker], param.is_to_be_ack);
            marker++;
            param.message_index = test_index;
            MS_PACK_LE_2_BYTE_VAL(&buffer[marker], param.message_index);
            marker += 2;
            param.osal_tick = osal_sys_tick&0xffff;
            MS_PACK_LE_2_BYTE_VAL(&buffer[marker], param.osal_tick);
            marker += 2;
            param.ttl = ttl;
            MS_PACK_LE_1_BYTE_VAL(&buffer[marker], param.ttl);
            marker++;
            param.data_len = test_len;
            MS_PACK_LE_1_BYTE_VAL(&buffer[marker], param.data_len);
            marker++;


            if(param.data_len)
            {
                EM_mem_set(&buffer[marker], 0, param.data_len);
                marker += param.data_len;
            }


            set_param.vendormodel_param = &buffer[0];

获取模型appkey

MS_access_get_appkey_handle
    (
        &vendormodel_client_model_handle,
        &key_handle
    );

MS_access_get_appkey_handle

retval = MS_access_raw_data
             (
                 &vendormodel_client_model_handle,
                 req_opcode,
                 dst_addr,
                 key_handle,
                 pdu_ptr,
                 marker,
                 MS_FALSE
             );

服务端:

注册vendor server 模型
/* Register Vendor Defined model server */
        retval = UI_register_vendor_defined_model_server(element_handle);

绑定appkey
vm_subscriptiong_binding_cb();

#ifdef  USE_VENDORMODEL
            retval=MS_access_bind_model_app(UI_vendor_defined_server_model_handle, handle);
            CONSOLE_OUT("BINDING App Key %04x (%04x %04x)\n",retval,UI_vendor_defined_server_model_handle,handle);
            #endif

vendor回调里面的notify数据处理

case MS_ACCESS_VENDORMODEL_NOTIFY_OPCODE:
    {
        uint16      message_index;
        uint32      osal_tick;
        UINT8       ack;
        UINT8       ttl;
        UINT8       len;
        ACCESS_CM_GET_RX_TTL(ttl);
        data_param = state_params->vendormodel_param;
        MS_UNPACK_LE_1_BYTE(&ack, data_param+marker);
        marker += 1;
        MS_UNPACK_LE_2_BYTE(&message_index, data_param+marker);
        marker += 2;
        MS_UNPACK_LE_2_BYTE(&osal_tick, data_param+marker);
        marker += 3;
        MS_UNPACK_LE_1_BYTE(&len, data_param+marker);
        marker += 1;
        marker = 0;


        if(ack)
        {
            MS_PACK_LE_1_BYTE_VAL(data_param+marker,++vendor_tid);
            marker++;
            MS_PACK_LE_1_BYTE_VAL(data_param+marker,0);
            marker++;
            MS_PACK_LE_2_BYTE_VAL(data_param+marker,message_index);
            marker += 2;
            MS_PACK_LE_2_BYTE_VAL(data_param+marker,osal_tick);
            marker += 2;
            MS_PACK_LE_1_BYTE_VAL(data_param+marker,ttl&0xff);
            marker++;
            MS_PACK_LE_1_BYTE_VAL(data_param+marker,len&0xff);
            marker++;


            if(len)
            {
                EM_mem_set(data_param+marker, 0, len);
                marker += len;
            }


            state_params->vendormodel_param = data_param;
            req_type->to_be_acked = 0x01;
        }


//            printf("[PDU_Rx] Pkt.INDEX:0x%04X\n",message_index);
    }

发送回复

/* See if to be acknowledged */
    if (0x01 == req_type->to_be_acked)
    {
//        CONSOLE_OUT(
//            "[VENDOR_EXAMPLE] Sending Response.\n");
        /* Parameters: Request Context, Current State, Target State (NULL: to be ignored), Remaining Time (0: to be ignored), Additional Parameters (NULL: to be ignored) */
        retval = MS_vendormodel_server_state_update(ctx, &current_state_params, NULL, 0, NULL,marker);
    }

客户端:
收到回复,打印信息

case MS_ACCESS_VENDORMODEL_NOTIFY_OPCODE:
    {
        uint16      message_index;
        UINT8       ttl;
        marker = 2;
        MS_UNPACK_LE_2_BYTE(&message_index, msg_raw->data_param+marker);
        marker += 4;
        MS_UNPACK_LE_1_BYTE(&ttl, msg_raw->data_param+marker);
        marker ++;
        CONSOLE_OUT("[PDU_Rx] Pkt.INDEX:0x%04X,SRC:0x%04X,DST:0x%04X,TTL:0x%02X,TICK:0x%08X\r\n",
                    message_index,ctx->daddr,saddr,ttl,osal_sys_tick);
    }
    break;

低功耗、朋友、中继、代理SDK代码参考

MS_DISABLE_RELAY_FEATURE();//关闭中继
MS_ENABLE_PROXY_FEATURE();//使能代理
MS_DISABLE_FRIEND_FEATURE();//关闭好友

感谢名单

以上均为个人收集和整理。对应原作者都提供原文链接。对你们说:感谢在互联网世界分享每一个知识的你!辛苦啦!可以联系我加署名,我懒没加,侵删。

──── 0人觉得很赞 ────

使用道具 举报

大佬厉害
非常详细
厉害厉害~
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 29855 个
  • 话题数: 43658 篇