ESP32连接xbox手柄

本文简单介绍一下如何使用ESP32连接xbox的蓝牙手柄,使之变成一个相对通用的遥控器。

硬件平台

XBOX ONE S手柄蓝牙版

EPS32-DOWN-V3,需要注意Xbox One S手柄使用经典蓝牙,ESP32有较多型号仅支持BLE,需要选一个支持经典蓝牙+BLE的型号,BLE模式下扫描不到Xbox 手柄

软件平台

使用espidf 5.2.1版本进行开发。

实现原理

输入解析

espidf打开example内的esp_hid_host工程,基于这个demo进行修改,工程首先将BLE关闭,修改为经典蓝牙的配置。
例程配置为了扫描到的最后一个HID设备将被连接,这个地方可以自己改一下,识别蓝牙名称进行选择连接,避免有多个设备的时候连到别的设备上去,连接部分内容不多,此处略过。
正常连接后,主要需要处理的就是键值上报后的解析,解析可以参考下面的结构体

#pragma pack(1)
typedef struct
{uint16_t left_x;       ///< 左0~右65535uint16_t left_y;       ///< 上0~下65535uint16_t right_x;      ///< 左0~右65535uint16_t right_y;      ///< 上0~下65535uint16_t triger_left;  ///< 不按0~按到底1023uint16_t triger_right; ///< 不按0~按到底1023uint8_t key_dir;       ///< 1~8 垂直向上为1,顺时针旋转递增,不按为0struct{uint8_t key_a : 1;uint8_t key_b : 1;uint8_t key_x : 1;uint8_t key_y : 1;uint8_t key_lb : 1;uint8_t key_rb : 1;uint8_t key_page : 1;uint8_t key_menu : 1;};uint8_t reserve;
} xbox_message_t;
typedef struct
{uint8_t cap_level : 2; ///< 电量等级 0马上没电,1低电量,2中电量,2满电量uint8_t mode : 2;      ///< 供电模式 0 USB供电 1 干电池供电(或者非官方的充电电池) 2 官方可充电电池供电uint8_t charging : 1;  ///< 正在充电(官方电池)uint8_t error : 1;     ///< 出现异常uint8_t reserve : 1;   ///< 保留字段uint8_t online : 1;    ///< 在线,始终是1
} xbox_bat_info_t;/* XBOX RUMBLE TYPE */
#define XBOX_RUMBLE_NONE 0x00
#define XBOX_RUMBLE_WEAK 0x01
#define XBOX_RUMBLE_STRONG 0x02
#define XBOX_RUMBLE_MAIN (XBOX_RUMBLE_WEAK | XBOX_RUMBLE_STRONG)
#define XBOX_RUMBLE_RIGHT 0x04
#define XBOX_RUMBLE_LEFT 0x08
#define XBOX_RUMBLE_TRIGGERS (XBOX_RUMBLE_RIGHT | XBOX_RUMBLE_LEFT)
#define XBOX_RUMBLE_ALL 0xfftypedef struct
{uint8_t enable;             ///< XBOX RUMBLE TYPE,操作电机的maskuint8_t magnitude_left;     ///< 左triger电机的力度 0~100uint8_t magnitude_right;    ///< 右triger电机的力度 0~100uint8_t magnitude_strong;   ///< 机身高频电机的力度(位于右侧手柄),0~100uint8_t magnitude_weak;     ///< 机身低频电机的力度(位于左侧手柄),0~100uint8_t pulse_sustain_10ms; ///< 振动使能时间,单位msuint8_t pulse_release_10ms; ///< 振动失能时间,单位msuint8_t loop_count;         ///< 循环次数-1 周期为振动使能时间+振动失能时间
} xbox_motor_cmd_t;
#pragma pack()

