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.rhkb.cn/news/371016.html

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

相关文章

Blazor SPA 的本质是什么以及服务器端渲染如何与 Blazor 的新 Web 应用程序配合使用

Blazor 通常被称为单页应用程序 (SPA) 框架。当我第一次开始使用 Blazor 时&#xff0c;我对 SPA 的含义、组件如何为 SPA 架构做出贡献以及所有这些如何与交互性联系在一起感到困惑。 今天&#xff0c;我将解答大家可能关心的三个问题&#xff1a; 什么是 SPA&#xff1f;了…

STM32 Cannot access memory

问题描述 最近自己做了一块STM32F103ZET6的板子&#xff0c;在焊接完成后可以在下载器界面看到idcode&#xff0c;但烧录时报错 Cannot access memory 。 解决办法 测量STM32各个供电项&#xff0c;发现时33脚处VDDA电压只有1.8V&#xff0c;是因为R3电阻过大&#xff0c;…

基于YOLOv9的脑肿瘤区域检测

数据集 脑肿瘤区域检测&#xff0c;我们直接采用kaggle公开数据集&#xff0c;Br35H 数据中已对医学图像中脑肿瘤位置进行标注 数据集我已经按照YOLO格式配置好&#xff0c;数据内容如下 数据集中共包含700张图像&#xff0c;其中训练集500张&#xff0c;验证集200张 模型训…

Xilinx FPGA:vivado关于真双端口的串口传输数据的实验

一、实验内容 用一个真双端RAM&#xff0c;端口A和端口B同时向RAM里写入数据0-99&#xff0c;A端口读出单数并存入单端口RAM1中&#xff0c;B端口读出双数并存入但端口RAM2中&#xff0c;当检测到按键1到来时将RAM1中的单数读出显示到PC端&#xff0c;当检测到按键2到来时&…

YOLO V7网络实现细节(2)—网络整体架构总结

YOLO V7网络整体架构总结 YOLO v7网络架构的整体介绍 不同GPU和对应模型&#xff1a; ​​​​​​​边缘GPU&#xff1a;YOLOv7-tiny普通GPU&#xff1a;YOLOv7​​​​​​​云GPU的基本模型&#xff1a; YOLOv7-W6 激活函数&#xff1a; YOLOv7 tiny&#xff1a; leaky R…

openmetadata1.3.1 自定义连接器 开发教程

openmetadata自定义连接器开发教程 一、开发通用自定义连接器教程 官网教程链接&#xff1a; 1.https://docs.open-metadata.org/v1.3.x/connectors/custom-connectors 2.https://github.com/open-metadata/openmetadata-demo/tree/main/custom-connector &#xff08;一&…

24西安电子科技大学经济与管理学院—考研录取情况

24西安电子科技大学—经理与管理学院—考研录取统计 01、经理与管理学院各个方向 02、24经济与管理近三年复试分数线对比 1、经管院24年院线相对于23年院线普遍下降2-15分&#xff0c;个别专业上涨4-10分。 2、经管院应用经济学2024年院线350分&#xff1b;管理科学与工程院线…

Apache Seata tcc 模块源码分析

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 一 .导读 spring 模块分析中讲到&#xff0c;Seata 的 spring 模块会对涉及到分布式业务的 b…

Linux——进程间通信一(共享内存、管道、systrem V)

一、进程间通信介绍 1.1、进程间通信的概念和意义 进程间通信(IPC interprocess communication)是一组编程接口&#xff0c;让不同进程之间相互传递、交换信息(让不同的进程看到同一份资源) 数据传输:一个进程需要将它的数据发送给另外一个进程 资源共享:多个进程之间共享同样…

如何选择一家适合自己的商城源码?

商城源码的选择取决于多个因素&#xff0c;包括商城的功能需求、稳定性、易用性、可定制性以及价格等。启山智软作为在市场上被广泛认可且表现优异的商城源码提供商&#xff0c;具有以下的特点和优势&#xff1a; 特点①&#xff1a;国内知名的B2B2C开源商城源码系统&#xff…

Golang | Leetcode Golang题解之第213题打家劫舍II

题目&#xff1a; 题解&#xff1a; func _rob(nums []int) int {first, second : nums[0], max(nums[0], nums[1])for _, v : range nums[2:] {first, second second, max(firstv, second)}return second }func rob(nums []int) int {n : len(nums)if n 1 {return nums[0]}…

python conda查看源,修改源

查看源 conda config --show-sources 修改源 可以直接vim .condarc修改源&#xff0c;

vue事件处理v-on或@

事件处理v-on或 我们可以使用v-on指令&#xff08;简写&#xff09;来监听DOM事件&#xff0c;并在事件触发时执行对应的Javascript。用法&#xff1a;v-on:click"methodName"或click"hander" 事件处理器的值可以是&#xff1a; 内敛事件处理器&#xff1…

如何让代码兼容 Python 2 和 Python 3?Future 库助你一臂之力

目录 01Future 是什么? 为什么选择 Future? 安装与配置 02Future 的基本用法 1、兼容 print 函数 2、兼容整数除法 3、兼容 Unicode 字符串 03Future 的高级功能 1. 处理字符串与字节 2. 统一异常处理…

基于SpringBoot的校园台球厅人员与设备管理系统

本系统是要设计一个校园台球厅人员与设备管理系统&#xff0c;这个系统能够满足校园台球厅人员与设备的管理及用户的校园台球厅人员与设备管理功能。系统的主要功能包括首页、个人中心、用户管理、会员账号管理、会员充值管理、球桌信息管理、会员预约管理、普通预约管理、留言…

LRU缓存算法设计

LRU 缓存算法的核⼼数据结构就是哈希链表&#xff0c;双向链表和哈希表的结合体。这个数据结构⻓这样&#xff1a; 创建的需要有两个方法&#xff0c;一个是get方法&#xff0c;一个是put方法。 一些问题&#xff1a;为什么需要使用双向链表呢&#xff1f;因为删除链表的本身&…

第一节 网络安全概述

一.网络空间安全 网络空间&#xff1a;一个由信息基础设施组成相互依赖的网络。 ---- 海陆空天&#xff08;大海、陆 地、天空、航天&#xff09; 通信保密阶段 ---- 计算机安全 ----- 信息系统安全 ----- 网络空间安全 计算机安全&#xff1a;开始秉持着“严于律己&#x…

《安富莱嵌入式周报》第339期:单片机运行苹果早期Mac系统模拟器,2GHz示波器有源探头,下一代矩阵开关面包板,卡片式声音分贝器,HP经典示波器,ReRAM

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频版 https://www.bilibili.com/video/BV1Kf421Q7Lh 《安富莱嵌入式周报》第339期&#xff1a;单片机运行苹果早期Ma…

Linux开发讲课33---线程实现与线程控制步骤简析

线程概述 进程是系统中程序执行和资源分配的基本单位。 每个进程都拥有自己的数据段、代码段和堆栈段&#xff0c;这就造成了进程在进行切换等操作时都需要有比较负责的上下文切换等动作。为了进一步减少处理机的空转时间支持多处理器和减少上下文切换开销&#xff0c;进程在演…

IDEA安装IDE Eval Reset插件,30天自动续期,无限激活

第一步&#xff1a; 下载idea 注意&#xff1a;版本要是2021.2.2以下 第二步&#xff1a;快捷键CtrlAlts打开设置 第三步&#xff1a;打开下图中蓝色按钮 第四步&#xff1a;点击弹窗的 “” &#xff0c;并输入 plugins.zhile.io 点击 “ok” 第五步&#xff1a;搜索IDE Ea…