Nordic 52832作为HID 键盘连接配对电视/投影后控制没反应问题的分析和解决

问题现象:我们的一款HID键盘硬件一直都工作的很好,连接配对后使用起来和原装键盘效果差不多,但是后面陆续有用户反馈家里的电视等蓝牙设备配对连接我们的键盘后,虽然显示已连接,但实际上控制不了。设备涉及到了好些品牌,比如坚果投影、海信电视等。

SDK版本: nRF5_SDK_17.0.2_d674dde

SoftDevice: S132

问题分析:

我们买了坚果投影回来测试,发现的确如用户反馈的现象一致,而且是必现的,必显就好分析。我们将nRF log日志级别改为debug,抓取了坚果配对过程的log,然后以同样方式抓取了正常使用的极米这一过程的log,对比日志发现连接都是正常的,日志都打印了Connected信息:

00> 

00> <info> app: Connected

00> 

00> <info> app: Connected

00> 

在main.c中我们对BLE事件注册了一个handler,针对BLE_GAP_EVT_CONNECTED和BLE_GAP_EVT_DISCONNECTED等事件进行了处理,上面的日志也说明了协议栈向我们的handler上报了已和设备连接上的事件。

 在日志后面按下按键发送数据的地方,坚果返回了错误码

00> <info> app: sd_ble_gatts_hvx err_code = 8.

 极米此处无错误

00> <info> app: sd_ble_gatts_hvx err_code = 0.

00> 

00> <info> app: hid send key err_code = 0

所以问题点就是此处,先来看一下报错位置的代码

 红色框中的代码就是当按下按键, 将HID键值通知到设备的处理,也就是说sd_ble_gatts_hvx返回了错误码0x08(NRF_ERROR_INVALID_STATE)。

根据sd_ble_gatts_hvx的方法定义,可以看到0x08错误的条件有三个:

* @retval ::NRF_ERROR_INVALID_STATE One or more of the following is true:

 *                                   - Invalid Connection State

 *                                   - Notifications and/or indications not enabled in the CCCD

 *                                   - An ATT_MTU exchange is ongoing

这段代码最开始的conn_handle != BLE_CONN_HANDLE_INVALID判断可以排除条件1,设备当前的连接是正常的,而且日志中也没有disconnected事件。

在Nordic论坛搜到类似的问题,说是CCCD没有使能,所以我们一开始也是从这个方向着手排查,在main.c中hids_init(void)方法里我们有对HID服务添加事件回调:

hids_init_obj.evt_handler   = on_hids_evt;

在坚果和极米的日志中都有输出相同数量的BLE_HIDS_EVT_NOTIF_ENABLED事件,所以HID 服务的CCCD应该已经使能了。

00> <info> app: ble_srv_is_notification_enabled = 1 p_hids->evt_handler = 295104 

00> <info> app: BLE_HIDS_EVT_NOTIF_ENABLED

接下来看an ATT_MTU exchange is ongoing这个情况是否存在。在两份日志的最开始处都有请求更新ATT MTU的信息,并且是在打印Connected之前:

00> <debug> nrf_ble_gatt: Requesting to update ATT MTU to 185 bytes on connection 0x0.

00> 

00> <info> app: HID on_connect conn_handle = 0.

00> 

00> <info> app: Connected

极米在后面的日志中有ATT MTU交换完成的信息

00> <debug> nrf_ble_gatt: ATT MTU updated to 185 bytes on connection 0x0 (response).

00> 

00> <info> app: Data len is set to 0xB6(182)

00> 

00> <debug> app: ATT MTU exchange completed. central 0xB9 peripheral 0xB9

而坚果没有,即使等待很长时间也没有,所以sd_ble_gatts_hvx发送数据会因为 An ATT_MTU exchange is ongoing 直接返回NRF_ERROR_INVALID_STATE,这样看很有可能就是我们目前碰到的问题的原因所在。那为什么要在连接后立马发MTU交换请求呢?为什么在坚果这里就没有交换完成的日志?

我们先分析第一个疑问。

蓝牙核心规范中只提到GATT client可以向GATT server发起ATT_EXCHANGE_MTU_REQ请求以告诉对方自己能够接收的最大数据长度(Client Rx MTU),server端收到请求后,需要通过ATT_EXCHANGE_MTU_RSP应答也告诉client自己这边的接收最大值(Server Rx MTU)。在MTU应答后两者的数据交互就使用两者能接收的MTU最小值了,一般MTU请求在连接后只会发一次。