传过来的键值,如代码所示,在输入回调内进行解析并打印,整个设备共有四个report,其中三个为输入,一个为输出(也就是控制振动)。输入的看下面代码即可

    case ESP_HIDH_INPUT_EVENT:{/* HID消息 */if (param->input.report_id == 1){xbox_message_t *message = (xbox_message_t *)param->input.data;ESP_LOGI(TAG, "map_index: %d", param->input.map_index);ESP_LOGI(TAG, "left_x:%d left_y:%d right_x:%d right_y:%d triger_l:%d triger_r:%d",message->left_x, message->left_y,message->right_x, message->right_y,message->triger_left, message->triger_right);ESP_LOGI(TAG, "key_dir:%d key_a:%d key_b:%d key_x:%d key_y:%d key_lb:%d key_rb:%d key_page:%d key_menu:%d",message->key_dir, message->key_a,message->key_b, message->key_x,message->key_y, message->key_lb, message->key_rb, message->key_page, message->key_menu);/* 利用键值测试振动控制 */xbox_motor_test(param->input.dev, *message);}/* 西瓜键 */else if (param->input.report_id == 2){ESP_LOGI(TAG, "key_xbox:%d", param->input.data[0]);}/* 电池信息 */if (param->input.report_id == 4){xbox_bat_info_t *info = (xbox_bat_info_t *)param->input.data;ESP_LOGI(TAG, "cap_level:%d mode:%d charging:%d error:%d online:%d",info->cap_level, info->mode, info->charging, info->error, info->online);}break;}

由于手上只有一款手柄,对于其他手柄的键值解析可能有偏差,这个要实际测试确定,但大概的结构是这样的。

输出控制

代码中还利用键值做了振动控制的测试,测试相关代码如下:

