Просмотр исходного кода

switch gyro should be perfect now, add rumble settings

Jariullah Safi 4 месяцев назад
Родитель
Сommit
8bcdb19bb5
6 измененных файлов с 35 добавлено и 20 удалено
  1. 5 3
      OpenPuck/config.cpp
  2. 1 0
      OpenPuck/config.h
  3. 4 0
      OpenPuck/haptics.cpp
  4. 9 10
      OpenPuck/mode_switch_pro.cpp
  5. 5 3
      OpenPuck/webusb_config.cpp
  6. 11 4
      docs/index.html

+ 5 - 3
OpenPuck/config.cpp

@@ -17,19 +17,20 @@ int     g_mDiv = 64, g_mFric = 94;
 uint8_t g_abSwap = 0;
 uint8_t g_back[4] = {5,6,7,8};   // L4->LB R4->RB L5->L3 R5->R3 (0..11 buttons, 12..15 D-pad U/D/L/R)
 uint8_t g_qamMap = 0;            // 0 = default (unmapped, uses hardcoded behavior per mode)
+uint8_t g_rumbleScale = 200;     // rumble strength % (200 = double); adjustable from the WebUSB panel
 
 // poll rate is fixed. Faster than the controller can refresh wastes airtime; slower adds latency. Any rate
 // persisted by an older build is ignored and overwritten with the default on boot (see loadCfg).
 const uint32_t g_pollUs = POLL_US_DEFAULT;
 
 #define CFG_FILE "/cfg.bin"
-#define CFG_MAGIC 0xC8   // bumped (qamMap): old cfg ignored -> clean defaults on first boot
-struct Cfg { uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100, persistMode, bootMode, chordBtn[3], qamMap; };  // rsvd0 = ex-padSmooth, now the one-shot debug-CDC arm
+#define CFG_MAGIC 0xC9   // bumped (rumbleScale): old cfg ignored -> clean defaults on first boot
+struct Cfg { uint8_t magic, mode, mDiv, mFric, rsvd0, abSwap, back[4], pollU100, persistMode, bootMode, chordBtn[3], qamMap, rumbleScale; };  // rsvd0 = ex-padSmooth, now the one-shot debug-CDC arm
 
 void saveCfg(){
   Cfg c={CFG_MAGIC,g_usbMode,(uint8_t)g_mDiv,(uint8_t)g_mFric,g_debugCdc,g_abSwap,
          {g_back[0],g_back[1],g_back[2],g_back[3]},(uint8_t)(g_pollUs/100),(uint8_t)(g_persistMode?1:0),g_bootMode,
-         {g_chordBtn[0],g_chordBtn[1],g_chordBtn[2]},g_qamMap};
+         {g_chordBtn[0],g_chordBtn[1],g_chordBtn[2]},g_qamMap,g_rumbleScale};
   InternalFS.remove(CFG_FILE); File f(InternalFS);
   if(f.open(CFG_FILE,FILE_O_WRITE)){ f.write((uint8_t*)&c,sizeof c); f.close(); }
 }
@@ -54,6 +55,7 @@ void loadCfg(){
       static const uint8_t CHORD_DEF[3]={MODE_LIZARD,MODE_XBOX,MODE_SW_HORI};
       for(int i=0;i<3;i++) g_chordBtn[i]=modeValid(c.chordBtn[i])?c.chordBtn[i]:CHORD_DEF[i];
       g_qamMap = c.qamMap;
+      g_rumbleScale = c.rumbleScale;   // 0 is a valid setting (rumble off)
     }
     f.close();
   }

+ 1 - 0
OpenPuck/config.h

@@ -64,6 +64,7 @@ extern int     g_mDiv, g_mFric; // xbox/lizard mouse sensitivity divisor / frict
 extern uint8_t g_abSwap;        // 1 = swap A/B and X/Y (Nintendo face-button layout)
 extern uint8_t g_back[4];       // back paddles L4,R4,L5,R5 -> button codes (0..15 standard, 16=PS Touch Click, 17=PS5 Mute)
 extern uint8_t g_qamMap;        // QAM (3 dots) physical button -> same code space (0 = default/unmapped)
+extern uint8_t g_rumbleScale;   // rumble strength, percent of decoded amplitude (100 = 1x, 200 = 2x default), all modes
 
 #define POLL_US_DEFAULT 4000u   // 250 Hz -- matches SC2 input report rate (1000000/250 = 4000 us)
 #define USB_STREAM_MS   4u      // host-side HID stream cadence for translated modes (~250 Hz)

+ 4 - 0
OpenPuck/haptics.cpp