规范并没有明确禁止server也可以发送ATT_EXCHANGE_MTU_REQ做同样的事情,即client 和server都能发出ATT MTU交换请求,所以Nordic协议栈会按照蓝牙规范处理MTU交换请求和应答。

这部分的处理主要在nrf_ble_gatt.c中

前面提到在connected打印前发出了MTU交换请求,上图红框部分on_connected_evt(p_gatt, p_ble_evt)就是发出请求的地方,正好是在BLE_GAP_EVT_CONNECTED事件里调用的。

先判断了当前设备的GAP角色,如果是peripheral, 将当前periph设备所需要的MTU赋值给当前连接conn_handle对应的p_link ->att_mtu_desired,目前我们的硬件是HID键盘,所以这里是peripheral

接着判断了当前连接需要的mtu是否大于生效的mtu,满足条件就请求MTU交换。可以看到在Nordic中,如果使用gatt库,默认设备连接上后满足这个条件就会发出交换请求。

那么需要看下p_link ->att_mtu_desired和p_link->att_mtu_effective值分别是多少,在哪里初始化的。

p_link->att_mtu_desired = p_gatt->att_mtu_desired_periph, 在当前文件中搜索att_mtu_desired_periph,找到下面这个方法

 nrf_ble_gatt_init有点眼熟,我们main.c中gatt_init()方法里调用这个方法进行了gatt初始化。

NRF_BLE_GATT_DEF(m_gatt);    /**< GATT module instance. */

static void gatt_init(void)

{

    ret_code_t err_code = nrf_ble_gatt_init(&m_gatt, gatt_evt_handler);

    APP_ERROR_CHECK(err_code);

}

我们这个工程NRF_BLE_GATT_LINK_COUNT=1, 会继续调用link_init(&p_gatt->links[i]);

 

p_link ->att_mtu_desired默认值是NRF_SDH_BLE_GATT_MAX_MTU_SIZE

p_link->att_mtu_effective默认值是BLE_GATT_ATT_MTU_DEFAULT

 //sdk_config.h

// <o> NRF_SDH_BLE_GATT_MAX_MTU_SIZE - Static maximum MTU size.

#ifndef NRF_SDH_BLE_GATT_MAX_MTU_SIZE

#define NRF_SDH_BLE_GATT_MAX_MTU_SIZE 185

#endif

//ble_gatt.h

/** @brief Default ATT MTU, in bytes. */

#define BLE_GATT_ATT_MTU_DEFAULT          23

综上,我们这个工程需要的mtu是185, 生效的是23(蓝牙协议允许的最小值, 蓝牙设备默认会使用这个值),所以连接上后会发出MTU交换请求,告诉对方本地能够接收的最大数据长度为185。但是不知道坚果为什么没有应答,导致HID设备的MTU交换状态一直处于进行中,蓝牙规范有提及如果发出MTU请求之后,在没有接收到应答之前,不可以向对端设备发送通知或指示。

我们看了下原始的sdk例子工程,NRF_SDH_BLE_GATT_MAX_MTU_SIZE是23, 改成23后, 重新和坚果配对,连接上后可以正常控制了,此时的log里也没有看到之前的请求交换的信息,证明了我们的分析是正确的,就是因为MTU交换没有完成造成的。

至于坚果为什么没有回复交换请求应答,是没有收到还是交换请求时机不对,我们也不清楚,不过作为Peripheral来说,本身也不用主动去请求MTU交换,交给Master来负责就好了,所以,最终决定连接后不发送这个请求,如果过后想要交换MTU, 调用sd_ble_gattc_exchange_mtu_request()即可。

改成23带来了新的问题,HID键盘除了要控制蓝牙设备外,还要和app连接进行通信,默认23字节会使得两者之间数据交互需要更长的时间,之前改成185就是为了缩短这个耗时。所以不能简单的改NRF_SDH_BLE_GATT_MAX_MTU_SIZE成23。因为如果是23,即使app这边请求185大小的client_mtu, HID键盘收到交换请求后,在这个逻辑中会使用p_link->att_mtu_desired(默认初始化为NRF_SDH_BLE_GATT_MAX_MTU_SIZE)来调用sd_ble_gatts_exchange_mtu_reply做应答,sd_ble_gatts_exchange_mtu_reply传入的是server_mtu,双方最终使用的是client_mtu和server_mtu的最小值,和这个方法里的p_link->att_mtu_effective = MIN(client_mtu, p_link->att_mtu_desired)一样。 

