Enable strong rumble for 8BitDo Switch mode

This commit is contained in:
Joey Yakimowich-Payne 2026-09-02 08:50:26 -06:00
commit 8351cb1d15
5 changed files with 214 additions and 7 deletions

View file

@ -259,10 +259,10 @@ index 0265f93..5c0f2bb 100644
return (psmove_instance_t*)&d->parser_data[0];
}
diff --git a/src/components/bluepad32/parser/uni_hid_parser_switch.c b/src/components/bluepad32/parser/uni_hid_parser_switch.c
index 599fc35..c72f056 100644
index 599fc35..9f073b4 100644
--- a/src/components/bluepad32/parser/uni_hid_parser_switch.c
+++ b/src/components/bluepad32/parser/uni_hid_parser_switch.c
@@ -51,7 +51,8 @@ static const int16_t DEFAULT_ACCEL_OFFSET = 0;
@@ -51,13 +51,15 @@ static const int16_t DEFAULT_ACCEL_OFFSET = 0;
static const int16_t DEFAULT_ACCEL_SCALE = 16384;
static const int16_t DEFAULT_GYRO_OFFSET = 0;
static const int16_t DEFAULT_GYRO_SCALE = 13371;
@ -272,7 +272,97 @@ index 599fc35..c72f056 100644
#define SWITCH_FACTORY_IMU_CAL_DATA_SIZE 24
static const uint16_t SWITCH_FACTORY_IMU_CAL_DATA_ADDR = 0x6020;
@@ -823,19 +824,26 @@ static void parse_imu(uni_hid_device_t* d, const struct switch_imu_data_s* r) {
#define SWITCH_DUMP_ROM_DATA_SIZE 24 // Max size is 24
#define SWITCH_SETUP_TIMEOUT_MS 800
+#define SWITCH_RUMBLE_REFRESH_MS 40
#if ENABLE_SPI_FLASH_DUMP
static const uint32_t SWITCH_DUMP_ROM_DATA_ADDR_START = 0x20000;
static const uint32_t SWITCH_DUMP_ROM_DATA_ADDR_END = 0x30000;
@@ -72,6 +74,7 @@ enum switch_state {
STATE_READ_FACTORY_IMU_CALIBRATION, // Factory IMU calibration info
STATE_SET_FULL_REPORT, // Request report 0x30
STATE_ENABLE_IMU, // Enable/Disable gyro/accel
+ STATE_ENABLE_RUMBLE, // Enable controller vibration
STATE_DUMP_FLASH, // Dump SPI Flash memory
STATE_UPDATE_LED, // Update LEDs
STATE_READY, // Gamepad setup ready!
@@ -111,6 +114,7 @@ enum switch_subcmd {
SUBCMD_SPI_FLASH_READ = 0x10,
SUBCMD_SET_PLAYER_LEDS = 0x30,
SUBCMD_ENABLE_IMU = 0x40,
+ SUBCMD_ENABLE_RUMBLE = 0x48,
};
typedef enum {
@@ -137,6 +141,7 @@ typedef struct switch_instance_s {
// Although technically, we can use one timer for delay and duration, easier to debug/maintain if we have two.
btstack_timer_source_t rumble_timer_duration;
btstack_timer_source_t rumble_timer_delayed_start;
+ btstack_timer_source_t rumble_timer_refresh;
switch_state_rumble_t rumble_state;
btstack_timer_source_t setup_timer;
@@ -322,6 +327,7 @@ static void fsm_read_user_stick_calibration(struct uni_hid_device_s* d);
static void fsm_read_factory_imu_calibration(struct uni_hid_device_s* d);
static void fsm_set_full_report(struct uni_hid_device_s* d);
static void fsm_enable_imu(struct uni_hid_device_s* d);
+static void fsm_enable_rumble(struct uni_hid_device_s* d);
static void fsm_update_led(struct uni_hid_device_s* d);
static void fsm_ready(struct uni_hid_device_s* d);
static void process_reply_read_spi_dump(struct uni_hid_device_s* d, const uint8_t* data, int len);
@@ -333,11 +339,16 @@ static void process_reply_set_report_mode(struct uni_hid_device_s* d, const stru
static void process_reply_spi_flash_read(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len);
static void process_reply_set_player_leds(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len);
static void process_reply_enable_imu(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len);
+static void process_reply_enable_rumble(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len);
static int32_t calibrate_axis(int32_t v, switch_cal_stick_t cal);
static void set_led(uni_hid_device_t* d, uint8_t leds);
static void on_switch_set_rumble_on(btstack_timer_source_t* ts);
static void on_switch_set_rumble_off(btstack_timer_source_t* ts);
+static void on_switch_refresh_rumble(btstack_timer_source_t* ts);
static void switch_stop_rumble_now(uni_hid_device_t* d);
+static void switch_send_dual_rumble_now(uni_hid_device_t* d,
+ uint8_t weak_magnitude,
+ uint8_t strong_magnitude);
static void switch_play_dual_rumble_now(uni_hid_device_t* d,
uint16_t duration_ms,
uint8_t weak_magnitude,
@@ -451,6 +462,10 @@ static void process_fsm(struct uni_hid_device_s* d) {
break;
case STATE_ENABLE_IMU:
logd("STATE_ENABLE_IMU\n");
+ fsm_enable_rumble(d);
+ break;
+ case STATE_ENABLE_RUMBLE:
+ logd("STATE_ENABLE_RUMBLE\n");
fsm_dump_rom(d);
break;
case STATE_DUMP_FLASH:
@@ -725,6 +740,12 @@ static void process_reply_enable_imu(struct uni_hid_device_s* d, const struct sw
ARG_UNUSED(r);
ARG_UNUSED(len);
}
+static void process_reply_enable_rumble(struct uni_hid_device_s* d, const struct switch_report_21_s* r, int len) {
+ ARG_UNUSED(d);
+ ARG_UNUSED(r);
+ ARG_UNUSED(len);
+}
+
// Process 0x21 input report: SWITCH_INPUT_SUBCMD_REPLY
static void process_input_subcmd_reply(struct uni_hid_device_s* d, const uint8_t* report, int len) {
@@ -752,6 +773,9 @@ static void process_input_subcmd_reply(struct uni_hid_device_s* d, const uint8_t
case SUBCMD_ENABLE_IMU:
process_reply_enable_imu(d, r, len);
break;
+ case SUBCMD_ENABLE_RUMBLE:
+ process_reply_enable_rumble(d, r, len);
+ break;
default:
loge("Switch: Error, unexpected subcmd_id=0x%02x in report 0x21\n", r->subcmd_id);
break;
@@ -823,19 +847,26 @@ static void parse_imu(uni_hid_device_t* d, const struct switch_imu_data_s* r) {
switch_instance_t* ins = get_switch_instance(d);
uni_controller_t* ctl = &d->controller;
@ -308,7 +398,7 @@ index 599fc35..c72f056 100644
if (ins->controller_type == SWITCH_CONTROLLER_TYPE_JCR) {
accel[1] = -accel[1];
accel[2] = -accel[2];
@@ -843,10 +851,13 @@ static void parse_imu(uni_hid_device_t* d, const struct switch_imu_data_s* r) {
@@ -843,10 +874,13 @@ static void parse_imu(uni_hid_device_t* d, const struct switch_imu_data_s* r) {
gyro[2] = -gyro[2];
}
@ -326,6 +416,121 @@ index 599fc35..c72f056 100644
}
// Process 0x30 input report: SWITCH_INPUT_IMU_DATA
@@ -1172,6 +1206,18 @@ static void fsm_enable_imu(struct uni_hid_device_s* d) {
req->data[0] = (ins->mode == SWITCH_MODE_IMU);
send_subcmd(d, req, sizeof(out));
}
+static void fsm_enable_rumble(struct uni_hid_device_s* d) {
+ switch_instance_t* ins = get_switch_instance(d);
+ ins->state = STATE_ENABLE_RUMBLE;
+
+ uint8_t out[sizeof(struct switch_subcmd_request) + 1] = {0};
+ struct switch_subcmd_request* req = (struct switch_subcmd_request*)&out[0];
+ req->report_id = OUTPUT_RUMBLE_AND_SUBCMD;
+ req->subcmd_id = SUBCMD_ENABLE_RUMBLE;
+ req->data[0] = 0x01;
+ send_subcmd(d, req, sizeof(out));
+}
+
static void fsm_update_led(struct uni_hid_device_s* d) {
switch_instance_t* ins = get_switch_instance(d);
@@ -1203,6 +1249,10 @@ static struct switch_rumble_freq_data find_rumble_freq(uint16_t freq) {
return rumble_freqs[i];
}
+static uint16_t switch_magnitude_to_amp(uint8_t magnitude) {
+ return (uint16_t)(((uint32_t)magnitude * 1003 + 127) / 255);
+}
+
static struct switch_rumble_amp_data find_rumble_amp(uint16_t amp) {
unsigned int i = 0;
if (amp > rumble_amps[0].amp) {
@@ -1259,6 +1309,7 @@ void uni_hid_parser_switch_play_dual_rumble(struct uni_hid_device_s* d,
break;
case SWITCH_STATE_RUMBLE_IN_PROGRESS:
btstack_run_loop_remove_timer(&ins->rumble_timer_duration);
+ btstack_run_loop_remove_timer(&ins->rumble_timer_refresh);
break;
default:
// Do nothing
@@ -1366,6 +1417,7 @@ static void switch_stop_rumble_now(uni_hid_device_t* d) {
// No need to protect it with a mutex since it runs in the same main thread
assert(ins->rumble_state == SWITCH_STATE_RUMBLE_IN_PROGRESS);
+ btstack_run_loop_remove_timer(&ins->rumble_timer_refresh);
ins->rumble_state = SWITCH_STATE_RUMBLE_DISABLED;
struct switch_subcmd_request req = {0};
@@ -1379,6 +1431,22 @@ static void switch_stop_rumble_now(uni_hid_device_t* d) {
send_subcmd(d, (struct switch_subcmd_request*)&req, sizeof(req) - 1);
}
+static void switch_send_dual_rumble_now(uni_hid_device_t* d,
+ uint8_t weak_magnitude,
+ uint8_t strong_magnitude) {
+ struct switch_subcmd_request req = {
+ .report_id = OUTPUT_RUMBLE_ONLY,
+ };
+ // Fixed frequencies match the standard Switch LRA envelope and the
+ // 8BitDo Switch-mode implementation. Magnitudes control amplitude only.
+ switch_encode_rumble(req.rumble_left, 453, 135,
+ switch_magnitude_to_amp(weak_magnitude));
+ switch_encode_rumble(req.rumble_right, 453, 99,
+ switch_magnitude_to_amp(strong_magnitude));
+ // Rumble request don't include the last byte of "switch_subcmd_request": subcmd_id
+ send_subcmd(d, &req, sizeof(req) - 1);
+}
+
static void switch_play_dual_rumble_now(uni_hid_device_t* d,
uint16_t duration_ms,
uint8_t weak_magnitude,
@@ -1391,14 +1459,17 @@ static void switch_play_dual_rumble_now(uni_hid_device_t* d,
return;
}
- struct switch_subcmd_request req = {
- .report_id = OUTPUT_RUMBLE_ONLY,
- };
- switch_encode_rumble(req.rumble_left, weak_magnitude << 2, weak_magnitude, 500);
- switch_encode_rumble(req.rumble_right, strong_magnitude << 2, strong_magnitude, 500);
+ ins->rumble_weak_magnitude = weak_magnitude;
+ ins->rumble_strong_magnitude = strong_magnitude;
+ switch_send_dual_rumble_now(d, weak_magnitude, strong_magnitude);
- // Rumble request don't include the last byte of "switch_subcmd_request": subcmd_id
- send_subcmd(d, &req, sizeof(req) - 1);
+ // Refresh active rumble for Switch-compatible controllers that do not
+ // retain a single output packet, including 8BitDo Switch mode.
+ ins->rumble_timer_refresh.process = &on_switch_refresh_rumble;
+ ins->rumble_timer_refresh.context = d;
+ btstack_run_loop_set_timer(&ins->rumble_timer_refresh,
+ SWITCH_RUMBLE_REFRESH_MS);
+ btstack_run_loop_add_timer(&ins->rumble_timer_refresh);
// Set timer to turn off rumble
ins->rumble_timer_duration.process = &on_switch_set_rumble_off;
@@ -1414,6 +1485,20 @@ static void on_switch_set_rumble_on(btstack_timer_source_t* ts) {
switch_play_dual_rumble_now(d, ins->rumble_duration_ms, ins->rumble_weak_magnitude, ins->rumble_strong_magnitude);
}
+static void on_switch_refresh_rumble(btstack_timer_source_t* ts) {
+ uni_hid_device_t* d = btstack_run_loop_get_timer_context(ts);
+ switch_instance_t* ins = get_switch_instance(d);
+ if (ins->rumble_state != SWITCH_STATE_RUMBLE_IN_PROGRESS) {
+ return;
+ }
+ switch_send_dual_rumble_now(
+ d, (uint8_t)ins->rumble_weak_magnitude,
+ (uint8_t)ins->rumble_strong_magnitude);
+ btstack_run_loop_set_timer(&ins->rumble_timer_refresh,
+ SWITCH_RUMBLE_REFRESH_MS);
+ btstack_run_loop_add_timer(&ins->rumble_timer_refresh);
+}
+
static void on_switch_set_rumble_off(btstack_timer_source_t* ts) {
uni_hid_device_t* d = btstack_run_loop_get_timer_context(ts);
diff --git a/src/components/bluepad32/parser/uni_hid_parser_wii.c b/src/components/bluepad32/parser/uni_hid_parser_wii.c
index be2103e..4819639 100644
--- a/src/components/bluepad32/parser/uni_hid_parser_wii.c