@@ -148,6 +148,10 @@ void haptic82HostReport(const uint8_t* p, uint16_t n){
   g_haptic82On = haptic82PayloadOn(p,n);
 }
 bool hapticSteamRumble(uint16_t lowFreq, uint16_t highFreq){
+  if(g_rumbleScale!=100){            // user rumble-strength scale (percent; 200 = double). Clamp to 16-bit.
+    uint32_t l=(uint32_t)lowFreq*g_rumbleScale/100, h=(uint32_t)highFreq*g_rumbleScale/100;
+    lowFreq=(l>0xFFFF)?0xFFFF:(uint16_t)l; highFreq=(h>0xFFFF)?0xFFFF:(uint16_t)h;
+  }
   bool on = lowFreq || highFreq;
   if(on && haptic82Blocked()) return false;       // same settle gate as native Steam haptics
   if(!on && !hapticLinkUp()) return false;

+ 9 - 10
OpenPuck/mode_switch_pro.cpp

@@ -112,18 +112,17 @@ static void jcInputPrefix(uint8_t* out){
 static void switchProBuild(uint8_t out[63]){
   memset(out,0,63);
   jcInputPrefix(out);
+  // Gyro slot order follows hid-nintendo: raw+6 = ROLL, raw+8 = PITCH, raw+10 = YAW. Steam-validated source
+  // routing: roll<-gy, pitch<--gx (inverted), yaw<-gz.
+  int16_t gpitch=(int16_t)(-(int16_t)g_in.gx);
   for(int k=0;k<3;k++){
     int o=12+k*12;
-    // Accel and gyro must share one axis convention or the Switch's gyro+gravity fusion reads a pure pitch as
-    // diagonal motion. The Steam IMU has X/Y transposed vs the Pro Controller frame, so swap X<->Y on BOTH
-    // sensors (Z/yaw unchanged): accel X<-ay, accel Y<-ax to match the gyro swap below.
-    out[o+0]=g_in.ay&0xFF; out[o+1]=g_in.ay>>8;     // accel X <- g_in.ay
-    out[o+2]=g_in.ax&0xFF; out[o+3]=g_in.ax>>8;     // accel Y <- g_in.ax
-    out[o+4]=g_in.az&0xFF; out[o+5]=g_in.az>>8;     // accel Z
-    int16_t gpitch = (int16_t)(-(int16_t)g_in.gy);  // pitch sense is inverted vs the Switch frame -> negate
-    out[o+6]=gpitch&0xFF; out[o+7]=(gpitch>>8)&0xFF; // gyro X (pitch) <- -g_in.gy
-    out[o+8]=g_in.gx&0xFF; out[o+9]=g_in.gx>>8;     // gyro Y (roll)  <- g_in.gx
-    out[o+10]=g_in.gz&0xFF; out[o+11]=g_in.gz>>8;   // gyro Z (yaw)
+    out[o+0]=g_in.ay&0xFF;  out[o+1]=g_in.ay>>8;    // accel X <- g_in.ay
+    out[o+2]=g_in.ax&0xFF;  out[o+3]=g_in.ax>>8;    // accel Y <- g_in.ax
+    out[o+4]=g_in.az&0xFF;  out[o+5]=g_in.az>>8;    // accel Z
+    out[o+6]=g_in.gy&0xFF;  out[o+7]=g_in.gy>>8;       // gyro ROLL  <- g_in.gy
+    out[o+8]=gpitch&0xFF;   out[o+9]=(gpitch>>8)&0xFF;  // gyro PITCH <- -g_in.gx (inverted vs Switch frame)
+    out[o+10]=g_in.gz&0xFF; out[o+11]=g_in.gz>>8;      // gyro YAW   <- g_in.gz
   }
 }
 // --- Canonical factory SPI dumps the host reads for calibration. Neutral IMU + centered sticks so a fresh

+ 5 - 3
OpenPuck/webusb_config.cpp

@@ -15,14 +15,14 @@ Adafruit_USBD_WebUSB usb_web;
 //                [qos][persistMode][chordBtn B][chordBtn X][chordBtn Y][pollsps_lo][pollsps_hi]
 //                [loopPeriod_lo][loopPeriod_hi][loopWorstIdx][loopWorstUs_lo][loopWorstUs_hi]
 //                [pollPeriod_lo][pollPeriod_hi][logEnabled][battery%][rssi|dBm|]
-//                [gitDirty][gitHash 12B ASCII, NUL-padded]
-#define WB_PAYLEN 51
+//                [gitDirty][gitHash 12B ASCII, NUL-padded][rumbleScale]
+#define WB_PAYLEN 52
 static void webusbSendBlob(){
   if(!usb_web.connected()) return;
   bool up = (g_connSlot>=0 && (millis()-g_connReplyMs) < 300);
   uint8_t p[2+WB_PAYLEN];
   p[0]=0xA5; p[1]=WB_PAYLEN;
-  p[2]=4;                          // protocol version (4 = +battery/rssi/git; 2 = chordBtn[3])
+  p[2]=5;                          // protocol version (5 = +rumbleScale; 4 = +battery/rssi/git; 2 = chordBtn[3])
   p[3]=g_usbMode; p[4]=(uint8_t)g_mDiv; p[5]=(uint8_t)g_mFric; p[6]=g_qamMap; p[7]=g_abSwap;
   p[8]=g_back[0]; p[9]=g_back[1]; p[10]=g_back[2]; p[11]=g_back[3];
   p[12]=(g_connSlot>=0)?(uint8_t)g_connSlot:0xFF;
@@ -46,6 +46,7 @@ static void webusbSendBlob(){
   p[40]=OPK_GIT_DIRTY ? 1 : 0;
   memset(&p[41],0,12);                                                 // 12B ASCII git hash, NUL-padded
   { const char* h=OPK_GIT_HASH; for(uint8_t i=0;i<12 && h[i];i++) p[41+i]=(uint8_t)h[i]; }
+  p[53]=g_rumbleScale;                                                 // rumble strength % (protocol v5)
   usb_web.write(p,sizeof p); usb_web.flush();
 }
 #if OPK_LOG
@@ -117,6 +118,7 @@ void webusbPoll(){
           case 17: case 18: case 19: if(modeValid(v)) g_chordBtn[f-17]=v; break;   // back4+B/X/Y mode assignments
           case 20: armDebugCdcNextBoot(); usb_web.flush(); delay(40); NVIC_SystemReset(); break;  // reboot once WITH the CDC serial console (puck mode), then auto-revert
           case 21: g_qamMap = v; break;   // QAM physical button remap code (0=default/unmapped)
+          case 22: g_rumbleScale = v; break;   // rumble strength % (0=off, 100=1x, 200=double)
         }
         if(persist) saveCfg();
         webusbSendBlob();

+ 11 - 4
docs/index.html

@@ -110,6 +110,12 @@
       <p class="note">Right-pad cursor in Steam-mode lizard (Steam closed) and in Xbox mode. Sensitivity is a divisor — lower = faster pointer. Glide = how long the cursor coasts after a flick.</p>
     </div>
 
+    <div class="card">
+      <h2>Rumble</h2>
+      <div class="row"><label>Strength</label><input type="range" id="rumble" min="0" max="255" step="5"><span class="val" id="rumbleV"></span></div>
+      <p class="note">Scales rumble amplitude across all modes (100% = native, 200% = double, 0% = off).</p>
+    </div>
+
 
     <div class="card">
       <h2>Button mapping</h2>
@@ -233,6 +239,7 @@ function applyBlob(p){
   $("#mouseCard").style.opacity = (mode===1||mode===3)?1:0.5;
   // status
   const battery=(p.length>36?p[36]:0);                    // controller battery % (report 0x43); 0 = unknown
+  const rumbleScale=(p.length>51?p[51]:200);              // rumble strength % (protocol v5+; older blob -> default)
   const rssi=(p.length>37?p[37]:0);                       // RAW signal strength |dBm| (0 = no sample)
   $("#stLink").innerHTML = up? '<span class="pill up">up</span>' : '<span class="pill dn">down</span>';
   $("#stBatt").textContent = (up && battery)? battery+"%" : "—";
@@ -252,14 +259,14 @@ function applyBlob(p){
     ? (gitHash + (gitDirty ? ' <span class="pill dn">dirty</span>' : ' <span class="pill up">clean</span>'))
     : "—";
   // sliders (only if not being dragged)
-  setSlider("mDiv",mDiv); setSlider("mFric",mFric);
+  setSlider("mDiv",mDiv); setSlider("mFric",mFric); setSlider("rumble",rumbleScale);
   $("#abSwap").textContent = abSwap? "on":"off"; $("#abSwap").classList.toggle("active",!!abSwap);
   $("#persistMode").textContent = persistMode? "on":"off"; $("#persistMode").classList.toggle("active",!!persistMode);
   for(const sel of document.querySelectorAll("select.back")){ if(document.activeElement!==sel) sel.value=back[+sel.dataset.i]; }
   { const sel=$("#qamMap"); if(document.activeElement!==sel) sel.value=qamMap; }
   for(const sel of document.querySelectorAll("select.chord")){ if(document.activeElement!==sel) sel.value=chord[+sel.dataset.i]; }
 }
-function fmtSlider(id,v){ return ""+v; }
+function fmtSlider(id,v){ return id==="rumble" ? v+"%" : ""+v; }
 function setSlider(id,v){ const el=$("#"+id); if(document.activeElement!==el){ el.value=v; } $("#"+id+"V").textContent=fmtSlider(id,el.value); }
 
 let inflight=false;
@@ -362,8 +369,8 @@ $("#factoryErase").onclick=async()=>{
 };
 $("#abSwap").onclick=()=>{ const on=$("#abSwap").classList.contains("active"); setField(4, on?0:1); };
 $("#persistMode").onclick=()=>{ const on=$("#persistMode").classList.contains("active"); setField(16, on?0:1); };
-for(const id of ["mDiv","mFric"]){
-  const field={mDiv:1,mFric:2}[id];
+for(const id of ["mDiv","mFric","rumble"]){
+  const field={mDiv:1,mFric:2,rumble:22}[id];
   $("#"+id).addEventListener("input", ()=>{ $("#"+id+"V").textContent=fmtSlider(id,$("#"+id).value); });
   $("#"+id).addEventListener("change", ()=>setField(field, +$("#"+id).value));
 }