海思
海思简介
华为芯片相关产品矩阵
- 开发工具
- 仓颉:编程语言
- HiSpark Studio:面向智能设备开发的一站式集成开发环境
- DevEco Studio:开发 HarmonyOS 应用及元服务的集成开发环境
- CodeArts:一站式软件开发平台
- 相关芯片
- 麒麟:手机及手表SoC芯片
- 巴龙:基带芯片
- 昇腾:AI处理器
- 鲲鹏:PC及服务器芯片
- 天罡:通信基站
- 凌霄:路由器芯片
- 鸿鹄:电视芯片
- 系统
- 鸿蒙操作系统
- 欧拉操作系统
- 大模型
- 盘古
- …
海思社区
海思社区
提供丰富的技术资源和开发支持,通过技术论坛可以学习其他开发者的开发经验。
HiSparkStudio
下载与安装
fbb_ws63-master
仓库
- 参考资料: fbb_ws63: fbb_ws63代码仓为支持ws63和ws63e解决方案SDK
- 下载
- 认识
fbb_ws63-master
仓库- 总体认识:
- fbb_ws63代码仓为支持ws63和ws63e解决方案SDK,该SDK包从统一开发平台FBB(Family Big Box,统一开发框架,统一API)构建而来,在该平台上开发的应用很容易被移植到其他星闪解决方案上,有效降低开发者门槛,缩短开发周期,支持开发者快速开发星闪产品。
docs
文件夹- 用户指南手册
board
文件夹:- 主要是各种官方资料,包括资料手册和IO复用关系表
hardware
文件夹:- BearPi-Pico_H3863(本次实验所使用的开发板)、HiHope_NearLink_DK_WS63E_V03两块开发板的硬件原理图
pic
文件夹:- 存放实验说明的截图(可忽略)
src
文件夹- SDK源码目录
- 本处只介绍几个常用的文件/文件夹,详细介绍请参考:“fbb_ws63-master\fbb_ws63-master\docs\board\WS63V100 SDK开发环境搭建 用户指南_03.pdf”
application
:应用层代码,其中包含的demo
是常用的参考文件samples
:存放demobt
:蓝牙、星闪demoperipheral
:常见外设demoradar
:雷达demo(ws63E才有雷达功能)WiFi
:WiFi demo
ws63
:整个代码的主函数入口
include
:API
头文件存放目录build.log
:编译的日志,方便查看报错信息
- SDK源码目录
tools
文件夹- 开发工具及环境搭建指南
vendor
文件夹- 存放各类开发板以及开发者贡献的demo
- 总体认识:
Hello HiSpark!
demo介绍及实验
- 认识
HiSparkStudio
工作环境
- 新建工程
- 新建工程
- 配置工程信息
- 开始编译(编译过程较慢,需耐心等待)
- 编译完成
- 若编译失败,可能是环境配置未完成或
CMakeLists.txt
配置有问题,请重新检查,或参考下列连接
- 新建工程
Hello HiSpark
编程- 新建文件夹
- 在
application/samples
文件夹下新建一个文件夹,命名为demo
(可自行更改命名,但后续的命名需要同步跟进),在demo
文件夹中新建一个文件,命名为Hello_HiSpark
,在Hello_HiSpark
新建两个文件夹,分别命名为inc
、src
,并在src
中新建文件Hello_HiSpark.c
- 在
- 构建代码框架
- 介绍
- 我们编写代码使用的系统是
LiteOS
,其是华为面向物联网(IoT)领域开发的轻量级实时操作系统,在构建代码的时候需要遵循其框架。 - 其代码基本框架是:函数任务 --> 为函数任务创建线程并注册优先级 --> 系统初始化和启动
- 以代码
application\samples\peripheral\blinky\blinky_demo.c
为例进行介绍static int blinky_task(const char *arg)
:函数主任务(可以理解为我们学习C
语言时写的小工程中的main
函数的作用)static void blinky_entry(void)
:在该函数中为上述函数static int blinky_task(const char *arg)
注册线程,并设置其线程优先级app_run
:调用该函数初始化并运行blinky_entry
函数,完成对blinky_task
函数的运行
- 我们编写代码使用的系统是
- 编写代码(以移植代码为方式)
- 复制
blinky_demo.c
到我们新建的文件Hello_HiSpark.c
中 - 修改代码
- 将所有
blinky
相关的代码替换成HiSpark
(红方框中的代码均需要进行修改) - 在
LiteOS
中,变量定义之后没有使用是会报错的,故变量定义之后若没有使用需要使用unused()
对其进行处理
- 将所有
- 最终代码:
#include "pinctrl.h" #include "gpio.h" #include "soc_osal.h" #include "app_init.h" #define HISPARK_TASK_PRIO 24 #define HISPARK_TASK_STACK_SIZE 0x1000 static int HiSpark_task(const char *arg) { unused(arg); while (1) { osal_msleep(500); osal_printk("Hello HiSpark.\r\n"); } return 0; } static void HiSpark_entry(void) { osal_task *task_handle = NULL; osal_kthread_lock(); task_handle = osal_kthread_create((osal_kthread_handler)HiSpark_task, 0, "HiSparkTask", HISPARK_TASK_STACK_SIZE); if (task_handle != NULL) { osal_kthread_set_priority(task_handle, HISPARK_TASK_PRIO); osal_kfree(task_handle); } osal_kthread_unlock(); } /* Run the HiSpark_entry. */ app_run(HiSpark_entry); ```
- 复制
- 配置编译路径
CMake
(简单介绍)- CMake(Cross-Platform Make)是一个跨平台的构建系统生成器,用于管理和自动化软件的构建过程。它通过编写配置文件(通常是
CMakeLists.txt
),生成适合不同编译器和开发环境的构建文件(如 Makefile、Visual Studio 解决方案、Xcode 项目等)。CMake 广泛用于 C、C++、Fortran 等语言的项目,但也可以用于其他语言的构建管理。 CmakeLists.txt
中的变量含义COMPONENT_NAME
:当前组件名称,如“Hello_HiSpark”SOURCES
:当前组件的 C 文件列表,其中CMAKE_CURRENT_SOURCE_DIR
变量标识当前CMakeLists.txt
所在的路径PUBLIC_HEADER
:当前组件需要对外提供的头文件的路径PRIVATE_HEADER
:当前组件内部的头文件搜索路径PRIVATE_DEFINES
:当前组件内部生效的宏定义PUBLIC_DEFINES
:当前组件需要对外提供的宏定义COMPONENT_PUBLIC_CCFLAGS
:当前组件需要对外提供的编译选项COMPONENT_CCFLAGS
:当前组件内部生效的编译选项
CMake
构建的是一个编译链,需要顺着这条链路不断进行设置,对于在HiSparkStudi
中,我们可以通过一下步骤将编译链指向我们想要编译的文件,我们将通过指向blinky_demo.c
这个代码为案例进行分析- 在最外层的
CMakeLists.txt
中,可以找到下列内容add_subdirectory_if_exist(application) add_subdirectory_if_exist(bt) add_subdirectory_if_exist(bootloader) add_subdirectory_if_exist(kernel) add_subdirectory_if_exist(drivers) add_subdirectory_if_exist(middleware) add_subdirectory_if_exist(open_source) add_subdirectory_if_exist(protocol) add_subdirectory_if_exist(test) add_subdirectory_if_exist(include) add_subdirectory_if_exist(vendor)
- 该内容主要是检查每个子目录(括号中的内容)是否存在,存在则将其作为子项包含到当前的构建系统中,其中就包含了
application
文件夹
- 该内容主要是检查每个子目录(括号中的内容)是否存在,存在则将其作为子项包含到当前的构建系统中,其中就包含了
- 进入到
application
文件夹的CMakeLists.txt
中,可以找到add_subdirectory_if_exist(samples)
,此时编译链会指向samples
文件夹 - 进入到
samples
文件夹中的CMakeLists.txt
中,可以找到下列内容if(DEFINED CONFIG_ENABLE_PERIPHERAL_SAMPLE) add_subdirectory_if_exist(peripheral) endif() install_sdk("${CMAKE_CURRENT_SOURCE_DIR}/peripheral" "*")
- 判断语句的作用是如果定义了该配置选项,则调用
add_subdirectory_if_exist(peripheral)
加载其子模块 install_sdk
:是将该目录中的文件安装到SDK中,方便编译时调用
- 判断语句的作用是如果定义了该配置选项,则调用
- 进入到
peripheral
文件夹中,对于其中的CMakeLists.txt
文件分析如下- 代码:
set(SOURCES "${SOURCES}" "${CMAKE_CURRENT_SOURCE_DIR}/blinky_demo.c" PARENT_SCOPE)
"${SOURCES}"
:表示当前SOURCES
变量的值"${CMAKE_CURRENT_SOURCE_DIR}/blinky_demo.c"
:表示将文件路径${CMAKE_CURRENT_SOURCE_DIR}/blinky_demo.c
添加到SOURCES
列表中PARENT_SCOPE
:set
命令的一个选项,表示将变量SOURCES
的值设置到父作用域
- 代码:
- 到此为止,对于CMake中的配置已经完成,但是其中还有一个关键点我们并没有完成配置,上述中提到了在判断语句中只有定义了配置选项才能够加载相对应的模块,如何定义与选择这个配置选项,就需要用到宏编译了
- 在最外层的
- CMake(Cross-Platform Make)是一个跨平台的构建系统生成器,用于管理和自动化软件的构建过程。它通过编写配置文件(通常是
- 宏编译(简单介绍)
- 在打开的文件夹中,除了
CMakeLists.txt
文件,我们还可以发现存在另一个文件Kconfig
,该文件对于主要是辅助CMakeLists.txt
配置编译路径,同时提供交互式的配置界面即系统配置界面,按照配置blinky
为例进行说明 - 下面将按照
CMakeLists.txt
的顺序来认识相应的Kconfig
内容- 在最外层的
Kconfig
中,找到关于application
的内容menu "Application" comment "Config the application." osource "application/Kconfig" endmenu
- 该内容直接定义了
Application
这个选项,并将其指向"application/Kconfig"
中
- 该内容直接定义了
- 按照其指引,打开
application
文件夹,找到Kconfig
文件,找到关于samples
的内容config SAMPLE_ENABLE bool prompt "Enable Sample." default n help This option means support Samples. if SAMPLE_ENABLE osource "application/samples/Kconfig" endif
config SAMPLE_ENABLE
:定义一个名为SAMPLE_ENABLE
的配置选项osource "application/samples/Kconfig"
:如果SAMPLE_ENABLE
被启用,则加载路径application/samples/Kconfig
中的配置选项- 点击
application
- 打开
samples
文件夹,找到Kconfig
文件,找到关于peripheral
的内容config ENABLE_PERIPHERAL_SAMPLE bool prompt "Enable the Sample of peripheral." default n depends on SAMPLE_ENABLE help This option means enable the sample of peripheral. if ENABLE_PERIPHERAL_SAMPLE osource "application/samples/peripheral/Kconfig" endif
- 理解方式同上
depends on SAMPLE_ENABLE
:依赖关系,depends on SAMPLE_ENABLE
被选择才会显示本内容- 点击
samples
- 打开
peripheral
文件夹,打开Kconfig
文件,找到blinky
内容如下:
config SAMPLE_SUPPORT_BLINKY bool prompt "Support BLINKY Sample." default n depends on ENABLE_PERIPHERAL_SAMPLE help This option means support BLINKY Sample. if SAMPLE_SUPPORT_BLINKY menu "Blinky Sample Configuration" osource "application/samples/peripheral/blinky/Kconfig" endmenu endif
- 理解方式同上
- 点击
peripheral
- 在
blinky
文件夹中的Kconfig
内容如下:config BLINKY_PIN int prompt "Choose blinky pin." default 2
- 上述代码的功能主要是提供系统配置中提供
Choose blinky pin
的功能
- 上述代码的功能主要是提供系统配置中提供
- 在最外层的
- 在打开的文件夹中,除了
- 构建
Hello_HiSpark.c
的编译链- 采用一层一层配置编译链的方式,因为我们创建的
demo
文件夹是在samples
文件夹中,所以我们只需要配置samples
–>demo
–>hello_HiSpark
这三层的CMakeLists
与Kconfig
文件即可,但又因我们的工程较小,可以不使用宏编译的方式,故我们只在samples
中配置demo
的宏编译,此后不配置宏编译,直接去掉判断语句,利用add_subdirectory_if_exist()
将对应的文件添加到编译路径中即可 - 配置
samples
文件夹中的CMakeLists.txt
和Kconfig
- 在
CMakeLists.txt
中添加下列内容if(DEFINED CONFIG_ENABLE_DEMO_SAMPLE) add_subdirectory_if_exist(demo) endif() install_sdk("${CMAKE_CURRENT_SOURCE_DIR}/demo" "*")
- 位置如图
- 位置如图
- 在
Kconfig
中添加下列内容config ENABLE_DEMO_SAMPLE bool prompt "Enable the Sample of DEMO." default n depends on SAMPLE_ENABLE help This option means enable the sample of DEMO.
- 位置如图所示
- 因为
demo
中不需要通过Kconfig
进行配置,故在此不需要通过osource
加载demo
中的内容 - 打开系统配置,可以看到已经有了
demo
的选项,但是无法继续展开,点击左上角save
保存,这样编译路径已经指向demo
文件夹来了
- 位置如图所示
- 在
- 配置
demo
中的CMakeLists.txt
- 在
demo
文件夹中新建一个文件,将其命名为CMakeLists.txt
,在其中编写下列内容add_subdirectory_if_exist(hello_HiSpark) set(SOURCES "${SOURCES}" PARENT_SCOPE) # C 文件链接 set(PUBLIC_HEADER "${PUBLIC_HEADER}" PARENT_SCOPE) # 头文件链接
- 如需更改其他文件夹,只需要修改
add_subdirectory_if_exist()
括号中的文件夹名称即可
- 如需更改其他文件夹,只需要修改
- 在
- 配置
Hello_HiSpark
中的CMakeLists.txt
- 在
Hello_HiSpark
文件夹中新建一个文件,将其命名为CMakeLists.txt
,在其中编写下列内容add_subdirectory_if_exist(src) set(SOURCES "${SOURCES}" PARENT_SCOPE) set(PUBLIC_HEADER "${PUBLIC_HEADER}" "${CMAKE_CURRENT_SOURCE_DIR}/inc" PARENT_SCOPE) #将整个inc包含进编译路径中
- 在
- 配置
src
中的CMakeLists.txt
- 在
src
文件夹中新建一个文件,将其命名为CMakeLists.txt
,在其中编写下列内容set(SOURCES "${SOURCES}" "${CMAKE_CURRENT_SOURCE_DIR}/Hello_HiSpark.c" #指向对应的 .c 文件 PARENT_SCOPE)
- 如需添加其他
.c
文件,只需要复制第二行的内容到第三行,修改文件名即可
- 在
- 采用一层一层配置编译链的方式,因为我们创建的
- 介绍
- 编译代码
- 点击编译,出现如图所示信息则表明编译成功
- 点击编译,出现如图所示信息则表明编译成功
- 检查
CH340
驱动- 用Type-C线连接电脑与开发板,开发板上红灯亮起则表明已连接成功
- 打开电脑设备管理器
- 打开端口,查看是否识别出开发板
- 安装CH340驱动
- 下载CH340驱动
- 下载链接:CH340驱动
- 下载驱动
- 点击安装
- 安装成功
- 重新回到设备管理器中查看是否能够显示出带有CH340的设备
- 下载CH340驱动
- 用Type-C线连接电脑与开发板,开发板上红灯亮起则表明已连接成功
- 烧录
- 点击工程配置,找到程序加载板块,修改端口为在设备管理器中看的带有
CH340
字样的端口 - 点击程序加载,根据指示按下开发板上的
RST
按键,烧录完成出现下列信息
- 点击工程配置,找到程序加载板块,修改端口为在设备管理器中看的带有
- 实验现象
- 打开监视器,修改端口与波特率,开始监视,可以看到每隔500ms监视器就会打印一个
Hello HiSpark
,实验现象符合代码逻辑
- 打开监视器,修改端口与波特率,开始监视,可以看到每隔500ms监视器就会打印一个
- 新建文件夹
- 练习
- 点灯大师:参考下列函数接口,复制
Hello_HiSpark
文件夹,在Hello_HiSpark
程序基础上进行修改,配置好相关参数,点亮一个LED
灯(杜邦线连接LED灯与开发板引脚) - 参考头文件:
"pinctrl.h"
、"gpio.h"
- 参考接口:
/* * @brief 设置引脚复用模式。 * @param [in] pin io,参考 @ref pin_t 。 * @param [in] mode 复用模式,参考 @ref pin_mode_t 。 * @retval ERRCODE_SUCC 成功。 * @retval Other 失败,参考 @ref errcode_t 。 * @endif */ errcode_t uapi_pin_set_mode(pin_t pin, pin_mode_t mode); /* * @brief 设置GPIO的输入输出方向函数。 * @param [in] pin IO, 参考 @ref pin_t 。 * @param [in] dir 输入输出方向, 参考 @ref gpio_direction_t 。 * @retval ERRCODE_SUCC 成功。 * @retval Other 失败,参考 @ref errcode_t 。 * @endif */ errcode_t uapi_gpio_set_dir(pin_t pin, gpio_direction_t dir); /* * @brief 设置GPIO的输出状态。 * @param [in] pin IO, 参考 @ref pin_t 。 * @param [in] level GPIO 输出设置为高或低, 参考 @ref gpio_level_t 。 * @retval ERRCODE_SUCC 成功。 * @retval Other 失败,参考 @ref errcode_t 。 * @endif */ errcode_t uapi_gpio_set_val(pin_t pin, gpio_level_t level);
- 点灯大师:参考下列函数接口,复制
星闪
星闪技术介绍
- 参考资料:星闪技术 | 海思官网
- 什么是星闪?
- 星闪(NearLink),中国原生的新一代无线短距通信技术。面向万物互联时代,星闪引入关键技术和创新理念,赋予智能终端新的连接方式。与传统短距传输技术方案相比,星闪在功耗、速度、覆盖范围和连接性能全面领先,可以在智能终端、智能家居、智能汽车、智能制造等各类细分场景下实现更极致的用户体验。2023年,星闪商用元年全面开启。
- 星闪的优势
- 低时延:星闪传输时延是传统无线技术的1/30,同等时间提供30倍的交互信息,由毫秒级迈进微秒级。星闪技术加持下,无线鼠标刷新率由传统的125-1000Hz提升至4000Hz、手写笔刷新率由传统的120Hz提升至360Hz,丝般流畅,如影随形,极大提高使用体验。
- 高吞吐:星闪在设备间信息传输最高速率是传统无线技术(2Mbps)的6倍(12Mbps),能耗只有之前的60%,不仅工作效率提高,质量还大幅提升。星闪高速率完美支持在无线耳机上的无损音频传输,让录音室级品质音频体验成为现实。
- 高并发:与传统技术直连设备只可连接数个相比,星闪最大可达到百量级,避免了传统技术组网能力弱、同步实现难度高导致的跑马灯现象,深度赋能智能家居IOT等领域。
- 高可靠:首次将“Polar码”等前沿技术应用于短距无线通信,信息传输可靠性由传统技术的90%提升至99.99%。
- 抗干扰:星闪首次将5G Polar码技术用于短距通信,配合干扰避让机制,抗干扰能力比传统无线技术提升7dB。
- 精定位:星闪将定位精度由传统无线技术的米级提升到分米级,依托领先的测距算法,有效克服人体遮挡、环境吸收和反射等因素叠加,解决测距结果不稳定、反复解闭锁的痛点。
- 原理分析
ws63
星闪测距
通信基本知识
- 参考资料:
- https://kdkce.edu.in/writereaddata/fckimagefile/ADBMS%20Unit%202%20(1).pdf
- 计算机网络之应用层(客户/服务器(C/S)模型)_系统内的服务端和客户端的层级结构模态化配置-CSDN博客
C/S
模型- 客户端/服务端模型,是一种集中式的网络架构,客户端(Client)通常是终端设备(如个人计算机、智能手机等),它通过网络协议向服务端发起请求来获取资源或服务;服务端(Server)则提供相应的服务,通常具有较强的计算能力和存储能力,根据客户端的请求提供所需的服务或资源(如文件、数据、计算能力等)
client
:发起请求、提供用户界面、本地数据处理及结果展示server
:监听请求、处理逻辑、管理资源(如数据库、文件)及并发响应多客户端
星闪连接编程
API
- 参考资料:
- “\fbb_ws63-master\fbb_ws63-master\docs\board\WS63V100 软件开发指南_03.pdf”
- “fbb_ws63-master\src\application\samples\bt\sle”
- 接口介绍
Device Discovery
:星闪设备发现协议,包括设备管理、设备公开和设备发现接口。Connection Manager
:星闪连接管理协议,包括设备连接、配对相关接口。SSAP
:星闪服务交互协议(SparkLink Service Access Protocol),包含服务注册、服务发现、属性数据读写等功能相关接口。Low Latency
:低时延初始化和低时延数据收发接口(暂不使用)
- 代码实现(简易版):
-
回调函数:在某个事件发生或条件满足时被调用,通常作为参数传递给另一个函数,以便在合适的时机执行特定的操作(类比于中断函数)
-
如果编译完
server
、client
之中的某一个之后要编译另一个,需要清楚编译信息在重编译,不清除容易报错
-
server
端- 逻辑链
- 初始化全局变量
data
- 注册各类回调函数
- 使能
sle
- 检查
NV
参数 - 本地设置
- 本端
MAC
地址 - 本端名称
- 本端
- 设备公开
- 设备公开参数
- 设备公开回调函数
- 设备广播参数
- 设备公开
- 设置服务端info(信息交换结构体)
- 设备连接
- 设备连接参数
- 设备连接管理回调函数
SSAP server
- ssap server 回调函数
- 数据交互
- 实时响应
- 根据
client
的请求做出相应的响应
- 根据
- 初始化全局变量
- 代码
sle_server.h
- 本头文件参考
application\samples\bt\sle\sle_speed_server\inc\sle_speed_server_adv.h
/** * @defgroup bluetooth_sle_adv API * @ingroup * @{ */ #ifndef SLE_SERVER_ADV_H #define SLE_SERVER_ADV_H #include "sle_ssap_server.h" /* 广播ID */ #define SLE_ADV_HANDLE_DEFAULT 1 /** * @if Eng * @brief Definitaion of BLE ADV 通用广播结构. * @else * @brief SLE 广播普通数据结构。 * @endif */ struct sle_adv_common_value { uint8_t length; uint8_t type; uint8_t value; }; /** * @if Eng * @brief Definitaion of BLE ADV Channel mapping. * @else * @brief SLE 广播信道映射。 * @endif */ typedef enum { SLE_ADV_CHANNEL_MAP_77 = 0x01, SLE_ADV_CHANNEL_MAP_78 = 0x02, SLE_ADV_CHANNEL_MAP_79 = 0x04, SLE_ADV_CHANNEL_MAP_DEFAULT = 0x07 } sle_adv_channel_map; /** * @if Eng * @brief Definitaion of SLE ADV Data Type. * @else * @brief SLE 广播数据类型 * @endif */ typedef enum { SLE_ADV_DATA_TYPE_DISCOVERY_LEVEL = 0x01, /*!< 发现等级 */ SLE_ADV_DATA_TYPE_ACCESS_MODE = 0x02, /*!< 接入层能力 */ SLE_ADV_DATA_TYPE_SERVICE_DATA_16BIT_UUID = 0x03, /*!< 标准服务数据信息 */ SLE_ADV_DATA_TYPE_SERVICE_DATA_128BIT_UUID = 0x04, /*!< 自定义服务数据信息 */ SLE_ADV_DATA_TYPE_COMPLETE_LIST_OF_16BIT_SERVICE_UUIDS = 0x05, /*!< 完整标准服务标识列表 */ SLE_ADV_DATA_TYPE_COMPLETE_LIST_OF_128BIT_SERVICE_UUIDS = 0x06, /*!< 完整自定义服务标识列表 */ SLE_ADV_DATA_TYPE_INCOMPLETE_LIST_OF_16BIT_SERVICE_UUIDS = 0x07, /*!< 部分标准服务标识列表 */ SLE_ADV_DATA_TYPE_INCOMPLETE_LIST_OF_128BIT_SERVICE_UUIDS = 0x08, /*!< 部分自定义服务标识列表 */ SLE_ADV_DATA_TYPE_SERVICE_STRUCTURE_HASH_VALUE = 0x09, /*!< 服务结构散列值 */ SLE_ADV_DATA_TYPE_SHORTENED_LOCAL_NAME = 0x0A, /*!< 设备缩写本地名称 */ SLE_ADV_DATA_TYPE_COMPLETE_LOCAL_NAME = 0x0B, /*!< 设备完整本地名称 */ SLE_ADV_DATA_TYPE_TX_POWER_LEVEL = 0x0C, /*!< 广播发送功率 */ SLE_ADV_DATA_TYPE_SLB_COMMUNICATION_DOMAIN = 0x0D, /*!< SLB通信域域名 */ SLE_ADV_DATA_TYPE_SLB_MEDIA_ACCESS_LAYER_ID = 0x0E, /*!< SLB媒体接入层标识 */ SLE_ADV_DATA_TYPE_EXTENDED = 0xFE, /*!< 数据类型扩展 */ SLE_ADV_DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF /*!< 厂商自定义信息 */ } sle_adv_data_type; /** * @if Eng * @brief sle adv data config. * @attention NULL * @retval ERRCODE_SLE_SUCCESS Excute successfully * @retval ERRCODE_SLE_FAIL Execute fail * @par Dependency: * @li NULL * @else * @brief sle广播数据配置。 * @attention NULL * @retval ERRCODE_SLE_SUCCESS 执行成功 * @retval ERRCODE_SLE_FAIL 执行失败 * @par 依赖: * @li NULL * @endif */ static errcode_t sle_uuid_server_adv_init(void); /* Service UUID */ #define SLE_UUID_SERVER_SERVICE 0xABCD /* Property UUID */ #define SLE_UUID_SERVER_NTF_REPORT 0x1122 /* Property Property */ #define SLE_UUID_TEST_PROPERTIES (SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE) /* Descriptor Property */ #define SLE_UUID_TEST_DESCRIPTOR (SSAP_PERMISSION_READ | SSAP_PERMISSION_WRITE) /** * @if Eng * @brief SLE uuid server inir. * @attention NULL * @retval ERRCODE_SLE_SUCCESS Excute successfully * @retval ERRCODE_SLE_FAIL Execute fail * @par Dependency: * @li sle_ssap_server.h * @else * @brief SLE UUID服务器初始化。 * @attention NULL * @retval ERRCODE_SLE_SUCCESS 执行成功 * @retval ERRCODE_SLE_FAIL 执行失败 * @par 依赖: * @li sle_ssap_server.h * @endif */ errcode_t sle_uuid_server_init(void); /** * @if Eng * @brief send data to peer device by uuid on uuid server. * @attention NULL * @param [in] value send value. * @param [in] len Length of send value。 * @retval ERRCODE_SLE_SUCCESS Excute successfully * @retval ERRCODE_SLE_FAIL Execute fail * @par Dependency: * @li sle_ssap_server.h * @else * @brief 通过uuid server 发送数据给对端。 * @attention NULL * @retval ERRCODE_SLE_SUCCESS 执行成功 * @retval ERRCODE_SLE_FAIL 执行失败 * @par 依赖: * @li sle_ssap_server.h * @endif */ errcode_t sle_uuid_server_send_report_by_uuid(uint8_t *data, uint16_t len); /** * @if Eng * @brief send data to peer device by handle on uuid server. * @attention NULL * @param [in] value send value. * @param [in] len Length of send value。 * @retval ERRCODE_SLE_SUCCESS Excute successfully * @retval ERRCODE_SLE_FAIL Execute fail * @par Dependency: * @li sle_ssap_server.h * @else * @brief 通过uuid server 发送数据给对端。 * @attention NULL * @retval ERRCODE_SLE_SUCCESS 执行成功 * @retval ERRCODE_SLE_FAIL 执行失败 * @par 依赖: * @li sle_ssap_server.h * @endif */ errcode_t sle_uuid_server_send_report_by_handle(const uint8_t *data, uint8_t len); #endif
- 本头文件参考
server.c
#include "app_init.h" #include "watchdog.h" #include "tcxo.h" #include "systick.h" #include "los_memory.h" #include "securec.h" #include "errcode.h" #include "osal_addr.h" #include "soc_osal.h" #include "common_def.h" #include "sle_common.h" #include "sle_errcode.h" #include "sle_ssap_server.h" #include "sle_connection_manager.h" #include "sle_device_discovery.h" #include "sle_transmition_manager.h" #include "nv.h" #include "sle_server.h" //线程参数 #define DISTANCE_DEFAULT_KTHREAD_SIZE 0x2000 #define DISTANCE_DEFAULT_KTHREAD_PROI 26 //本地设备参数 #define NAME_MAX_LENGTH 30 static uint8_t mac[SLE_ADDR_LEN] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; static uint8_t sle_local_name[NAME_MAX_LENGTH] = "sle_connect_server"; //数据包长度 #define PKT_DATA_LEN 600 static unsigned char data[PKT_DATA_LEN]; //确保为小端格式存储 #define encode2byte_little(_ptr, data) \ do { \ *(uint8_t *)((_ptr) + 1) = (uint8_t)((data) >> 8); \ *(uint8_t *)(_ptr) = (uint8_t)(data); \ } while (0) //设备公开 #define SLE_CONN_INTV_MIN_DEFAULT 0xA //连接最小调度间隔12.5ms,单位125us #define SLE_CONN_INTV_MAX_DEFAULT 0xA //连接最大调度间隔12.5ms,单位125us #define SLE_ADV_INTERVAL_MIN_DEFAULT 0xC8 //连接调度间隔25ms,单位125us #define SLE_ADV_INTERVAL_MAX_DEFAULT 0xC8 //超时时间5000ms,单位10ms #define SLE_CONN_SUPERVISION_TIMEOUT_DEFAULT 0x1F4 //超时时间4990ms,单位10ms #define SLE_CONN_MAX_LATENCY 0x1F3 //广播发送功率 #define SLE_ADV_TX_POWER 20 //最大广播数据长度 #define SLE_ADV_DATA_LEN_MAX 251 //设备连接 #define DISTANCE_DEFAULT_CONN_INTERVAL 0xA0 #define DISTANCE_DEFAULT_TIMEOUT_MULTIPLIER 0x1f4 #define DISTANCE_DEFAULT_SCAN_INTERVAL 400 #define DISTANCE_DEFAULT_SCAN_WINDOW 20 //蓝牙地址索引 #define BT_INDEX_4 4 #define BT_INDEX_5 5 #define BT_INDEX_0 0 static uint16_t g_conn_id = 0; // SSAP 参数 #define OCTET_BIT_LEN 8 //8比特长度 #define UUID_LEN_2 2 //两个字节 static char g_sle_uuid_app_uuid[UUID_LEN_2] = {0x0, 0x0}; static char g_sle_property_value[OCTET_BIT_LEN] = {0x0, 0x0, 0x0, 0x0, 0x0, 0x0}; //传输参数 #define DEFAULT_SLE_DISTANCE_DATA_LEN 1500 #define DEFAULT_SLE_DISTANCE_MTU_SIZE 1500 static uint8_t g_server_id = 0; static uint16_t g_service_handle = 0; static uint16_t g_property_handle = 0; static uint8_t sle_uuid_base[] = { 0x37, 0xBE, 0xA8, 0x80, 0xFC, 0x70, 0x11, 0xEA, 0xB7, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; extern uint8_t gle_tx_acb_data_num_get(void); /* 检查 BTC 功率档位 */ void sle_distance_server_set_nv(void) { uint16_t nv_value_len = 0; uint8_t nv_value = 0; //读取指定NV数据项的值 uapi_nv_read(0x20A0, sizeof(uint16_t), &nv_value_len, &nv_value); if (nv_value != 7) { // 7:btc功率档位 nv_value = 7; // 7:btc功率档位 //写入NV数据项 uapi_nv_write(0x20A0, (uint8_t *)&(nv_value), sizeof(nv_value)); } osal_printk("[distance server] The value of nv is set to %d.\r\n", nv_value); } /* 本地设备参数 */ //设置本端 MAC 地址 void sle_set_local_addr_init(void) { sle_addr_t addr = {0}; addr.type = 0; memcpy_s(addr.addr, SLE_ADDR_LEN, mac, SLE_ADDR_LEN); sle_set_local_addr(&addr); } //设置本端名称 static uint16_t sle_set_adv_local_name(uint8_t *adv_data, uint16_t max_len) { errno_t ret; uint8_t index = 0; uint8_t *local_name = sle_local_name; uint8_t local_name_len = (uint8_t)strlen((char *)local_name); for (uint8_t i = 0; i < local_name_len; i++) { osal_printk("local_name[%d] = 0x%02x\r\n", i, local_name[i]); } adv_data[index++] = local_name_len + 1; adv_data[index++] = SLE_ADV_DATA_TYPE_COMPLETE_LOCAL_NAME; ret = memcpy_s(&adv_data[index], max_len - index, local_name, local_name_len); if (ret != EOK) { osal_printk("memcpy fail\r\n"); return 0; } return (uint16_t)index + local_name_len; } /* 设备公开 */ //设备公开参数 static int sle_set_default_announce_param(void) { //初始化 sle_announce_param_t param = {0}; param.announce_mode = SLE_ANNOUNCE_MODE_CONNECTABLE_SCANABLE; //公开类型 param.announce_handle = SLE_ADV_HANDLE_DEFAULT; //公开句柄 param.announce_gt_role = SLE_ANNOUNCE_ROLE_T_CAN_NEGO; //gt角色协商指示 param.announce_level = SLE_ANNOUNCE_LEVEL_NORMAL; //发现等级 param.announce_channel_map = SLE_ADV_CHANNEL_MAP_DEFAULT; //设备公开信道 param.announce_interval_min = SLE_ADV_INTERVAL_MIN_DEFAULT; //最小设备公开周期 param.announce_interval_max = SLE_ADV_INTERVAL_MAX_DEFAULT; //最大设备公开周期 param.conn_interval_min = SLE_CONN_INTV_MIN_DEFAULT; //连接间隔最小取值 param.conn_interval_max = SLE_CONN_INTV_MAX_DEFAULT; //连接间隔最大取值 param.conn_max_latency = SLE_CONN_MAX_LATENCY; //最大休眠连接间隔 param.conn_supervision_timeout = SLE_CONN_SUPERVISION_TIMEOUT_DEFAULT; //最大超时时间 param.announce_tx_power = SLE_ADV_TX_POWER; //广播发射攻略 param.own_addr.type = 0; //本端地址 memcpy_s(param.own_addr.addr, SLE_ADDR_LEN, mac, SLE_ADDR_LEN); //设置参数 return sle_set_announce_param(param.announce_handle, ¶m); } void sle_enable_cbk(errcode_t status) { osal_printk("sle enable status:%02x\r\n", status); } void sle_announce_enable_cbk(uint32_t announce_id, errcode_t status) { osal_printk("sle announce enable id:%02x, state:%02x\r\n", announce_id, status); } void sle_announce_terminal_cbk(uint32_t announce_id) { osal_printk("sle announce terminal id:%02x\r\n", announce_id); } void sle_announce_disable_cbk(uint32_t announce_id, errcode_t status) { osal_printk("sle announce disable id:%02x, state:%02x\r\n", announce_id, status); } //设备公开回调函数 void sle_announce_register_cbks(void) { sle_announce_seek_callbacks_t seek_cbks = {0}; seek_cbks.sle_enable_cb = sle_enable_cbk; //SLE协议栈使能回调函数 seek_cbks.announce_enable_cb = sle_announce_enable_cbk; //设备公开使能回调函数 seek_cbks.announce_terminal_cb = sle_announce_terminal_cbk; //设备公开停止回调函数 seek_cbks.announce_disable_cb = sle_announce_disable_cbk; //设备公开关闭回调函数 sle_announce_seek_register_callbacks(&seek_cbks); //将回调函数注册到协议层 } //设置广播数据 static uint16_t sle_set_adv_data(uint8_t *adv_data) { size_t len = 0; uint16_t idx = 0; errno_t ret = 0; //填充设备发现级别 len = sizeof(struct sle_adv_common_value); struct sle_adv_common_value adv_disc_level = { .length = len - 1, .type = SLE_ADV_DATA_TYPE_DISCOVERY_LEVEL, .value = SLE_ANNOUNCE_LEVEL_NORMAL, }; ret = memcpy_s(&adv_data[idx], SLE_ADV_DATA_LEN_MAX - idx, &adv_disc_level, len); if (ret != EOK) { osal_printk("adv_disc_level memcpy fail\r\n"); return 0; } idx += len; //填充访问模式 len = sizeof(struct sle_adv_common_value); struct sle_adv_common_value adv_access_mode = { .length = len - 1, .type = SLE_ADV_DATA_TYPE_ACCESS_MODE, .value = 0, }; ret = memcpy_s(&adv_data[idx], SLE_ADV_DATA_LEN_MAX - idx, &adv_access_mode, len); if (ret != EOK) { osal_printk("memcpy fail\r\n"); return 0; } idx += len; return idx; } static uint16_t sle_set_scan_response_data(uint8_t *scan_rsp_data) { uint16_t idx = 0; errno_t ret; size_t scan_rsp_data_len = sizeof(struct sle_adv_common_value); struct sle_adv_common_value tx_power_level = { .length = scan_rsp_data_len - 1, .type = SLE_ADV_DATA_TYPE_TX_POWER_LEVEL, .value = SLE_ADV_TX_POWER, }; ret = memcpy_s(scan_rsp_data, SLE_ADV_DATA_LEN_MAX, &tx_power_level, scan_rsp_data_len); if (ret != EOK) { osal_printk("sle scan response data memcpy fail\r\n"); return 0; } idx += scan_rsp_data_len; /* set local name */ idx += sle_set_adv_local_name(&scan_rsp_data[idx], SLE_ADV_DATA_LEN_MAX - idx); return idx; } static int sle_set_default_announce_data(void) { errcode_t ret; uint8_t announce_data_len = 0; uint8_t seek_data_len = 0; sle_announce_data_t data = {0}; uint8_t adv_handle = SLE_ADV_HANDLE_DEFAULT; uint8_t announce_data[SLE_ADV_DATA_LEN_MAX] = {0}; uint8_t seek_rsp_data[SLE_ADV_DATA_LEN_MAX] = {0}; osal_printk("set adv data default\r\n"); //设置广播数据 announce_data_len = sle_set_adv_data(announce_data); data.announce_data = announce_data; data.announce_data_len = announce_data_len; //设置扫描相应数据 seek_data_len = sle_set_scan_response_data(seek_rsp_data); data.seek_rsp_data = seek_rsp_data; data.seek_rsp_data_len = seek_data_len; //设置参数 ret = sle_set_announce_data(adv_handle, &data); if (ret == ERRCODE_SLE_SUCCESS) { osal_printk("[SLE DD SDK] set announce data success."); } else { osal_printk("[SLE DD SDK] set adv param fail."); } return ERRCODE_SLE_SUCCESS; } static errcode_t sle_uuid_server_adv_init(void) { osal_printk("sle_uuid_server_adv_init in\r\n"); sle_announce_register_cbks(); sle_set_default_announce_param(); sle_set_default_announce_data(); sle_start_announce(SLE_ADV_HANDLE_DEFAULT); //开始设备公开 osal_printk("sle_uuid_server_adv_init out\r\n"); return ERRCODE_SLE_SUCCESS; } // ssap 信息交换设置 void sle_ssaps_set_info(void) { ssap_exchange_info_t info = {0}; info.mtu_size = DEFAULT_SLE_DISTANCE_MTU_SIZE; info.version = 1; ssaps_set_info(g_server_id, &info); } /* 设备连接 */ //连接参数 void sle_distance_connect_param_init(void) { sle_default_connect_param_t param = {0}; param.enable_filter_policy = 0; //过滤功能 param.gt_negotiate = 0; param.initiate_phys = 1; param.max_interval = DISTANCE_DEFAULT_CONN_INTERVAL; param.min_interval = DISTANCE_DEFAULT_CONN_INTERVAL; param.scan_interval = DISTANCE_DEFAULT_SCAN_INTERVAL; param.scan_window = DISTANCE_DEFAULT_SCAN_WINDOW; param.timeout = DISTANCE_DEFAULT_TIMEOUT_MULTIPLIER; sle_default_connection_param_set(¶m); } //连接状态改变回调函数 static void sle_connect_state_changed_cbk(uint16_t conn_id, const sle_addr_t *addr, sle_acb_state_t conn_state, sle_pair_state_t pair_state, sle_disc_reason_t disc_reason) { osal_printk("[distance server] connect state changed conn_id:0x%02x, conn_state:0x%x, pair_state:0x%x, \ disc_reason:0x%x\r\n", conn_id, conn_state, pair_state, disc_reason); osal_printk("[distance server] connect state changed addr:%02x:**:**:**:%02x:%02x\r\n", addr->addr[BT_INDEX_0], addr->addr[BT_INDEX_4], addr->addr[BT_INDEX_5]); g_conn_id = conn_id; sle_connection_param_update_t parame = {0}; parame.conn_id = conn_id; parame.interval_min = DISTANCE_DEFAULT_CONN_INTERVAL; parame.interval_max = DISTANCE_DEFAULT_CONN_INTERVAL; parame.max_latency = 0; parame.supervision_timeout = DISTANCE_DEFAULT_TIMEOUT_MULTIPLIER; if (conn_state == SLE_ACB_STATE_CONNECTED) { sle_update_connect_param(¶me); } else if (conn_state == SLE_ACB_STATE_DISCONNECTED) { sle_start_announce(SLE_ADV_HANDLE_DEFAULT); } } //连接参数请求更新回调函数 void sle_sample_update_req_cbk(uint16_t conn_id, errcode_t status, const sle_connection_param_update_req_t *param) { unused(conn_id); unused(status); osal_printk("[ssap server] sle_sample_update_req_cbk interval_min:%02x, interval_max:%02x\n", param->interval_min, param->interval_max); } //连接参数更新完成回调函数 void sle_sample_update_cbk(uint16_t conn_id, errcode_t status, const sle_connection_param_update_evt_t *param) { unused(status); osal_printk("[ssap server] updat state changed conn_id:%d, interval = %02x\n", conn_id, param->interval); } //配对完成回调函数 static void sle_pair_complete_cbk(uint16_t conn_id, const sle_addr_t *addr, errcode_t status) { osal_printk("[distance server] pair complete conn_id:%02x, status:%x\r\n", conn_id, status); osal_printk("[distance server] pair complete addr:%02x:**:**:**:%02x:%02x\r\n", addr->addr[BT_INDEX_0], addr->addr[BT_INDEX_4], addr->addr[BT_INDEX_5]); } static void sle_conn_register_cbks(void) { sle_connection_callbacks_t conn_cbks = {0}; conn_cbks.connect_state_changed_cb = sle_connect_state_changed_cbk; //连接状态改变回调函数 conn_cbks.connect_param_update_req_cb = sle_sample_update_req_cbk; //连接参数请求更新回调函数 conn_cbks.connect_param_update_cb = sle_sample_update_cbk; //连接参数更新完成回调函数 conn_cbks.pair_complete_cb = sle_pair_complete_cbk; //配对完成回调函数 sle_connection_register_callbacks(&conn_cbks); //将回调函数结构体注册到协议层 } /* SSAP server */ //启动服务回调函数 static void ssaps_start_service_cbk(uint8_t server_id, uint16_t handle, errcode_t status) { osal_printk("[distance server] start service cbk server_id:%d, handle:%d, status:%d\r\n", server_id, handle, status); } //mtu 大小更新回调函数 static void ssaps_mtu_changed_cbk(uint8_t server_id, uint16_t conn_id, ssap_exchange_info_t *mtu_size, errcode_t status) { osal_printk("[distance server] ssaps write request cbk server_id:%d, conn_id:%d, mtu_size:%d, status:%d\r\n", server_id, conn_id, mtu_size->mtu_size, status); } //发送信息 static errcode_t sle_uuid_server_send_report_by_handle_id(uint8_t *data, uint16_t len, uint16_t connect_id) { ssaps_ntf_ind_t param = {0}; param.handle = g_property_handle; param.type = SSAP_PROPERTY_TYPE_VALUE; param.value = data; param.value_len = len; //向对端发送通知 ssaps_notify_indicate(g_server_id, connect_id, ¶m); return ERRCODE_SLE_SUCCESS; } static uint8_t sle_flow_ctrl_flag(void) { return gle_tx_acb_data_num_get(); } static void send_data_thread_function(void) { //设置最大传输长度 sle_set_data_len(g_conn_id, DEFAULT_SLE_DISTANCE_DATA_LEN); osal_printk("code: GFSK, PHY 1MHZ, power: 20dbm \r\n"); int i = 0; while (1) { if (sle_flow_ctrl_flag() > 0) { i++; //填充数据 data[0] = (i >> 8) & 0xFF; data[1] = i & 0xFF; //发送数据包 sle_uuid_server_send_report_by_handle_id(data, PKT_DATA_LEN, g_conn_id); } } } //收到远端读请求回调函数 static void ssaps_read_request_cbk(uint8_t server_id, uint16_t conn_id, ssaps_req_read_cb_t *read_cb_para, errcode_t status) { osal_printk("[distance server] ssaps read request cbk server_id:%x, conn_id:%x, handle:%x, status:%x\r\n",server_id, conn_id, read_cb_para->handle, status); //创建新线程完成发送任务 osal_task *task_handle = NULL; osal_kthread_lock(); task_handle = osal_kthread_create((osal_kthread_handler)send_data_thread_function,0, "RadarTask", DISTANCE_DEFAULT_KTHREAD_SIZE); osal_kthread_set_priority(task_handle, DISTANCE_DEFAULT_KTHREAD_PROI + 1); if (task_handle != NULL) { osal_kfree(task_handle); } osal_kthread_unlock(); printf("kthread success\r\n"); } //收到远端写请求回调函数 static void ssaps_write_request_cbk(uint8_t server_id, uint16_t conn_id, ssaps_req_write_cb_t *write_cb_para, errcode_t status) { osal_printk("[distance server] ssaps write request cbk server_id:%d, conn_id:%d, handle:%d, status:%d\r\n", server_id, conn_id, write_cb_para->handle, status); } //注册 ssap server 回调 static void sle_ssaps_register_cbks(void) { ssaps_callbacks_t ssaps_cbk = {0}; ssaps_cbk.start_service_cb = ssaps_start_service_cbk; //启动服务回调函数 ssaps_cbk.mtu_changed_cb = ssaps_mtu_changed_cbk; //mtu 大小更新回调函数 ssaps_cbk.read_request_cb = ssaps_read_request_cbk; //收到远端读请求回调函数 ssaps_cbk.write_request_cb = ssaps_write_request_cbk; //收到远端写请求回调函数 ssaps_register_callbacks(&ssaps_cbk); } //复制128-bit UUID 到结构体 static void sle_uuid_set_base(sle_uuid_t *out) { (void)memcpy_s(out->uuid, SLE_UUID_LEN, sle_uuid_base, SLE_UUID_LEN); out->len = UUID_LEN_2; } //将 uuid 转换为小端格式 static void sle_uuid_setu2(uint16_t u2, sle_uuid_t *out) { sle_uuid_set_base(out); out->len = UUID_LEN_2; encode2byte_little(&out->uuid[14], u2); } static errcode_t sle_uuid_server_service_add(void) { errcode_t ret; sle_uuid_t service_uuid = {0}; sle_uuid_setu2(SLE_UUID_SERVER_SERVICE, &service_uuid); //添加一个ssap服务 ret = ssaps_add_service_sync(g_server_id, &service_uuid, 1, &g_service_handle); if (ret != ERRCODE_SLE_SUCCESS) { osal_printk("[distance server] sle uuid add service fail, ret:%x\r\n", ret); return ERRCODE_SLE_FAIL; } return ERRCODE_SLE_SUCCESS; } static errcode_t sle_uuid_server_property_add(void) { errcode_t ret; ssaps_property_info_t property = {0}; ssaps_desc_info_t descriptor = {0}; uint8_t ntf_value[] = {0x01, 0x0}; //添加特征信息 property.permissions = SLE_UUID_TEST_PROPERTIES; //特征权限 sle_uuid_setu2(SLE_UUID_SERVER_NTF_REPORT, &property.uuid); //SSAP 特征 UUID property.value = osal_vmalloc(sizeof(g_sle_property_value)); //响应的数据 property.operate_indication = SSAP_OPERATE_INDICATION_BIT_READ | SSAP_OPERATE_INDICATION_BIT_NOTIFY; //操作指示 if (property.value == NULL) { osal_printk("[distance server] sle property mem fail\r\n"); return ERRCODE_SLE_FAIL; } if (memcpy_s(property.value, sizeof(g_sle_property_value), g_sle_property_value, sizeof(g_sle_property_value)) != EOK) { osal_vfree(property.value); osal_printk("[distance server] sle property mem cpy fail\r\n"); return ERRCODE_SLE_FAIL; } //添加一个ssap特征 ret = ssaps_add_property_sync(g_server_id, g_service_handle, &property, &g_property_handle); if (ret != ERRCODE_SLE_SUCCESS) { osal_printk("[distance server] sle uuid add property fail, ret:%x\r\n", ret); osal_vfree(property.value); return ERRCODE_SLE_FAIL; } descriptor.permissions = SLE_UUID_TEST_DESCRIPTOR; //特征权限 descriptor.operate_indication = SSAP_OPERATE_INDICATION_BIT_READ | SSAP_OPERATE_INDICATION_BIT_WRITE; //操作指示 descriptor.type = SSAP_DESCRIPTOR_USER_DESCRIPTION; //描述符类型 descriptor.value = ntf_value; //数据 descriptor.value_len = sizeof(ntf_value); //添加一个ssap特征描述符 ret = ssaps_add_descriptor_sync(g_server_id, g_service_handle, g_property_handle, &descriptor); if (ret != ERRCODE_SLE_SUCCESS) { osal_printk("[distance server] sle uuid add descriptor fail, ret:%x\r\n", ret); osal_vfree(property.value); return ERRCODE_SLE_FAIL; } osal_vfree(property.value); return ERRCODE_SLE_SUCCESS; } //创建 server 实体 static errcode_t sle_uuid_server_add(void) { errcode_t ret; sle_uuid_t app_uuid = {0}; osal_printk("[distance server] sle uuid add service in\r\n"); app_uuid.len = sizeof(g_sle_uuid_app_uuid); //设置 uuid 长度 if (memcpy_s(app_uuid.uuid, app_uuid.len, g_sle_uuid_app_uuid, sizeof(g_sle_uuid_app_uuid)) != EOK) { return ERRCODE_SLE_FAIL; } //注册ssap服务端 ssaps_register_server(&app_uuid, &g_server_id); //若添加服务失败则注销服务端 if (sle_uuid_server_service_add() != ERRCODE_SLE_SUCCESS) { ssaps_unregister_server(g_server_id); return ERRCODE_SLE_FAIL; } //若添加特征失败则注销服务端 if (sle_uuid_server_property_add() != ERRCODE_SLE_SUCCESS) { ssaps_unregister_server(g_server_id); return ERRCODE_SLE_FAIL; } osal_printk("[distance server] sle uuid add service, server_id:%x, service_handle:%x, property_handle:%x\r\n", g_server_id, g_service_handle, g_property_handle); //开始一个SSAP服务 ret = ssaps_start_service(g_server_id, g_service_handle); if (ret != ERRCODE_SLE_SUCCESS) { osal_printk("[distance server] sle uuid add service fail, ret:%x\r\n", ret); return ERRCODE_SLE_FAIL; } osal_printk("[distance server] sle uuid add service out\r\n"); return ERRCODE_SLE_SUCCESS; } /* 初始化distance server */ errcode_t sle_distance_server_init(void) { uapi_watchdog_disable(); enable_sle(); printf("sle enable\r\n"); sle_distance_server_set_nv(); sle_conn_register_cbks(); sle_ssaps_register_cbks(); sle_uuid_server_add(); sle_uuid_server_adv_init(); sle_ssaps_set_info(); sle_distance_connect_param_init(); sle_set_local_addr_init(); osal_printk("[distance server] init ok\r\n"); return ERRCODE_SLE_SUCCESS; } /* 函数入口 */ int sle_distance_init(void) { //初始化全局变量 data for (int i = 0; i < PKT_DATA_LEN; i++) { data[i] = 'A'; data[PKT_DATA_LEN - 1] = '\0'; //最后一个字节填充空字符,作为字符串的结尾标识 } osal_msleep(1000); //休眠1s sle_distance_server_init(); return 0; } /* 线程与优先级设置 */ static void sle_distance_entry(void) { osal_task *task_handle1 = NULL; osal_kthread_lock(); task_handle1= osal_kthread_create((osal_kthread_handler)sle_distance_init, 0, "distance", DISTANCE_DEFAULT_KTHREAD_SIZE); if (task_handle1 != NULL) { osal_kthread_set_priority(task_handle1, DISTANCE_DEFAULT_KTHREAD_PROI); osal_kfree(task_handle1); } osal_kthread_unlock(); } app_run(sle_distance_entry);
- 逻辑链
-
client
端- 逻辑链
- 注册各类回调函数
- 使能
sle
- 本地设置
- 本地设备参数
- 对端设备参数
- 设备发现
- 设备发现参数初始化
- 设备发现回调函数
- 设备连接
- 连接参数初始化
- 设备连接回调函数
SSAP client
ssap client
回调函数
- 代码
#include <stdio.h> #include <stdlib.h> #include <math.h> #include "app_init.h" #include "soc_osal.h" #include "common_def.h" #include "systick.h" #include "securec.h" #include "tcxo.h" #include "sle_common.h" #include "sle_device_discovery.h" #include "sle_connection_manager.h" #include "sle_ssap_client.h" //线程参数 #define SLE_DISTANCE_CLIENT_TASK_PRIO 26 //线程优先级 #define SLE_DISTANCE_CLIENT_TASK_STACK_SIZE 0x2000 //线程的栈大小 //本地设备参数 static uint8_t local_addr[SLE_ADDR_LEN] = {0x13, 0x67, 0x5c, 0x07, 0x00, 0x51}; //自身 mac 地址 static uint64_t g_conn_id; //地址 //对端设备参数 static uint8_t mac[SLE_ADDR_LEN] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66}; //待匹配的 server 端的 mac static sle_addr_t g_remote_addr = {0}; //创建设备地址结构体,初始化为0 //设备发现 #define SLE_SEEK_INTERVAL_DEFAULT 100 //扫描间隔 #define SLE_SEEK_WINDOW_DEFAULT 100 //扫描窗口 //设备连接 #define DISTANCE_DEFAULT_CONN_INTERVAL 0x14 //连接时间 #define DISTANCE_DEFAULT_SCAN_INTERVAL 400 //扫描间隔 #define DISTANCE_DEFAULT_SCAN_WINDOW 20 //扫描时间 #define DISTANCE_DEFAULT_TIMEOUT_MULTIPLIER 0x1f4 //超时时间 //设备匹配 #define SLE_MTU_SIZE_DEFAULT 1500 //链路最大传输单元 // SSAP 参数 #define UUID_16BIT_LEN 2 //UUID 长度为 16 位 #define UUID_128BIT_LEN 16 //UUID 长度为 128 位 static ssapc_find_service_result_t g_find_service_result = {0}; //存储服务信息的全局变量 /* 设备发现 */ //设备发现参数初始化 void sle_start_scan(void){ sle_seek_param_t param = {0}; param.own_addr_type = 0; param.filter_duplicates = 0; param.seek_filter_policy = 0; param.seek_phys = 1; //扫描设备所使用的PHY,1M param.seek_type[0] = 0; //被动扫描 param.seek_interval[0] = SLE_SEEK_INTERVAL_DEFAULT; //扫描间隔,time = N * 0.125ms param.seek_window[0] = SLE_SEEK_WINDOW_DEFAULT; //扫描窗口,time = N * 0.125ms sle_set_seek_param(¶m); //公开扫描参数 sle_start_seek(); //开始公开扫描 } void sle_sample_sle_enable_cbk(errcode_t status){ if (status == 0) { sle_start_scan(); } } void sle_sample_seek_enable_cbk(errcode_t status){ if (status == 0) { return; } } //检验是否是目标对象,是则将其地址存入外部变量 g_remote_addr 中 void sle_sample_seek_result_info_cbk(sle_seek_result_info_t *seek_result_data){ //传入一个指向扫描结果的指针 if (seek_result_data != NULL) { if (memcmp(seek_result_data->addr.addr, mac, SLE_ADDR_LEN) == 0) { // mac 地址比较 (void)memcpy_s(&g_remote_addr, sizeof(sle_addr_t), &seek_result_data->addr, sizeof(sle_addr_t)); //将 mac 地址存储到 addr 中 sle_stop_seek(); //停止扫描 } } } void sle_sample_seek_disable_cbk(errcode_t status){ if (status == 0) { sle_connect_remote_device(&g_remote_addr); //发送连接请求 } } //注册设备发现回调函数 static void sle_init(void){ static sle_announce_seek_callbacks_t g_seek_cbk = {0}; g_seek_cbk.sle_enable_cb = sle_sample_sle_enable_cbk; // sle 协议栈回调函数使能 g_seek_cbk.seek_enable_cb = sle_sample_seek_enable_cbk; //扫描回调函数使能 g_seek_cbk.seek_result_cb = sle_sample_seek_result_info_cbk; //根据结果关闭回调函数 g_seek_cbk.seek_disable_cb = sle_sample_seek_disable_cbk; //关闭扫描回调函数 sle_announce_seek_register_callbacks(&g_seek_cbk); } /* 设备连接 */ //连接参数 void sle_distance_connect_param_init(void) { sle_default_connect_param_t param = {0}; param.enable_filter_policy = 0; //不过滤 param.gt_negotiate = 0; //gt交互 param.initiate_phys = 1; //通信带宽 param.max_interval = DISTANCE_DEFAULT_CONN_INTERVAL; //最大连接间隔 param.min_interval = DISTANCE_DEFAULT_CONN_INTERVAL; //最小连接间隔 param.scan_interval = DISTANCE_DEFAULT_SCAN_INTERVAL; //扫描时间间隔 param.scan_window = DISTANCE_DEFAULT_SCAN_WINDOW; //扫描时间 param.timeout = DISTANCE_DEFAULT_TIMEOUT_MULTIPLIER; //超时时间 sle_default_connection_param_set(¶m); } void sle_sample_connect_state_changed_cbk(uint16_t conn_id, const sle_addr_t *addr,sle_acb_state_t conn_state, sle_pair_state_t pair_state, sle_disc_reason_t disc_reason) { osal_printk("[ssap client] conn state changed conn_id:%d, addr:%02x***%02x%02x\n", conn_id, addr->addr[0],addr->addr[4], addr->addr[5]); /* 0 4 5: addr index */ osal_printk("[ssap client] conn state changed disc_reason:0x%x\n", disc_reason); if (conn_state == SLE_ACB_STATE_CONNECTED) { //设备已连接 if (pair_state == SLE_PAIR_NONE) { //未匹配则发起匹配 sle_pair_remote_device(&g_remote_addr); } g_conn_id = conn_id; } } void sle_sample_update_req_cbk(uint16_t conn_id, errcode_t status, const sle_connection_param_update_req_t *param) { unused(conn_id); unused(status); osal_printk("[ssap client] sle_sample_update_req_cbk interval_min = %02x, interval_max = %02x\n",param->interval_min, param->interval_max); } void sle_sample_pair_complete_cbk(uint16_t conn_id, const sle_addr_t *addr, errcode_t status) { osal_printk("[ssap client] pair complete conn_id:%d, addr:%02x***%02x%02x\n", conn_id, addr->addr[0],addr->addr[4], addr->addr[5]); /* 0 4 5: addr index */ if (status == 0) { //是否配对成功 ssap_exchange_info_t info = {0}; info.mtu_size = SLE_MTU_SIZE_DEFAULT; info.version = 1; ssapc_exchange_info_req(1, g_conn_id, &info); //发送链路连接请求,配置连接参数 } } void sle_sample_update_cbk(uint16_t conn_id, errcode_t status, const sle_connection_param_update_evt_t *param) { unused(status); osal_printk("[ssap client] updat state changed conn_id:%d, interval = %02x\n", conn_id, param->interval); } //连接设备连接回调函数 void sle_sample_connect_cbk_register(void) { static sle_connection_callbacks_t g_connect_cbk = {0}; g_connect_cbk.connect_state_changed_cb = sle_sample_connect_state_changed_cbk; //连接状态改变 g_connect_cbk.connect_param_update_req_cb = sle_sample_update_req_cbk; //请求连接参数更新 g_connect_cbk.pair_complete_cb = sle_sample_pair_complete_cbk; //配对完成 g_connect_cbk.connect_param_update_cb = sle_sample_update_cbk; //更新连接参数(已完成) sle_connection_register_callbacks(&g_connect_cbk); } /* SSAP client */ //更新mtu大小,启动服务发现 void sle_sample_exchange_info_cbk(uint8_t client_id, uint16_t conn_id, ssap_exchange_info_t *param,errcode_t status) { osal_printk("[ssap client] pair complete client id:%d status:%d\n", client_id, status); osal_printk("[ssap client] exchange mtu, mtu size: %d, version: %d.\n",param->mtu_size, param->version); //服务发现 ssapc_find_structure_param_t find_param = {0}; find_param.type = SSAP_FIND_TYPE_PRIMARY_SERVICE; //寻找指定的服务 find_param.start_hdl = 1; //起始句柄 find_param.end_hdl = 0xFFFF; //结束句柄 //调用服务发现函数 ssapc_find_structure(0, conn_id, &find_param); } //为应用层提供信息 void sle_sample_find_structure_cbk(uint8_t client_id, uint16_t conn_id, ssapc_find_service_result_t *service,errcode_t status) { osal_printk("[ssap client] find structure cbk client: %d conn_id:%d status: %d \n",client_id, conn_id, status); osal_printk("[ssap client] find structure start_hdl:[0x%02x], end_hdl:[0x%02x], uuid len:%d\r\n",service->start_hdl, service->end_hdl, service->uuid.len); if (service->uuid.len == UUID_16BIT_LEN) {osal_printk("[ssap client] structure uuid:[0x%02x][0x%02x]\r\n",service->uuid.uuid[14], service->uuid.uuid[15]); /* 14 15: uuid index */ } else { for (uint8_t idx = 0; idx < UUID_128BIT_LEN; idx++) { osal_printk("[ssap client] structure uuid[%d]:[0x%02x]\r\n", idx, service->uuid.uuid[idx]); } } //保存服务信息到全局变量 g_find_service_result.start_hdl = service->start_hdl; g_find_service_result.end_hdl = service->end_hdl; memcpy_s(&g_find_service_result.uuid, sizeof(sle_uuid_t), &service->uuid, sizeof(sle_uuid_t)); } //处理服务或特征发现完成的事件,发送数据到对端设备 void sle_sample_find_structure_cmp_cbk(uint8_t client_id, uint16_t conn_id,ssapc_find_structure_result_t *structure_result, errcode_t status) { osal_printk("[ssap client] find structure cmp cbk client id:%d status:%d type:%d uuid len:%d \r\n",client_id, status, structure_result->type, structure_result->uuid.len); if (structure_result->uuid.len == UUID_16BIT_LEN) { osal_printk("[ssap client] find structure cmp cbk structure uuid:[0x%02x][0x%02x]\r\n",structure_result->uuid.uuid[14], structure_result->uuid.uuid[15]); /* 14 15: uuid index */ } else { for (uint8_t idx = 0; idx < UUID_128BIT_LEN; idx++) { osal_printk("[ssap client] find structure cmp cbk structure uuid[%d]:[0x%02x]\r\n", idx,structure_result->uuid.uuid[idx]); } } //构造一个写操作(发送操作) uint8_t data[] = {0x11, 0x22, 0x33, 0x44}; uint8_t len = sizeof(data); ssapc_write_param_t param = {0}; param.handle = g_find_service_result.start_hdl; //写操作的目标句柄 param.type = SSAP_PROPERTY_TYPE_VALUE; //写类型 param.data_len = len; //数据长度 param.data = data; //写入数据 ssapc_write_req(0, conn_id, ¶m); //发起写申请 } //写操作完成确认函数 void sle_sample_write_cfm_cbk(uint8_t client_id, uint16_t conn_id, ssapc_write_result_t *write_result,errcode_t status) { osal_printk("[ssap client] write cfm cbk, client id: %d status:%d.\n", client_id, status); ssapc_read_req(0, conn_id, write_result->handle, write_result->type); } //发现特征回调函数 void sle_sample_find_property_cbk(uint8_t client_id, uint16_t conn_id,ssapc_find_property_result_t *property, errcode_t status) { osal_printk("[ssap client] find property cbk, client id: %d, conn id: %d, operate ind: %d, ""descriptors count: %d status:%d.\n", client_id, conn_id, property->operate_indication,property->descriptors_count, status); for (uint16_t idx = 0; idx < property->descriptors_count; idx++) { osal_printk("[ssap client] find property cbk, descriptors type [%d]: 0x%02x.\n",idx, property->descriptors_type[idx]); } if (property->uuid.len == UUID_16BIT_LEN) { osal_printk("[ssap client] find property cbk, uuid: %02x %02x.\n",property->uuid.uuid[14], property->uuid.uuid[15]); /* 14 15: uuid index */ } else if (property->uuid.len == UUID_128BIT_LEN) { for (uint16_t idx = 0; idx < UUID_128BIT_LEN; idx++) { osal_printk("[ssap client] find property cbk, uuid [%d]: %02x.\n",idx, property->uuid.uuid[idx]); } } } //解析读取数据并打印 void sle_sample_read_cfm_cbk(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *read_data,errcode_t status) { osal_printk("[ssap client] read cfm cbk client id: %d conn id: %d status: %d\n",client_id, conn_id, status); osal_printk("[ssap client] read cfm cbk handle: %d, type: %d , len: %d\n",read_data->handle, read_data->type, read_data->data_len); for (uint16_t idx = 0; idx < read_data->data_len; idx++) { osal_printk("[ssap client] read cfm cbk[%d] 0x%02x\r\n", idx, read_data->data[idx]); } } static void sle_distance_indication_cb(uint8_t client_id, uint16_t conn_id, ssapc_handle_value_t *data, errcode_t status) { unused(status); unused(conn_id); unused(client_id); osal_printk("\n sle_distance_indication_cb sle uart recived data : %s\r\n", data->data); } //ssap client回调函数 void sle_sample_ssapc_cbk_register(ssapc_notification_callback indication_cb) { static ssapc_callbacks_t g_ssapc_cbk = {0}; g_ssapc_cbk.exchange_info_cb = sle_sample_exchange_info_cbk; //更新mtu大小回调钩子,启动服务发现 g_ssapc_cbk.find_structure_cb = sle_sample_find_structure_cbk; //发现服务回调函数,为应用层提供信息 g_ssapc_cbk.find_structure_cmp_cb = sle_sample_find_structure_cmp_cbk; //发现特征完成回调函数,完成写操作 g_ssapc_cbk.write_cfm_cb = sle_sample_write_cfm_cbk; //写操作完成确认函数 g_ssapc_cbk.ssapc_find_property_cbk = sle_sample_find_property_cbk; //发现特征回调函数. g_ssapc_cbk.read_cfm_cb = sle_sample_read_cfm_cbk; //解析读取数据并打印 g_ssapc_cbk.indication_cb = indication_cb; //指示事件上报钩子 ssapc_register_callbacks(&g_ssapc_cbk); } /* 主函数 */ static int sle_distance_client_task(const char *arg) { unused(arg); sle_addr_t local_address; local_address.type = 0; (void)memcpy_s(local_address.addr, SLE_ADDR_LEN, local_addr, SLE_ADDR_LEN); sle_init(); sle_distance_connect_param_init(); sle_sample_connect_cbk_register(); sle_sample_ssapc_cbk_register(sle_distance_indication_cb); enable_sle(); sle_set_local_addr(&local_address); osal_msleep(1000); return 0; } //创建运行任务的线程并进行初始化 static void sle_distance_client_entry(void) { osal_task *task_handle = NULL; osal_kthread_lock(); //加锁 //创建线程 task_handle = osal_kthread_create((osal_kthread_handler)sle_distance_client_task, 0, "Sle_Distance_Task", SLE_DISTANCE_CLIENT_TASK_STACK_SIZE); if (task_handle != NULL) { //设置优先级 osal_kthread_set_priority(task_handle, SLE_DISTANCE_CLIENT_TASK_PRIO);// osal_kfree(task_handle); //释放内存 } osal_kthread_unlock(); //解锁 } //运行该app app_run(sle_distance_client_entry);
- 逻辑链
-
实验效果
- 在
CMakeList.txt
中修改,分别选择两个client
、server
文件,并烧录到板子中,使用串口工具查看板子信息server
端
client
端
- 在
-
星闪测距编程
RSSI
(简单介绍)
- 参考资料
- http://www.edatop.com/down/hwrf/mprf/MP-RF-19971.pdf
- https://www.macrothink.org/journal/index.php/npa/article/download/527/400
- 基于RSSI室内定位算法介绍_蓝牙rssi指纹定位-CSDN博客
- 介绍
Received Signal Strength Indication
:指接收信号强度指示,用来衡量无线设备接收到的信号强度,通常以dBm
为单位,对于蓝牙与星闪而言,一般是-1dBm ~ -100dBm
,数值越小信号越弱,一般数值的变化与距离的变化有关且为非线性关系,但仍受到其他复杂因素的影响,如阻挡、温湿度、多径效应等
- 测距原理
- 根据不同距离
RSSI
数值的不同确定不同的距离
- 根据不同距离
- 代码实现
- 读取
RSSI
的接口在sle_connection_callbacks_t
中/** * @if Eng * @brief Struct of SLE connection manager callback function. * @else * @brief SLE连接管理回调函数接口定义。 * @endif */ typedef struct { sle_connect_state_changed_callback connect_state_changed_cb; /*!< @if Eng Connect state changed callback. @else 连接状态改变回调函数。 @endif */ sle_connect_param_update_req_callback connect_param_update_req_cb; /*!< @if Eng Connect param updated callback. @else 连接参数更新回调函数。 @endif */ sle_connect_param_update_callback connect_param_update_cb; /*!< @if Eng Connect param updated callback. @else 连接参数更新回调函数。 @endif */ sle_auth_complete_callback auth_complete_cb; /*!< @if Eng Authentication complete callback. @else 认证完成回调函数。 @endif */ sle_pair_complete_callback pair_complete_cb; /*!< @if Eng Pairing complete callback. @else 配对完成回调函数。 @endif */ sle_read_rssi_callback read_rssi_cb; /*!< @if Eng Read rssi callback. @else 读取rssi回调函数。 @endif */ sle_low_latency_callback low_latency_cb; /*!< @if Eng Set low latency callback. @else 设置low latency回调函数。 @endif */ sle_set_phy_callback set_phy_cb; /*!< @if Eng Set PHY callback. @else 设置PHY回调函数。 @endif */ } sle_connection_callbacks_t;
- 代码
server
端、client
端均可配置读取RSSI
,不相互干扰,就算另一方未在sle_connection_callbacks_t
结构体中配置read_rssi_cb
也是可以直接读取的,后续处理的代码均是在client
端进行,可自由配置
- 代码
//配置回调函数 g_connect_cbk.read_rssi_cb = sle_sample_read_rssi_cbk; //读取rssi回调函数 //回调函数内容,每次调用读取rssi的函数之后会挑战到该回调函数 void sle_sample_read_rssi_cbk(uint16_t conn_id, int8_t rssi, errcode_t status) { unused(conn_id); unused(status); osal_printk("rssi: %d \r\n",rssi); } //主函数中调用读取rssi的函数 while (1) { sle_read_remote_device_rssi(g_conn_id); osal_msleep(1000); }
- 读取
- 读取效果
- 两块板子的位置固定不变,可读取
RSSI
如下
- 由输出的结果可知:
RSSI
的数值是不稳定的,而且波动是比较大的,图中的数据的极差为 15 15 15,是非常大的,就算舍弃最大值和最小值,其他的数据也是一直飘忽不定的,这种数据是无法直接使用的 - 经过简单的数学处理,将我们接收到的数据进行处理之后绘制成曲线,可以更加清晰地感受
RSSI
值的不稳定性
- 由上述所示,
RSSI
数值是不稳定的,这个不稳定除了硬件本身的问题之外,还有一部分是由于环境造成的噪声,这种不稳定性是可以通过数学处理减少的,在此我们选择卡尔曼滤波进行处理
- 两块板子的位置固定不变,可读取
卡尔曼滤波(简单介绍)
- 参考资料
- 原理
- 递归数据处理算法,主要用于动态系统的状态估计
- 通过结合系统模型和测量数据来减少估计误差,贝叶斯滤波器的原理,通过预测和更新两个步骤来不断优化状态估计
- 说人话:把传感器观测值和理论预测值做加权平均;既不完全相信传感器,也不完全相信理论模型
- 多维卡尔曼滤波模型与一维简化卡尔曼滤波模型介绍
公式类型 | 多维卡尔曼滤波(矩阵形式) | 一维卡尔曼滤波(标量形式) | 简化说明 |
---|---|---|---|
状态预测方程 | x ^ k − = A x ^ k − 1 + B u k \hat{x}_k^- = \mathbf{A}\hat{\mathbf{x}}_{k-1} + \mathbf{B}\mathbf{u}_k x^k−=Ax^k−1+Buk | x ^ k − = A x ^ k − 1 + B u k \mathbf{\hat{x}}_k^- = \mathbf{A}\mathbf{\hat{x}}_{k-1} + \mathbf{B}\mathbf{u}_k x^k−=Ax^k−1+Buk | 多维状态向量 X X X 退化为标量 X X X,矩阵 A , B A,B A,B 退化为标量 A , B A,B A,B |
协方差预测方程 | P k − = A P k − 1 A T + Q \mathbf{P}_k^- = \mathbf{A}\mathbf{P}_{k-1}\mathbf{A}^T + \mathbf{Q} Pk−=APk−1AT+Q | P k − = A 2 P k − 1 + Q \mathbf{P}_k^- = \mathbf{A}^2\mathbf{P}_{k-1} + \mathbf{Q} Pk−=A2Pk−1+Q | 协方差矩阵 P , Q P,Q P,Q,退化为标量 P , Q P,Q P,Q,矩阵乘法简化为平方运算 |
卡尔曼增益计算 | K k = P k − H T ( H P k − H T + R ) − 1 \mathbf{K}_k = \mathbf{P}_k^-\mathbf{H}^T(\mathbf{H}\mathbf{P}_k^-\mathbf{H}^T + \mathbf{R})^{-1} Kk=Pk−HT(HPk−HT+R)−1 | K k = P k − H H T P k − H + R \mathbf{K}_k = \frac{\mathbf{P}_k^-\mathbf{H}}{\mathbf{H}^T\mathbf{P}_k^-\mathbf{H} + \mathbf{R}} Kk=HTPk−H+RPk−H | 协方差矩阵 P , Q P,Q P,Q,退化为标量 P , Q P,Q P,Q,矩阵乘法简化为平方运算 |
状态更新方程 | x ^ k = x ^ k − + K k ( z k − H x ^ k − ) \mathbf{\hat{x}}_k = \mathbf{\hat{x}}_k^- + \mathbf{K}_k(\mathbf{z}_k -\mathbf{H}\mathbf{\hat{x}}_k^-) x^k=x^k−+Kk(zk−Hx^k−) | x ^ k = x ^ k − + K k ( z k − H x ^ k − ) \mathbf{\hat{x}}_k = \mathbf{\hat{x}}_k^- + \mathbf{K}_k(\mathbf{z}_k - \mathbf{H}\mathbf{\hat{x}}_k^-) x^k=x^k−+Kk(zk−Hx^k−) | 向量运算退化为标量加减乘除 |
协方差更新方程 | P k = ( I − K k H ) P k − \mathbf{P}_k = (\mathbf{I} - \mathbf{K}_k\mathbf{H})\mathbf{P}_k^- Pk=(I−KkH)Pk− | P k = ( 1 − K k H ) P k − \mathbf{P}_k = (1 - \mathbf{K}_k\mathbf{H})\mathbf{P}_k^- Pk=(1−KkH)Pk− | 单位矩阵 I I I 退化为标量 I I I,矩阵减法退化为标量运算 |
- 代码实现
- 一维简化卡尔曼滤波模型
C
语言代码demo
- 参数
- 估算协方差:量化当前估计结果的不确定性
- 卡尔曼增益:动态平衡模型预测与测量值的权重
- 过程噪声协方差:描述模型预测误差的方差(调)
- 测量噪声协方差:传感器测量误差的方差(可实际测量得出)
#include <stdio.h> #define TEST_PULSE_BUF_LEN 800 //测试数量 extern float testPulseBuf[]; // 结构体类型定义 typedef struct { float P; //估算协方差 float G; //卡尔曼增益 float Q; //过程噪声协方差,Q增大,动态响应变快,收敛稳定性变坏 float R; //测量噪声协方差,R增大,动态响应变慢,收敛稳定性变好 float Output; //卡尔曼滤波器输出 }KFPTypeS; // 定义卡尔曼结构体参数并初始化 KFPTypeS kfpVar = { 20, //估算协方差. 初始化值为 0.02 0, //卡尔曼增益. 初始化值为 0 1, //过程噪声协方差,Q增大,动态响应变快,收敛稳定性变坏. 初始化值为 0.001 1000, //测量噪声协方差,R增大,动态响应变慢,收敛稳定性变好. 初始化值为 1 930 //卡尔曼滤波器输出. 初始化值为 0 }; /* ****************************************************************************** * @brief 卡尔曼滤波器 函数 * @param *kfp - 卡尔曼结构体参数 * @param input - 需要滤波的参数的测量值(即传感器的采集值) * @return 卡尔曼滤波器输出值(最优值) * @note ****************************************************************************** */ float KalmanFilter(KFPTypeS *kfp, float input) { //估算协方差方程:当前 估算协方差 = 上次更新 协方差 + 过程噪声协方差 kfp->P = kfp->P + kfp->Q; //卡尔曼增益方程:当前 卡尔曼增益 = 当前 估算协方差 / (当前 估算协方差 + 测量噪声协方差) kfp->G = kfp->P / (kfp->P + kfp->R); //更新最优值方程:当前 最优值 = 当前 估算值 + 卡尔曼增益 * (当前 测量值 - 当前 估算值) kfp->Output = kfp->Output + kfp->G * (input - kfp->Output); //当前 估算值 = 上次 最优值 //更新 协方差 = (1 - 卡尔曼增益) * 当前 估算协方差。 kfp->P = (1 - kfp->G) * kfp->P; return kfp->Output; } /* ****************************************************************************** * @brief 主函数 * @param None * @return None * @note ****************************************************************************** */ void main(void) { for(int i=0; i<TEST_PULSE_BUF_LEN; i++) { printf("%4d\r\n", (int)KalmanFilter(&kfpVar, testPulseBuf[i])); //打印 } }
- 参数
- 基于上述代码的结果
- 移植代码
// 卡尔曼滤波 static double P = 0.1; // 估算协方差 static double G = 0; // 卡尔曼增益 static double Q = 0.001; // 过程噪声协方差,Q增大,动态响应变快,收敛稳定性变坏 static double R = 3; // 测量噪声协方差,R增大,动态响应变慢,收敛稳定性变好 static double Output = -20; // 卡尔曼滤波器输出 //卡尔曼滤波 static double KalmanFilter(int32_t input) { P = P + Q; // 估算协方差方程:当前 估算协方差 = 上次更新 协方差 + 过程噪声协方差 G = P / (P + R); // 卡尔曼增益方程:当前 卡尔曼增益 = 当前 估算协方差 / (当前 估算协方差 + 测量噪声协方差) Output = Output + G * (input - Output); // 更新最优值方程:当前 最优值 = 当前 估算值 + 卡尔曼增益 * (当前 测量值 - 当前 估算值) P = (1 - G) * P; // 更新 协方差 = (1 - 卡尔曼增益) * 当前 估算协方差 return Output; } void sle_sample_read_rssi_cbk(uint16_t conn_id, int8_t rssi, errcode_t status) { unused(conn_id); unused(status); int32_t rssi_k; rssi_k = (int32_t)KalmanFilter(rssi*100); //进行卡尔曼滤波计算 osal_printk("rssi: %d rssi_k:%d\r\n",rssi*100,rssi_k); }
- 一维简化卡尔曼滤波模型
- 效果
- 蓝色曲线为原始数据,橙色曲线为经过卡尔曼滤波之后的数据
- 由上图可见,卡尔曼滤波对数据的处理能力十分强大,所得的数据基本满足使用要求,但从x轴上0-100与600-800段的数据可以看出,该滤波参数延迟比较严重,只适合顶点数据的处理,不适合运动状态的数据的处理
- 蓝色曲线为原始数据,橙色曲线为经过卡尔曼滤波之后的数据
数据标定(简单介绍)
- 原理
- 在不考虑干扰环境的情况下RSSI
是随着距离而变化的,故可利用不同的RSSI
数值确定不同的距离 - 实验操作
- 为了减少误差,我们需要在完成卡尔曼滤波之后读取不同距离的
RSSI
值(相对稳定),求出其平均值,再将不同位置对应的RSSI
进行匹配处理,绘制出其变化曲线,利用工具计算出距离与RSSI
值的方程(可使用excel)- 以下展示在标定过程中的现象
- 10cm处的数据
20cm
- 10cm处的数据
- 经过多次的数据采集,最终可以得到以下的数据
- 以上数据经过excel处理得来
- 以下展示在标定过程中的现象
- 为了减少误差,我们需要在完成卡尔曼滤波之后读取不同距离的
- 代码实现
- 计算得出的方程是非线性的,且大多数包含 e e e ,需要不小的计算能力才能计算;而在单片机中,计算资源是有限的,无法进行非常复杂的计算(复杂计算还可能影响运行速度,使得输出速度满足不了我们的要求),故我们采用泰勒展开的方式对方程进行简化,方便单片机进行计算,对于上述数据得到的公式进行泰勒展开,取其前八项进行计算
void sle_sample_read_rssi_cbk(uint16_t conn_id, int8_t rssi, errcode_t status) { unused(conn_id); unused(status); int32_t rssi_k; double distance; rssi_k = (int32_t)KalmanFilter(rssi*100); distance = 1.0933 - 1.0933 * 0.001 * rssi_k + 1.0933 * (0.001 * 0.001 * rssi_k * rssi_k) / 2 - 1.0933 * (0.001 * 0.001 * 0.001 * rssi_k * rssi_k * rssi_k) / 6 + 1.0933 * (0.001 * 0.001 * 0.001 * 0.001 * rssi_k * rssi_k * rssi_k * rssi_k) / 24- 1.0933 * (0.001 * 0.001 * 0.001 * 0.001 * 0.001 * rssi_k * rssi_k * rssi_k* rssi_k * rssi_k) / 120 + 1.0933 * (0.001 * 0.001 * 0.001 * 0.001 * 0.001 * 0.001 * rssi_k * rssi_k * rssi_k * rssi_k * rssi_k * rssi_k) / 720 - 1.0933 * (0.001 * 0.001 * 0.001 * 0.001 * 0.001 * 0.001 * 0.001 * rssi_k * rssi_k * rssi_k * rssi_k * rssi_k * rssi_k * rssi_k) / 5040; osal_printk("rssi: %d rssi_k:%d distance:%d\r\n",rssi*100,(int32_t)rssi_k,(int32_t)distance); } ```
- 最终效果
- 因读取到的
RRSI
数值并不是与距离成稳定反比例关系,故在此次实验中,能够相对准确读取的范围只有 10~50cm,若需进步处理以得到更加准确的距离,请改进算法或期待更先进的芯片发布,此次实验仅仅作为一个初识HiSparkStudio
与星闪的练习,并不作为一个真正可以落地的项目,请以练习为主。
- 因读取到的