void xbox_motor_test(esp_hidh_dev_t *dev, xbox_message_t message)
{static xbox_message_t message_last = {0};xbox_motor_cmd_t xbox_motor_cmd = {0};if (message_last.key_x != message.key_x){if (message.key_x){xbox_motor_cmd.enable = XBOX_RUMBLE_LEFT;             // 仅修改左triger按键xbox_motor_cmd.magnitude_left = message.left_x / 700; // 根据左摇杆键值调整力度xbox_motor_cmd.pulse_sustain_10ms = 20;               // 200ms}esp_hidh_dev_output_set(dev, 0, 3, (uint8_t *)&xbox_motor_cmd, sizeof(xbox_motor_cmd));}if (message_last.key_b != message.key_b){if (message.key_b){xbox_motor_cmd.enable = XBOX_RUMBLE_RIGHT;xbox_motor_cmd.magnitude_right = message.left_x / 700;xbox_motor_cmd.pulse_sustain_10ms = 20;}esp_hidh_dev_output_set(dev, 0, 3, (uint8_t *)&xbox_motor_cmd, sizeof(xbox_motor_cmd));}if (message_last.key_a != message.key_a){if (message.key_a){xbox_motor_cmd.enable = XBOX_RUMBLE_WEAK;xbox_motor_cmd.magnitude_weak = message.left_x / 700;xbox_motor_cmd.pulse_sustain_10ms = 20;}esp_hidh_dev_output_set(dev, 0, 3, (uint8_t *)&xbox_motor_cmd, sizeof(xbox_motor_cmd));}if (message_last.key_y != message.key_y){if (message.key_y){xbox_motor_cmd.enable = XBOX_RUMBLE_STRONG;xbox_motor_cmd.magnitude_strong = message.left_x / 700;xbox_motor_cmd.pulse_sustain_10ms = 20;}esp_hidh_dev_output_set(dev, 0, 3, (uint8_t *)&xbox_motor_cmd, sizeof(xbox_motor_cmd));}if (message_last.key_lb != message.key_lb){if (message.key_lb){xbox_motor_cmd.enable = XBOX_RUMBLE_MAIN;xbox_motor_cmd.magnitude_strong = message.left_x / 700;xbox_motor_cmd.magnitude_weak = message.left_x / 700;xbox_motor_cmd.pulse_sustain_10ms = 20;}esp_hidh_dev_output_set(dev, 0, 3, (uint8_t *)&xbox_motor_cmd, sizeof(xbox_motor_cmd));}if (message_last.key_rb != message.key_rb){if (message.key_rb){xbox_motor_cmd.enable = XBOX_RUMBLE_TRIGGERS;xbox_motor_cmd.magnitude_left = message.left_x / 700;xbox_motor_cmd.magnitude_right = message.left_x / 700;xbox_motor_cmd.pulse_sustain_10ms = 20;}esp_hidh_dev_output_set(dev, 0, 3, (uint8_t *)&xbox_motor_cmd, sizeof(xbox_motor_cmd));}if (message_last.key_page != message.key_page){if (message.key_page){xbox_motor_cmd.enable = XBOX_RUMBLE_ALL;xbox_motor_cmd.magnitude_left = message.left_x / 700;xbox_motor_cmd.pulse_sustain_10ms = 20;xbox_motor_cmd.pulse_release_10ms = 10;xbox_motor_cmd.loop_count = 1; // 振两次}esp_hidh_dev_output_set(dev, 0, 3, (uint8_t *)&xbox_motor_cmd, sizeof(xbox_motor_cmd));}memcpy(&message_last, &message, sizeof(message_last));
}

 关于振动电机控制的部分,看控制的结构体就能看明白了,代码里面利用不同的键按下事件,测试了不同的震动效果。

实际测试

I (12119) ESP_HID_GAP: BT GAP MODE_CHG_EVT mode:2
I (12139) XBOX : ec:83:50:de:44:5a OPEN: 
BDA:ec:83:50:de:44:5a, Status: OK, Connected: YES, Handle: 0, Usage: GAMEPAD
Name: , Manufacturer: , Serial Number:
PID: 0x02e0, VID: 0x045e, VERSION: 0x0903
Report Map Length: 306GENERIC   INPUT REPORT, ID:   4, Length:   1GENERIC  OUTPUT REPORT, ID:   3, Length:   8GENERIC   INPUT REPORT, ID:   2, Length:   1GAMEPAD   INPUT REPORT, ID:   1, Length:  15
I (14919) XBOX : cap_level:2 mode:1 charging:0 error:0 online:1
I (23219) XBOX : left_x:34757 left_y:33821 right_x:32584 right_y:34502 triger_l:0 triger_r:0
I (23219) XBOX : key_dir:0 key_a:1 key_b:0 key_x:0 key_y:0 key_lb:0 key_rb:0 key_page:0 key_menu:0
I (23219) XBOX : left_x:34757 left_y:33821 right_x:32584 right_y:34502 triger_l:0 triger_r:0
I (23229) XBOX : key_dir:0 key_a:1 key_b:0 key_x:0 key_y:0 key_lb:0 key_rb:0 key_page:0 key_menu:0
I (23239) XBOX : left_x:34757 left_y:33821 right_x:32584 right_y:34502 triger_l:0 triger_r:0
I (23249) XBOX : key_dir:0 key_a:1 key_b:0 key_x:0 key_y:0 key_lb:0 key_rb:0 key_page:0 key_menu:0
I (23339) XBOX : left_x:34757 left_y:33821 right_x:32584 right_y:34502 triger_l:0 triger_r:0
I (23339) XBOX : key_dir:0 key_a:0 key_b:0 key_x:0 key_y:0 key_lb:0 key_rb:0 key_page:0 key_menu:0
I (23339) XBOX : left_x:34757 left_y:33821 right_x:32584 right_y:34502 triger_l:0 triger_r:0
I (23349) XBOX : key_dir:0 key_a:0 key_b:0 key_x:0 key_y:0 key_lb:0 key_rb:0 key_page:0 key_menu:0
I (23359) XBOX : left_x:34757 left_y:33821 right_x:32584 right_y:34502 triger_l:0 triger_r:0
I (23369) XBOX : key_dir:0 key_a:0 key_b:0 key_x:0 key_y:0 key_lb:0 key_rb:0 key_page:0 key_menu:0

可以看打印的LOG,可以看到可以正常的进行xbox的连接并输出对应的键值情况。需要注意的是,xbox手柄输入是事件型的,也就是只有键值改变的时候才会进行键值的上报,这样可以极大的降低功耗。电量信息则是定时上报。并且在对应的按键按下时,可以接收到震动反馈。

结语

到此,整个工程的实现就已经结束了。对于不同型号的xbox手柄(比如精英手柄),可以使用类似的方式进行解析,不过需要自己进行额外键值的分析和解析,或许可以参考下面的代码。

https://github.com/atar-axis/xpadneo/blob/master/hid-xpadneo/src/hid-xpadneo.c

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

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

相关文章

AXI Quad SPI IP核中命令的使用

1 双通道SPI和混合内存模式下支持的常用命令 对于配置中Mode设置为Dual且Slave Device设置为Mixed的情况&#xff0c;IP核支持表3-1中列出的命令。这些命令在Winbond、Micron和Spansion内存设备上具有相同的命令、地址和数据行为。 某些命令&#xff0c;如fast read、dual I/…

从年金理论到杠杆效应,再到财务报表与投资评估指标

一、解释普通年金终值和普通年金现值的概念。 普通年金终值&#xff1a;以利率为1%&#xff0c;每期收款100元&#xff0c;5期为例&#xff0c;普通年金终值的折算过程如图&#xff1a; 普通年金现值&#xff1a;以利率为1%&#xff0c;每期收款100元&#xff0c;5期为例&am…

【RabbitMQ】RabbitMQ配置与交换机学习

【RabbitMQ】RabbitMQ配置与交换机学习 文章目录 【RabbitMQ】RabbitMQ配置与交换机学习简介安装和部署1. 安装RabbitMQ2.创建virtual-host3. 添加依赖4.修改配置文件 WorkQueues模型1.编写消息发送测试类2.编写消息接收&#xff08;监听&#xff09;类3. 实现能者多劳 交换机F…

(C++)string模拟实现

string底层是一个是字符数组 为了跟库里的string区别&#xff0c;所以定义一个命名空间将类string包含 一、构造 1.构造函数 注意&#xff1a;将char*传给const char*是范围缩小&#xff0c;因此只能1&#xff1a;1构造一个 strlen遇到nullptr解引用会报错&#xff0c;因此…

C++类与对象(拷贝与类的内存管理)

感谢大佬的光临各位&#xff0c;希望和大家一起进步&#xff0c;望得到你的三连&#xff0c;互三支持&#xff0c;一起进步 个人主页&#xff1a;LaNzikinh-CSDN博客 文章目录 前言一.对象的动态建立和释放二.多个对象的构造和析构三.深拷贝与浅拷贝四.C类的内存管理总结 前言 …

element-plus日历组件el-calendar自定义内容,每天绑定不同的值

效果 代码 <template><el-calendar v-model"calendarDate"><template #date-cell"{ data }"><p :class"data.isSelected ? is-selected : ">{{ data.day.split("-").slice(1).join("-") }}{{ d…

LE Audio音频广播新功能Auracast介绍

LE Audio音频广播新功能Auracast介绍 /*! \copyright Copyright (c) 2019-2022 Qualcomm Technologies International, Ltd. All Rights Reserved. Qualcomm Technologies International, Ltd. Confidential and Proprietary. \file audio_sources.h \defgroup audio_so…

最新网赚骗局详细解析

最新网赚骗局多种多样&#xff0c;而且随着技术的发展&#xff0c;这些骗局也在不断演变和升级。以下是一些揭秘最新网赚骗局的要点&#xff1a; 1. 刷单返利类诈骗&#xff1a;骗子往往通过微信群、QQ群等社交平台发布虚假的刷单任务&#xff0c;承诺完成刷单任务后可以获得高…

IDEA创建SpringBoot项目的时候,如何使用Java8,怎么办?

在创建springboot项目的时候,IDEA提示&#xff0c;最低Java版本要求17&#xff0c;但是实际上我们可能不需要这么高的版本&#xff0c;怎么使用Java8呢&#xff1f; 解决办法 修改Server URL地址即可&#xff1a;https://start.aliyun.com

pika消费者退出

pika消费者退出问题 https://stackoverflow.com/questions/32220057/interrupt-thread-with-start-consuming-method-of-pika import threading import pikaclass WorkerThread(threading.Thread):def __init__(self):super(WorkerThread, self).__init__()self._is_interrupte…

天才程序员周弈帆 | Stable Diffusion 解读(二):论文精读

本文来源公众号“天才程序员周弈帆”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;Stable Diffusion 解读&#xff08;二&#xff09;&#xff1a;论文精读 【小小题外话】端午安康&#xff01; 在上一篇文章天才程序员周弈帆 …

【数据结构】排序(上)

个人主页~ 堆排序看这篇~ 还有这篇~ 排序 一、排序的概念及应用1、概念2、常见的排序算法 二、常见排序的实现1、直接插入排序&#xff08;1&#xff09;基本思想&#xff08;2&#xff09;代码实现&#xff08;3&#xff09;时间复杂度&#xff08;4&#xff09;空间复杂度 2…

【设计模式】创建型设计模式之 工厂模式

一、介绍 工厂模式可以分为 3 个小类 简单工厂模式工厂方法模式抽象工厂模式 工厂模式的工厂类&#xff0c;并不一定以 Factory 结尾&#xff0c;例如 DataFormat、Calender 他们都是工厂类&#xff0c;通过静态方法来创建实例。 除此之外&#xff0c;创建对象的方法名称一…

VBA即用型代码手册:删除重复行Delete Duplicate Rows

我给VBA下的定义&#xff1a;VBA是个人小型自动化处理的有效工具。可以大大提高自己的劳动效率&#xff0c;而且可以提高数据的准确性。我这里专注VBA,将我多年的经验汇集在VBA系列九套教程中。 作为我的学员要利用我的积木编程思想&#xff0c;积木编程最重要的是积木如何搭建…

电商项目-day01

文章目录 虚拟机的导入 虚拟机的导入 现根据镜像文件进行导入

streamlit:如何快速构建一个应用,不会前端也能写出好看的界面

通过本文你可以了解到&#xff1a; 如何安装streamlit&#xff0c;运行起来第一个demo熟悉streamlit的基本语法&#xff0c;常用的一些组件使用streamlit库构建应用 大模型学习参考&#xff1a; 大模型学习资料整理&#xff1a;如何从0到1学习大模型&#xff0c;搭建个人或企业…

(二)深度学习基础练习题(54道选择题)

本文整理了深度学习基础知识相关的练习题&#xff0c;共54道&#xff0c;适用于想巩固深度学习基础的同学。来源&#xff1a;如荷学数据科学题库&#xff08;技术专项-深度学习&#xff09;。 1&#xff09; 2&#xff09; 3&#xff09; 4&#xff09; 5&#xff09; 6&#…

音程与和弦 音程协和度

2个音符之间的音程计算 1234567&#xff0c;1到7的音程是7度&#xff0c;音程是计算总长度&#xff0c;看音级的个数。 Cubase中的音程计算 下面一个是4度&#xff0c;一个是3度&#xff0c;格子中深色的行就是黑键行。 根据半音数量来确定对应音程的专业术语叫法 旋律音程、…

用咖啡来理解springboot3的自动配置机制

大家好&#xff0c;这里是教授.F 目录 前提知识&#xff1a; 场景引入&#xff1a; 1.Starter依赖&#xff1a; 2.默认配置&#xff1a; 3.自定义配置&#xff1a; 4.条件化配置&#xff1a; 5.自动装配&#xff1a; 具体过程&#xff1a; 扫包路径的配置&#xff1a; 配置…

解锁ChatGPT:从GPT-2实践入手解密ChatGPT

⭐️我叫忆_恒心&#xff0c;一名喜欢书写博客的研究生&#x1f468;‍&#x1f393;。 如果觉得本文能帮到您&#xff0c;麻烦点个赞&#x1f44d;呗&#xff01; 近期会不断在专栏里进行更新讲解博客~~~ 有什么问题的小伙伴 欢迎留言提问欧&#xff0c;喜欢的小伙伴给个三连支…