feat:adjust gain in speaker volume. add led and auto-disconnect toggle in mute.

This commit is contained in:
awalol
2026-03-22 00:33:30 +08:00
parent 49eba414cb
commit 845656aedd
7 changed files with 50 additions and 37 deletions
+1 -1
View File
@@ -62,7 +62,7 @@ target_compile_definitions(ds5-bridge PRIVATE
)
pico_set_program_name(ds5-bridge "ds5-bridge")
pico_set_program_version(ds5-bridge "0.2")
pico_set_program_version(ds5-bridge "0.3")
# Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(ds5-bridge 1)
+5 -1
View File
@@ -10,11 +10,15 @@
3. 将 DS5 手柄进入蓝牙配对模式
4. Enjoy it
- 调整扬声器音量为改变震动增益倍数,范围 [1,2]
- 开启扬声器静音为关闭LED连接提示 (手柄重连后生效)
- 开启麦克风静音为禁用静默断连
# 当前问题:
当前无,待反馈
# 未来计划
~~1. 耳机与扬声器支持~~ (pico 性能不够)
~~1. 耳机与扬声器支持~~ (pico 性能不够用于opus编码)
# 编译
需要将pico sdk里面的tinyusb版本升级到最新
+4 -3
View File
@@ -6,6 +6,7 @@
#include "bt.h"
#include "resample.h"
#include "tusb.h"
#include "usb.h"
#include <algorithm>
#define INPUT_CHANNELS 4
@@ -13,7 +14,7 @@
#define SAMPLE_SIZE 64
#define REPORT_SIZE 142
#define REPORT_ID 0x32
#define VOLUME_GAIN 2
// #define VOLUME_GAIN 2
#define BUFFER_LENGTH 55
static WDL_Resampler resampler;
@@ -49,8 +50,8 @@ void audio_loop() {
// 4. 转换为int8并缓冲,满64字节即组包发送
for (int i = 0; i < out_frames; i++) {
int val_l = (int) (out_buf[i * 2] * 127.0f * VOLUME_GAIN);
int val_r = (int) (out_buf[i * 2 + 1] * 127.0f * VOLUME_GAIN);
int val_l = (int) (out_buf[i * 2] * 127.0f * (volume[0] ?: 1));
int val_r = (int) (out_buf[i * 2 + 1] * 127.0f * (volume[0] ?: 1));
haptic_buf[haptic_buf_pos++] = (int8_t) std::clamp(val_l, -128, 127); // 似乎clamp有点多余?还是以防万一吧
haptic_buf[haptic_buf_pos++] = (int8_t) std::clamp(val_r, -128, 127);
+17 -12
View File
@@ -15,6 +15,7 @@
#include "l2cap.h"
#include "pico/cyw43_arch.h"
#include "pico/stdio.h"
#include "usb.h"
#include "utils.h"
#include "bsp/board_api.h"
#include "pico/sync.h"
@@ -28,7 +29,7 @@ static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t
static btstack_packet_callback_registration_t hci_event_callback_registration, l2cap_event_callback_registration;
static bd_addr_t current_device_addr;
static bool device_found = false;
static bool new_pair = false;
static bool new_pair = false; // 只有新匹配的设备才用创建channel,自动重连走的是service
static hci_con_handle_t acl_handle = HCI_CON_HANDLE_INVALID;
static uint16_t hid_control_cid;
static uint16_t hid_interrupt_cid;
@@ -302,6 +303,7 @@ static void hci_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t *p
hid_control_cid = 0;
hid_interrupt_cid = 0;
feature_data.clear();
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, false);
printf("[HCI] Disconnected reason=0x%02X, start inquiry\n", reason);
gap_inquiry_start(30);
break;
@@ -317,7 +319,9 @@ static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t
// printf("[L2CAP] HID Interrupt data len=%u\n", size);
// printf_hexdump(packet, size);
bt_data_callback(INTERRUPT, packet, size);
if (packet[3] < 120 || packet[3] > 140) {
// 静默检测
if (!mute[1] && (packet[3] < 120 || packet[3] > 140)) {
inactive_time = time_us_32();
}else if (time_us_32() - inactive_time > 600 * 1000 * 1000){
printf("disconnect when inactive\n");
@@ -354,33 +358,34 @@ static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t
printf("[L2CAP] HID Interrupt opened cid=0x%04X\n", local_cid);
hid_interrupt_cid = local_cid;
printf("Init DualSense\n");
uint8_t get_feature[41] = {
0x43,
0x05
};
l2cap_send(hid_control_cid, get_feature, 41);
if (!mute[0]) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
}
printf("Init DualSense\n");
init_feature();
// 初始化手柄状态
uint8_t report32[142];
report32[0] = 0x32;
report32[1] = 0x10;
report32[1] = 0x10; // reportSeqCounter
uint8_t packet_0x10[] =
{
0x90, // Packet: 0x10
0x3f, // 63
// SetStateData
0xfd, 0xf7, 0x0, 0x0, 0x7f, 0x7f,
0xfd, 0xf7, 0x0, 0x0,
0x7f, 0x7f, // Headphones, Speaker
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
0xff, 0xd7, 0x00 // RGB LED: R, G, B (Nijika Color!)✨
};
memcpy(report32 + 2, packet_0x10, sizeof(packet_0x10));
bt_write(report32, sizeof(report32));
init_feature();
} else {
printf("[L2CAP] Unknown Channel psm: 0x%02X", psm);
}
-1
View File
@@ -12,7 +12,6 @@
int reportSeqCounter = 0;
uint8_t packetCounter = 0;
uint32_t lastTime = 0;
uint8_t interrupt_in_data[63] = {
0x7f, 0x7d, 0x7f, 0x7e, 0x00, 0x00, 0xa7,
+20 -19
View File
@@ -5,8 +5,9 @@
#include "tusb.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
uint8_t mute[2]; // 0: SPEAKER(0x02) 1: MIC(0x05)
int16_t volume[2]; // 0: SPEAKER(0x02) 1: MIC(0x05)
#define UAC1_ENTITY_SPK_FEATURE_UNIT 0x02
#define UAC1_ENTITY_MIC_FEATURE_UNIT 0x05
@@ -38,9 +39,10 @@ static bool audio10_set_req_entity(tusb_control_request_t const *p_request, uint
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);
uint8_t index = entityID == UAC1_ENTITY_SPK_FEATURE_UNIT ? 0 : 1;
// If request is for our speaker feature unit
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT) {
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT || entityID == UAC1_ENTITY_MIC_FEATURE_UNIT) {
switch (ctrlSel) {
case AUDIO10_FU_CTRL_MUTE:
switch (p_request->bRequest) {
@@ -48,9 +50,9 @@ static bool audio10_set_req_entity(tusb_control_request_t const *p_request, uint
// Only 1st form is supported
TU_VERIFY(p_request->wLength == 1);
mute[channelNum] = pBuff[0];
mute[index] = pBuff[0];
TU_LOG2(" Set Mute: %d of channel: %u\r\n", mute[channelNum], channelNum);
TU_LOG2(" Set Mute: %d of entity: %u\r\n", mute[index], entityID);
return true;
default:
@@ -63,9 +65,9 @@ static bool audio10_set_req_entity(tusb_control_request_t const *p_request, uint
// Only 1st form is supported
TU_VERIFY(p_request->wLength == 2);
volume[channelNum] = (int16_t) tu_unaligned_read16(pBuff) / 256;
volume[index] = (int16_t) tu_unaligned_read16(pBuff) / 256;
TU_LOG2(" Set Volume: %d dB of channel: %u\r\n", volume[channelNum], channelNum);
TU_LOG2(" Set Volume: %d dB of entity: %u\r\n", volume[index], entityID);
return true;
default:
@@ -86,7 +88,7 @@ static bool audio10_get_req_entity(uint8_t rhport, tusb_control_request_t const
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);
// 当前音量的调节不会影响音量大小,看看后面有没有用再补全吧
uint8_t index = entityID == UAC1_ENTITY_SPK_FEATURE_UNIT ? 0 : 1;
// If request is for our speaker feature unit
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT || entityID == UAC1_ENTITY_MIC_FEATURE_UNIT) {
@@ -94,36 +96,35 @@ static bool audio10_get_req_entity(uint8_t rhport, tusb_control_request_t const
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);
TU_LOG2(" Get Mute of entity: %u\r\n", entityID);
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &mute[index], 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];
TU_LOG2(" Get Volume of entity: %u\r\n", entityID); {
int16_t vol = volume[index];
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 = -100; // -100 dB
TU_LOG2(" Get Volume min of entity: %u\r\n", entityID); {
int16_t min = 1; // 1 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 = 0; // 0 dB
TU_LOG2(" Get Volume max of entity: %u\r\n", entityID); {
int16_t max = 2; // 2 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
TU_LOG2(" Get Volume res of entity: %u\r\n", entityID); {
int16_t res = 0.1 * 256; // 0.1 dB
return tud_audio_buffer_and_schedule_control_xfer(rhport, p_request, &res, sizeof(res));
}
// Unknown/Unsupported control
+3
View File
@@ -5,4 +5,7 @@
#ifndef DS5_BRIDGE_USB_H
#define DS5_BRIDGE_USB_H
extern uint8_t mute[2]; // 0: SPEAKER(0x02) 1: MIC(0x05)
extern int16_t volume[2]; // 0: SPEAKER(0x02) 1: MIC(0x05)
#endif //DS5_BRIDGE_USB_H