Files
DS5Dongle-OLED-Edition-stea…/src/usb.cpp
T
MarcelineVPQandClaude Opus 4.7 4b6a72b70a rebase: Phase B — trivial files for the v0.6.0 base
Build-system, config, usb, and CI wiring on top of upstream's
v0.6.0-hotfix tree. Build will still fail until Phase C+D land bt.h
accessors that oled.cpp references; that's expected mid-rebase.

CMakeLists.txt: add src/oled.cpp + src/slots.cpp to add_executable,
link hardware_spi, restore OUTPUT_NAME ds5-bridge-oled.

src/config.h: append the 4 OLED Edition Config_body fields
(current_slot + auto_haptics_enable/gain/lowpass). Preserves
upstream's field order for the first 8 fields.

src/config.cpp:
- speaker_volume validity default -100 dB → 0 dB (footgun fix)
- 4 new validity guards for the appended fields with sensible
  defaults (Fallback / 100 % / 160 Hz / slot 0)
- config_default() impl (was declared in upstream's config.h since
  v0.5.4 but never defined; the OLED Edition's "Reset to defaults"
  Settings item calls it)

src/usb.cpp: drop the UAC1 SET_CUR Volume → flash-config sync that
let PipeWire/Pulse silently override the user's saved Spk Vol on
every device reconnect. Borrowed from loteran/DS5Dongle commit
03fa1e4.

src/usb_descriptors.cpp: restore iSerialNumber = STRID_SERIAL.
Upstream's commit e79c762 zeroed it to work around SpecialK (#32),
but that broke Windows device identity (#100) — users lost
per-device volume / app preferences when moving USB ports. The
OLED Edition prioritizes Windows device-identity stability for
the broader user base over SpecialK compat for a narrower one.

.github/workflows/{build,release}.yml: artifact filenames use
ds5-bridge-oled.uf2 (and -debug / -picow variants) per the
OUTPUT_NAME rebrand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-17 07:53:41 -06:00

193 lines
7.5 KiB
C++

//
// Created by awalol on 2026/3/4.
//
#include "tusb.h"
#include "bsp/board_api.h"
#include "config.h"
uint8_t mute[2]; // 0: SPEAKER(0x02) 1: MIC(0x05)
float volume[2] = {-100.0f,0.0f}; // 0: SPEAKER(0x02) 1: MIC(0x05)
#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);
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) {
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[index] = pBuff[0];
TU_LOG2(" Set Mute: %d of entity: %u\r\n", mute[index], entityID);
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[index] = static_cast<float>(*reinterpret_cast<int16_t const *>(pBuff)) / 256;
// Do NOT sync the USB-side UAC1 volume control into our
// flash-persisted config. The host (PipeWire/Pulse) re-applies
// its last-known UAC1 volume on every device reconnect, which
// would silently override the user's saved speaker_volume.
// Fix borrowed from loteran/DS5Dongle commit 03fa1e4.
(void)entityID;
TU_LOG2(" Set Volume: %d dB of entity: %u\r\n", volume[index], entityID);
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);
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) {
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 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 entity: %u\r\n", entityID); {
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT) {
volume[index] = get_config().speaker_volume;
}
int16_t vol = volume[index] * 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 entity: %u\r\n", entityID); {
uint8_t min[2];
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT) {
min[0] = 0x00;
min[1] = 0x9c;
}else {
min[0] = 0x00;
min[1] = 0x00;
}
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 entity: %u\r\n", entityID); {
uint8_t max[2];
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT) {
max[0] = 0x00;
max[1] = 0x00;
}else {
max[0] = 0x00;
max[1] = 0x30;
}
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 entity: %u\r\n", entityID); {
uint8_t res[2];
if (entityID == UAC1_ENTITY_SPK_FEATURE_UNIT) {
res[0] = 0x00;
res[1] = 0x01;
}else {
res[0] = 0x7a;
res[1] = 0x00;
}
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;
}