Ver Fonte

Go back to a fixed 250hz polling

Jari há 3 meses atrás
pai
commit
ed55ee84bd

+ 4 - 3
OpenPuck/config.cpp

@@ -65,9 +65,10 @@ void applyActiveType()
 // rumble strength % (200 = double); adjustable from the WebUSB panel
 uint8_t g_rumbleScale = 200;
 
-// poll rate defaults to POLL_US_DEFAULT (333 Hz), just above the controller's ~270 Hz drag peak (see
-// config.h for the RE rationale). Live-adjustable via console "PR<hz>" for on-HW sweeps; session-only, so
-// any rate persisted by an older build is ignored and boot always starts at the default (see loadCfg).
+// poll rate defaults to POLL_US_DEFAULT (250 Hz), matching the real Valve puck (see config.h). The
+// delivered report rate equals the poll rate (fresh IMU in every reply). Live-adjustable via console
+// "PR<hz>" for on-HW sweeps; session-only, so any rate persisted by an older build is ignored and boot
+// always starts at the default (see loadCfg).
 uint32_t g_pollUs = POLL_US_DEFAULT;
 
 #define CFG_FILE "/cfg.bin"

+ 7 - 10
OpenPuck/config.h

@@ -156,16 +156,13 @@ extern uint8_t g_swGyroScale10;
 // persist g_swProRate + g_swGyroScale10 to their flash file
 void swProSaveCfg();
 
-// RF poll cadence: 333 Hz (3000 us). The controller does NOT stream on its own -- the RE air capture
-// (nrf-sniffer HANDOFF.md) shows the puck POLLS (short frame) and the controller auto-ACKs one input
-// frame ~90 us later, so the controller's observed ~137-270 Hz "packet counter" IS the real puck's poll
-// rate (peaking ~270 Hz during a fast trackpad drag). We poll slightly above that peak so a fast drag is
-// never undersampled, without the wasteful stale-ACK repeats that a much higher rate produces (polling
-// faster than the controller refreshes just re-reads the same ACK payload, deduped by seq -> burned
-// airtime, no new samples). The old 250 Hz default sat BELOW the drag peak -> dropped pad samples ->
-// Steam's pad->mouse velocity mapping stair-stepped ("feels like it misses inputs"). Multi-controller
-// degrades gracefully via the rfConnStep catch-up reset.
-#define POLL_US_DEFAULT 3000u
+// RF poll cadence: 250 Hz (4000 us) -- matches the real Valve puck's rate. The controller does NOT stream
+// on its own; the puck POLLS (short frame) and the controller auto-ACKs one input frame per poll (RE air
+// capture, nrf-sniffer HANDOFF.md), and it stuffs fresh IMU into every report, so the DELIVERED report
+// rate simply equals the poll rate. The real puck polls ~230-250 Hz, so we match it here. (The genuine
+// smoothness bug was never the rate -- it was onReport45 dropping ~1/3 of captured reports via a bogus
+// hid.ready() gate; see puck_hid.cpp. Poll rate is live-tunable via console "PR<hz>" for sweeps.)
+#define POLL_US_DEFAULT 4000u
 // host-side HID stream cadence for translated modes (~250 Hz)
 #define USB_STREAM_MS 4u
 // How long a USB suspend must PERSIST before we power the controllers off. A brief selective-suspend

+ 27 - 1
OpenPuck/puck_hid.cpp

@@ -14,6 +14,13 @@
 #include <string.h>
 
 uint8_t g_fwdNewOnly = 1;
+// Content dedup for the Steam input forward: only forward a 0x45/0x42 report when its body EXCLUDING the
+// counter byte differs from the last one forwarded on that slot. The controller's counter (rep[1]) FREE-RUNS
+// -- it advances on every reply even with static input -- so the seq-based g_fwdNewOnly can't suppress true
+// repeats, and a poll rate above the controller's ~250 Hz sample rate forwards identical inputs with only the
+// counter bumped (host tester reads >250/s for a <=250 Hz controller). Content dedup is what the real puck
+// effectively does. On by default; console "CD" toggles for A/B.
+uint8_t g_fwdContentDedup = 1;
 SteamPuckController g_steamPuck;
 
 // Cloned puck HID report descriptor: mouse(0x40)+keyboard(0x41)+vendor(FF00) with the 63-byte FEATURE
