Ricerca — Manticore XInput (repository di riferimento)
Ambito. Manticore XInput è un progetto di ricerca: non è pensato per realizzare una fighting board specifica, ma come repository di riferimento da cui partire per altri progetti.
Una richiesta, non un obbligo legale. Se il codice — o anche solo il suo approccio e la sua struttura — ti è utile, ci fa piacere che tu citi il produttore: «FULVIO MASSIMO MARIANI & ENTH 2026». Vale in ogni campo, gaming o no: tastiere, periferici, controlli industriali, didattica.
Perché non ci troverai una board completa. Non abbiamo l'intenzione di entrare in concorrenza con Brook o con GP2040 / GP2040-CE: questo codice è materiale di studio e di riferimento. Ti chiediamo quindi di non ripubblicare o vendere il progetto così com'è, integralmente, come fighting board. Idee, parti, adattamenti e derivati: benvenuti, con la citazione sopra.
Software fornito così com'è, senza garanzie: prova sempre tutto sul tuo hardware.
Il progetto
Manticore XInput è un progetto di ricerca su firmware e strumentazione host per microcontroller della classe RP2040. Studia quanto un percorso di input digitale può sapere di sé: la latenza end-to-end, l'assestamento elastico dei contatti meccanici e il modo in cui una politica di debounce adattiva può imparare da quel segnale invece di scegliere una finestra fissa a tavolino.
- Percorso caldo — interrupt GPIO esclusivo in RAM e refresh del report HID in place, con gate basato sulla previsione della prossima transazione IN: 5 µs di latenza minima, 0.51 ms di media, re-arm medio 14 µs.
- Debounce adattivo — lockout per contatto appreso dall'energia di rimbalzo misurata, con floor, margine, cap, promozione e ripieghi sicuri, persistito fra accensioni.
- Black box — contatori, istogramma di latenza, trace e ring di rimbalzi per contatto: ogni apertura/chiusura con timestamp a 1 µs.
- Analisi — segmentazione delle raffiche per silenzio, crescita degli intervalli, frequenza istantanea e STFT del segnale ricostruito. Si misura il tempo, mai l'ampiezza.
Paper tecnico (testo completo)
Indice del paper
Testo integrale, senza tagli: dalla strumentazione ai risultati misurati, con tabelle, definizioni delle metriche e appendice di riproducibilità.
- Avviso — ambito, divieto e attribuzione
- Sommario
- 1. Introduzione
- 2. Lavori correlati
- 3. Panoramica del sistema
- 4. Percorso caldo d'ingresso
- 5. Debounce adattivo
- 6. Strumentazione
- 7. Strumenti host
- 8. Verifica
- 9. Risultati
- 10. Discussione
- 11. Limiti e lavoro futuro
- 12. Conclusione
- Riferimenti
- Appendice A — Comandi della modalità config
- Appendice B — Metriche dei rimbalzi
- Appendice C — Riproducibilità
Manticore XInput: un controller XInput a bassa latenza e auto-strumentato per RP2040
Rapporto tecnico — versione 9.9 (8 ottobre 2026)
Autore: Fulvio Massimo Mariani, supportato da OpenCode + DeepSeek — progetto Manticore XInput Stato: rapporto tecnico interno / bozza per sottomissione esterna. Figure citate come F1–F6; F1 è il diagramma di architettura generato (webapp/architecture.html), F2–F6 sono prodotte dai dati di cattura esportati (vedi §9.4).
Avviso — ambito, divieto e attribuzione
1. Ambito
Questo codice non è destinato alla realizzazione di fighting board specifiche: è il repository di un progetto di ricerca e sviluppo che serve come base per implementarne altri. Nulla qui è un prodotto finito, una configurazione consigliata per un controller determinato, né la specifica di un hardware particolare: pinout, parametri e scelte di progetto vanno riadattati al proprio caso.
2. Uso integrale vietato
È vietato usare questo codice nella sua interezza — o come base sostanzialmente integrale, con modifiche di sola superficie — per realizzare, distribuire o vendere fighting board, in qualunque forma: prodotto finito, kit, servo-assemblaggio o servizio. Il motivo è esplicito: questo progetto non vuole diventare un concorrente di Brook o di GP2040 / GP2040-CE. Il divieto riguarda l'uso integrale del codice; riuso parziale, adattamenti, estrazioni di singole idee e derivati restano ammessi alle condizioni del punto 3.
3. Attribuzione richiesta (in ogni campo, non solo gaming)
Se questo codice — o il suo approccio e la sua struttura — viene riutilizzato, copiato, derivato o "saccheggiato", anche parzialmente e anche dentro progetti diversi, è richiesto il riferimento esplicito al produttore:
> «FULVIO MASSIMO MARIANI & ENTH 2026»
La stessa politica di crediti si applica fuori dal gaming e senza limiti di settore: per esempio tastiere (meccaniche, hot-swap, custom), periferici da puntamento, controlli industriali, strumentazione di misura, progetti didattici. In ogni caso il riferimento va inserito nella documentazione del progetto derivato (README, pagina prodotto, paper o crediti) in posizione leggibile.
4. Nessuna garanzia
Software fornito "così com'è", senza garanzie di sorta. Chi lo usa su hardware reale si assume la responsabilità dei propri test, delle proprie certificazioni e di ogni conseguenza d'uso.
Sommario
Presentiamo Manticore XInput, un firmware aperto per controller arcade basati su RP2040 che punta a due proprietà di solito trattate separatamente: latenza d'ingresso end-to-end e osservabilità del contatto fisico. Il percorso caldo d'ingresso è un interrupt GPIO esclusivo in RAM che esegue il refresh in place del buffer del report USB HID, condizionato da una previsione della prossima transazione interrupt-IN; un lockout di debounce appreso per pin si adatta all'energia di rimbalzo misurata di ciascun contatto. Attorno a questo abbiamo costruito una black box auto-strumentata: contatori di fronti con timestamp al microsecondo, un istogramma di latenza, una traccia di eventi e — dalla versione 9.7 — anelli di rimbalzo per contatto che registrano i singoli istanti di chiusura/apertura di ogni rimbalzo meccanico. Un'applicazione web hostata legge il dispositivo via USB CDC in modalità config, segmenta i treni di rimbalzo per silenzio e li analizza nei domini del tempo, dell'intervallo e della frequenza (trasformata di Fourier a tempo breve del segnale a due livelli ricostruito, frequenza di rimbalzo istantanea, metriche di assestamento per raffica).
Su un singolo controller abbiamo misurato una cadenza di poll effettiva di 999 Hz, una latenza fronte→report minima di 5 µs e media di 0.51 ms, un re-arm dell'endpoint medio di 14 µs e un'accuratezza del 99.95 % della previsione di intervallo usata dal gate di refresh in place; 0 eventi di traccia persi e 0 self-check in place falliti su 2.34 M trasferimenti IN completati. La strumentazione dei rimbalzi mostra un comportamento dei contatti che i contatori grossolani nascondono: in una cattura l'host ha segmentato 112 eventi, di cui 76 raffiche di chatter e 36 transizioni pulite; le finestre di assestamento delle raffiche spaziavano da 50 µs a 480 µs, con fattori di growth (rapporto fra intervallo massimo e intervallo minimo) da 2.1× a 68×, e contenuto spettrale dominante da 3.9 kHz a 23.4 kHz. Descriviamo il metodo, i costi misurati (94.5 KB di 264 KB di RAM, 53 KB di 2 MB di flash), la strategia di verifica (nove suite di test emulati ARM che compilano i sorgenti reali contro hardware mockato, più un test JavaScript lato host) e i limiti, inclusa l'assenza di informazione di ampiezza — un GPIO può solo riportare quando un contatto si apre e si chiude, mai con quanta forza.
Parole chiave: USB HID, XInput, RP2040, latenza d'ingresso, rimbalzo dei contatti, debounce adattivo, telemetria embedded, analisi tempo–frequenza.
1. Introduzione
Gli ingressi digitali basati su contatti meccanici sono dispositivi analogici che indossano una maschera binaria. Quando un interruttore si chiude, il contatto mobile non si assesta istantaneamente: urta il contro-contatto, rimbalza e oscilla finché l'energia elastica dell'impatto non è dissipata e la forza di contatto non supera la forza di richiamo della molla. Un oscilloscopio sul nodo elettrico vede una raffica di transizioni di chiusura/apertura — il rimbalzo dei contatti — che dura tipicamente da decine a centinaia di microsecondi [1], [2]. Il firmware nasconde questo con una finestra di debounce: dopo un fronte, gli altri fronti vengono ignorati per un tempo fisso. La finestra è di solito scelta in modo conservativo (2–10 ms) e la scelta è invisibile all'utente — finché non costa latenza, o non fallisce su un contatto usurato.
I controller arcade/per fighting game rendono il compromesso critico. I giocatori premono gli stessi pulsanti centinaia di volte al minuto; il percorso d'ingresso gira a 125 MHz ma la policy è ancora una costante scelta da uno sviluppatore. Il firmware aperto esistente come GP2040-CE [7] espone la configurazione ma non misura nulla dei contatti stessi.
Questo rapporto descrive un approccio diverso: rendere lo strumento parte del prodotto. (i) Manteniamo il percorso caldo minimale e residente in RAM, (ii) apprendiamo la finestra di debounce per contatto dalle energie di rimbalzo effettivamente osservate e (iii) conserviamo i singoli timestamp dei rimbalzi, così che lo stesso segnale che guida il debounce possa essere analizzato in seguito — sull'host, offline, nei domini del tempo e della frequenza.
Contributi:
1. Un'architettura firmware XInput + CDC per RP2040 in cui l'ISR d'ingresso esegue il refresh in place del buffer del report HID sotto una previsione del prossimo IN token (§4). 2. Una politica di debounce adattiva per pin che converte gli intervalli di rimbalzo misurati in un lockout, con promozione, tetti e fallback, persistita attraverso i cicli di alimentazione senza mai bloccare il percorso d'ingresso (§5). 3. Una black box auto-strumentata: contatori aggregati, un istogramma di latenza a bucket di 64 µs, un anello di traccia da 512 voci e anelli per pin di 128 fronti con timestamp in µs per contatto (§6). 4. Una pipeline host e un'applicazione web che trasformano il treno di fronti in firme quantitative del contatto — finestra di chatter, growth degli intervalli, frequenza di rimbalzo istantanea, STFT del livello ricostruito (§7, §9.4). 5. Un metodo di verifica: esecuzione ARM emulata dei sorgenti reali contro hardware mockato, più verifica strutturale dell'UF2, in un progetto senza laboratorio (§8).
2. Lavori correlati
La fisica dei contatti e l'affidabilità dei contatti sono soggetti classici: il trattato di Holm sul contatto elettrico [1] e la tribologia moderna dei connettori [2] spiegano il rimbalzo come risposta elastodinamica del sistema di contatto. Gli strumenti di elaborazione del segnale usati qui sono standard [3], [4]. La pratica del debounce nel progetto digitale è materiale da manuale [5]; l'analogia sismologica per sintesi scalari in stile energetico di un record non stazionario segue Arias [6]. Sul lato firmware, GP2040-CE [7] è il progetto aperto più vicino (configurazione, SOCD, protocolli multipli) ma non misura il comportamento dei contatti. TinyUSB [8] fornisce lo stack USB device; la classe HID e la semantica dei report seguono la specifica USB HID [9]. Il Cortex-M0+ dual-core dell'RP2040 e i suoi 264 KB di SRAM (con la possibilità di eseguire codice dalla RAM) sono documentati in [10].
3. Panoramica del sistema
Obiettivo: un pannello arcade a 19 contatti (pulsanti, direzioni dello stick, grilletti) cablato in logica attiva-bassa ai GPIO di un RP2040 (Cortex-M0+ dual-core, 125 MHz, 264 KB di SRAM, 2 MB di flash). Sono usate due personalità USB:
- Modalità XInput — la normale modalità operativa: un'interfaccia vendor
(0xff/0x5d) con VID:PID 045e:028e, un report di 20 byte consegnato su un endpoint interrupt IN (pacchetto da 32 byte) all'intervallo di poll dell'host, più quattro richieste di controllo vendor usate dalle dashboard: 0x01 GET_CAPABILITIES, 0x02 host action, 0x03 live lock table, 0x04 black-box summary (binarie, little-endian, versionate).
-
Modalità config — ri-enumerata come CDC-ACM (
045e:0c00) dopo un gesto
deliberato (combo di boot, 3 s L3+R3+Guide) o un comando dell'host. Un protocollo a righe sulla porta seriale espone impostazioni e diagnostica (§6.3). Il soft reset verso la modalità config preserva la SRAM, quindi la black box vi sopravvive.
Lo stato persistente vive negli ultimi due settori di flash: impostazioni (magic + version + CRC32) e la sintesi appresa di debounce/black box, scritta solo attraverso un percorso flash_safe_execute con core 1 parcheggiato (§5.4). L'architettura è illustrata in F1 (webapp/architecture.html, generata con la stessa pipeline del resto della documentazione).
4. Percorso caldo d'ingresso
Cattura dei fronti. Tutti e 19 gli ingressi sono configurati con interrupt su entrambi i fronti, raggruppati in un'unica IRQ esclusiva a priorità 0 (sopra USB) il cui handler è collocato in RAM (__not_in_flash_func). L'handler legge un timer a 1 µs, classifica il fronte rispetto al lockout per pin appreso nel §5 e lo accetta (cambio di stato) oppure lo conta come rimbalzo. Non esegue divisioni e non chiama codice in flash; il push nell'anello di rimbalzo per contatto è un append a singolo produttore (§6.2).
Refresh in place del report. Invece di ricostruire il report HID nel loop principale, l'ISR aggiorna il buffer verso cui il controller USB è già puntato (cioè il buffer DPRAM dell'endpoint), il che elimina del tutto la latenza di "arm dopo il cambiamento" per le modifiche a una sola parola. Le modifiche multi-parola (es. stick + pulsante nello stesso frame) non devono essere viste lacerate da una transazione IN, quindi il gate stima il tempo alla prossima completion dalla cadenza di poll osservata (una EMA e una previsione di intervallo minimo) e impegna in place solo quando il margine residuo supera 80 µs; altrimenti il cambiamento è rinviato al prossimo re-arm, sicuro per costruzione. La previsione ha totalizzato 10 339 hit / 5 miss nella sessione misurata (§9.1).
Re-arm. La callback del trasferimento IN ri-arma immediatamente l'endpoint con lo stato più fresco, così un cambiamento arriva nel frame successivo invece di aspettare un'iterazione del loop principale; il gap medio completion→re-arm misurato è 14 µs.
5. Debounce adattivo
Segnale. Per ogni transizione accettata il firmware registra l'intervallo: il tempo dal fronte accettato all'ultimo fronte di rimbalzo osservato dentro la finestra. È un proxy robusto dell'energia di rimbalzo di quell'evento e non richiede di memorizzare l'intero treno (cosa che il §6.2 fa comunque, per l'analisi).
Politica. I campioni sono accumulati in blocchi di 3 transizioni (MANTICORE_BLOCK_SAMPLES); il massimo del blocco guida il valore appreso (bounce_max_us). Il lockout risultante è
lockout = clamp(bounce_max + margin, floor, cap)
con margin = 100 µs, floor configurabile (default 250 µs, 100–4000 µs) e cap = 6000 µs. Tre affinamenti contano in pratica:
- Promozione — un pin con una lunga serie pulita (≥24 transizioni) può
scendere sotto il floor configurato, ma solo fino a 150 µs (MANTICORE_PROMOTED_FLOOR_US), e mai sotto un floor scelto esplicitamente dall'utente.
- Pin inutilizzati mantengono un lockout conservativo di 1000 µs finché non
vengono azionati almeno una volta, così un contatto non misurato non può produrre pressioni fantasma.
- Pulsanti di sistema (START/BACK/GUIDE) usano un lockout fisso di 5 ms e
sono esclusi dall'adattamento: non sono critici per la latenza e sono spesso cablati a gesti multi-purpose.
L'adattamento gira sul core 1; se il suo heartbeat si ferma per 200 ms, il core 0 ripiega sull'ultimo valore sicuro, così un core bloccato non può lasciare il percorso d'ingresso senza protezione.
Classificazione, non filtraggio. Una coppia rilascio→ri-pressione più rapida della finestra di chatter (default 8 ms, SET chattergap) è classificata come pressione auto-riaprente (dfire) invece di essere filtrata; il contatore è esposto per contatto ed è la metrica operativa di usura usata dalla UI host. Una versione precedente del firmware filtrava questi eventi, causando lag visibile; la misura ha mostrato che il problema era il filtro, non il percorso.
Persistenza. Valori appresi, serie e la sintesi della black box sono esportati in un record versionato (LEARN_VERSION 2) scritto solo durante una finestra di inattività (>30 s senza fronti accettati, al massimo una scrittura al minuto, forzata all'ingresso in modalità config). Lo stallo XIP di ~46 ms di una cancellazione/programmazione non cade quindi mai tra due ingressi. Gli override manuali per pin (SET pinlock) sono persistiti immediatamente.
6. Strumentazione
6.1 Contatori e distribuzioni
La black box (diag.c) mantiene contatori cumulativi (fronti, report, rimbalzi, commit in place, self-check in place falliti, voci di coda perse, rinvii) più distribuzioni: intervallo di poll min/media/max, latenza fronte→report min/media/max con un istogramma a 16 bucket da 64 µs, statistiche di fase nel frame e gap di re-arm. I contatori sono esportati nella flash come parte dello stato appreso, quindi sopravvivono ai cicli di alimentazione fino a un esplicito CLR/DIAGCLR.
6.2 Anelli di rimbalzo
Dalla 9.8 la black box contiene un anello per pin — 128 voci × 32 pin di {uint32_t time_us; uint8_t type}, ~32 KB nella sezione di RAM non inizializzata:
- ogni fronte accettato (
E) è registrato — segna l'inizio di una raffica; - ogni fronte rifiutato (
B) è registrato con la stessa risoluzione di 1 µs.
Gli anelli sono scritti solo dall'ISR d'ingresso (singolo produttore, nessun lock, nessuna divisione — il wrap è una maschera) e letti in modalità config con gli interrupt GPIO disabilitati. Un layout per pin è stato introdotto perché un singolo contatto rumoroso poteva altrimenti riempire un anello condiviso ed espellere tutti gli altri contatti (osservato: 1024/1024 fronti da tre contatti in una sessione).
6.3 Protocollo host
GET | SET | SAVE | STATS | LOCKS | TRACE | BOUNCE | CLR | DIAGCLR | RESET | REBOOT | BOOT, terminati da newline, trasmessi in streaming da un buffer in RAM (i dump sono molto più grandi della FIFO CDC). BOUNCE emette
BOUNCE pins=<contatti con dati> ev=<righe evento emesse> rec=<eventi registrati>
E <time_us> <pin> fronte accettato
B <time_us> <pin> fronte rifiutato (un rimbalzo)
...
OK
raggruppati per pin, dal più vecchio al più recente all'interno di ogni pin, con un budget totale di 1024 righe evento condivise tra i pin che hanno dati — così un contatto chiacchierone non può nascondere gli altri. L'header distingue gli eventi emessi da quelli registrati, ed è ciò che rende visibile all'host un dump troncato.
7. Strumenti host
Un'applicazione web single-page (Chrome/Edge, WebSerial, nessuno step di build, nessun codice di terze parti) si connette alla modalità config e, alla connessione, carica tutto automaticamente: impostazioni, black box, treno di rimbalzo e il manifest del firmware. Visualizza:
- la black box (istogramma di latenza, contatori per pin, tabella di degradazione
con rapporti dfire);
- la tabella e le barre dei lockout adattivi;
- il visualizzatore dei rimbalzi: le catture più recenti per contatto, una
forma d'onda a gradini ricostruita dagli istanti dei fronti, barre per gli intervalli tra fronti di rimbalzo consecutivi e un pannello spettrale (§9.4);
- la cronologia in IndexedDB con export/import;
- l'updater del firmware:
BOOT→ scrittura dell'UF2 sul volumeRPI-RP2
tramite la File System Access API, dopo aver verificato l'immagine rispetto a size e sha256 fissati in manifest.json (§11).
L'elaborazione statistica avviene sull'host perché il dispositivo non ha ampiezza da offrire: la pagina ricostruisce il segnale a due livelli ricampionando a 1 µs, lo divide in raffiche per silenzio (gap di default 5 ms, regolabile) e calcola le metriche dell'Appendice B.
8. Verifica
Senza un laboratorio, la correttezza è imposta eseguendo i sorgenti reali del firmware su un emulatore ARM (Unicorn 2.1.4) contro hardware mockato: nove suite (firmware_test, dpram_test, settings_test, diag_test, config_test, ui_test, descriptors_test, lcd_test, telemetry_test) che coprono l'ISR d'ingresso, il gate in place, la persistenza, il protocollo di dump, i descrittori USB e il display; la pipeline host ha un proprio test Node senza dipendenze (segmentazione, FFT, STFT, windowing, input degeneri). Uno script verify_uf2.py rilegge l'immagine prodotta e verifica la struttura UF2, la stringa di identità di versione, il vettore di reset e la corrispondenza con il BIN linkato, così un artefatto rilasciato non può differire silenziosamente dall'albero testato.
La strumentazione è anche ciò che rende verificabile il firmware: una revisione esterna multi-agente della 9.8 (quattro revisori indipendenti su firmware, codice host, documentazione e percorso di aggiornamento) ha trovato un difetto bloccante (uno spettrogramma senza limiti che poteva congelare la scheda del browser: aggiunti tetti di 240 frame e 200 k campioni) e diversi problemi di correttezza (un wrap del timer a 32 bit che poteva fondere due raffiche; un flag busy latched che poteva disabilitare silenziosamente ogni comando successivo; un percorso di aggiornamento che avrebbe flashato un'immagine quando al manifest mancava un digest), tutti corretti nella 9.9 con test di regressione.
9. Risultati
9.1 Percorso d'ingresso (singolo controller, una sessione)
| Grandezza | Valore |
|---|---|
| Cadenza di poll effettiva | 999 Hz (intervallo medio 1.00 ms) |
| Latenza fronte→report, min | 5 µs |
| Latenza fronte→report, media | 0.51 ms |
| Latenza fronte→report, max | 45.88 ms (uno stallo dell'host, vedi §10) |
| Fase nel frame, media | 29 µs |
| completion→re-arm, media | 14 µs |
| Previsione di intervallo | 10 339 hit / 5 miss (99.95 %) |
| Voci di traccia perse | 0 |
| Self-check in place falliti | 0 |
| Fronti accettati / trasferimenti IN completati | 21 910 / 2 341 553 |
| Fronti rifiutati (rimbalzi) | 734 968 (33.5 per fronte accettato) |
9.2 Costo
| Risorsa | 9.9 |
|---|---|
| Immagine flash (BIN) | 53 904 B di 2 MB (2.6 %) |
.text / .rodata (XIP, residente in flash) |
43 248 / 2 652 B |
.data (RAM) |
7 716 B |
.bss (RAM, incl. buffer di dump da 20 KB) |
37 804 B |
| RAM non inizializzata (black box + anelli di rimbalzo) | 38 696 B |
| Heap + due stack da 4 KB | 10 240 B |
| RAM totale | 94 456 B ≈ 92 KiB di 264 KiB (35 %) |
Il percorso caldo non aggiunge costo misurabile: la registrazione dei rimbalzi è un incremento di indice e due store per fronte rifiutato in codice residente in RAM.
9.3 Caso di studio sui rimbalzi (singolo dispositivo, una cattura)
Un dump BOUNCE da un pannello a 19 contatti ha registrato 1097 fronti negli anelli, coprendo gli 11 contatti azionati dall'accensione; il budget di 1024 righe ha emesso 843 righe di fronti, dalle quali l'host ha segmentato 76 raffiche di chatter e 36 transizioni pulite. Tre contatti illustrano la gamma (P1, K2, K3 sono le etichette delle posizioni di pulsante frontale e grilletto del pannello):
| Contatto | Fronti | Finestra di chatter | Growth (intervallo max/min) | Dominante | Energia >20 kHz |
|---|---|---|---|---|---|
| P1 (X) | 3 | 50 µs | ×2.1 | 23.4 kHz | 68 % |
| K2 (B) | 17 | 250 µs | ×3.8 | 3.9 kHz | 45 % |
| K3 (RT) | 18 | 480 µs | ×68 | 3.9 kHz | 31 % |
Le sequenze di intervalli corrispondenti (F2–F4) mostrano la firma meccanica attesa: gli intervalli crescono monotonicamente mentre il contatto si assesta (K3: frequenza di rimbalzo istantanea che scende da 71.4 kHz a 1.0 kHz lungo 0.48 ms), mentre P1 termina dopo tre transizioni — lo stesso tipo di evento, due ordini di grandezza in meno di energia. La sostituzione di un interruttore clicky con uno lineare sullo stesso contatto ne ha cambiato qualitativamente la firma, coerente con il meccanismo del click che eccita la lamella del contatto alla chiusura (§10.3).
9.4 Parametri della pipeline di analisi
Ricampionamento 1 µs (1 MHz), finestra di Hann, FFT a 256 punti (finestra 0.256 ms, hop 64 µs), risoluzione in frequenza 3.91 kHz; frame limitati a 240 e campioni a 200 k per contenere il lavoro dell'host; sopra ~100 kHz quanto riportato è jitter dell'ISR, non contenuto del contatto. Le metriche per raffica sono definite nell'Appendice B; l'applicazione web può esportare i flussi di fronti grezzi (JSON) e le metriche per raffica (CSV), così ognuno di questi numeri può essere ricalcolato.
10. Discussione
10.1 Che cosa dà la politica adattiva
I dati sui rimbalzi spiegano perché una finestra fissa è lo strumento sbagliato: tra tre contatti dello stesso controller la finestra di assestamento differiva di un ordine di grandezza (50 µs vs 480 µs). Un lockout fisso di 1 ms proteggerebbe tutti e tre ma aggiungerebbe ~1 ms di latenza al contatto più pulito; una finestra di 100 µs lascerebbe passare rimbalzi su K3. Apprendere per pin e limitare a un floor mantiene reattivi i contatti reattivi e sicuri quelli rumorosi.
10.2 Che cosa significa (e non significa) latency max
latency max = 45.88 ms è un singolo outlier su 21 910 campioni. La metrica è definita come fronte accettato → primo trasferimento IN completato dopo, quindi include necessariamente il comportamento di poll dell'host: un valore di 45 ms significa che l'host ha smesso di servire l'endpoint per ~45 ms (scheduling, power management della porta, contesa sul bus), non che il firmware ci abbia messo 45 ms. Le prove stanno nei valori vicini a quel numero: re-arm 14 µs, latenza minima 5 µs, media 0.51 ms. Trattiamo quindi l'istogramma come la vista onesta e il massimo come indicatore dell'ambiente host.
10.3 Il click influenza il rimbalzo?
Su un interruttore clicky, il click è prodotto da una lamella separata che scatta al punto di attuazione — accoppiata meccanicamente alla lamella di contatto e liberata nell'istante esatto in cui il contatto si chiude. Il cambiamento osservato sostituendo un interruttore clicky con uno lineare sulla stessa posizione (forma della raffica diversa, finestra di assestamento diversa) supporta l'ipotesi che il click contribuisca al treno di rimbalzi. Un esperimento pulito richiede un attuatore ripetibile (servo/peso), modelli di interruttore identici in entrambe le varianti e confronto delle mediane su ≥5 attuazioni per contatto; quell'esperimento è lavoro futuro.
10.4 Distinguere il rimbalzo meccanico dal rumore di soglia
I contatti il cui fronte digitale deriva da un segnale analogico (soglia di comparatore o ADC) possono produrre un ri-trigger elettrico vicino alla soglia. I due fenomeni si separano per forma: il rimbalzo meccanico mostra intervalli che crescono monotonicamente e una forma ripetibile; il rumore di soglia mostra intervalli irregolari senza growth e occorrenza stocastica. Le cinque raffiche memorizzate per contatto dal visualizzatore rendono possibile quel confronto senza strumentazione aggiuntiva.
11. Limiti e lavoro futuro
- Nessuna ampiezza. Un GPIO riporta quando il contatto si apre e si chiude,
mai con quanta forza né di quanto. L'ampiezza — transitori di resistenza di contatto, forza, spostamento — richiede hardware aggiuntivo (un ADC su un partitore sul contatto, o un accelerometro/vibrometro laser esterno). Riportiamo deliberatamente i proxy (growth degli intervalli, contenuto spettrale) come proxy.
- Tetto di banda. I timestamp dei fronti portano una quantizzazione di ~1 µs
e jitter sub-µs dell'ISR; il contenuto sopra ~100 kHz è rumore della strumentazione, non comportamento del contatto. Il visualizzatore lo dichiara su ogni spettro.
- Dati da un solo dispositivo. Le tabelle misurate provengono da un
controller e da un numero limitato di sessioni. Il metodo (e il percorso di export) è progettato per la replica su dispositivi, interruttori e stili di attuazione diversi, ma i numeri qui sono un caso di studio, non una popolazione.
- Variabilità di attuazione. L'attuazione con le dita introduce una varianza
che domina la differenza tra contatti, a meno che l'attuatore non sia fissato; la procedura raccomandata è ≥5 raffiche per contatto e confronto delle mediane.
- Fiducia nell'aggiornamento. L'updater verifica l'immagine rispetto a
size e sha256 presi da manifest.json, rifiuta immagini non verificate e valida il percorso, ma il digest viaggia nello stesso file dell'immagine: HTTPS e l'integrità dell'hosting sono l'unica radice di fiducia. Un manifest firmato è lavoro futuro.
- Funzionalità pianificate. Rimappatura dei pulsanti con profili per gioco,
turbo opzionale e una variante di ingresso analogica (Hall/ADC) sono nella roadmap; l'ultima è il modo naturale di ottenere l'ampiezza che questo strumento non può vedere.
12. Conclusione
Un microcontrollore che spende il proprio budget sul percorso caldo d'ingresso può comunque permettersi di essere uno strumento. Manticore XInput mantiene il percorso fronte→report a 5 µs minimi e 14 µs di re-arm, registrando ogni fronte con un timestamp al microsecondo, apprendendo un lockout per contatto dall'energia di rimbalzo misurata ed esportando treni di rimbalzo grezzi che l'host trasforma in firme di assestamento quantitative. Gli stessi dati che selezionano la finestra di debounce rispondono anche alle domande ingegneristiche — quanto è elastico questo contatto, come decade, quel click è udibile nel segnale — senza un oscilloscopio attaccato alla macchina.
Riferimenti
1. R. Holm, Electric Contacts: Theory and Application, 4th ed. Springer, 1967. 2. M. Braunovic, V. V. Konchits, N. K. Myshkin, Electrical Contacts: Fundamentals, Applications and Technology. CRC Press, 2007. 3. F. J. Harris, "On the use of windows for harmonic analysis with the discrete Fourier transform," Proceedings of the IEEE, vol. 66, no. 1, pp. 51–83, 1978. 4. A. V. Oppenheim, R. W. Schafer, Discrete-Time Signal Processing, 3rd ed. Pearson, 2010. 5. P. Horowitz, W. Hill, The Art of Electronics, 3rd ed. Cambridge University Press, 2015 (switch debouncing and Schmitt-trigger practice). 6. C. P. Arias, "A measure of earthquake intensity," in Seismic Design for Nuclear Power Plants. MIT Press, 1970 (intensity summaries of a non-stationary record). 7. GP2040-CE — open-source firmware for RP2040-based game controllers, https://github.com/OpenStickCommunity/GP2040-CE 8. TinyUSB — an open-source cross-platform USB stack for embedded systems, https://github.com/hathach/tinyusb 9. USB Implementers Forum, Device Class Definition for Human Interface Devices (HID), version 1.11, 2001. 10. Raspberry Pi Ltd, RP2040 Datasheet: A microcontroller by Raspberry Pi, 2021.
Nota alle citazioni: la lista è deliberatamente limitata a opere che il progetto può nominare in modo affidabile; espandere e verificare prima di qualsiasi sottomissione esterna.
Appendice A — Comandi della modalità config
GET dump delle impostazioni (debounce, socd, forget, chattergap, ...)
SET debounce <us> floor per pin, 100..4000 (unità sul filo: µs)
SET socd <mode> neutral | last | first | up
SET forget <s> secondi senza rimbalzi prima di dimenticare il lockout appreso
SET pinlock <gp> <us> override manuale (persistito immediatamente, 0 = automatico)
SET chattergap <ms> finestra di chatter di malfunzionamento (1..50)
SAVE persiste le impostazioni
STATS | LOCKS | TRACE | BOUNCE dump in streaming, terminati da OK
CLR | DIAGCLR azzera la black box (e lo stato adattivo per CLR)
RESET | REBOOT | BOOT ripristina i default / reboot / bootloader ROM UF2
Appendice B — Metriche dei rimbalzi
Per una raffica con fronte accettato a t0 e fronti di rimbalzo t1 … tn (n fronti rifiutati, n+1 fronti in totale), con intervalli dk = tk − t0:
-
finestra di chatter
= max(dk)— dal fronte accettato all'ultimo rimbalzo; -
fronti
= n + 1; -
intervallo min / mediana / max — statistiche d'ordine di
{dk}; -
growth
= intervallo max / intervallo min(≥1; grande = il contatto è
partito stretto e si è allentato, cioè un'oscillazione di assestamento);
-
frequenza istantanea
= 1/(2·dk)— una coppia chiusura/apertura per
periodo;
- dominante / centroide / quota ad alta frequenza — dalla FFT con finestra
di Hann del livello ricampionato a 1 µs (quota = frazione di energia sopra 20 kHz);
- inizia con un fronte accettato — se il primo evento della raffica era una
transizione accettata (altrimenti è una coda la cui raffica è uscita dall'anello).
Appendice C — Riproducibilità
Firmware 9.9 standard UF2 sha256 58f019f7b85d11d70517526b8c110b44c043f6e312f256d38f797787de7f54fa
timing UF2 sha256 2ecc3048231a3589be3077b283b7e36fe7c4f06daecf8b73606cb4be05898969
Build cmake -S . -B build-9.9 -G Ninja -DPICO_SDK_PATH=<pico-sdk-1.5.1> \
-DPICO_BOARD=pico -DCMAKE_BUILD_TYPE=Release && ninja -C build-9.9
Test python3 tests/run_tests.py <firmware_test|dpram_test|settings_test|diag_test|
config_test|ui_test|descriptors_test|lcd_test|telemetry_test>
node tests/bounce_js_test.mjs
Artefatto python3 tests/verify_uf2.py build-9.9/manticore_xinput.uf2 build-9.9/manticore_xinput.bin
Cattura gioco → modalità config (L3+R3+Guide 3 s) → web app → Refresh bounce → Export captures
(fronti grezzi JSON + metriche per raffica CSV)
Analisi stessi parametri del §9.4 (ricampionamento 1 µs, Hann, FFT a 256 punti, hop 64 µs,
gap 5 ms default, tetti 240 frame / 200 k campioni)
Traduzione italiana di docs/paper-manticore-xinput.md (edizione inglese).
Schematica
Il diagramma dell'architettura è in fondo a questa pagina, in versione zoomabile (Ctrl+rotella o pinch, trascina per spostare, doppio click per 1:1 / 2x). Il vettoriale è anche scaricabile: architecture.svg.
Documenti e download
- Paper tecnico: italiano · English · 日本語 (testo completo anche qui sotto)
- Pacchetto sorgente 9.9: manticore-xinput-9.9-src.zip (1.122931 MB, sha256
b27d9663f76d21b9f6575c4319037e9ddd6eb93c1fe3ac71e676fb1a61d1528d) - Web app di riferimento (WebSerial, Chrome/Edge): nel pacchetto sorgente,
webapp/index.html(apri con Chrome/Edge)
Come è verificato
Nove suite di test emulano ARM ed eseguono i sorgenti reali con hardware finto, un test Node senza dipendenze copre la pipeline host, e un verificatore controlla l'immagine UF2 rilasciata contro il binario collegato e l'identità di versione. Un audit multi-agente esterno sulla 9.8 ha prodotto un difetto bloccante e diverse correzioni, tutte in 9.9 con test di regressione.
Schema dell'architettura
Ctrl+rotella o pinch per lo zoom, trascina per spostare, doppio click per 1:1 / 2x. Il pulsante SVG apre il file originale.