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.mzph.cn/news/867691.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Sentinel-1 Level 1数据处理的详细算法定义(一)

《Sentinel-1 Level 1数据处理的详细算法定义》文档定义和描述了Sentinel-1实现的Level 1处理算法和方程&#xff0c;以便生成Level 1产品。这些算法适用于Sentinel-1的Stripmap、Interferometric Wide-swath (IW)、Extra-wide-swath (EW)和Wave模式。 今天介绍的内容如下&…

Ubuntu下安装配置和调优WordPress技术指南

今天我要给你们带来一篇关于在阿贝云免费服务器上部署和调优WordPress的技术指南。先说一下&#xff0c;今天的实验都是在阿贝云免费服务器上进行的&#xff0c;我得赞一下这个免费云服务器。它配置还不错&#xff0c;1核CPU、1G内存、10G硬盘、5M带宽&#xff0c;免费的居然这…

【工具推荐】FOFA

文章目录 FOFA介绍FOFA语法 FOFA介绍 FOFA官网&#xff1a;https://fofa.info/ FOFA&#xff08;Fingerprinting Organizations with Advanced Tools&#xff09;是一款网络空间测绘的搜索引擎&#xff0c;它专注于帮助用户收集和分析互联网上的设备和服务信息。FOFA的主要特…

linux软链接和硬链接的区别

1 创建软链接和硬链接 如下图所示&#xff0c;一开始有两个文件soft和hard。使用 ln -s soft soft1创建软链接&#xff0c;soft1是soft的软链接&#xff1b;使用ln hard hard1创建硬链接&#xff0c;hard1是hard的硬链接。可以看到软链接的文件类型和其它3个文件的文件类型是不…

【JVM系列】Full GC(完全垃圾回收)的原因及分析

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

集合排序 题目

题目 JAVA44 集合排序描述输入描述&#xff1a;输出描述&#xff1a; 示例:分析&#xff1a;代码&#xff1a; JAVA44 集合排序 描述 有三个客户对象&#xff0c;将三个客户对象存入集合中&#xff0c;并将其按照消费总额从高到低排序 输入描述&#xff1a; 三个整数&#xff0…

使用Python实现CartPole游戏

在深度强化学习内容的介绍中&#xff0c;提出了CartPole游戏进行深度强化学习&#xff0c;现在提供一种用Python简单实现Cart Pole游戏的方法。 1. 游戏介绍 CartPole 游戏是一个经典的强化学习问题&#xff0c;其中有一个小车&#xff08;cart&#xff09;和一个杆&#xff…

用网络编程完成windows和linux跨平台之间的通信(服务器)

服务器代码逻辑&#xff1a; 服务器功能 创建 Socket&#xff1a; 服务器首先创建一个 Socket 对象&#xff0c;用于进行网络通信。通常使用 socket() 函数创建。 绑定&#xff08;Bind&#xff09;&#xff1a; 服务器将 Socket 绑定到一个特定的 IP 地址和端口号上。这是通过…

昇思25天学习打卡营第19天 | RNN实现情感分类

RNN实现情感分类 概述 情感分类是自然语言处理中的经典任务&#xff0c;是典型的分类问题。本节使用MindSpore实现一个基于RNN网络的情感分类模型&#xff0c;实现如下的效果&#xff1a; 输入: This film is terrible 正确标签: Negative 预测标签: Negative输入: This fil…

PyTorch实现InceptionResNetV2:预训练模型适应多类别任务代码解析

系列文章目录 9种经典图片分类卷积模型系列合集&#xff08;推荐程度依次递减&#xff09;&#xff1a; Se_resnet50Resnet50Xceptioninceptionresnetv2resnextbninceptionshufflenetv2polynetvggm Imagenet的预训练inceptionresnetv2是1000个类别&#xff0c;根据笔者添加了…

Go 中的类型推断

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

【三级等保】等保整体建设方案(Word原件)

建设要点目录&#xff1a; 1、系统定级与安全域 2、实施方案设计 3、安全防护体系建设规划 软件全文档&#xff0c;全方案获取方式&#xff1a;本文末个人名片直接获取。

【Python】基于KMeans的航空公司客户数据聚类分析

&#x1f490;大家好&#xff01;我是码银~&#xff0c;欢迎关注&#x1f490;&#xff1a; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 实验目的和要求 会用Python创建Kmeans聚类分析模型使用KMeans模型对航空公司客户价值进行聚类分析会对聚类结果进行分析评价 实…

Python酷库之旅-第三方库Pandas(008)

目录 一、用法精讲 16、pandas.DataFrame.to_json函数 16-1、语法 16-2、参数 16-3、功能 16-4、返回值 16-5、说明 16-6、用法 16-6-1、数据准备 16-6-2、代码示例 16-6-3、结果输出 17、pandas.read_html函数 17-1、语法 17-2、参数 17-3、功能 17-4、返回值…

CountDownLatch简介

引言 在多线程编程中&#xff0c;线程之间的协调和同步是一个常见的需求。Java 提供了多种工具来实现这一目标&#xff0c;其中 CountDownLatch 是一种简单而强大的同步机制。本文将详细介绍 CountDownLatch 的概念、使用方法和实际应用场景。 1. CountDownLatch 概述 Count…

Redis新手教程

Redis新手教程 目录 什么是RedisRedis的安装 安装前准备安装步骤 Redis的基本数据类型 字符串哈希列表集合有序集合 Redis的持久化 快照AOF Redis的高可用性 主从复制Redis SentinelRedis Cluster Redis的使用场景Redis的优缺点总结 1. 什么是Redis Redis&#xff08;Remot…

IPython 调试秘籍:精通 %xmode 命令的错误显示模式设置

IPython 调试秘籍&#xff1a;精通 %xmode 命令的错误显示模式设置 在使用 IPython 进行交互式编程时&#xff0c;错误信息的显示模式对于调试代码至关重要。%xmode 命令是 IPython 中专门用于控制错误信息展示方式的魔术命令。本文将详细解释 %xmode 命令的使用方法&#xff…

介绍东芝TB62262FTAG芯片:高性能两相双极步进电机驱动器

在当今快速发展的科技领域&#xff0c;高性能的电机驱动器对于许多工程项目来说至关重要。东芝的TB62262FTAG这款两相双极步进电机驱动器采用PWM斩波技术&#xff0c;集成了多个先进功能&#xff0c;适用于各种工业和消费类应用。本文将详细介绍TB62262FTAG的参数、性能、优势及…

ubuntu22 设置开机直接登录桌面

专栏总目录 一、打开设置文件 sudo vi /etc/gdm3/custom.conf 二、修改设置 在[daemon] 找到AutomaticLoginEnable和AutomaticLogin选项&#xff0c;取消注释并修改为&#xff1a; [daemon] # 自动登录用户名 AutomaticLoginEnableTrue AutomaticLoginusername 其中usernam…

《向量数据库指南》——Milvus Cloud检索器增强的深度探讨:句子窗口检索与元数据过滤

检索器增强的深度探讨&#xff1a;句子窗口检索与元数据过滤 在信息爆炸的时代&#xff0c;高效的检索系统成为了连接用户与海量数据的关键桥梁。为了进一步提升检索的准确性和用户满意度&#xff0c;检索器增强技术应运而生&#xff0c;其中句子窗口检索与元数据过滤作为两大…