First Commit
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
build
|
||||
!.vscode/*
|
||||
@@ -0,0 +1,92 @@
|
||||
# Generated Cmake Pico project file
|
||||
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(CMAKE_C_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# Initialise pico_sdk from installed location
|
||||
# (note this can come from environment, CMake cache etc)
|
||||
|
||||
# == DO NOT EDIT THE FOLLOWING LINES for the Raspberry Pi Pico VS Code Extension to work ==
|
||||
if (WIN32)
|
||||
set(USERHOME $ENV{USERPROFILE})
|
||||
else ()
|
||||
set(USERHOME $ENV{HOME})
|
||||
endif ()
|
||||
set(sdkVersion 2.2.0)
|
||||
set(toolchainVersion 14_2_Rel1)
|
||||
set(picotoolVersion 2.2.0-a4)
|
||||
set(picoVscode ${USERHOME}/.pico-sdk/cmake/pico-vscode.cmake)
|
||||
if (EXISTS ${picoVscode})
|
||||
include(${picoVscode})
|
||||
endif ()
|
||||
# ====================================================================================
|
||||
set(PICO_BOARD pico2_w CACHE STRING "Board type")
|
||||
|
||||
# Pull in Raspberry Pi Pico SDK (must be before project)
|
||||
include(pico_sdk_import.cmake)
|
||||
|
||||
project(ds5-bridge C CXX ASM)
|
||||
|
||||
# Initialise the Raspberry Pi Pico SDK
|
||||
pico_sdk_init()
|
||||
|
||||
# Add executable. Default name is the project name, version 0.1
|
||||
|
||||
add_executable(ds5-bridge
|
||||
src/usb_descriptors.c
|
||||
src/main.cpp
|
||||
src/bt.cpp
|
||||
src/usb.cpp
|
||||
src/audio.cpp
|
||||
)
|
||||
|
||||
add_library(wdl_resampler STATIC
|
||||
lib/WDL/resample.cpp
|
||||
)
|
||||
|
||||
target_include_directories(wdl_resampler PUBLIC
|
||||
lib/WDL
|
||||
)
|
||||
|
||||
target_compile_options(wdl_resampler PRIVATE -fsigned-char)
|
||||
target_compile_options(ds5-bridge PRIVATE -fsigned-char)
|
||||
|
||||
pico_set_program_name(ds5-bridge "ds5-bridge")
|
||||
pico_set_program_version(ds5-bridge "0.1")
|
||||
|
||||
# Modify the below lines to enable/disable output over UART/USB
|
||||
pico_enable_stdio_uart(ds5-bridge 0)
|
||||
pico_enable_stdio_usb(ds5-bridge 1)
|
||||
|
||||
# Add the standard library to the build
|
||||
target_link_libraries(ds5-bridge
|
||||
pico_stdlib
|
||||
pico_multicore
|
||||
)
|
||||
|
||||
# Add the standard include files to the build
|
||||
#target_include_directories(ds5-bridge PRIVATE
|
||||
# ${CMAKE_CURRENT_LIST_DIR}
|
||||
#)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
)
|
||||
|
||||
# Add any user requested libraries
|
||||
target_link_libraries(ds5-bridge
|
||||
hardware_interp
|
||||
hardware_timer
|
||||
pico_btstack_classic
|
||||
pico_cyw43_arch_none
|
||||
pico_btstack_cyw43
|
||||
tinyusb_device
|
||||
tinyusb_board
|
||||
wdl_resampler
|
||||
)
|
||||
|
||||
pico_add_extra_outputs(ds5-bridge)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Pico2W DualSense 5 Bridge
|
||||
> 将 Pico2W 变成 DS5 手柄的无线适配器
|
||||
|
||||
# 功能特点
|
||||
- 支持HD震动
|
||||
|
||||
# 使用方法
|
||||
1. 按住 Pico 上的BOOTSEL进入刷机
|
||||
2. 将 .uf2 文件拖入进去
|
||||
3. 将 DS5 手柄进入蓝牙配对模式
|
||||
4. Enjoy it
|
||||
|
||||
# 当前问题:
|
||||
1. 使用PS键进行重连时,会配对失败
|
||||
2. 陀螺仪功能在游戏异常(但是在线检测工具正常)
|
||||
|
||||
# 未来计划
|
||||
1. 耳机与扬声器支持
|
||||
|
||||
# 致谢
|
||||
- [rafaelvaloto/Pico_W-Dualsense](https://github.com/rafaelvaloto/Pico_W-Dualsense) - 灵感来源
|
||||
- [egormanga/SAxense](https://github.com/egormanga/SAxense) - 震动报文
|
||||
@@ -0,0 +1,121 @@
|
||||
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||
|
||||
# This can be dropped into an external project to help locate this SDK
|
||||
# It should be include()ed prior to project()
|
||||
|
||||
# Copyright 2020 (c) 2020 Raspberry Pi (Trading) Ltd.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
|
||||
# following conditions are met:
|
||||
#
|
||||
# 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
|
||||
# disclaimer.
|
||||
#
|
||||
# 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
|
||||
# disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
#
|
||||
# 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
|
||||
# derived from this software without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||
# INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
|
||||
# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||
endif ()
|
||||
|
||||
if (PICO_SDK_FETCH_FROM_GIT AND NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||
message("Using master as default value for PICO_SDK_FETCH_FROM_GIT_TAG")
|
||||
endif()
|
||||
|
||||
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
|
||||
set(PICO_SDK_FETCH_FROM_GIT_TAG "${PICO_SDK_FETCH_FROM_GIT_TAG}" CACHE FILEPATH "release tag for SDK")
|
||||
|
||||
if (NOT PICO_SDK_PATH)
|
||||
if (PICO_SDK_FETCH_FROM_GIT)
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR})
|
||||
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif ()
|
||||
FetchContent_Declare(
|
||||
pico_sdk
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
)
|
||||
|
||||
if (NOT pico_sdk)
|
||||
message("Downloading Raspberry Pi Pico SDK")
|
||||
# GIT_SUBMODULES_RECURSE was added in 3.17
|
||||
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.17.0")
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
GIT_SUBMODULES_RECURSE FALSE
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
else ()
|
||||
FetchContent_Populate(
|
||||
pico_sdk
|
||||
QUIET
|
||||
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||
|
||||
SOURCE_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-src
|
||||
BINARY_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-build
|
||||
SUBBUILD_DIR ${FETCHCONTENT_BASE_DIR}/pico_sdk-subbuild
|
||||
)
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||
endif ()
|
||||
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE})
|
||||
else ()
|
||||
message(FATAL_ERROR
|
||||
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||
)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_BINARY_DIR}")
|
||||
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||
endif ()
|
||||
|
||||
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||
|
||||
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||
@@ -0,0 +1,96 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/5.
|
||||
//
|
||||
|
||||
#include "audio.h"
|
||||
#include "bt.h"
|
||||
#include "resample.h"
|
||||
#include "tusb.h"
|
||||
#include "pico/time.h"
|
||||
#include <algorithm>
|
||||
|
||||
#define INPUT_CHANNELS 4
|
||||
#define OUTPUT_CHANNELS 2
|
||||
#define SAMPLE_SIZE 64
|
||||
#define REPORT_SIZE 142
|
||||
#define REPORT_ID 0x32
|
||||
|
||||
static WDL_Resampler resampler;
|
||||
static uint8_t reportSeqCounter = 0;
|
||||
static uint8_t packetCounter = 0;
|
||||
|
||||
void process_audio() {
|
||||
// 1. 读取 USB 音频数据
|
||||
if (!tud_audio_available()){
|
||||
return;
|
||||
}
|
||||
|
||||
int16_t raw[1024];
|
||||
uint32_t bytes_read = tud_audio_read(raw, sizeof(raw));
|
||||
int frames = bytes_read / (INPUT_CHANNELS * sizeof(int16_t));
|
||||
if (frames == 0){
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. 从4ch中提取ch3/ch4,转换为float输入重采样器
|
||||
WDL_ResampleSample *in_buf;
|
||||
int nsamples = resampler.ResamplePrepare(frames, OUTPUT_CHANNELS, &in_buf);
|
||||
|
||||
for (int i = 0; i < nsamples; i++) {
|
||||
in_buf[i * 2] = (WDL_ResampleSample) raw[i * INPUT_CHANNELS + 2] / 32768.0f;
|
||||
in_buf[i * 2 + 1] = (WDL_ResampleSample) raw[i * INPUT_CHANNELS + 3] / 32768.0f;
|
||||
}
|
||||
|
||||
// 3. 48kHz -> 3kHz 重采样
|
||||
WDL_ResampleSample out_buf[SAMPLE_SIZE]; // 64 floats = 32帧 × 2ch
|
||||
int out_frames = resampler.ResampleOut(out_buf, nsamples, SAMPLE_SIZE / OUTPUT_CHANNELS, OUTPUT_CHANNELS);
|
||||
|
||||
static int8_t haptic_buf[SAMPLE_SIZE];
|
||||
static int haptic_buf_pos = 0;
|
||||
|
||||
// 4. 转换为int8并缓冲,满64字节即组包发送
|
||||
for (int i = 0; i < out_frames; i++) {
|
||||
int val_l = (int) (out_buf[i * 2] * 254.0f);
|
||||
int val_r = (int) (out_buf[i * 2 + 1] * 254.0f);
|
||||
haptic_buf[haptic_buf_pos++] = (int8_t) std::clamp(val_l, -128, 127);
|
||||
haptic_buf[haptic_buf_pos++] = (int8_t) std::clamp(val_r, -128, 127);
|
||||
|
||||
if (haptic_buf_pos != SAMPLE_SIZE) {
|
||||
continue;
|
||||
}
|
||||
uint8_t pkt[REPORT_SIZE] = {};
|
||||
pkt[0] = REPORT_ID;
|
||||
pkt[1] = reportSeqCounter << 4;
|
||||
reportSeqCounter = (reportSeqCounter + 1) & 0x0F;
|
||||
pkt[2] = 0x11 | (1 << 7);
|
||||
pkt[3] = 7;
|
||||
pkt[4] = 0b11111110;
|
||||
pkt[5] = 0;
|
||||
pkt[6] = 0;
|
||||
pkt[7] = 0;
|
||||
pkt[8] = 0;
|
||||
pkt[9] = 0xFF;
|
||||
pkt[10] = packetCounter++;
|
||||
pkt[11] = 0x12 | (1 << 7);
|
||||
pkt[12] = SAMPLE_SIZE;
|
||||
memcpy(pkt + 13, haptic_buf, SAMPLE_SIZE);
|
||||
|
||||
bt_write(pkt, sizeof(pkt));
|
||||
haptic_buf_pos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void core1_entry() {
|
||||
resampler.SetMode(true, 0, false);
|
||||
resampler.SetRates(48000, 3000);
|
||||
resampler.SetFeedMode(true);
|
||||
resampler.Prealloc(2, 480, 32);
|
||||
|
||||
while (1) {
|
||||
if (!tud_ready()) {
|
||||
sleep_ms(10);
|
||||
continue;
|
||||
}
|
||||
process_audio();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/5.
|
||||
//
|
||||
|
||||
#ifndef DS5_BRIDGE_AUDIO_H
|
||||
#define DS5_BRIDGE_AUDIO_H
|
||||
|
||||
void core1_entry();
|
||||
|
||||
#endif //DS5_BRIDGE_AUDIO_H
|
||||
+387
@@ -0,0 +1,387 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/4.
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
#include "bt.h"
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "btstack_event.h"
|
||||
#include "l2cap.h"
|
||||
#include "pico/cyw43_arch.h"
|
||||
#include "pico/stdio.h"
|
||||
#include "utils.h"
|
||||
|
||||
static btstack_packet_callback_registration_t hci_event_callback_registration,l2cap_event_callback_registration;
|
||||
static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size);
|
||||
static bd_addr_t current_device_addr;
|
||||
static bool device_found = false;
|
||||
static uint16_t hid_control_cid;
|
||||
static uint16_t hid_interrupt_cid;
|
||||
static bt_data_callback_t bt_data_callback = NULL;
|
||||
std::unordered_map<uint8_t, std::vector<uint8_t>> feature_data;
|
||||
|
||||
void bt_register_data_callback(bt_data_callback_t callback) {
|
||||
bt_data_callback = callback;
|
||||
}
|
||||
|
||||
void bt_send_packet(uint8_t *data, uint16_t len) {
|
||||
if (hid_interrupt_cid != 0) {
|
||||
l2cap_send(hid_interrupt_cid, data, len);
|
||||
}
|
||||
}
|
||||
|
||||
void bt_send_control(uint8_t *data, uint16_t len) {
|
||||
if (hid_control_cid != 0) {
|
||||
l2cap_send(hid_control_cid, data, len);
|
||||
}
|
||||
}
|
||||
|
||||
int bt_init() {
|
||||
if (cyw43_arch_init()) {
|
||||
printf("Failed to initialize CYW43\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
l2cap_init();
|
||||
gap_connectable_control(1);
|
||||
gap_discoverable_control(1);
|
||||
l2cap_register_service(l2cap_packet_handler, PSM_HID_CONTROL, 0xffff, LEVEL_2);
|
||||
l2cap_register_service(l2cap_packet_handler, PSM_HID_INTERRUPT, 0xffff, LEVEL_2);
|
||||
|
||||
hci_event_callback_registration.callback = &hci_packet_handler;
|
||||
hci_add_event_handler(&hci_event_callback_registration);
|
||||
l2cap_event_callback_registration.callback = & l2cap_packet_handler;
|
||||
l2cap_add_event_handler(&l2cap_event_callback_registration);
|
||||
|
||||
hci_power_control(HCI_POWER_ON);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*int main() {
|
||||
stdio_init_all();
|
||||
|
||||
while (!stdio_usb_connected()) {
|
||||
sleep_ms(100);
|
||||
}
|
||||
printf("USB Serial connected!\n");
|
||||
|
||||
init();
|
||||
|
||||
while (1) {
|
||||
sleep_ms(10);
|
||||
}
|
||||
}*/
|
||||
|
||||
static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
(void) channel;
|
||||
|
||||
const uint8_t event_type = hci_event_packet_get_type(packet);
|
||||
|
||||
switch (event_type) {
|
||||
case BTSTACK_EVENT_STATE: {
|
||||
const uint8_t state = btstack_event_state_get_state(packet);
|
||||
printf("[BT] State: %u\n",state);
|
||||
if (state == HCI_STATE_WORKING) {
|
||||
printf("[BT] Stack ready, start inquiry\n");
|
||||
gap_inquiry_start(30);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HCI_EVENT_INQUIRY_RESULT:
|
||||
case HCI_EVENT_INQUIRY_RESULT_WITH_RSSI:
|
||||
case HCI_EVENT_EXTENDED_INQUIRY_RESPONSE: {
|
||||
bd_addr_t addr;
|
||||
uint32_t cod;
|
||||
|
||||
if (event_type == HCI_EVENT_INQUIRY_RESULT) {
|
||||
cod = hci_event_inquiry_result_get_class_of_device(packet);
|
||||
hci_event_inquiry_result_get_bd_addr(packet, addr);
|
||||
} else if (event_type == HCI_EVENT_INQUIRY_RESULT_WITH_RSSI) {
|
||||
cod = hci_event_inquiry_result_with_rssi_get_class_of_device(packet);
|
||||
hci_event_inquiry_result_with_rssi_get_bd_addr(packet, addr);
|
||||
} else {
|
||||
cod = hci_event_extended_inquiry_response_get_class_of_device(packet);
|
||||
hci_event_extended_inquiry_response_get_bd_addr(packet, addr);
|
||||
}
|
||||
|
||||
// CoD 0x002508 = Gamepad (Major: Peripheral, Minor: Gamepad)
|
||||
if ((cod & 0x000F00) == 0x000500) {
|
||||
printf("[HCI] Gamepad found: %s (CoD: 0x%06x)\n", bd_addr_to_str(addr), (unsigned int)cod);
|
||||
bd_addr_copy(current_device_addr, addr);
|
||||
device_found = true;
|
||||
gap_inquiry_stop();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GAP_EVENT_INQUIRY_COMPLETE:
|
||||
case HCI_EVENT_INQUIRY_COMPLETE: {
|
||||
printf("[HCI] Inquiry complete\n");
|
||||
if (device_found) {
|
||||
printf("[HCI] Connecting to %s...\n", bd_addr_to_str(current_device_addr));
|
||||
hci_send_cmd(&hci_create_connection, current_device_addr,
|
||||
hci_usable_acl_packet_types(), 0, 0, 0, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case HCI_EVENT_COMMAND_STATUS: {
|
||||
const uint8_t status = hci_event_command_status_get_status(packet);
|
||||
const uint16_t opcode = hci_event_command_status_get_command_opcode(packet);
|
||||
printf("[HCI] CmdStatus %s(0x%04X) status=0x%02X\n", opcode_to_str(opcode), opcode, status);
|
||||
if (opcode == HCI_OPCODE_HCI_CREATE_CONNECTION && status != ERROR_CODE_SUCCESS) {
|
||||
device_found = false;
|
||||
printf("[HCI] Create connection rejected, restart inquiry\n");
|
||||
gap_inquiry_start(30);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_COMMAND_COMPLETE: {
|
||||
const uint8_t status = hci_event_command_complete_get_return_parameters(packet)[0];
|
||||
const uint16_t opcode = hci_event_command_complete_get_command_opcode(packet);
|
||||
printf("[HCI] CmdComplete %s(0x%04X) status=0x%02X\n", opcode_to_str(opcode), opcode, status);
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_CONNECTION_COMPLETE: {
|
||||
const uint8_t status = hci_event_connection_complete_get_status(packet);
|
||||
if (status == 0) {
|
||||
const hci_con_handle_t handle = hci_event_connection_complete_get_connection_handle(packet);
|
||||
hci_event_connection_complete_get_bd_addr(packet, current_device_addr);
|
||||
printf("[HCI] ACL connected handle=0x%04X\n", handle);
|
||||
printf("[HCI] Request authentication on handle=0x%04X\n", handle);
|
||||
hci_send_cmd(&hci_authentication_requested, handle);
|
||||
} else {
|
||||
device_found = false;
|
||||
printf("[HCI] ACL connect failed status=0x%02X, restart inquiry\n", status);
|
||||
gap_inquiry_start(30);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_LINK_KEY_REQUEST: {
|
||||
bd_addr_t addr;
|
||||
hci_event_link_key_request_get_bd_addr(packet, addr);
|
||||
link_key_t link_key;
|
||||
link_key_type_t link_key_type;
|
||||
if (gap_get_link_key_for_bd_addr(addr, link_key, &link_key_type)) {
|
||||
printf("[HCI] Link key request from %s, reply stored key type=%u\n", bd_addr_to_str(addr),
|
||||
(unsigned int)link_key_type);
|
||||
hci_send_cmd(&hci_link_key_request_reply, addr, link_key);
|
||||
} else {
|
||||
printf("[HCI] Link key request from %s, no key, force re-pair\n", bd_addr_to_str(addr));
|
||||
hci_send_cmd(&hci_link_key_request_negative_reply, addr);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_USER_CONFIRMATION_REQUEST: {
|
||||
bd_addr_t addr;
|
||||
hci_event_user_confirmation_request_get_bd_addr(packet, addr);
|
||||
printf("[HCI] User confirmation request from %s, accept\n", bd_addr_to_str(addr));
|
||||
hci_send_cmd(&hci_user_confirmation_request_reply, addr);
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_PIN_CODE_REQUEST: {
|
||||
bd_addr_t addr;
|
||||
hci_event_pin_code_request_get_bd_addr(packet, addr);
|
||||
printf("[HCI] Legacy pin request from %s, reply 0000\n", bd_addr_to_str(addr));
|
||||
gap_pin_code_response(addr, "0000");
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_AUTHENTICATION_COMPLETE: {
|
||||
const uint8_t status = hci_event_authentication_complete_get_status(packet);
|
||||
const hci_con_handle_t handle = hci_event_authentication_complete_get_connection_handle(packet);
|
||||
printf("[HCI] Authentication complete handle=0x%04X status=0x%02X\n", handle, status);
|
||||
if (status != ERROR_CODE_SUCCESS) {
|
||||
printf("[HCI] Authentication failed, drop stored key for %s\n", bd_addr_to_str(current_device_addr));
|
||||
gap_drop_link_key_for_bd_addr(current_device_addr);
|
||||
}else {
|
||||
hci_send_cmd(&hci_set_connection_encryption, handle, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_ENCRYPTION_CHANGE: {
|
||||
const uint8_t status = hci_event_encryption_change_get_status(packet);
|
||||
const hci_con_handle_t handle = hci_event_encryption_change_get_connection_handle(packet);
|
||||
const uint8_t enabled = hci_event_encryption_change_get_encryption_enabled(packet);
|
||||
printf("[HCI] Encryption change handle=0x%04X status=0x%02X enabled=%u\n", handle, status, enabled);
|
||||
if (status == ERROR_CODE_SUCCESS && enabled) {
|
||||
printf("[L2CAP] Open HID channels\n");
|
||||
if (hid_control_cid == 0) {
|
||||
l2cap_create_channel(l2cap_packet_handler, current_device_addr, PSM_HID_CONTROL, 0xffff, &hid_control_cid);
|
||||
} else if (hid_interrupt_cid == 0){
|
||||
l2cap_create_channel(l2cap_packet_handler,current_device_addr,PSM_HID_INTERRUPT,0xffff,&hid_interrupt_cid);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_CONNECTION_REQUEST: {
|
||||
bd_addr_t addr;
|
||||
hci_event_connection_request_get_bd_addr(packet, addr);
|
||||
const uint32_t cod = hci_event_connection_request_get_class_of_device(packet);
|
||||
printf("[HCI] Incoming ACL request from %s cod=0x%06x\n", bd_addr_to_str(addr), (unsigned int) cod);
|
||||
if ((cod & 0x000F00) == 0x000500) {
|
||||
bd_addr_copy(current_device_addr, addr);
|
||||
gap_inquiry_stop();
|
||||
hci_send_cmd(&hci_accept_connection_request, addr, 0x01);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case HCI_EVENT_DISCONNECTION_COMPLETE: {
|
||||
const uint8_t reason = hci_event_disconnection_complete_get_reason(packet);
|
||||
device_found = false;
|
||||
hid_control_cid = 0;
|
||||
hid_interrupt_cid = 0;
|
||||
feature_data.clear();
|
||||
printf("[HCI] Disconnected reason=0x%02X, restart inquiry\n", reason);
|
||||
gap_inquiry_start(30);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *packet, uint16_t size) {
|
||||
(void) channel;
|
||||
|
||||
if (packet_type == L2CAP_DATA_PACKET) {
|
||||
if (channel == hid_interrupt_cid) {
|
||||
printf("[L2CAP] HID Interrupt data len=%u\n", size);
|
||||
printf_hexdump(packet, size);
|
||||
bt_data_callback(INTERRUPT, packet, size);
|
||||
} else if (channel == hid_control_cid) {
|
||||
if (size > 1 && packet[0] == 0xA3) {
|
||||
uint8_t report_id = packet[1];
|
||||
feature_data[report_id].assign(packet + 1, packet + size);
|
||||
printf("[L2CAP] Stored Feature Report %d, len=%u\n", report_id, size - 1);
|
||||
}
|
||||
printf("[L2CAP] HID Control data len=%u\n", size);
|
||||
printf_hexdump(packet, size);
|
||||
bt_data_callback(CONTROL, packet, size);
|
||||
} else {
|
||||
printf("[L2CAP] Data on unknown channel 0x%04X (Interrupt: 0x%04X, Control: 0x%04X)\n", channel, hid_interrupt_cid, hid_control_cid);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint8_t event_type = hci_event_packet_get_type(packet);
|
||||
switch (event_type) {
|
||||
case L2CAP_EVENT_CHANNEL_OPENED: {
|
||||
const uint8_t status = l2cap_event_channel_opened_get_status(packet);
|
||||
if (status == 0) {
|
||||
const uint16_t psm = l2cap_event_channel_opened_get_psm(packet);
|
||||
|
||||
if (psm == PSM_HID_CONTROL) {
|
||||
printf("[L2CAP] HID Control opened cid=0x%04X\n", hid_control_cid);
|
||||
} else if (psm == PSM_HID_INTERRUPT) {
|
||||
printf("[L2CAP] HID Interrupt opened cid=0x%04X\n", hid_interrupt_cid);
|
||||
|
||||
printf("Ready\n");
|
||||
uint8_t get_feature[41] = {
|
||||
0x43,
|
||||
0x05
|
||||
};
|
||||
l2cap_send(hid_control_cid, get_feature, 41);
|
||||
|
||||
uint8_t report32[142];
|
||||
report32[0] = 0x32;
|
||||
report32[1] = 0x10;
|
||||
uint8_t packet_0x10[] =
|
||||
{
|
||||
0x90, // Packet: 0x10
|
||||
0x3f, // 63
|
||||
// SetStateData
|
||||
0xfd, 0xf7, 0x0, 0x0, 0x7f, 0x7f,
|
||||
0xff, 0x9, 0x0, 0xf, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xa,
|
||||
0x7, 0x0, 0x0, 0x2, 0x1,
|
||||
0x00,
|
||||
0xff,0xd7,0x00 // RGB LED: R, G, B
|
||||
};
|
||||
memcpy(report32 + 2,packet_0x10,sizeof(packet_0x10));
|
||||
bt_write(report32,sizeof(report32));
|
||||
init_feature();
|
||||
}
|
||||
|
||||
if (hid_control_cid != 0 && hid_interrupt_cid != 0) {
|
||||
printf("[L2CAP] HID channels ready, request CAN_SEND_NOW for SET_PROTOCOL\n");
|
||||
l2cap_request_can_send_now_event(hid_control_cid);
|
||||
}
|
||||
} else {
|
||||
const uint16_t psm = l2cap_event_channel_opened_get_psm(packet);
|
||||
hid_control_cid = 0;
|
||||
hid_interrupt_cid = 0;
|
||||
device_found = false;
|
||||
printf("[L2CAP] Open failed psm=0x%04X status=0x%02X\n", psm, status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case L2CAP_EVENT_INCOMING_CONNECTION: {
|
||||
const uint16_t local_cid = l2cap_event_incoming_connection_get_local_cid(packet);
|
||||
const uint16_t psm = l2cap_event_incoming_connection_get_psm(packet);
|
||||
printf("[L2CAP] Incoming connection psm=0x%04X cid=0x%04X\n", psm, local_cid);
|
||||
l2cap_accept_connection(local_cid);
|
||||
break;
|
||||
}
|
||||
|
||||
case L2CAP_EVENT_CHANNEL_CLOSED: {
|
||||
const uint16_t local_cid = l2cap_event_channel_closed_get_local_cid(packet);
|
||||
if (local_cid == hid_control_cid) {
|
||||
hid_control_cid = 0;
|
||||
printf("[L2CAP] HID Control closed cid=0x%04X\n", local_cid);
|
||||
} else if (local_cid == hid_interrupt_cid) {
|
||||
hid_interrupt_cid = 0;
|
||||
printf("[L2CAP] HID Interrupt closed cid=0x%04X\n", local_cid);
|
||||
} else {
|
||||
printf("[L2CAP] Channel closed cid=0x%04X\n", local_cid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case L2CAP_EVENT_CAN_SEND_NOW: {
|
||||
printf("L2CAP_EVENT_CAN_SEND_NOW\n");
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void bt_write(uint8_t* data,uint16_t len) {
|
||||
if (!device_found) return;
|
||||
uint8_t buffer[len + 1];
|
||||
buffer[0] = 0xA2;
|
||||
memcpy(buffer + 1,data,len);
|
||||
fill_output_report_checksum(buffer + 1,len);
|
||||
l2cap_send(hid_interrupt_cid,buffer,sizeof(buffer));
|
||||
}
|
||||
|
||||
uint8_t* get_feature_data(uint8_t reportId,uint16_t len) {
|
||||
if (feature_data.find(reportId) == feature_data.end() || feature_data[reportId].empty()) {
|
||||
if (hid_control_cid != 0) {
|
||||
uint8_t get_feature[] = {0x43, reportId};
|
||||
l2cap_send(hid_control_cid, get_feature, len);
|
||||
printf("[L2CAP] Requesting Feature Report 0x%02X\n", reportId);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return feature_data[reportId].data();
|
||||
}
|
||||
|
||||
void init_feature() {
|
||||
get_feature_data(0x09,20);
|
||||
get_feature_data(0x20,64);
|
||||
get_feature_data(0x05,41);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/4.
|
||||
//
|
||||
|
||||
#ifndef DS5_BRIDGE_BT_H
|
||||
#define DS5_BRIDGE_BT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
enum CHANNEL_TYPE {
|
||||
INTERRUPT,
|
||||
CONTROL
|
||||
};
|
||||
|
||||
typedef void (*bt_data_callback_t)(CHANNEL_TYPE channel, uint8_t *data, uint16_t len);
|
||||
|
||||
int bt_init();
|
||||
void bt_register_data_callback(bt_data_callback_t callback);
|
||||
void bt_send_packet(uint8_t *data, uint16_t len);
|
||||
void bt_send_control(uint8_t *data, uint16_t len);
|
||||
void bt_write(uint8_t* data,uint16_t len);
|
||||
uint8_t* get_feature_data(uint8_t reportId,uint16_t len);
|
||||
void init_feature();
|
||||
|
||||
#endif //DS5_BRIDGE_BT_H
|
||||
@@ -0,0 +1,42 @@
|
||||
// https://github.com/rafaelvaloto/Pico_W-Dualsense/blob/main/btstack_config.h
|
||||
|
||||
// c
|
||||
#ifndef BTSTACK_CONFIG_H
|
||||
#define BTSTACK_CONFIG_H
|
||||
|
||||
#define ENABLE_L2CAP_ENHANCED_RETRANSMISSION_MODE
|
||||
#ifndef ENABLE_CLASSIC
|
||||
#define ENABLE_CLASSIC
|
||||
#endif
|
||||
|
||||
|
||||
// CYW43 HCI Transport requires pre-buffer space for packet header
|
||||
|
||||
// Se estiver 1 ou 2, o 0x31 do DualSense causa estouro
|
||||
#define MAX_NR_HCI_ACL_PACKETS 4
|
||||
|
||||
#define MAX_NR_HCI_CONNECTIONS 4
|
||||
#define MAX_NR_L2CAP_CHANNELS 4
|
||||
#define MAX_NR_L2CAP_SERVICES 4
|
||||
//
|
||||
#define HCI_ACL_PAYLOAD_SIZE 256
|
||||
#define HCI_ACL_CHUNK_SIZE_ALIGNMENT 4
|
||||
#define HCI_OUTGOING_PRE_BUFFER_SIZE 4
|
||||
|
||||
|
||||
#define MAX_NR_RFCOMM_MULTIPLEXERS 0
|
||||
#define MAX_NR_RFCOMM_SERVICES 0
|
||||
#define MAX_NR_RFCOMM_CHANNELS 0
|
||||
|
||||
// CYW43 específico - necessário para o transport layer
|
||||
|
||||
#define NVM_NUM_LINK_KEYS 4
|
||||
#define NVM_NUM_DEVICE_DB_ENTRIES 4
|
||||
#define HAVE_EMBEDDED_TIME_MS
|
||||
|
||||
// Logging
|
||||
#define ENABLE_PRINTF_HEXDUMP
|
||||
#define ENABLE_LOG_INFO
|
||||
#define ENABLE_LOG_ERROR
|
||||
|
||||
#endif
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/4.
|
||||
//
|
||||
|
||||
#include <cstdio>
|
||||
#include "bsp/board_api.h"
|
||||
#include "usb.h"
|
||||
#include "bt.h"
|
||||
#include "utils.h"
|
||||
#include "pico/multicore.h"
|
||||
#include "resample.h"
|
||||
#include "audio.h"
|
||||
|
||||
int reportSeqCounter = 0;
|
||||
uint8_t packetCounter = 0;
|
||||
|
||||
uint8_t interrupt_in_data[63];
|
||||
|
||||
bool interrupt_in_cb(repeating_timer* rt) {
|
||||
if (tud_ready()) {
|
||||
tud_hid_report(0x01,interrupt_in_data,63);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void on_bt_data(CHANNEL_TYPE channel, uint8_t *data, uint16_t len) {
|
||||
printf("[Main] BT data callback: channel=%u len=%u\n", channel, len);
|
||||
if (channel == INTERRUPT && data[1] == 0x31) {
|
||||
memcpy(interrupt_in_data,data + 3,63);
|
||||
}
|
||||
}
|
||||
|
||||
// Invoked when received GET_REPORT control request
|
||||
// Application must fill buffer report's content and return its length.
|
||||
// Return zero will cause the stack to STALL request
|
||||
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer,
|
||||
uint16_t reqlen) {
|
||||
(void) itf;
|
||||
(void) report_id;
|
||||
(void) report_type;
|
||||
(void) buffer;
|
||||
(void) reqlen;
|
||||
|
||||
uint8_t *feature_data = get_feature_data(report_id, reqlen);
|
||||
if (feature_data) {
|
||||
memcpy(buffer, feature_data, reqlen);
|
||||
}
|
||||
|
||||
return feature_data ? reqlen : 0;
|
||||
}
|
||||
|
||||
// Invoked when received SET_REPORT control request or
|
||||
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
|
||||
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer,
|
||||
uint16_t bufsize) {
|
||||
// This example doesn't use multiple report and report ID
|
||||
(void) itf;
|
||||
(void) report_id;
|
||||
(void) report_type;
|
||||
(void) buffer;
|
||||
(void) bufsize;
|
||||
|
||||
switch (buffer[0]) {
|
||||
case 0x02: {
|
||||
uint8_t outputData[78];
|
||||
outputData[0] = 0x31;
|
||||
outputData[1] = reportSeqCounter << 4;
|
||||
if (++reportSeqCounter == 256) {
|
||||
reportSeqCounter = 0;
|
||||
}
|
||||
outputData[2] = 0x10;
|
||||
memcpy(outputData + 3,buffer + 1,bufsize - 1);
|
||||
bt_write(outputData,sizeof(outputData));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
board_init();
|
||||
|
||||
tusb_rhport_init_t dev_init = {
|
||||
.role = TUSB_ROLE_DEVICE,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUD_RHPORT, &dev_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
bt_init();
|
||||
bt_register_data_callback(on_bt_data);
|
||||
|
||||
multicore_launch_core1(core1_entry);
|
||||
repeating_timer rt{};
|
||||
add_repeating_timer_ms(4,interrupt_in_cb,nullptr,&rt);
|
||||
|
||||
while (1) {
|
||||
tud_task();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2019 Ha Thach (tinyusb.org)
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TUSB_CONFIG_H_
|
||||
#define TUSB_CONFIG_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Board Specific Configuration
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// RHPort number used for device can be defined by board.mk, default to port 0
|
||||
#ifndef BOARD_TUD_RHPORT
|
||||
#define BOARD_TUD_RHPORT 0
|
||||
#endif
|
||||
|
||||
// RHPort max operational speed can defined by board.mk
|
||||
#ifndef BOARD_TUD_MAX_SPEED
|
||||
#define BOARD_TUD_MAX_SPEED OPT_MODE_DEFAULT_SPEED
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// COMMON CONFIGURATION
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
// defined by compiler flags for flexibility
|
||||
#ifndef CFG_TUSB_MCU
|
||||
#error CFG_TUSB_MCU must be defined
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_OS
|
||||
#define CFG_TUSB_OS OPT_OS_NONE
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_DEBUG
|
||||
#define CFG_TUSB_DEBUG 0
|
||||
#endif
|
||||
|
||||
// Enable Device stack
|
||||
#define CFG_TUD_ENABLED 1
|
||||
|
||||
// Default is max speed that hardware controller could support with on-chip PHY
|
||||
#define CFG_TUD_MAX_SPEED BOARD_TUD_MAX_SPEED
|
||||
|
||||
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
|
||||
* Tinyusb use follows macros to declare transferring memory so that they can be put
|
||||
* into those specific section.
|
||||
* e.g
|
||||
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
|
||||
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
|
||||
*/
|
||||
#ifndef CFG_TUSB_MEM_SECTION
|
||||
#define CFG_TUSB_MEM_SECTION
|
||||
#endif
|
||||
|
||||
#ifndef CFG_TUSB_MEM_ALIGN
|
||||
#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4)))
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// DEVICE CONFIGURATION
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
#ifndef CFG_TUD_ENDPOINT0_SIZE
|
||||
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||
#endif
|
||||
|
||||
//------------- CLASS -------------//
|
||||
#define CFG_TUD_AUDIO 1
|
||||
#define CFG_TUD_HID 1
|
||||
#define CFG_TUD_CDC 1
|
||||
#define CFG_TUD_MSC 0
|
||||
#define CFG_TUD_MIDI 0
|
||||
#define CFG_TUD_VENDOR 0
|
||||
|
||||
// HID buffer size Should be sufficient to hold ID (if any) + Data
|
||||
#define CFG_TUD_HID_EP_BUFSIZE 64
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
// AUDIO CLASS DRIVER CONFIGURATION
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX 4
|
||||
|
||||
// 16bit data in 16bit slots
|
||||
#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_RX 2
|
||||
#define CFG_TUD_AUDIO_FUNC_1_RESOLUTION_RX 16
|
||||
|
||||
// Microphone (IN/TX) path: 2-channel, 16-bit
|
||||
#define CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_TX 2
|
||||
#define CFG_TUD_AUDIO_FUNC_1_N_BYTES_PER_SAMPLE_TX 2
|
||||
#define CFG_TUD_AUDIO_FUNC_1_RESOLUTION_TX 16
|
||||
|
||||
// UAC1 Full-Speed endpoint size
|
||||
#define CFG_TUD_AUDIO_FUNC_1_SAMPLE_RATE 48000
|
||||
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SZ_MAX 392
|
||||
#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX 196
|
||||
|
||||
#define CFG_TUD_AUDIO_FUNC_1_EP_OUT_SW_BUF_SZ 2048
|
||||
#define CFG_TUD_AUDIO_FUNC_1_EP_IN_SW_BUF_SZ (4 * CFG_TUD_AUDIO_FUNC_1_EP_IN_SZ_MAX)
|
||||
|
||||
// Enable OUT EP (speaker) and IN EP (mic)
|
||||
#define CFG_TUD_AUDIO_ENABLE_EP_OUT 1
|
||||
#define CFG_TUD_AUDIO_ENABLE_EP_IN 1
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* TUSB_CONFIG_H_ */
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/4.
|
||||
//
|
||||
|
||||
#include "usb.h"
|
||||
#include "bsp/board_api.h"
|
||||
|
||||
uint8_t mute[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0
|
||||
int16_t volume[CFG_TUD_AUDIO_FUNC_1_N_CHANNELS_RX + 1]; // +1 for master channel 0
|
||||
#define UAC1_ENTITY_SPK_FEATURE_UNIT 0x02
|
||||
#define UAC1_ENTITY_MIC_FEATURE_UNIT 0x05
|
||||
|
||||
/*int main() {
|
||||
board_init();
|
||||
|
||||
tusb_rhport_init_t dev_init = {
|
||||
.role = TUSB_ROLE_DEVICE,
|
||||
.speed = TUSB_SPEED_AUTO
|
||||
};
|
||||
tusb_init(BOARD_TUD_RHPORT, &dev_init);
|
||||
|
||||
board_init_after_tusb();
|
||||
|
||||
while (1) {
|
||||
tud_task();
|
||||
}
|
||||
}*/
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Audio Callback Functions
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// UAC1 Helper Functions
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
static bool audio10_set_req_entity(tusb_control_request_t const *p_request, uint8_t *pBuff) {
|
||||
uint8_t channelNum = TU_U16_LOW(p_request->wValue);
|
||||
uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
|
||||
uint8_t entityID = TU_U16_HIGH(p_request->wIndex);
|
||||
|
||||
// If request is for our speaker feature unit
|
||||
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT) {
|
||||
switch (ctrlSel) {
|
||||
case AUDIO10_FU_CTRL_MUTE:
|
||||
switch (p_request->bRequest) {
|
||||
case AUDIO10_CS_REQ_SET_CUR:
|
||||
// Only 1st form is supported
|
||||
TU_VERIFY(p_request->wLength == 1);
|
||||
|
||||
mute[channelNum] = pBuff[0];
|
||||
|
||||
TU_LOG2(" Set Mute: %d of channel: %u\r\n", mute[channelNum], channelNum);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false; // not supported
|
||||
}
|
||||
|
||||
case AUDIO10_FU_CTRL_VOLUME:
|
||||
switch (p_request->bRequest) {
|
||||
case AUDIO10_CS_REQ_SET_CUR:
|
||||
// Only 1st form is supported
|
||||
TU_VERIFY(p_request->wLength == 2);
|
||||
|
||||
volume[channelNum] = (int16_t) tu_unaligned_read16(pBuff) / 256;
|
||||
|
||||
TU_LOG2(" Set Volume: %d dB of channel: %u\r\n", volume[channelNum], channelNum);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false; // not supported
|
||||
}
|
||||
|
||||
// Unknown/Unsupported control
|
||||
default:
|
||||
TU_BREAKPOINT();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool audio10_get_req_entity(uint8_t rhport, tusb_control_request_t const *p_request) {
|
||||
uint8_t channelNum = TU_U16_LOW(p_request->wValue);
|
||||
uint8_t ctrlSel = TU_U16_HIGH(p_request->wValue);
|
||||
uint8_t entityID = TU_U16_HIGH(p_request->wIndex);
|
||||
|
||||
// If request is for our speaker feature unit
|
||||
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT || entityID == UAC1_ENTITY_MIC_FEATURE_UNIT) {
|
||||
switch (ctrlSel) {
|
||||
case AUDIO10_FU_CTRL_MUTE:
|
||||
// Audio control mute cur parameter block consists of only one byte - we thus can send it right away
|
||||
// There does not exist a range parameter block for mute
|
||||
TU_LOG2(" Get Mute of channel: %u\r\n", channelNum);
|
||||
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &mute[channelNum], 1);
|
||||
|
||||
case AUDIO10_FU_CTRL_VOLUME:
|
||||
switch (p_request->bRequest) {
|
||||
case AUDIO10_CS_REQ_GET_CUR:
|
||||
TU_LOG2(" Get Volume of channel: %u\r\n", channelNum); {
|
||||
int16_t vol = (int16_t) volume[channelNum];
|
||||
vol = vol * 256; // convert to 1/256 dB units
|
||||
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &vol, sizeof(vol));
|
||||
}
|
||||
|
||||
case AUDIO10_CS_REQ_GET_MIN:
|
||||
TU_LOG2(" Get Volume min of channel: %u\r\n", channelNum); {
|
||||
int16_t min = -90; // -90 dB
|
||||
min = min * 256; // convert to 1/256 dB units
|
||||
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &min, sizeof(min));
|
||||
}
|
||||
|
||||
case AUDIO10_CS_REQ_GET_MAX:
|
||||
TU_LOG2(" Get Volume max of channel: %u\r\n", channelNum); {
|
||||
int16_t max = 30; // +30 dB
|
||||
max = max * 256; // convert to 1/256 dB units
|
||||
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &max, sizeof(max));
|
||||
}
|
||||
|
||||
case AUDIO10_CS_REQ_GET_RES:
|
||||
TU_LOG2(" Get Volume res of channel: %u\r\n", channelNum); {
|
||||
int16_t res = 1; // 1 dB
|
||||
res = res * 256; // convert to 1/256 dB units
|
||||
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &res, sizeof(res));
|
||||
}
|
||||
// Unknown/Unsupported control
|
||||
default:
|
||||
TU_BREAKPOINT();
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
|
||||
// Unknown/Unsupported control
|
||||
default:
|
||||
TU_BREAKPOINT();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Invoked when audio class specific get request received for an entity
|
||||
bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request) {
|
||||
(void) rhport;
|
||||
|
||||
return audio10_get_req_entity(rhport, p_request);
|
||||
}
|
||||
|
||||
// Invoked when audio class specific set request received for an entity
|
||||
bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *buf) {
|
||||
(void) rhport;
|
||||
|
||||
return audio10_set_req_entity(p_request, buf);
|
||||
}
|
||||
|
||||
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const *report, uint16_t len) {
|
||||
(void) instance;
|
||||
(void) len;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/4.
|
||||
//
|
||||
|
||||
#ifndef DS5_BRIDGE_USB_H
|
||||
#define DS5_BRIDGE_USB_H
|
||||
|
||||
#include "tusb.h"
|
||||
|
||||
// typedef void (*usb_hid_get_report_callback_t)(uint16_t channel, uint8_t *data, uint16_t len);
|
||||
// typedef void (*usb_hid_set_report_callback_t)(uint16_t channel, uint8_t *data, uint16_t len);
|
||||
|
||||
/*uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer,
|
||||
uint16_t reqlen);
|
||||
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer,
|
||||
uint16_t bufsize);*/
|
||||
bool tud_audio_get_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request);
|
||||
bool tud_audio_set_req_entity_cb(uint8_t rhport, tusb_control_request_t const *p_request, uint8_t *buf);
|
||||
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const *report, uint16_t len);
|
||||
|
||||
#endif //DS5_BRIDGE_USB_H
|
||||
@@ -0,0 +1,550 @@
|
||||
/*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2023 HiFiPhile
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
* THE SOFTWARE.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "bsp/board_api.h"
|
||||
#include "tusb.h"
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Device Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
static tusb_desc_device_t const desc_device =
|
||||
{
|
||||
.bLength = sizeof(tusb_desc_device_t),
|
||||
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||
.bcdUSB = 0x0200,
|
||||
|
||||
// Use Interface Association Descriptor (IAD) for Audio
|
||||
// As required by USB Specs IAD's subclass must be common class (2) and protocol must be IAD (1)
|
||||
/*.bDeviceClass = TUSB_CLASS_MISC,
|
||||
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||
.bDeviceProtocol = MISC_PROTOCOL_IAD,*/
|
||||
.bDeviceClass = 0x00,
|
||||
.bDeviceSubClass = 0x00,
|
||||
.bDeviceProtocol = 0x00,
|
||||
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||
|
||||
.idVendor = 0x054C,
|
||||
.idProduct = 0x0CE6,
|
||||
.bcdDevice = 0x0100,
|
||||
|
||||
.iManufacturer = 0x01,
|
||||
.iProduct = 0x02,
|
||||
.iSerialNumber = 0x03,
|
||||
|
||||
.bNumConfigurations = 0x01
|
||||
};
|
||||
|
||||
// Invoked when received GET DEVICE DESCRIPTOR
|
||||
// Application return pointer to descriptor
|
||||
uint8_t const *tud_descriptor_device_cb(void) {
|
||||
return (uint8_t const *) &desc_device;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// Configuration Descriptor
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
|
||||
#define EPNUM_AUDIO 0x01
|
||||
|
||||
#define CONFIG_UAC1_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_AUDIO10_SPEAKER_STEREO_FB_DESC_LEN(2))
|
||||
|
||||
uint8_t const descriptor_configuration[] = {
|
||||
// --- CONFIGURATION DESCRIPTOR ---
|
||||
0x09, // bLength
|
||||
0x02, // bDescriptorType (CONFIGURATION)
|
||||
0xE3, 0x00, // wTotalLength: 227
|
||||
0x04, // bNumInterfaces: 4
|
||||
0x01, // bConfigurationValue: 1
|
||||
0x00, // iConfiguration: 0
|
||||
0xC0, // bmAttributes: SELF-POWERED, NO REMOTE-WAKEUP
|
||||
0xFA, // bMaxPower: 500mA (250 * 2mA)
|
||||
|
||||
// --- INTERFACE DESCRIPTOR (0.0): Audio Control ---
|
||||
0x09, // bLength
|
||||
0x04, // bDescriptorType (INTERFACE)
|
||||
0x00, // bInterfaceNumber: 0
|
||||
0x00, // bAlternateSetting: 0
|
||||
0x00, // bNumEndpoints: 0
|
||||
0x01, // bInterfaceClass: Audio (0x01)
|
||||
0x01, // bInterfaceSubClass: Audio Control (0x01)
|
||||
0x00, // bInterfaceProtocol: 0x00
|
||||
0x00, // iInterface: 0
|
||||
|
||||
// Class-specific AC Interface Header Descriptor
|
||||
0x0A, // bLength: 10
|
||||
0x24, // bDescriptorType: CS_INTERFACE (0x24)
|
||||
0x01, // bDescriptorSubtype: Header (0x01)
|
||||
0x00, 0x01, // bcdADC: 1.00
|
||||
0x49, 0x00, // wTotalLength: 73 (0x0049)
|
||||
0x02, // bInCollection: 2 streaming interfaces
|
||||
0x01, // baInterfaceNr(1): Interface 1
|
||||
0x02, // baInterfaceNr(2): Interface 2
|
||||
|
||||
// Input Terminal Descriptor (Terminal ID 1: USB Streaming → Output to Speaker)
|
||||
0x0C, // bLength: 12
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x02, // bDescriptorSubtype: Input Terminal
|
||||
0x01, // bTerminalID: 1
|
||||
0x01, 0x01, // wTerminalType: USB Streaming (0x0101)
|
||||
0x06, // bAssocTerminal: 6 (paired with USB OUT terminal)
|
||||
0x04, // bNrChannels: 4
|
||||
0x33, 0x00, // wChannelConfig: L/R Front + L/R Surround (0x0033)
|
||||
0x00, // iChannelNames: 0
|
||||
0x00, // iTerminal: 0
|
||||
|
||||
// Feature Unit Descriptor (Unit ID 2 ← from Terminal 1)
|
||||
0x0C, // bLength: 12
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x06, // bDescriptorSubtype: Feature Unit
|
||||
0x02, // bUnitID: 2
|
||||
0x01, // bSourceID: 1
|
||||
0x01, // bControlSize: 1 byte per control
|
||||
0x03, // bmaControls[0]: Master – Mute, Volume
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // bmaControls[1..4]: No per-channel controls
|
||||
|
||||
// Output Terminal Descriptor (Terminal ID 3: Speaker ← from Unit 2)
|
||||
0x09, // bLength: 9
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x03, // bDescriptorSubtype: Output Terminal
|
||||
0x03, // bTerminalID: 3
|
||||
0x01, 0x03, // wTerminalType: Speaker (0x0301)
|
||||
0x04, // bAssocTerminal: 4 (paired with mic input)
|
||||
0x02, // bSourceID: 2 (Feature Unit)
|
||||
0x00, // iTerminal: 0
|
||||
|
||||
// Input Terminal Descriptor (Terminal ID 4: Headset Mic)
|
||||
0x0C, // bLength: 12
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x02, // bDescriptorSubtype: Input Terminal
|
||||
0x04, // bTerminalID: 4
|
||||
0x02, 0x04, // wTerminalType: Headset (0x0402)
|
||||
0x03, // bAssocTerminal: 3 (paired with speaker)
|
||||
0x02, // bNrChannels: 2
|
||||
0x03, 0x00, // wChannelConfig: L/R Front (0x0003)
|
||||
0x00, // iChannelNames: 0
|
||||
0x00, // iTerminal: 0
|
||||
|
||||
// Feature Unit Descriptor (Unit ID 5 ← from Terminal 4)
|
||||
0x09, // bLength: 9
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x06, // bDescriptorSubtype: Feature Unit
|
||||
0x05, // bUnitID: 5
|
||||
0x04, // bSourceID: 4
|
||||
0x01, // bControlSize: 1
|
||||
0x03, // bmaControls[0]: Master – Mute, Volume
|
||||
0x00, // bmaControls[1]: Ch1 – no controls
|
||||
0x00, // iFeature: 0
|
||||
|
||||
// Output Terminal Descriptor (Terminal ID 6: USB Streaming ← from Unit 5)
|
||||
0x09, // bLength: 9
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x03, // bDescriptorSubtype: Output Terminal
|
||||
0x06, // bTerminalID: 6
|
||||
0x01, 0x01, // wTerminalType: USB Streaming (0x0101)
|
||||
0x01, // bAssocTerminal: 1
|
||||
0x05, // bSourceID: 5
|
||||
0x00, // iTerminal: 0
|
||||
|
||||
// --- INTERFACE DESCRIPTOR (1.0): Audio Streaming (OUT - Alternate 0) ---
|
||||
0x09, // bLength
|
||||
0x04, // bDescriptorType (INTERFACE)
|
||||
0x01, // bInterfaceNumber: 1
|
||||
0x00, // bAlternateSetting: 0
|
||||
0x00, // bNumEndpoints: 0
|
||||
0x01, // bInterfaceClass: Audio
|
||||
0x02, // bInterfaceSubClass: Audio Streaming
|
||||
0x00, // bInterfaceProtocol
|
||||
0x00, // iInterface
|
||||
|
||||
// --- INTERFACE DESCRIPTOR (1.1): Audio Streaming (OUT - Alternate 1) ---
|
||||
0x09, // bLength
|
||||
0x04, // bDescriptorType (INTERFACE)
|
||||
0x01, // bInterfaceNumber: 1
|
||||
0x01, // bAlternateSetting: 1
|
||||
0x01, // bNumEndpoints: 1
|
||||
0x01, // bInterfaceClass: Audio
|
||||
0x02, // bInterfaceSubClass: Audio Streaming
|
||||
0x00, // bInterfaceProtocol
|
||||
0x00, // iInterface
|
||||
|
||||
// AS General Descriptor (for Interface 1.1)
|
||||
0x07, // bLength: 7
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x01, // bDescriptorSubtype: AS_GENERAL
|
||||
0x01, // bTerminalLink: connected to Terminal ID 1
|
||||
0x01, // bDelay: 1 frame
|
||||
0x01, 0x00, // wFormatTag: PCM (0x0001)
|
||||
|
||||
// Format Type Descriptor (4-channel, 16-bit, 48kHz)
|
||||
0x0B, // bLength: 11
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x02, // bDescriptorSubtype: FORMAT_TYPE
|
||||
0x01, // bFormatType: TYPE_I
|
||||
0x04, // bNrChannels: 4
|
||||
0x02, // bSubframeSize: 2 bytes/sample
|
||||
0x10, // bBitResolution: 16 bits
|
||||
0x01, // bSamFreqType: 1 discrete frequency
|
||||
0x80, 0xBB, 0x00, // tSamFreq: 48000 Hz (0x00BB80)
|
||||
|
||||
// Endpoint Descriptor (Audio OUT: EP1)
|
||||
0x09, // bLength
|
||||
0x05, // bDescriptorType (ENDPOINT)
|
||||
0x01, // bEndpointAddress: OUT EP1
|
||||
0x09, // bmAttributes: Isochronous, Adaptive
|
||||
0x88, 0x01, // wMaxPacketSize: 392 bytes
|
||||
0x04, // bInterval: 4 (1/(2^(4-1)) ms ≈ 125 µs/frame)
|
||||
0x00, // bRefresh
|
||||
0x00, // bSynchAddress
|
||||
|
||||
// Class-specific Audio Streaming Endpoint Descriptor (EP1)
|
||||
0x07, // bLength
|
||||
0x25, // bDescriptorType: CS_ENDPOINT
|
||||
0x01, // bDescriptorSubtype: GENERAL
|
||||
0x00, // Attributes: No pitch/sampling freq control
|
||||
0x00, // Lock Delay Units: Undefined
|
||||
0x00, 0x00, // Lock Delay: 0
|
||||
|
||||
// --- INTERFACE DESCRIPTOR (2.0): Audio Streaming IN (Alternate 0) ---
|
||||
0x09, // bLength
|
||||
0x04, // bDescriptorType (INTERFACE)
|
||||
0x02, // bInterfaceNumber: 2
|
||||
0x00, // bAlternateSetting: 0
|
||||
0x00, // bNumEndpoints: 0
|
||||
0x01, // bInterfaceClass: Audio
|
||||
0x02, // bInterfaceSubClass: Audio Streaming
|
||||
0x00, // bInterfaceProtocol
|
||||
0x00, // iInterface
|
||||
|
||||
// --- INTERFACE DESCRIPTOR (2.1): Audio Streaming IN (Alternate 1) ---
|
||||
0x09, // bLength
|
||||
0x04, // bDescriptorType (INTERFACE)
|
||||
0x02, // bInterfaceNumber: 2
|
||||
0x01, // bAlternateSetting: 1
|
||||
0x01, // bNumEndpoints: 1
|
||||
0x01, // bInterfaceClass: Audio
|
||||
0x02, // bInterfaceSubClass: Audio Streaming
|
||||
0x00, // bInterfaceProtocol
|
||||
0x00, // iInterface
|
||||
|
||||
// AS General Descriptor (for Interface 2.1)
|
||||
0x07, // bLength: 7
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x01, // bDescriptorSubtype: AS_GENERAL
|
||||
0x06, // bTerminalLink: connected to Terminal ID 6
|
||||
0x01, // bDelay: 1 frame
|
||||
0x01, 0x00, // wFormatTag: PCM (0x0001)
|
||||
|
||||
// Format Type Descriptor (2-channel, 16-bit, 48kHz)
|
||||
0x0B, // bLength: 11
|
||||
0x24, // bDescriptorType: CS_INTERFACE
|
||||
0x02, // bDescriptorSubtype: FORMAT_TYPE
|
||||
0x01, // bFormatType: TYPE_I
|
||||
0x02, // bNrChannels: 2
|
||||
0x02, // bSubframeSize: 2
|
||||
0x10, // bBitResolution: 16
|
||||
0x01, // bSamFreqType: 1
|
||||
0x80, 0xBB, 0x00, // tSamFreq: 48000 Hz
|
||||
|
||||
// Endpoint Descriptor (Audio IN: EP2)
|
||||
0x09, // bLength
|
||||
0x05, // bDescriptorType (ENDPOINT)
|
||||
0x82, // bEndpointAddress: IN EP2
|
||||
0x05, // bmAttributes: Isochronous, Asynchronous
|
||||
0xC4, 0x00, // wMaxPacketSize: 196 bytes
|
||||
0x04, // bInterval: 4
|
||||
0x00, // bRefresh
|
||||
0x00, // bSynchAddress
|
||||
|
||||
// Class-specific Audio Streaming Endpoint Descriptor (EP2)
|
||||
0x07, // bLength
|
||||
0x25, // bDescriptorType: CS_ENDPOINT
|
||||
0x01, // bDescriptorSubtype: GENERAL
|
||||
0x00, // Attributes: No controls
|
||||
0x00, // Lock Delay Units
|
||||
0x00, 0x00, // Lock Delay
|
||||
|
||||
// --- INTERFACE DESCRIPTOR (3.0): HID (DualSense 5 Gamepad + Touchpad) ---
|
||||
0x09, // bLength
|
||||
0x04, // bDescriptorType (INTERFACE)
|
||||
0x03, // bInterfaceNumber: 3
|
||||
0x00, // bAlternateSetting: 0
|
||||
0x02, // bNumEndpoints: 2 (IN + OUT)
|
||||
0x03, // bInterfaceClass: HID
|
||||
0x00, // bInterfaceSubClass: None
|
||||
0x00, // bInterfaceProtocol: None
|
||||
0x00, // iInterface
|
||||
|
||||
// HID Descriptor
|
||||
0x09, // bLength: 9
|
||||
0x21, // bDescriptorType (HID)
|
||||
0x11, 0x01, // bcdHID: 1.11
|
||||
0x00, // bCountryCode: Not localized
|
||||
0x01, // bNumDescriptors: 1 report descriptor
|
||||
0x22, // bDescriptorType: Report
|
||||
0x21, 0x01, // wDescriptorLength: 289 (0x0121)
|
||||
|
||||
// Endpoint Descriptor (HID IN: EP4)
|
||||
0x07, // bLength
|
||||
0x05, // bDescriptorType (ENDPOINT)
|
||||
0x84, // bEndpointAddress: IN EP4
|
||||
0x03, // bmAttributes: Interrupt
|
||||
0x40, 0x00, // wMaxPacketSize: 64
|
||||
0x06, // bInterval: 6 (polling every 8ms)
|
||||
|
||||
// Endpoint Descriptor (HID OUT: EP3)
|
||||
0x07, // bLength
|
||||
0x05, // bDescriptorType (ENDPOINT)
|
||||
0x03, // bEndpointAddress: OUT EP3
|
||||
0x03, // bmAttributes: Interrupt
|
||||
0x40, 0x00, // wMaxPacketSize: 64
|
||||
0x06, // bInterval: 6
|
||||
};
|
||||
|
||||
// Invoked when received GET CONFIGURATION DESCRIPTOR
|
||||
// Application return pointer to descriptor
|
||||
// Descriptor contents must exist long enough for transfer to complete
|
||||
uint8_t const *tud_descriptor_configuration_cb(uint8_t index) {
|
||||
(void) index; // for multiple configurations
|
||||
return descriptor_configuration;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// HID Report Descriptor
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
uint8_t const desc_hid_report[] = {
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x05, // Usage (Game Pad)
|
||||
0xA1, 0x01, // Collection (Application)
|
||||
0x85, 0x01, // Report ID (1)
|
||||
0x09, 0x30, // Usage (X)
|
||||
0x09, 0x31, // Usage (Y)
|
||||
0x09, 0x32, // Usage (Z)
|
||||
0x09, 0x35, // Usage (Rz)
|
||||
0x09, 0x33, // Usage (Rx)
|
||||
0x09, 0x34, // Usage (Ry)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x06, // Report Count (6)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
|
||||
0x09, 0x20, // Usage (0x20)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
|
||||
0x09, 0x39, // Usage (Hat switch)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x07, // Logical Maximum (7)
|
||||
0x35, 0x00, // Physical Minimum (0)
|
||||
0x46, 0x3B, 0x01, // Physical Maximum (315)
|
||||
0x65, 0x14, // Unit (System: English Rotation, Length: Centimeter)
|
||||
0x75, 0x04, // Report Size (4)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0x81, 0x42, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,Null State)
|
||||
0x65, 0x00, // Unit (None)
|
||||
0x05, 0x09, // Usage Page (Button)
|
||||
0x19, 0x01, // Usage Minimum (0x01)
|
||||
0x29, 0x0F, // Usage Maximum (0x0F)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x25, 0x01, // Logical Maximum (1)
|
||||
0x75, 0x01, // Report Size (1)
|
||||
0x95, 0x0F, // Report Count (15)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
|
||||
0x09, 0x21, // Usage (0x21)
|
||||
0x95, 0x0D, // Report Count (13)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x06, 0x00, 0xFF, // Usage Page (Vendor Defined 0xFF00)
|
||||
0x09, 0x22, // Usage (0x22)
|
||||
0x15, 0x00, // Logical Minimum (0)
|
||||
0x26, 0xFF, 0x00, // Logical Maximum (255)
|
||||
0x75, 0x08, // Report Size (8)
|
||||
0x95, 0x34, // Report Count (52)
|
||||
0x81, 0x02, // Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
|
||||
0x85, 0x02, // Report ID (2)
|
||||
0x09, 0x23, // Usage (0x23)
|
||||
0x95, 0x2F, // Report Count (47)
|
||||
0x91, 0x02, // Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x05, // Report ID (5)
|
||||
0x09, 0x33, // Usage (0x33)
|
||||
0x95, 0x28, // Report Count (40)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x08, // Report ID (8)
|
||||
0x09, 0x34, // Usage (0x34)
|
||||
0x95, 0x2F, // Report Count (47)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x09, // Report ID (9)
|
||||
0x09, 0x24, // Usage (0x24)
|
||||
0x95, 0x13, // Report Count (19)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x0A, // Report ID (10)
|
||||
0x09, 0x25, // Usage (0x25)
|
||||
0x95, 0x1A, // Report Count (26)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x0B, 0x09, 0x41, 0x95, 0x29, 0xB1, 0x02, // ID 0B (新增项)
|
||||
0x85, 0x0C, 0x09, 0x42, 0x95, 0x29, 0xB1, 0x02, // ID 0C (新增项)
|
||||
0x85, 0x20, // Report ID (32)
|
||||
0x09, 0x26, // Usage (0x26)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x21, // Report ID (33)
|
||||
0x09, 0x27, // Usage (0x27)
|
||||
0x95, 0x04, // Report Count (4)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x22, // Report ID (34)
|
||||
0x09, 0x40, // Usage (0x40)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x80, // Report ID (128)
|
||||
0x09, 0x28, // Usage (0x28)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x81, // Report ID (129)
|
||||
0x09, 0x29, // Usage (0x29)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x82, // Report ID (130)
|
||||
0x09, 0x2A, // Usage (0x2A)
|
||||
0x95, 0x09, // Report Count (9)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x83, // Report ID (131)
|
||||
0x09, 0x2B, // Usage (0x2B)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x84, // Report ID (132)
|
||||
0x09, 0x2C, // Usage (0x2C)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0x85, // Report ID (133)
|
||||
0x09, 0x2D, // Usage (0x2D)
|
||||
0x95, 0x02, // Report Count (2)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0xA0, // Report ID (160)
|
||||
0x09, 0x2E, // Usage (0x2E)
|
||||
0x95, 0x01, // Report Count (1)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0xE0, // Report ID (224)
|
||||
0x09, 0x2F, // Usage (0x2F)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0xF0, // Report ID (240)
|
||||
0x09, 0x30, // Usage (0x30)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0xF1, // Report ID (241)
|
||||
0x09, 0x31, // Usage (0x31)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0xF2, // Report ID (242)
|
||||
0x09, 0x32, // Usage (0x32)
|
||||
0x95, 0x0F, // Report Count (15)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0xF4, // Report ID (244)
|
||||
0x09, 0x35, // Usage (0x35)
|
||||
0x95, 0x3F, // Report Count (63)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0x85, 0xF5, // Report ID (245)
|
||||
0x09, 0x36, // Usage (0x36)
|
||||
0x95, 0x03, // Report Count (3)
|
||||
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
|
||||
0xC0, // End Collection
|
||||
};
|
||||
|
||||
// Invoked when received GET HID REPORT DESCRIPTOR
|
||||
// Application return pointer to descriptor
|
||||
// Descriptor contents must exist long enough for transfer to complete
|
||||
uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) {
|
||||
(void) itf;
|
||||
return desc_hid_report;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------+
|
||||
// String Descriptors
|
||||
//--------------------------------------------------------------------+
|
||||
|
||||
// String Descriptor Index
|
||||
enum {
|
||||
STRID_LANGID = 0,
|
||||
STRID_MANUFACTURER,
|
||||
STRID_PRODUCT,
|
||||
STRID_SERIAL,
|
||||
};
|
||||
|
||||
// array of pointer to string descriptors
|
||||
static char const *string_desc_arr[] =
|
||||
{
|
||||
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
|
||||
"TinyUSB", // 1: Manufacturer
|
||||
"Wireless Controller", // 2: Product
|
||||
NULL, // 3: Serials will use unique ID if possible
|
||||
"UAC2 Speaker", // 4: Audio Interface
|
||||
"UAC1 Speaker", // 5: UAC1 Audio Interface
|
||||
};
|
||||
|
||||
static uint16_t _desc_str[60 + 1];
|
||||
|
||||
// Invoked when received GET STRING DESCRIPTOR request
|
||||
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
|
||||
uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
|
||||
(void) langid;
|
||||
size_t chr_count;
|
||||
|
||||
switch (index) {
|
||||
case STRID_LANGID:
|
||||
memcpy(&_desc_str[1], string_desc_arr[0], 2);
|
||||
chr_count = 1;
|
||||
break;
|
||||
|
||||
case STRID_SERIAL:
|
||||
chr_count = board_usb_get_serial(_desc_str + 1, 32);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
|
||||
|
||||
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) return NULL;
|
||||
|
||||
const char *str = string_desc_arr[index];
|
||||
|
||||
// Cap at max char
|
||||
chr_count = strlen(str);
|
||||
size_t const max_count = sizeof(_desc_str) / sizeof(_desc_str[0]) - 1; // -1 for string type
|
||||
if (chr_count > max_count) chr_count = max_count;
|
||||
|
||||
// Convert ASCII string into UTF-16
|
||||
for (size_t i = 0; i < chr_count; i++) {
|
||||
_desc_str[1 + i] = str[i];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// first byte is length (including header), second byte is string type
|
||||
_desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
|
||||
|
||||
return _desc_str;
|
||||
}
|
||||
+215
@@ -0,0 +1,215 @@
|
||||
//
|
||||
// Created by awalol on 2026/3/4.
|
||||
//
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "hci_cmd.h"
|
||||
|
||||
inline const char *opcode_to_str(const uint16_t opcode) {
|
||||
switch (opcode) {
|
||||
case HCI_OPCODE_HCI_INQUIRY:
|
||||
return "HCI_INQUIRY";
|
||||
case HCI_OPCODE_HCI_INQUIRY_CANCEL:
|
||||
return "HCI_INQUIRY_CANCEL";
|
||||
case HCI_OPCODE_HCI_CREATE_CONNECTION:
|
||||
return "HCI_CREATE_CONNECTION";
|
||||
case HCI_OPCODE_HCI_ACCEPT_CONNECTION_REQUEST:
|
||||
return "HCI_ACCEPT_CONNECTION_REQUEST";
|
||||
case HCI_OPCODE_HCI_LINK_KEY_REQUEST_REPLY:
|
||||
return "HCI_LINK_KEY_REQUEST_REPLY";
|
||||
case HCI_OPCODE_HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY:
|
||||
return "HCI_LINK_KEY_REQUEST_NEGATIVE_REPLY";
|
||||
case HCI_OPCODE_HCI_REJECT_CONNECTION_REQUEST:
|
||||
return "HCI_REJECT_CONNECTION_REQUEST";
|
||||
case HCI_OPCODE_HCI_AUTHENTICATION_REQUESTED:
|
||||
return "HCI_AUTHENTICATION_REQUESTED";
|
||||
case HCI_OPCODE_HCI_SET_CONNECTION_ENCRYPTION:
|
||||
return "HCI_SET_CONNECTION_ENCRYPTION";
|
||||
case HCI_OPCODE_HCI_READ_REMOTE_SUPPORTED_FEATURES_COMMAND:
|
||||
return "HCI_READ_REMOTE_SUPPORTED_FEATURES";
|
||||
case HCI_OPCODE_HCI_READ_REMOTE_EXTENDED_FEATURES_COMMAND:
|
||||
return "HCI_READ_REMOTE_EXTENDED_FEATURES";
|
||||
case HCI_OPCODE_HCI_SWITCH_ROLE_COMMAND:
|
||||
return "HCI_SWITCH_ROLE";
|
||||
case HCI_OPCODE_HCI_IO_CAPABILITY_REQUEST_REPLY:
|
||||
return "HCI_IO_CAPABILITY_REQUEST_REPLY";
|
||||
case HCI_OPCODE_HCI_USER_CONFIRMATION_REQUEST_REPLY:
|
||||
return "HCI_USER_CONFIRMATION_REQUEST_REPLY";
|
||||
case HCI_OPCODE_HCI_DISCONNECT:
|
||||
return "HCI_DISCONNECT";
|
||||
case HCI_OPCODE_HCI_SET_EVENT_MASK:
|
||||
return "HCI_SET_EVENT_MASK";
|
||||
case HCI_OPCODE_HCI_RESET:
|
||||
return "HCI_RESET";
|
||||
case HCI_OPCODE_HCI_WRITE_LOCAL_NAME:
|
||||
return "HCI_WRITE_LOCAL_NAME";
|
||||
case HCI_OPCODE_HCI_READ_LOCAL_NAME:
|
||||
return "HCI_READ_LOCAL_NAME";
|
||||
case HCI_OPCODE_HCI_WRITE_PAGE_TIMEOUT:
|
||||
return "HCI_WRITE_PAGE_TIMEOUT";
|
||||
case HCI_OPCODE_HCI_WRITE_SCAN_ENABLE:
|
||||
return "HCI_WRITE_SCAN_ENABLE";
|
||||
case HCI_OPCODE_HCI_WRITE_CLASS_OF_DEVICE:
|
||||
return "HCI_WRITE_CLASS_OF_DEVICE";
|
||||
case HCI_OPCODE_HCI_WRITE_INQUIRY_MODE:
|
||||
return "HCI_WRITE_INQUIRY_MODE";
|
||||
case HCI_OPCODE_HCI_WRITE_EXTENDED_INQUIRY_RESPONSE:
|
||||
return "HCI_WRITE_EXTENDED_INQUIRY_RESPONSE";
|
||||
case HCI_OPCODE_HCI_WRITE_PAGE_SCAN_TYPE:
|
||||
return "HCI_WRITE_PAGE_SCAN_TYPE";
|
||||
case HCI_OPCODE_HCI_WRITE_SIMPLE_PAIRING_MODE:
|
||||
return "HCI_WRITE_SIMPLE_PAIRING_MODE";
|
||||
case HCI_OPCODE_HCI_SET_EVENT_MASK_2:
|
||||
return "HCI_SET_EVENT_MASK_2";
|
||||
case HCI_OPCODE_HCI_WRITE_LE_HOST_SUPPORTED:
|
||||
return "HCI_WRITE_LE_HOST_SUPPORTED";
|
||||
case HCI_OPCODE_HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT:
|
||||
return "HCI_WRITE_SECURE_CONNECTIONS_HOST_SUPPORT";
|
||||
case HCI_OPCODE_HCI_WRITE_DEFAULT_LINK_POLICY_SETTING:
|
||||
return "HCI_WRITE_DEFAULT_LINK_POLICY_SETTING";
|
||||
case HCI_OPCODE_HCI_READ_LOCAL_VERSION_INFORMATION:
|
||||
return "HCI_READ_LOCAL_VERSION_INFORMATION";
|
||||
case HCI_OPCODE_HCI_READ_LOCAL_SUPPORTED_COMMANDS:
|
||||
return "HCI_READ_LOCAL_SUPPORTED_COMMANDS";
|
||||
case HCI_OPCODE_HCI_READ_LOCAL_SUPPORTED_FEATURES:
|
||||
return "HCI_READ_LOCAL_SUPPORTED_FEATURES";
|
||||
case HCI_OPCODE_HCI_READ_BUFFER_SIZE:
|
||||
return "HCI_READ_BUFFER_SIZE";
|
||||
case HCI_OPCODE_HCI_READ_BD_ADDR:
|
||||
return "HCI_READ_BD_ADDR";
|
||||
case HCI_OPCODE_HCI_READ_ENCRYPTION_KEY_SIZE:
|
||||
return "HCI_READ_ENCRYPTION_KEY_SIZE";
|
||||
case 0xFC01:
|
||||
return "HCI_VENDOR_0xFC01";
|
||||
default:
|
||||
return "UNKNOWN_OPCODE";
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t crc32(const uint8_t* data, size_t size) {
|
||||
uint32_t crc = ~0xEADA2D49; // 0xA2 seed
|
||||
|
||||
while (size--) {
|
||||
crc ^= *data++;
|
||||
for (unsigned i = 0; i < 8; i++)
|
||||
crc = ((crc >> 1) ^ (0xEDB88320 & -(crc & 1)));
|
||||
}
|
||||
|
||||
return ~crc;
|
||||
}
|
||||
|
||||
inline void fill_output_report_checksum(uint8_t* outputData,size_t len)
|
||||
{
|
||||
uint32_t crc = crc32(outputData, len - 4);
|
||||
outputData[len - 4] = (crc >> 0) & 0xFF;
|
||||
outputData[len - 3] = (crc >> 8) & 0xFF;
|
||||
outputData[len - 2] = (crc >> 16) & 0xFF;
|
||||
outputData[len - 1] = (crc >> 24) & 0xFF;
|
||||
}
|
||||
|
||||
enum PowerState : uint8_t {
|
||||
Discharging = 0x00, // Use PowerPercent
|
||||
Charging = 0x01, // Use PowerPercent
|
||||
Complete = 0x02, // PowerPercent not valid? assume 100%?
|
||||
AbnormalVoltage = 0x0A, // PowerPercent not valid?
|
||||
AbnormalTemperature = 0x0B, // PowerPercent not valid?
|
||||
ChargingError = 0x0F // PowerPercent not valid?
|
||||
};
|
||||
|
||||
enum Direction : uint8_t {
|
||||
North = 0,
|
||||
NorthEast,
|
||||
East,
|
||||
SouthEast,
|
||||
South,
|
||||
SouthWest,
|
||||
West,
|
||||
NorthWest,
|
||||
None = 8
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) TouchFingerData { // 4
|
||||
/*0.0*/ uint32_t Index : 7;
|
||||
/*0.7*/ uint32_t NotTouching : 1;
|
||||
/*1.0*/ uint32_t FingerX : 12;
|
||||
/*2.4*/ uint32_t FingerY : 12;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) TouchData { // 9
|
||||
/*0*/ TouchFingerData Finger[2];
|
||||
/*8*/ uint8_t Timestamp;
|
||||
};
|
||||
|
||||
struct __attribute__((packed)) USBGetStateData { // 63
|
||||
/* 0 */ uint8_t LeftStickX;
|
||||
/* 1 */ uint8_t LeftStickY;
|
||||
/* 2 */ uint8_t RightStickX;
|
||||
/* 3 */ uint8_t RightStickY;
|
||||
/* 4 */ uint8_t TriggerLeft;
|
||||
/* 5 */ uint8_t TriggerRight;
|
||||
/* 6 */ uint8_t SeqNo; // always 0x01 on BT
|
||||
/* 7.0*/ Direction DPad : 4;
|
||||
/* 7.4*/ uint8_t ButtonSquare : 1;
|
||||
/* 7.5*/ uint8_t ButtonCross : 1;
|
||||
/* 7.6*/ uint8_t ButtonCircle : 1;
|
||||
/* 7.7*/ uint8_t ButtonTriangle : 1;
|
||||
/* 8.0*/ uint8_t ButtonL1 : 1;
|
||||
/* 8.1*/ uint8_t ButtonR1 : 1;
|
||||
/* 8.2*/ uint8_t ButtonL2 : 1;
|
||||
/* 8.3*/ uint8_t ButtonR2 : 1;
|
||||
/* 8.4*/ uint8_t ButtonCreate : 1;
|
||||
/* 8.5*/ uint8_t ButtonOptions : 1;
|
||||
/* 8.6*/ uint8_t ButtonL3 : 1;
|
||||
/* 8.7*/ uint8_t ButtonR3 : 1;
|
||||
/* 9.0*/ uint8_t ButtonHome : 1;
|
||||
/* 9.1*/ uint8_t ButtonPad : 1;
|
||||
/* 9.2*/ uint8_t ButtonMute : 1;
|
||||
/* 9.3*/ uint8_t UNK1 : 1; // appears unused
|
||||
/* 9.4*/ uint8_t ButtonLeftFunction : 1; // DualSense Edge
|
||||
/* 9.5*/ uint8_t ButtonRightFunction : 1; // DualSense Edge
|
||||
/* 9.6*/ uint8_t ButtonLeftPaddle : 1; // DualSense Edge
|
||||
/* 9.7*/ uint8_t ButtonRightPaddle : 1; // DualSense Edge
|
||||
/*10 */ uint8_t UNK2; // appears unused
|
||||
/*11 */ uint32_t UNK_COUNTER; // Linux driver calls this reserved, tools leak calls the 2 high bytes "random"
|
||||
/*15 */ int16_t AngularVelocityX;
|
||||
/*17 */ int16_t AngularVelocityZ;
|
||||
/*19 */ int16_t AngularVelocityY;
|
||||
/*21 */ int16_t AccelerometerX;
|
||||
/*23 */ int16_t AccelerometerY;
|
||||
/*25 */ int16_t AccelerometerZ;
|
||||
/*27 */ uint32_t SensorTimestamp;
|
||||
/*31 */ int8_t Temperature; // reserved2 in Linux driver
|
||||
/*32 */ TouchData Touch;
|
||||
/*41.0*/ uint8_t TriggerRightStopLocation: 4; // trigger stop can be a range from 0 to 9 (F/9.0 for Apple interface)
|
||||
/*41.4*/ uint8_t TriggerRightStatus: 4;
|
||||
/*42.0*/ uint8_t TriggerLeftStopLocation: 4;
|
||||
/*42.4*/ uint8_t TriggerLeftStatus: 4; // 0 feedbackNoLoad
|
||||
// 1 feedbackLoadApplied
|
||||
// 0 weaponReady
|
||||
// 1 weaponFiring
|
||||
// 2 weaponFired
|
||||
// 0 vibrationNotVibrating
|
||||
// 1 vibrationIsVibrating
|
||||
/*43 */ uint32_t HostTimestamp; // mirrors data from report write
|
||||
/*47.0*/ uint8_t TriggerRightEffect: 4; // Active trigger effect, previously we thought this was status max
|
||||
/*47.4*/ uint8_t TriggerLeftEffect: 4; // 0 for reset and all other effects
|
||||
// 1 for feedback effect
|
||||
// 2 for weapon effect
|
||||
// 3 for vibration
|
||||
/*48 */ uint32_t DeviceTimeStamp;
|
||||
/*52.0*/ uint8_t PowerPercent : 4; // 0x00-0x0A
|
||||
/*52.4*/ PowerState Power : 4;
|
||||
/*53.0*/ uint8_t PluggedHeadphones : 1;
|
||||
/*53.1*/ uint8_t PluggedMic : 1;
|
||||
/*53.2*/ uint8_t MicMuted: 1; // Mic muted by powersave/mute command
|
||||
/*53.3*/ uint8_t PluggedUsbData : 1;
|
||||
/*53.4*/ uint8_t PluggedUsbPower : 1; // appears that this cannot be 1 if PluggedUsbData is 1
|
||||
/*53.5*/ uint8_t UsbPowerOnBT : 1; // appears this is only 1 if BT connected and USB powered
|
||||
/*53.5*/ uint8_t DockDetect : 1;
|
||||
/*53.5*/ uint8_t PluggedUnk : 1;
|
||||
/*54.0*/ uint8_t PluggedExternalMic : 1; // Is external mic active (automatic in mic auto mode)
|
||||
/*54.1*/ uint8_t HapticLowPassFilter : 1; // Is the Haptic Low-Pass-Filter active?
|
||||
/*54.2*/ uint8_t PluggedUnk3 : 6;
|
||||
/*55 */ uint8_t AesCmac[8];
|
||||
};
|
||||
Reference in New Issue
Block a user