Prechádzať zdrojové kódy

pro controller maps

Jariullah Safi 4 mesiacov pred
rodič
commit
ef83b98111
1 zmenil súbory, kde vykonal 124 pridanie a 5 odobranie
  1. 124 5
      OpenPuck/OpenPuck.ino

+ 124 - 5
OpenPuck/OpenPuck.ino

@@ -1103,6 +1103,8 @@ static uint32_t codeToJc(uint8_t c, uint32_t fA,uint32_t fB,uint32_t fX,uint32_t
 static int16_t g_swProAX=0,g_swProAY=0,g_swProAZ=0,g_swProGX=0,g_swProGY=0,g_swProGZ=0;
 static unsigned long g_swProLastMs=0;
 Adafruit_USBD_HID g_swPro;
+static uint8_t g_swProReportMode=0;   // 0 until the host's subcommand 0x03 selects 0x30 -> THEN we stream input
+static const uint8_t JC_MAC[6]={0x7C,0xBB,0x8A,0x00,0x00,0x01};   // synthetic but stable controller MAC
 static void jcPackStick(uint8_t s[3], int16_t lx, int16_t ly){
   uint8_t LX=swStick(lx,false), LY=swStick(ly,true);
   int plx=(int)LX-127;
@@ -1110,7 +1112,10 @@ static void jcPackStick(uint8_t s[3], int16_t lx, int16_t ly){
   s[1]=(uint8_t)((plx>>4)&0x0F);
   s[2]=(uint8_t)LY;
 }
-static void switchProBuild(uint8_t out[63]){
+// Standard input-report prefix [0..11] (timer, battery/conn, 3 button bytes, both packed sticks, vibrator),
+// shared by the streamed 0x30 report and the 0x21 subcommand-reply reports the host reads during init.
+static uint8_t g_jcTimer=0;
+static void jcInputPrefix(uint8_t* out){
   uint32_t b=g_swBtns;
   if((b&(TB_R4|TB_L4|TB_R5|TB_L5))==(TB_R4|TB_L4|TB_R5|TB_L5)) b&=~(uint32_t)(TB_A|TB_B|TB_X|TB_Y);
   uint32_t fA=g_abSwap?JC_BTN_B:JC_BTN_A, fB=g_abSwap?JC_BTN_A:JC_BTN_B;
@@ -1126,13 +1131,16 @@ static void switchProBuild(uint8_t out[63]){
   if(b&TB_DRT)jc|=JC_BTN_RIGHT; if(b&TB_DLF)jc|=JC_BTN_LEFT;
   if(b&TB_L4)jc|=codeToJc(g_back[0],fA,fB,fX,fY); if(b&TB_R4)jc|=codeToJc(g_back[1],fA,fB,fX,fY);
   if(b&TB_L5)jc|=codeToJc(g_back[2],fA,fB,fX,fY); if(b&TB_R5)jc|=codeToJc(g_back[3],fA,fB,fX,fY);
-  memset(out,0,63); static uint8_t ts=0;
-  out[0]=ts++;
+  out[0]=g_jcTimer++;
   out[1]=0x81;   // full battery + USB powered (hid-nintendo bat_con)
   out[2]=(uint8_t)(jc); out[3]=(uint8_t)(jc>>8); out[4]=(uint8_t)(jc>>16);
   jcPackStick(out+5, g_swLX, g_swLY);
   jcPackStick(out+8, g_swRX, g_swRY);
   out[11]=0;
+}
+static void switchProBuild(uint8_t out[63]){
+  memset(out,0,63);
+  jcInputPrefix(out);
   for(int k=0;k<3;k++){
     int o=12+k*12;
     out[o+0]=g_swProAX&0xFF; out[o+1]=g_swProAX>>8;
@@ -1143,8 +1151,117 @@ static void switchProBuild(uint8_t out[63]){
     out[o+10]=g_swProGZ&0xFF; out[o+11]=g_swProGZ>>8;
   }
 }
-static void swProStream(){
+// --- Canonical factory SPI dumps the host reads for calibration. Neutral IMU + centered sticks so a fresh
+// "device" calibrates sane; user-cal regions (0x80xx) read 0xFF so the host falls back to these factory blocks.
+static const uint8_t SPI_IMU_CAL[24]={0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x40,0x00,0x40,0x00,0x40,
+                                      0x00,0x00,0x00,0x00,0x00,0x00, 0x3B,0x34,0x3B,0x34,0x3B,0x34};
+static const uint8_t SPI_PARAMS1[24]={0x50,0xFD,0x00,0x00,0xC6,0x0F, 0x0F,0x30,0x61,0x96,0x30,0xF3,
+                                      0xD4,0x14,0x54,0x41,0x15,0x54, 0xC7,0x79,0x9C,0x33,0x36,0x63};
+static const uint8_t SPI_PARAMS2[18]={0x0F,0x30,0x61,0x96,0x30,0xF3, 0xD4,0x14,0x54,0x41,0x15,0x54,
+                                      0xC7,0x79,0x9C,0x33,0x36,0x63};
+static const uint8_t SPI_COLOR[13]={0x32,0x32,0x32, 0xE6,0xE6,0xE6, 0x32,0x32,0x32, 0x32,0x32,0x32, 0xFF};
+static uint8_t g_spiStickCal[18];   // built at boot: left[9]+right[9] factory stick calibration (packed 12-bit)
+static void jcPack12(uint8_t* o9, const uint16_t v[6]){   // pack 6 12-bit values into 9 bytes (Switch stick-cal format)
+  o9[0]=v[0]&0xFF;                       o9[1]=((v[1]&0x0F)<<4)|((v[0]>>8)&0x0F); o9[2]=(v[1]>>4)&0xFF;
+  o9[3]=v[2]&0xFF;                       o9[4]=((v[3]&0x0F)<<4)|((v[2]>>8)&0x0F); o9[5]=(v[3]>>4)&0xFF;
+  o9[6]=v[4]&0xFF;                       o9[7]=((v[5]&0x0F)<<4)|((v[4]>>8)&0x0F); o9[8]=(v[5]>>4)&0xFF;
+}
+static void jcBuildStickCal(){
+  const uint16_t C=2048, R=1800;   // center, +/- range per axis
+  uint16_t L[6]={R,R, C,C, R,R};   // left  order: max(x,y), center(x,y), min(x,y)
+  uint16_t Rr[6]={C,C, R,R, R,R};  // right order: center(x,y), min(x,y), max(x,y)
+  jcPack12(&g_spiStickCal[0], L); jcPack12(&g_spiStickCal[9], Rr);
+}
+static void spiRead(uint32_t addr, uint8_t len, uint8_t* dst){
+  for(uint8_t i=0;i<len;i++){
+    uint32_t a=addr+i; uint8_t v=0xFF;
+    if     (a>=0x6020 && a<0x6020+24) v=SPI_IMU_CAL[a-0x6020];
+    else if(a>=0x603D && a<0x603D+18) v=g_spiStickCal[a-0x603D];
+    else if(a>=0x6050 && a<0x6050+13) v=SPI_COLOR[a-0x6050];
+    else if(a>=0x6080 && a<0x6080+24) v=SPI_PARAMS1[a-0x6080];
+    else if(a>=0x6098 && a<0x6098+18) v=SPI_PARAMS2[a-0x6098];
+    dst[i]=v;   // unmapped (incl. user-cal 0x80xx) -> 0xFF = "blank flash" -> host uses factory blocks above
+  }
+}
+// Reply FIFO: the host's handshake/subcommand reports arrive in the USB ISR (jcSet); we enqueue the canonical
+// 0x81/0x21 reply and drain it from loop() (swProTask) where sendReport is safe. SPSC ring, volatile indices.
+// Every reply is sent as a full 63-byte report (64 with the id). macOS IOHIDManager silently DROPS any input
+// report shorter than the descriptor-declared length (0x3F), so short 0x81/0x21 replies never reached the host.
+#define JC_REPLEN 63
+struct JcRep { uint8_t rid; uint8_t data[JC_REPLEN]; };
+#define JCQ_N 8
+static volatile uint8_t g_jcQh=0, g_jcQt=0;
+static JcRep g_jcQ[JCQ_N];
+static void jcEnq(uint8_t rid, const uint8_t* d, uint8_t len){
+  uint8_t nt=(uint8_t)((g_jcQt+1)%JCQ_N);
+  if(nt==g_jcQh) return;                 // full -> drop; the host re-requests on timeout
+  if(len>JC_REPLEN) len=JC_REPLEN;
+  g_jcQ[g_jcQt].rid=rid;
+  memset(g_jcQ[g_jcQt].data,0,JC_REPLEN); memcpy(g_jcQ[g_jcQt].data,d,len);   // zero-pad to full report length
+  g_jcQt=nt;
+}
+// Build the 0x21 reply (standard input prefix + ACK + echoed subcommand id + reply data) for a report-0x01 subcommand.
+static void jcSubcmd(uint8_t sub, const uint8_t* args, uint16_t alen){
+  uint8_t p[JC_REPLEN]; memset(p,0,sizeof p);
+  jcInputPrefix(p);
+  p[13]=sub;
+  switch(sub){
+    case 0x02:                         // request device info
+      p[12]=0x82;
+      p[14]=0x04; p[15]=0x21;          // firmware version (4.33)
+      p[16]=0x03;                      // controller type: Pro Controller
+      p[17]=0x02;                      // fixed
+      memcpy(&p[18], JC_MAC, 6);
+      p[24]=0x01;                      // colors stored in SPI (0x6050)
+      p[25]=0x02;                      // fixed
+      break;
+    case 0x10: {                       // SPI flash read -> echo [addr][len] then the data
+      if(alen<5){ p[12]=0x80; break; }
+      uint32_t a=(uint32_t)args[0]|((uint32_t)args[1]<<8)|((uint32_t)args[2]<<16)|((uint32_t)args[3]<<24);
+      uint8_t rl=args[4]; if(rl>0x1D) rl=0x1D;
+      p[12]=0x90;
+      p[14]=args[0]; p[15]=args[1]; p[16]=args[2]; p[17]=args[3]; p[18]=rl;
+      spiRead(a, rl, &p[19]);
+      break; }
+    case 0x03:                         // set input report mode (0x30 = standard full) -> begin streaming
+      if(alen>=1 && args[0]==0x30) g_swProReportMode=0x30;
+      p[12]=0x80; break;
+    case 0x04: p[12]=0x83; break;      // trigger buttons elapsed time
+    case 0x21: p[12]=0xA0; break;      // set NFC/IR config
+    default:   p[12]=0x80; break;      // 0x06/0x08/0x30/0x38/0x40/0x41/0x48/... generic positive ACK
+  }
+  jcEnq(0x21, p, JC_REPLEN);
+}
+// USB-ISR callback for the Pro Controller's OUT endpoint. Data arrives via the interrupt OUT endpoint, where
+// this TinyUSB core passes report_id=0 and leaves the real report id in buffer[0] (it does NOT strip it). Also
+// accept the control SET_REPORT shape (id already in `rid`) for safety. report 0x80 = USB handshake; 0x01 = subcommand.
+static void jcSet(uint8_t rid, hid_report_type_t type, uint8_t const* b, uint16_t n){
+  if(type!=HID_REPORT_TYPE_OUTPUT || n<1) return;
+  uint8_t id; const uint8_t* p; uint16_t pn;
+  if(rid==0){ id=b[0]; p=b+1; pn=(uint16_t)(n-1); }   // EP-OUT: id is the first payload byte
+  else      { id=rid;  p=b;   pn=n;               }   // control SET_REPORT: id already split out
+  if(id==0x80){
+    if(pn<1) return;
+    if(p[0]==0x01){ uint8_t d[9]={0x01,0x00,0x03,JC_MAC[0],JC_MAC[1],JC_MAC[2],JC_MAC[3],JC_MAC[4],JC_MAC[5]}; jcEnq(0x81,d,9); } // device type + MAC
+    else if(p[0]==0x02){ uint8_t d[1]={0x02}; jcEnq(0x81,d,1); }   // handshake
+    else if(p[0]==0x03){ uint8_t d[1]={0x03}; jcEnq(0x81,d,1); }   // set baudrate
+    // 0x04 force-USB / 0x05 enable-timeout / 0x06 reset: no reply expected
+    return;
+  }
+  if(id==0x01){                        // [timer][rumble x8][subcmd][args...]
+    if(pn<10) return;
+    jcSubcmd(p[9], p+10, (pn>10)?(uint16_t)(pn-10):0);
+  }
+  // report 0x10 (rumble only) / 0x82: ignored
+}
+static void swProTask(){
   if(g_usbMode!=MODE_SW_PRO || !g_swPro.ready()) return;
+  if(g_jcQh!=g_jcQt){                                   // drain handshake/subcommand replies first (ordered)
+    JcRep* r=&g_jcQ[g_jcQh];
+    if(g_swPro.sendReport(r->rid, r->data, JC_REPLEN)) g_jcQh=(uint8_t)((g_jcQh+1)%JCQ_N);
+    return;                                             // one report per call; the rest next loop
+  }
+  if(g_swProReportMode!=0x30) return;                   // not until the host has finished init + selected 0x30
   if(millis()-g_swProLastMs<USB_STREAM_MS) return; g_swProLastMs=millis();
   uint8_t p[63]; switchProBuild(p);
   g_swPro.sendReport(0x30, p, sizeof p);
@@ -1738,7 +1855,9 @@ void setup() {
     USBDevice.setDeviceVersion(0x0210);
     USBDevice.setManufacturerDescriptor("Nintendo Co., Ltd.");
     USBDevice.setProductDescriptor("Pro Controller");
+    jcBuildStickCal();
     g_swPro.enableOutEndpoint(true);
+    g_swPro.setReportCallback(NULL, jcSet);   // answer the Nintendo USB handshake + subcommands (else Steam never binds it)
     g_swPro.setReportDescriptor(SWPRO_HID_DESC, sizeof SWPRO_HID_DESC);
     g_swPro.setPollInterval(4);
     g_swPro.begin();
@@ -1802,7 +1921,7 @@ void loop() {
   if (g_dirty) { g_dirty = false; saveBonds(); }
   webusbPoll();
   swStream();
-  swProStream();
+  swProTask();
   ps5Stream();
   hidGyroStream();
   rfSerialPoll();