feat: config manager

This commit is contained in:
awalol
2026-05-05 01:17:15 +08:00
parent 572d017ec5
commit fb8624b61b
9 changed files with 263 additions and 15 deletions
+4 -1
View File
@@ -44,6 +44,8 @@ add_executable(ds5-bridge
src/usb.cpp
src/audio.cpp
src/bt.cpp
src/config.cpp
src/cmd.cpp
)
# Cockos WDLResampler
@@ -67,7 +69,7 @@ target_compile_definitions(ds5-bridge PRIVATE
CYW43_LWIP=0
PICO_FLASH_ASSUME_CORE1_SAFE=1
WDL_RESAMPLE_TYPE=float
CYW43_PIO_CLOCK_DIV_INT=4
CYW43_PIO_CLOCK_DIV_INT=5
)
option(ENABLE_DSE "Enable DSE" OFF)
if(ENABLE_DSE)
@@ -98,6 +100,7 @@ target_link_libraries(ds5-bridge
pico_multicore
hardware_watchdog
hardware_timer
hardware_flash
pico_btstack_classic
# pico_cyw43_arch_threadsafe_background
pico_cyw43_arch_poll
+5 -6
View File
@@ -6,14 +6,13 @@
#include "bt.h"
#include "resample.h"
#include "tusb.h"
#include "usb.h"
#include <algorithm>
#include <cstdio>
#include "opus.h"
#include "utils.h"
#include "pico/multicore.h"
#include "pico/util/queue.h"
#include "config.h"
#define INPUT_CHANNELS 4
#define OUTPUT_CHANNELS 2
@@ -72,8 +71,8 @@ void audio_loop() {
int nframes = resampler.ResamplePrepare(frames, OUTPUT_CHANNELS, &in_buf);
for (int i = 0; i < nframes; i++) {
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS] / 32768.0f * (volume[0] - 1.0f);
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS + 1] / 32768.0f * (volume[0] - 1.0f);
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS] / 32768.0f * (get_config().speaker_volume - 1.0f);
audio_buf[audio_buf_pos++] = raw[i * INPUT_CHANNELS + 1] / 32768.0f * (get_config().speaker_volume - 1.0f);
if (audio_buf_pos == 512 * 2) {
static audio_raw_element element{};
memcpy(element.data,audio_buf,512 * 2 * 4);
@@ -99,8 +98,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 * max(volume[1],1.0f));
int val_r = (int) (out_buf[i * 2 + 1] * 127.0f * max(volume[1],1.0f));
int val_l = (int) (out_buf[i * 2] * 127.0f * max(get_config().haptics_gain,1.0f));
int val_r = (int) (out_buf[i * 2 + 1] * 127.0f * max(get_config().haptics_gain,1.0f));
haptic_buf[haptic_buf_pos++] = (int8_t) clamp(val_l, -128, 127); // 似乎clamp有点多余?还是以防万一吧
haptic_buf[haptic_buf_pos++] = (int8_t) clamp(val_r, -128, 127);
+3 -5
View File
@@ -4,13 +4,10 @@
#include <cstdio>
#include <cstring>
#include "bt.h"
#include <queue>
#include <unordered_map>
#include <vector>
#include "btstack_event.h"
#include "l2cap.h"
#include "pico/cyw43_arch.h"
@@ -20,6 +17,7 @@
#include "bsp/board_api.h"
#include "pico/sync.h"
#include "classic/sdp_server.h"
#include "config.h"
#define MTU 672
@@ -321,7 +319,7 @@ static void l2cap_packet_handler(uint8_t packet_type, uint16_t channel, uint8_t
bt_data_callback(INTERRUPT, packet, size);
// 静默检测
if (mute[1]) { // 麦克风静音开启
if (get_config().disable_inactive_disconnect) {
return;
}
if (packet[3] < 120 || packet[3] > 140) {
@@ -361,7 +359,7 @@ 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;
if (!mute[0]) {
if (!get_config().disable_pico_led) {
cyw43_arch_gpio_put(CYW43_WL_GPIO_LED_PIN, true);
}
inactive_time = time_us_32();
+60
View File
@@ -0,0 +1,60 @@
//
// Created by awalol on 2026/5/4.
//
#include "cmd.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include "config.h"
#include "device/usbd.h"
#include "pico/time.h"
bool is_pico_cmd(uint8_t report_id) {
if (report_id == 0xf6 ||
report_id == 0xf7
) {
return true;
}
return false;
}
uint16_t pico_cmd_get(uint8_t report_id, uint8_t *buffer, uint16_t reqlen) {
if (report_id == 0xf7) {
printf("[HID] Receive 0xf7 getting config\n");
if (sizeof(Config_body) > reqlen) {
printf("[Config] Warning: Config_body overflow\n");
}
const auto len = std::min(sizeof(Config_body),static_cast<size_t>(reqlen));
memcpy(buffer,&get_config(),len);
return len;
}
return 0;
}
void pico_cmd_set(uint8_t report_id, uint8_t const *buffer, uint16_t bufsize) {
(void) report_id;
if (bufsize == 0) {
return;
}
// 0x01 update config in variable
// 0x02 write config to flash
// 0x03 reconnect tinyusb device;
if (buffer[0] == 0x01) {
printf("[CMD] Enter config set func\n");
set_config(buffer + 1, bufsize - 1);
}
if (buffer[0] == 0x02) {
printf("[CMD] Enter config save func\n");
config_save();
}
if (buffer[0] == 0x03) {
printf("[CMD] Enter tud reconnect func\n");
tud_disconnect();
sleep_ms(1000);
tud_connect();
}
}
+14
View File
@@ -0,0 +1,14 @@
//
// Created by awalol on 2026/5/4.
//
#ifndef DS5_BRIDGE_CMD_H
#define DS5_BRIDGE_CMD_H
#include <stdint.h>
bool is_pico_cmd(uint8_t report_id);
uint16_t pico_cmd_get(uint8_t report_id, uint8_t *buffer,uint16_t reqlen);
void pico_cmd_set(uint8_t report_id, uint8_t const *buffer,uint16_t bufsize);
#endif //DS5_BRIDGE_CMD_H
+110
View File
@@ -0,0 +1,110 @@
//
// Created by awalol on 2026/5/4.
//
#include "config.h"
#include <cmath>
#include <cstring>
#include "utils.h"
#include "hardware/flash.h"
#include "hardware/sync.h"
constexpr uint32_t CONFIG_MAGIC = 0x66ccff00;
constexpr uint16_t CONFIG_VERSION = 1;
constexpr uint32_t CONFIG_FLASH_OFFSET = PICO_FLASH_SIZE_BYTES - FLASH_SECTOR_SIZE;
static Config config{};
// 编译期保护
// 判断Config结构体是否能放进flash 256bytes
static_assert(sizeof(Config) <= FLASH_PAGE_SIZE);
// 配置区起始地址必须按 flash sector 对齐。
static_assert(CONFIG_FLASH_OFFSET % FLASH_SECTOR_SIZE == 0);
uint32_t calc_config_crc(const Config &con) {
return crc32(reinterpret_cast<const uint8_t *>(&con.body), sizeof(Config_body));
}
const Config *flash_config() {
return reinterpret_cast<const Config *>(XIP_BASE + CONFIG_FLASH_OFFSET);
}
void config_valid() {
// valid config and set default value
if (config.magic != CONFIG_MAGIC) {
config.magic = CONFIG_MAGIC;
printf("[Config] Config Magic Header is invalid\n");
}
if (config.version != CONFIG_VERSION) {
config.version = CONFIG_VERSION;
printf("[Config] Config Version is invalid\n");
}
if (config.size != sizeof(Config_body)) {
config.size = sizeof(Config_body);
printf("[Config] Config Body size is invalid\n");
}
auto body = &config.body;
if (std::isnan(body->haptics_gain) || body->haptics_gain < 1.0f || body->haptics_gain > 2.0f) {
body->haptics_gain = 1.0f;
printf("[Config] Haptics Gain value is invalid\n");
}
if (std::isnan(body->speaker_volume) || body->speaker_volume < 1.0f || body->speaker_volume > 2.0f) {
body->speaker_volume = 2.0f;
printf("[Config] Speaker Volume is invalid\n");
}
if (body->disable_inactive_disconnect > 1) {
body->disable_inactive_disconnect = 0;
printf("[Config] disable_auto_disconnect is invalid\n");
}
if (body->disable_pico_led > 1) {
body->disable_pico_led = 0;
printf("[Config] disable_pico_led is invalid\n");
}
if (body->polling_rate_mode > 2) {
body->polling_rate_mode = 0;
printf("[Config] polling_rate_mode is invalid\n");
}
if (body->haptics_buffer_length < 16 || body->haptics_buffer_length > 255) {
body->haptics_buffer_length = 48;
printf("[Config] haptics_buffer_length is invalid\n");
}
}
void config_load() {
memcpy(&config, flash_config(), sizeof(Config));
config_valid();
}
bool config_save() {
config.crc32 = calc_config_crc(config);
alignas(4) uint8_t page[FLASH_PAGE_SIZE];
memset(page, 0xff, sizeof(page));
memcpy(page, &config, sizeof(Config));
const uint32_t interrupts = save_and_disable_interrupts();
flash_range_erase(CONFIG_FLASH_OFFSET, FLASH_SECTOR_SIZE);
flash_range_program(CONFIG_FLASH_OFFSET, page, sizeof(page));
restore_interrupts(interrupts);
Config verify{};
memcpy(&verify, flash_config(), sizeof(verify));
const auto verify_crc32 = calc_config_crc(verify);
if (verify_crc32 == config.crc32) {
printf("[Config] Config write flash verify success\n");
return true;
}
printf("[Config] Config write flash verify failed\n");
return false;
}
const Config_body& get_config() {
return config.body;
}
void set_config(const uint8_t *new_config, const uint16_t len) {
const auto copy_len = len < sizeof(Config_body) ? len : sizeof(Config_body);
memcpy(&config.body, new_config, copy_len);
config_valid();
}
+34
View File
@@ -0,0 +1,34 @@
//
// Created by awalol on 2026/5/4.
//
#ifndef DS5_BRIDGE_CONFIG_H
#define DS5_BRIDGE_CONFIG_H
#include <cstdint>
struct __attribute__((packed)) Config_body {
float haptics_gain; // [1.0,2.0]
float speaker_volume; // [1.0,2.0]
uint8_t disable_inactive_disconnect; // bool: 0 disable,1 enable
uint8_t disable_pico_led; // bool
uint8_t polling_rate_mode; // 0: 250Hz,1: 500Hz,2: instant
uint8_t haptics_buffer_length; // [16,255]
};
struct __attribute__((packed)) Config {
uint32_t magic;
uint16_t version;
uint32_t crc32; // Config_body crc32, only calc and verify when save
uint16_t size; // Config_body size
Config_body body;
};
void config_default();
void config_load();
bool config_save();
const Config_body& get_config();
void set_config(const uint8_t *new_config, const uint16_t len);
void config_valid();
#endif //DS5_BRIDGE_CONFIG_H
+14
View File
@@ -12,6 +12,8 @@
#include "hardware/vreg.h"
#include "hardware/watchdog.h"
#include "pico/cyw43_arch.h"
#include "config.h"
#include "cmd.h"
// Pico SDK speciifically for waiting on conditions
#include "pico/critical_section.h"
@@ -94,6 +96,10 @@ uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t
(void) buffer;
(void) reqlen;
if (is_pico_cmd(report_id)) {
return pico_cmd_get(report_id,buffer,reqlen);
}
std::vector<uint8_t> feature_data = get_feature_data(report_id, reqlen);
if (!feature_data.empty()) {
memcpy(buffer, feature_data.data() + 1, feature_data.size() - 1);
@@ -112,6 +118,12 @@ void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t rep
(void) buffer;
(void) bufsize;
if (is_pico_cmd(report_id)) {
printf("[HID] Receive 0xf6 setting config, funcid:0x%02X\n",buffer[0]);
pico_cmd_set(report_id,buffer,bufsize);
return;
}
// INTERRUPT OUT
if (report_id == 0) {
switch (buffer[0]) {
@@ -173,6 +185,8 @@ int main() {
// Initialize the critical section for the report buffer
critical_section_init(&report_cs);
config_load();
bt_init();
bt_register_data_callback(on_bt_data);
+19 -3
View File
@@ -303,9 +303,9 @@ uint8_t const descriptor_configuration[] = {
0x01, // bNumDescriptors: 1 report descriptor
0x22, // bDescriptorType: Report
#ifdef ENABLE_DSE
0x95, 0x01, // wDescriptorLength: 405 (0x0121)
0xA1, 0x01, // wDescriptorLength: 417 (0x01A1)
#else
0x21, 0x01, // wDescriptorLength: 289 (0x0121)
0x31, 0x01, // wDescriptorLength: 289 (0x0121)
#endif
// Endpoint Descriptor (HID IN: EP4)
@@ -480,6 +480,14 @@ uint8_t const desc_hid_report_ds[] = {
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)
0x85, 0xF6,
0x09, 0x37,
0x95, 0x3F,
0xB1, 0x02,
0x85, 0xF7,
0x09, 0x38,
0x95, 0x3F,
0xB1, 0x02,
0xC0, // End Collection
// 289 bytes
};
@@ -686,8 +694,16 @@ uint8_t const desc_hid_report_dse[] = {
0x85, 0x7B, // Report ID (123)
0x09, 0x53, // Usage (0x53)
0xB1, 0x02, // Feature (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0x85, 0xF6,
0x09, 0x37,
0x95, 0x3F,
0xB1, 0x02,
0x85, 0xF7,
0x09, 0x38,
0x95, 0x3F,
0xB1, 0x02,
0xC0, // End Collection
// 405 bytes
// 417 bytes
};
#endif