First Commit

This commit is contained in:
awalol
2026-03-05 16:21:25 +08:00
commit 9a929530d9
15 changed files with 1980 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
build
!.vscode/*
+92
View File
@@ -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)
+22
View File
@@ -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) - 震动报文
+121
View File
@@ -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})
+96
View 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
View File
@@ -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
View File
@@ -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);
}
+25
View File
@@ -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
+42
View File
@@ -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
View File
@@ -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();
}
}
+135
View File
@@ -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
View File
@@ -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;
}
+21
View File
@@ -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
+550
View File
@@ -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
View File
@@ -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];
};