我们的需求是连接上后不要自动发交换请求,所以在sdk_config.h里定义了一个新常量

// NRF_BLE_GATT_MTU_EXCHANGE_INITIATION_ENABLED 是否启用连接后发MTU交换请求, 1为启用,我们这里关闭

#ifndef NRF_BLE_GATT_MTU_EXCHANGE_INITIATION_ENABLED

#define NRF_BLE_GATT_MTU_EXCHANGE_INITIATION_ENABLED 0

#endif

NRF_SDH_BLE_GATT_MAX_MTU_SIZE还是保持185,这样app可以申请最大为185的MTU用来和设备通信。

修改nrf_ble_gatt.c中的on_connected_evt方法,在之前的p_link->att_mtu_desired > p_link->att_mtu_effective条件前增加了启用开关:

 

这样即实现了作为HID外设时不主动发MTU交换请求,又能正确处理Master请求交换MTU大小。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/769499.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【操作与配置】VSCode配置Python及Jupyter

Python环境配置 可以参见&#xff1a;【操作与配置】Python&#xff1a;CondaPycharm_pycharmconda-CSDN博客 官网下载Python&#xff1a;http://www.python.org/download/官网下载Conda&#xff1a;Miniconda — Anaconda documentation VSCode插件安装 插件安装后需重启V…

layui-表格

1.使用方法 加上table标签 加上classlayui-table colgroup是列属性 tr是行td是列 thead是表头&#xff0c;后面一一对应 2.基础属性 加lay-even逐行换色 加lay-skin 设置边框风格

竞赛 深度学习+opencv+python实现昆虫识别 -图像识别 昆虫识别

文章目录 0 前言1 课题背景2 具体实现3 数据收集和处理3 卷积神经网络2.1卷积层2.2 池化层2.3 激活函数&#xff1a;2.4 全连接层2.5 使用tensorflow中keras模块实现卷积神经网络 4 MobileNetV2网络5 损失函数softmax 交叉熵5.1 softmax函数5.2 交叉熵损失函数 6 优化器SGD7 学…

企业多存储方式如何兼顾安全统一管理、便捷流畅访问的双向需求?

数据和文件存储是企业最基础的需求&#xff0c;常见的存储方式有磁盘存储、NAS存储、SAN存储、云存储、分布式存储、闪存存储等&#xff1b;随着企业规模的扩大、业务结构的复杂化&#xff0c;企业内部可能会同时出现多种存储方式、多个存储设备并行使用的情况。 这样的使用场景…

Ubuntu24.04(22.04+版本通用)Miniconda与Isaacgym

1. ubuntu24.04安装minicondda mkdir -p ~/miniconda3 wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda3/miniconda.sh解释下这段代码 bash ~/miniconda3/miniconda.sh -b -u -p ~/miniconda3~/miniconda3/miniconda.sh: 指向Mi…

Meta 发布 Meta 3D Gen 文本生成3D模型

Meta推出了 Meta 3D Gen &#xff08;3DGen&#xff09;&#xff0c;这是一种用于文本到 3D 资产生成的最先进的快速管道。3DGen 可在一分钟内提供具有高提示保真度和高质量 3D 形状和纹理的 3D 资产创建。 它支持基于物理的渲染 &#xff08;PBR&#xff09;&#xff0c;这是…

【笔记】强化学习,gym的命令行图形化界面适配

搞了一大堆还是搞不出来放弃了 最后用matplotlib画出来看 import gym import matplotlib.pyplot as plt from IPython import display import numpy as np %matplotlib inlineenv gym.make(CartPole-v1, render_mode"rgb_array") observation env.reset() a 0 f…

新手教学系列——使用uWSGI对Flask应用提速

在构建和部署Flask应用时,性能和稳定性是两个关键的因素。为了提升Flask应用的性能,我们可以借助uWSGI这个强大的工具。本文将详细介绍为什么要使用uWSGI、uWSGI的底层原理,并提供一个实例配置,帮助你更好地理解和应用这个工具。 为什么要使用uWSGI uWSGI 是一个应用服务…

CentOS 7.9 快速更换 阿里云源教程

CentOS 7.9 更换源教程 总结 # 下载 wget yum -y install wget # 备份 yum 源 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak # 下载阿里云的yum源到 /etc/yum.repos.d/ # 此处以 CentOS 7 为例&#xff0c;如果是其它版本或者系统的话&#…