@@ -726,7 +733,26 @@ void SteamPuckController::onReport45(int slot, const uint8_t *rep, bool fresh,
 		// fresh report that landed while the endpoint was busy (~1 ms after each send), which at
 		// 300+ captured reports/s silently discarded ~a third of them (measured: RF new/s ~313 but
 		// host delivered ~180). Always enqueue; the ring paces delivery to the host without loss.
-		if (fresh || !g_fwdNewOnly)
+		bool send = (fresh || !g_fwdNewOnly);
+		// Content dedup: the counter byte (rep[1]) free-runs, so drop a report whose body AFTER the
+		// counter is byte-identical to the last one forwarded on this slot -- a true repeat (same
+		// physical input, bumped counter), not a new sample. Caps delivery at the controller's real
+		// distinct-report rate instead of the (higher) poll rate. See g_fwdContentDedup above.
+		if (send && g_fwdContentDedup && blen >= 2) {
+			static uint8_t lastBody[NSLOT][53];
+			static uint8_t lastBodyLen[NSLOT] = { 0 };
+			uint8_t clen = (uint8_t)(blen - 1); // bytes after the counter
+			if (clen > sizeof lastBody[0])
+				clen = sizeof lastBody[0];
+			if (lastBodyLen[slot] == clen &&
+			    memcmp(lastBody[slot], rep + 2, clen) == 0)
+				send = false; // identical input, only the counter moved
+			else {
+				lastBodyLen[slot] = clen;
+				memcpy(lastBody[slot], rep + 2, clen);
+			}
+		}
+		if (send)
 			usbTxHid(
 				&hid[slot], rid, rep + 1,
 				blen); // Steam/SDL Triton: input report 0x45 (old) / 0x42 (new fw)

+ 4 - 0
OpenPuck/puck_hid.h

@@ -17,6 +17,10 @@
 // Forward report 0x45 only when the seq advanced (dedupe like the real puck); sending stale
 // repeats makes Steam's trackpad smoothing stair-step. (WebUSB field 14)
 extern uint8_t g_fwdNewOnly;
+// Content dedup: forward a 0x45/0x42 report only when its body EXCLUDING the free-running counter byte
+// changed, so the delivered rate tracks the controller's real distinct-report rate (not the poll rate).
+// On by default; console "CD" toggles for A/B. (see puck_hid.cpp for the why)
+extern uint8_t g_fwdContentDedup;
 
 // True while we are presenting the desktop keyboard/mouse instead of forwarding the gamepad to Steam
 // (MODE_LIZARD always, or MODE_STEAM once Steam's heartbeat has stopped). Exposed so the haptic layer can

+ 4 - 4
OpenPuck/rf_link.cpp

@@ -44,7 +44,7 @@ bool g_e1keepalive = true;
 bool g_connVerbose = false;
 // poll RX-window (us): the poll BUSY-WAITS up to this long for the controller's reply. The reply returns
 // EARLY (EVENTS_END) on a successful poll, so this window is only paid IN FULL on a genuine no-reply --
-// one slot needs (g_rxWin + overhead) < g_pollUs(3000) so a no-reply poll still fits inside the 333 Hz
+// one slot needs (g_rxWin + overhead) < g_pollUs(4000) so a no-reply poll still fits inside the 250 Hz
 // cycle. 1200 is the proven value; it was briefly raised to 2000 (issue-72, delayed-reply tolerance) which
 // dropped the rate. FIXED + not configurable (like g_pollUs): there is no good reason to raise it in the
 // field, and doing so silently starves the poll cycle. Any persisted/old value is ignored.
@@ -137,7 +137,7 @@ static uint8_t g_lastSeq[NSLOT] = { 0 };
 static uint32_t g_stNew[NSLOT] = {};
 static uint32_t g_stCrc[NSLOT] = {}, g_stNoRx[NSLOT] = {};
 static uint32_t g_chF1[3] = { 0, 0, 0 };
-// Cycle gate: fires once per g_pollUs; each fire polls every warm slot so all run at ~333 Hz (oversampling
+// Cycle gate: fires once per g_pollUs; each fire polls every warm slot so all run at ~250 Hz (oversampling
 // the controller's ~270 Hz report generation so no fresh trackpad sample is dropped -- see config.h).
 static uint32_t g_lastPollUs = 0;
 static uint32_t g_connRx = 0;
@@ -786,7 +786,7 @@ uint8_t rfConnTx(uint8_t ch, uint8_t s1, const uint8_t *payload, uint8_t plen,
 #define SLOT_QUIET_RETRY_MS 50u
 static unsigned long g_slotLastAttemptMs[NSLOT] = {};
 
-// Drive the connected-mode sequence. The cycle gate fires once per g_pollUs (333 Hz); each fire
+// Drive the connected-mode sequence. The cycle gate fires once per g_pollUs (250 Hz); each fire
 // polls EVERY warm slot back-to-back so all bonded controllers run at full rate regardless of count.
 // "Cold" means the slot WAS connected but has been silent for > SLOT_COLD_MS; slots that have never
 // replied stay warm so new controllers can connect at any time after boot. Cold slots retry every
@@ -826,7 +826,7 @@ static void rfConnStep()
 		return;
 	}
 
-	// Cycle gate: fires once per g_pollUs (333 Hz). On each fire we poll EVERY warm slot
+	// Cycle gate: fires once per g_pollUs (250 Hz). On each fire we poll EVERY warm slot
 	// back-to-back so all bonded controllers run at full rate -- the real puck services all
 	// controllers per cycle. Polling one-slot-per-call instead tied the per-slot rate to the
 	// loop frequency AND split it across slots, collapsing 2 controllers to ~90 Hz.

+ 2 - 2
OpenPuck/rf_link.h

@@ -44,7 +44,7 @@ extern bool
 // full multi-line debug dump vs compact "I45 <hex>" stream
 extern bool g_connVerbose;
 // poll RX-window (us): the full window is paid only on a no-reply poll (a reply returns early), so it must
-// stay < g_pollUs to fit inside the 333 Hz cycle. FIXED, not configurable (like g_pollUs) -- raising it
+// stay < g_pollUs to fit inside the 250 Hz cycle. FIXED, not configurable (like g_pollUs) -- raising it
 // starves the poll cycle, so it's pinned rather than exposed as a footgun knob.
 extern const uint32_t g_rxWin;
 
@@ -96,7 +96,7 @@ extern uint8_t g_slotCrcps[NSLOT], g_slotNoRxps[NSLOT], g_slotRelayps[NSLOT];
 // firmware caught and recovered from a full-link wedge that previously needed a manual puck replug.
 extern uint16_t g_rfStallRecover;
 
-// MEASURED avg us between GET-poll fires (compare to the intended g_pollUs=3000)
+// MEASURED avg us between GET-poll fires (compare to the intended g_pollUs=4000)
 extern uint16_t g_pollPeriodUs;
 
 // Smoothed controller->puck signal strength, sampled by the radio (RSSISAMPLE) on each CRC-good controller

+ 7 - 0
OpenPuck/serial_console.cpp

@@ -40,6 +40,13 @@ void serialConsolePoll()
 				Serial.printf(
 					"# land amp/haptic 0x87 config %s\n",
 					g_landAmp ? "ON" : "off");
+			} else if (!strcmp(line, "CD")) {
+				// A/B: content dedup on the Steam input forward (drop reports whose body minus the
+				// free-running counter is unchanged) -- caps delivered rate at the controller's real
+				// distinct-report rate instead of the poll rate.
+				g_fwdContentDedup = !g_fwdContentDedup;
+				Serial.printf("# content dedup %s\n",
+					      g_fwdContentDedup ? "ON" : "off");
 			} else if (!strcmp(line, "SS")) {
 				// on-demand smoothness snapshot: print the last-second cached rates in ONE line, NOW,
 				// instead of waiting for the guarded 1 Hz "# stat" (which the I45/FC flood starves during