微软工具合集 -- Microsoft PowerToys v0.82.0

软件简介 PowerToys是由微软开发的一款免费系统工具集&#xff0c;专为Windows 10和Windows 11系统设计。它提供了一系列高效实用的工具&#xff0c;旨在帮助用户提升操作系统的使用效率和个性化程度。PowerToys工具集深受系统管理员和高级用户的喜爱&#xff0c;通过集成多种…

Laravel介绍与学习入门

Laravel 是一款优雅且功能强大的 PHP Web 开发框架&#xff0c;它被广泛认为是 PHP 领域内构建现代 Web 应用程序的最佳选择之一。Laravel 提供了一套简洁、富有表现力的语法&#xff0c;使得开发者能够高效地编写清晰、可维护的代码。以下是 Laravel 的一些关键特点和入门概念…

Java线程同步的特征和安全类型

一线程同步的特征 ◆不同的线程在执行以同一个对象作为锁标记的同步代码块或同步方法时&#xff0c;因为要获得这个对象的锁而相互牵制&#xff0c;多个并发线程访问同一资源的同步代码块或同步方法时。 ◆同一时刻只能有一个线程进入synchronized(this)同步代码块。 ◆当一个…

大数据开发如何快速进阶

目录 1. 个人经验与心得分享1.1 试错的价值与机会把握1.2 投入产出比的考量1.3 刻意练习与技能提升1.4 目标设定与职业规划1.5 自我驱动与成长1.6 第一性原理的应用 2. 大数据开发领域的挑战与机遇2.1 技术革新的挑战2.2 数据治理的难题2.3 人才短缺的问题2.4 投入产出比的考量…

ruoyi-cloud登录接口实现滑块验证码

一、前言 ruoyi项目默认的验证码是这样的 今天来尝试增加滑块验证码&#xff0c;我们用到的是tianai-captcha。 文档地址&#xff1a;http://doc.captcha.tianai.cloud/ 源码地址&#xff1a;https://gitee.com/tianai/tianai-captcha 下面来看具体的步骤。 二、后端 在g…

路由的基本使用

1.安装 npm i vue-router3 2.引入 import VueRouter from vue-router 3.使用 Vue.use(VueRouter) 4.在src目录下创建router 5.创建两个组件 5.1创建About组件 <template><div> <h1>我是About的内容</h1></div> </template><script> …

PyMuPDF 操作手册 - 08 API - Document属性方法和简短说明

文章目录 https://pymupdf.readthedocs.io/en/latest/document.html#Document 方法/属性简短描述Document.add_layer()仅限 PDF:进行新的可选内容配置Document.add_ocg()仅限 PDF:添加新的可选内容组Document.authenticate()访问加密文档Document.bake()仅限 PDF:将…

Redis 高可用(理论)

目录 Redis 高可用 Redis 持久化 RDB 持久化 触发条件 手动触发 自动触发 ##其他自动触发机制## 执行流程 启动时加载 AOF 持久化 执行流程 &#xff08;1&#xff09;命令追加(append) &#xff08;2&#xff09;文件写入(write)和文件同步(sync) &#xff08;3&…

C++11新特性【下】{lambda表达式、可变模板参数、包装器}

一、lambda表达式 在C98中&#xff0c;如果想要对一个数据集合中的元素进行排序&#xff0c;可以使用std::sort方法。如果待排序元素为自定义类型&#xff0c;需要用户定义排序时的比较规则&#xff0c;随着C语法的发展&#xff0c;人们开始觉得上面的写法太复杂了&#xff0c…

Linux服务器升级openssh9.8最新版全过程,及遇到问题处理

前言&#xff1a;由于2024年7月1日&#xff0c;openssh发布了最新版9.8&#xff0c;所以服务器需要升级一下&#xff0c;特此做个详细记录&#xff1a; 由于下载最新版openssh9.8&#xff0c;需要将openssl也一并进行升级 一、下载openssh最新版本与openssl对应版本&#xff…

2-24 基于图像处理的细胞计数方法

基于图像处理的细胞计数方法。经过初次二值化、中值滤波后二值化、优化后二值化图像、填充背景色的二进制图像、开运算后的图像一系列运算后&#xff0c;进行标签设置&#xff0c;最终得到细胞总数。程序已调通&#xff0c;可直接运行。 2-24 细胞计数方法 中值滤波后二值化 - …