diff --git a/SD/config/settings.ini b/SD/config/settings.ini new file mode 100644 index 0000000..44911df --- /dev/null +++ b/SD/config/settings.ini @@ -0,0 +1,53 @@ + +[hid report] +DeviceName=RP2040-HOTAS +enableMouse=false +enableKeyboard=false +UsagePage=1 +Usage=4 +ButtonCount=100 +HatCount=1 +AxisCount=4 +AxisResolution=11 +ADCResolution=11 + + + + + + + + + + + + + + +EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE +enableMouse=false +enableKeyboard=false +UsagePage=1 +Usage=4 +ButtonCount=64 +HatCount=1 +AxisCount=8 +AxisResolution=11 +ADCResolution=11 + + + + + + + + + + + + + + + + + diff --git a/SD/index.html b/SD/index.html index 61b9e40..90a0346 100644 --- a/SD/index.html +++ b/SD/index.html @@ -8,7 +8,7 @@

PicoW Index page


- Pinout + Pinout
AJAX-testpage
diff --git a/SD/pinout/pinout.css b/SD/pinout/pinout.css new file mode 100644 index 0000000..02941f9 --- /dev/null +++ b/SD/pinout/pinout.css @@ -0,0 +1,397 @@ +body { + display: grid; + place-items: center; + font-family: Verdana, Geneva, Tahoma, sans-serif; + margin: 0; + padding: 0; +} + +article { + display: grid; + grid-template-columns: auto auto auto; + align-items: center; + margin-top: 10px; + padding: 0 10px; +} + +footer { + display: grid; + align-items: center; + margin: 20px 0; +} + +footer p { + padding: 5px 10px; + margin: 0; + text-align: center; +} + +nav { + text-align: center; + margin-top: 20px; +} + +header { + background: #002b36; + color: #e9e5d2; + display: block; + width: 100%; + line-height: 22px; + padding: 10px; + border-bottom: 5px solid #268bd2; + display: grid; + grid-template-columns: 10px auto auto 10px; +} + +header h1 { + margin: 0; + padding: 0; + font-size: 20px; + grid-column: 2; +} + +header ul { + grid-column: 3; + list-style: none; + margin: 0; + padding: 0; + text-align: right; +} + +header li { + display: inline-block; + padding: 0 0 0 10px; + white-space: pre; +} + +header li:nth-child(2) a, +header li:nth-child(2) a:visited, +header li:nth-child(2) a:hover { + color: #268bd2; +} + +header li:nth-child(3) a, +header li:nth-child(3) a:visited, +header li:nth-child(3) a:hover { + color: #d33682; +} + +header li:nth-child(4) a, +header li:nth-child(4) a:visited, +header li:nth-child(4) a:hover { + color: #dc322f; +} + +.pico { + padding: 0; + position: relative; + top: -8px; +} + +.labels.right { + counter-reset: pin 41; + display: grid; + align-items: left; +} + +.tiny2040 .labels.right { + counter-reset: pin 17; +} + +.labels.left { + direction: rtl; + display: grid; + align-items: right; +} + +.labels tr { + counter-increment: pin; + line-height: 20px; + color: #333; + font-size: 0; +} + +.labels.left tr { + direction: ltr; +} + +.labels.right tbody tr { + counter-increment: pin -1; +} + +.labels.below { + grid-column: 2; + margin: -5px auto 0; + position: relative; + left: 2px; +} + +.labels.below td { + width: 23px; + height: auto; + border: none; + padding: 0; + writing-mode: sideways-lr; + vertical-align: top; +} + +.underside-view .labels.below { + left: -2px; +} + +.labels th, +.labels thead { + display: none; +} + +.labels tbody tr:before { + content: counter(pin); + text-align: center; + float: right; + background-color: #ccc; + width: 20px; + height: 20px; + font-size: 11px; +} + +.labels.right tbody tr:before { + float: left; +} + +.labels.below tbody tr:before { + display: none; +} + +td, +th { + height: 20px; + text-align: left; + overflow: hidden; + color: #222; + font-size: 12px; + cursor: default; + white-space: nowrap; + position: relative; + border-width: 0 4px; + border-style: solid; + padding: 0 8px; +} + +.extra td { + border: none; + background-color: #9e9d9b33; +} + +.extra th { + border: none; + background-color: transparent; +} + +.labels .advanced { + visibility: hidden; +} + +.labels .hidden, +.extra.advanced, +.below.advanced { + display: none; +} + +.filter li.advanced { + display: none; +} + +.gpio { + background-color: #85990033; + border-color: #859900; +} + +.ground { + background-color: #002b3633; + border-color: #002b36; +} + +.power { + background-color: #dc322f33; + border-color: #dc322f; +} + +.adc { + background-color: #2aa19833; + border-color: #2aa198; +} + +.system { + background-color: #df8f8e33; + border-color: #df8f8e; +} + +.i2c { + background-color: #268bd233; + border-color: #268bd2; +} + +.spi { + background-color: #d3368233; + border-color: #d33682; +} + +.uart { + background-color: #6c71c433; + border-color: #6c71c4; +} + +.pwm { + background-color: #33333333; + border-color: #333; +} + +.labels.left td { + border-width: 0 0 0 4px; +} + +.labels.right td { + border-width: 0 4px 0 0; +} + +.labels tr:hover:before { + background-color: #eee; +} + +.labels tr:hover .gpio { + background-color: #9fb80366; +} + +.labels tr:hover .ground { + background-color: #01435366; +} + +.labels tr:hover .power { + background-color: #f13a3766; +} + +.labels tr:hover .adc { + background-color: #39d1c466; +} + +.labels tr:hover .system { + background-color: #fab4b266; +} + +.labels tr:hover .i2c { + background-color: #3ba6f166; +} + +.labels tr:hover .spi { + background-color: #f34f9f66; +} + +.labels tr:hover .uart { + background-color: #8d91e666; +} + +.labels tr:hover .pwm { + background-color: #44444466; +} + +.filter { + list-style: none; + margin: 0; + padding: 0; +} + +.filter li { + display: inline; +} + +.filter label { + display: inline-block; + color: #002b36; + padding: 0 10px; + line-height: 30px; + user-select: none; + margin-bottom: 5px; +} + +.interfaces label { + border-width: 0 0 0 30px; + border-style: solid; +} + +.filter input { + margin: 0 10px 0 0; +} + +.filter.interfaces label { + color: #222; +} + +.filter.interfaces { + margin-bottom: 20px; +} + +.nojs ul { + display: none; +} + +.underside { + display: none; +} + +.underside-view .pico { + display: none; +} + +.underside-view .underside { + display: block; +} + +.underside-view { + direction: rtl; +} + +.underside-view .labels.left { + direction: ltr; +} + +@media (prefers-color-scheme: dark) { + body { + background-color: #000; + } + + header { + background: #111; + color: #666; + border-bottom-color: #222; + } + + .filter label, + footer p, + nav p { + color: #666; + } + + footer a, + footer a:visited { + color: #1d6ca5; + } + + footer a:hover { + color: #3ba6f1; + } + + .labels tbody tr:before { + background-color: #002b36; + color: #e9e5d2; + } + + .labels tbody tr:hover:before { + background-color: #013441; + } + + td, + th, + .filter.interfaces label { + color: #e9e5d2; + } +} \ No newline at end of file diff --git a/SD/pinout/pinout.html b/SD/pinout/pinout.html new file mode 100644 index 0000000..05f4d13 --- /dev/null +++ b/SD/pinout/pinout.html @@ -0,0 +1,363 @@ + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PinNameSPII2CUARTPWM
1GP0SPI0 RXI2C0 SDAUART0 TXPWM0 A
2GP1SPI0 CSnI2C0 SCLUART0 RXPWM0 B
3Ground
4GP2SPI0 SCKI2C1 SDAUART0 CTSPWM1 A
5GP3SPI0 TXI2C1 SCLUART0 RTSPWM1 B
6GP4SPI0 RXI2C0 SDAUART1 TXPWM2 A
7GP5SPI0 CSnI2C0 SCLUART1 RXPWM2 B
8Ground
9GP6SPI0 SCKI2C1 SDAUART1 CTSPWM3 A
10GP7SPI0 TXI2C1 SCLUART1 RTSPWM3 B
11GP8SPI1 RXI2C0 SDAUART1 TXPWM4 A
12GP9SPI1 CSnI2C0 SCLUART1 RXPWM4 B
13Ground
14GP10SPI1 SCKI2C1 SDAUART1 CTSPWM5 A
15GP11SPI1 TXI2C1 SCLUART1 RTSPWM5 B
16GP12SPI1 RXI2C0 SDAUART0 TXPWM6 A
17GP13SPI1 CSnI2C0 SCLUART0 RXPWM6 B
18Ground
19GP14SPI1 SCKI2C1 SDAUART0 CTSPWM7 A
20GP15SPI1 TXI2C1 SCLUART0 RTSPWM7 B
+
+ Raspberry Pi Pico standing face-up. +
+
+ Raspberry Pi Pico standing bottom-up. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PinName/ADCSPII2CUARTPWM
40VBUS
39VSYS
38Ground
373V3 En
363V3 Out
35ADC VRef
34GP28 / A2SPI1 RXI2C0 SDAUART0 TXPWM6 A
33ADC Gnd
32GP27 / A1SPI1 TXI2C1 SCLUART1 RTSPWM5 B
31GP26 / A0SPI1 SCKI2C1 SDAUART1 CTSPWM5 A
30RUN
29GP22SPI0 SCKI2C1 SDAUART1 CTSPWM3 A
28Ground
27GP21SPI0 CSnI2C0 SCLUART1 RXPWM2 B
26GP20SPI0 RXI2C0 SDAUART1 TXPWM2 A
25GP19SPI0 TXI2C1 SCLUART0 RTSPWM1 B
24GP18SPI0 SCKI2C1 SDAUART0 CTSPWM1 A
23Ground
22GP17SPI0 CSnI2C0 SCLUART0 RXPWM0 B
21GP16SPI0 RXI2C0 SDAUART0 TXPWM0 A
+ + + + +
SWCLKGNDSWDIO
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PinFunctionNotes
GP23RT6150B-33GQW Power-SelectLOW (default) for high efficiency (PFM)
HIGH for improved ripple (PWM)
GP24VBUS SenseDetect USB power or VBUS pin
GP25User LED
GP29 / A3VSYS SenseRead VSYS/3 through resistor divider and FET Q1
A4TemperatureRead onboard temperature sensor
+
+ + + + diff --git a/SD/pinout/pinout.js b/SD/pinout/pinout.js new file mode 100644 index 0000000..e8bc824 --- /dev/null +++ b/SD/pinout/pinout.js @@ -0,0 +1,41 @@ +'use strict'; +var pinout=document.getElementById("pinout"); +var inputs=document.getElementsByTagName("input"); +var advanced=document.querySelectorAll(".advanced"); +document.getElementById("nav").classList.remove("nojs"); +for(var i=0;i + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 3V3 + ADC_VREF + GP 28_A2 + AGND + VBUS + VSYS + GND + 3V3_EN + GP 27_A1 + GP 26_A0 + RUN + GP 22 + GND + GP 21 + GP 20 + GP 19 + GP 18 + GND + GP 16 + GP 17 + GP 3 + GP 4 + GP 5 + GND + GP 0 + GP 1 + GND + GP 2 + GP 6 + GP 7 + GP 8 + GP 9 + GND + GP 10 + GP 11 + GP 12 + GP 13 + GND + GP 15 + GP 14 + SWDIO + GND + SWCLK + TP 2 + TP 1 + TP 3 + TP 4 + TP 5 + TP 6 + \ No newline at end of file diff --git a/SD/pinout/raspberry-pi-pico.svg b/SD/pinout/raspberry-pi-pico.svg new file mode 100644 index 0000000..f00928a --- /dev/null +++ b/SD/pinout/raspberry-pi-pico.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/SD/pinout/register_serviceworker.js b/SD/pinout/register_serviceworker.js new file mode 100644 index 0000000..6568fd2 --- /dev/null +++ b/SD/pinout/register_serviceworker.js @@ -0,0 +1,8 @@ +if('serviceWorker'in navigator){ + let registration; + const registerServiceWorker = async() => { + registration = await navigator.serviceWorker.register('sw.js'); + console.log(`Service Worker Registration (Scope: ${registration.scope})`); + }; + registerServiceWorker(); +} \ No newline at end of file diff --git a/SD/settings/settings.html b/SD/settings/settings.html new file mode 100644 index 0000000..890a810 --- /dev/null +++ b/SD/settings/settings.html @@ -0,0 +1,153 @@ + + + + + Pico Index + + + +

PicoW settings page

+
+ Main page +
+ +
+ + + + + + + +
I2C ID's
+ +
+ + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ +
+ + +
+ + + +
+ +

test:

+

Device name:

+

ADC resolution:

+

Axis resolution:

+

Axis count:

+

Button count:

+

Hat count:

+ + + + diff --git a/SD/settings/settings.js b/SD/settings/settings.js new file mode 100644 index 0000000..61629c7 --- /dev/null +++ b/SD/settings/settings.js @@ -0,0 +1,204 @@ +var hexCol = ["0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F"] +var hexRow = ["00:","10:","20:","30:","40:","50:","60:","70:","80:","90:","A0:","B0:","C0:","D0:","E0:","F0:"] +var usagePageList = { + "Undefined": 0x00, + "Generic Desktop Controls": 0x01, + "Simulation Controls": 0x02, + "VR Controls": 0x03, + "Sport Controls": 0x04, + "Game Controls": 0x05, +} +var usagePageSubList = { + "Undefined": { + "Undefined":0x00 + }, + "Generic Desktop Controls": { + "Undefined": 0x00, + "Pointer": 0x01, + "Mouse": 0x02, + "Joystick": 0x04, + "Game Pad": 0x05, + "keyboard": 0x06, + "Multi-axis COntroller": 0x08 + }, + "Simulation Controls": { + "Undefined" : 0x00, + "Flight Simulation Device": 0x01, + "Automobile Simulation Device": 0x02, + "Tank Simulation Device": 0x03, + "Spaceship Simulation Device": 0x04, + "Submarine Simulation Device": 0x05, // OceanGate anyone? + "Sailing Simulation Device": 0x06, + "Motorcycle Simulation Device": 0x07, + "Sports Simulation Device": 0x08, + "Airplane Simulation Device": 0x09, + "Helicopter Simulation Device": 0x0A, + "Magic Carpet Simulation Device": 0x0B, + "Bicycle Simulation Device": 0x0C + } +} + +// test + +var usagePage = 0; +var usage = 0; +var axisCount=0; +var ButtonCount=0; +var HatCount=0; + +function onBodyLoad() { + console.log("we are loaded!!"); + setTimeout(getI2CList, 500); + getHIDValues(); + onListSelect(); + populateUsagePage(); + //document.getElementById("devicename").addEventListener("change", ); + //document.getElementById("axiscount").addEventListener("change", updateAxis); + //document.getElementById("hatcount").addEventListener("change", updateHats); + //document.getElementById("buttoncount").addEventListener("change", updateButtons); + document.getElementById("UsagePageType").addEventListener("change", UsagePageTypeChanged); + document.getElementById("UsagePageSubType").addEventListener("change", UsagePageSubTypeChanged); +} + +function onListSelect() { + var mylist = document.getElementById("myList"); + document.getElementById("favourite").value = mylist.options[mylist.selectedIndex].text; +} + +async function getI2CList() { + const res = await fetch("listI2C?"); + var ids = await res.json(); + var tbodyRef = document.getElementById('I2CTable').getElementsByTagName('tbody')[0]; + // Clear the existing data + tbodyRef.innerHTML = ''; + // Insert a row at the end of table + var newRow = tbodyRef.insertRow(); + var newCell = newRow.insertCell(); + var newText = document.createTextNode(""); + newCell.appendChild(newText); + var row = 0; + for (var i = 0; i<16; i++) { + var newCell = newRow.insertCell(); + var newText = document.createTextNode(hexCol[i]); + newCell.appendChild(newText); + } + var newRow = tbodyRef.insertRow(); + var row = 0; + for (var j = 0; j<128; j++) { + if ((j % 16) == 0) { + var newCell = newRow.insertCell(); + var newText = document.createTextNode(hexRow[row]); + newCell.appendChild(newText); + } + var newCell = newRow.insertCell(); + if (ids.includes(j) == true) { + if (j<7) + var newText = document.createTextNode('!'); + else + var newText = document.createTextNode('X'); + } else { + if (j<7) + var newText = document.createTextNode(' '); + else + var newText = document.createTextNode('-'); + } + newCell.appendChild(newText); + // Insert a row at the end of table when we have 16 items + if ( (j+1)%16 == 0) { + var newRow = tbodyRef.insertRow(); + row++; + } + } + setTimeout(getI2CList, 500); +} + +function updateAxis() { +// fetch("settings/updatehid?axis="+ document.getElementById("axiscount").value); +} + +function updateButtons() { +// fetch("settings/updatehid?buttons="+ document.getElementById("buttoncount").value); +} + +function updateHats() { +// fetch("settings/updatehid?hats="+ document.getElementById("hatcount").value); +} + +async function getHIDValues() { + //const current_state = await fetch("settings/updatehid?getValues="); + //var values = await current_state.json(); + var response; + + response = await fetch("settings/updatehid?usagepage="); + //document.getElementById("axiscount").value = response.text(); + response = await fetch("settings/updatehid?usage="); + //document.getElementById("axiscount").value = response.text(); + response = await fetch("settings/updatehid?devicename="); + document.getElementById("devicename").value = await response.text(); + response = await fetch("settings/updatehid?adcresolution="); + document.getElementById("adcresolution").value = await response.text(); + response = await fetch("settings/updatehid?axisresolution="); + document.getElementById("axisresolution").value = await response.text(); + response = await fetch("settings/updatehid?axis="); + document.getElementById("axiscount").value = await response.text(); // values[0]; + response = await fetch("settings/updatehid?buttons="); + document.getElementById("buttoncount").value = await response.text(); //values[1]; + response = await fetch("settings/updatehid?hats="); + document.getElementById("hatcount").value = await response.text(); //values[2]; +} + +function updateHID() { + fetch("settings/updatehid?"+ + "devicename="+document.getElementById("devicename").value+ + "&axisresolution="+document.getElementById("axisresolution").value+ + "&adcresolution="+document.getElementById("adcresolution").value+ + "&usagepage="+ usagePage+ + "&usage="+ usage+ + "&axis="+ document.getElementById("axiscount").value+ + "&buttons="+ document.getElementById("buttoncount").value+ + "&hats="+ document.getElementById("hatcount").value+ + "&restart=" + ); + //await fetch("settings/updatehid?usagepage="+ usagePage); + //await fetch("settings/updatehid?usage="+ usage); + //await fetch("settings/updatehid?axis="+ document.getElementById("axiscount").value); + //await fetch("settings/updatehid?hats="+ document.getElementById("hatcount").value); + //await fetch("settings/updatehid?buttons="+ document.getElementById("buttoncount").value); + //await fetch("settings/updatehid?restart="); +} + +function populateUsagePage() { + var usageListPageType = document.getElementById("UsagePageType"); + usageListPageType.innerHTML = ''; + console.log(usagePageList); + + for (const [k,v] of Object.entries(usagePageList)) { + console.log(k + "-" + v); + usageListPageType.add( new Option(k, v)); + } +} + +function UsagePageTypeChanged(e) { + var index = e.target.options.selectedIndex; + console.log(index); + usagePage = index; + var key = e.target.options[index].innerText; + console.log(key); + var sublist = usagePageSubList[key]; + console.log(sublist); + var UsagePageSubType = document.getElementById("UsagePageSubType"); + UsagePageSubType.innerHTML = ''; + if (sublist) { + for (const [k,v] of Object.entries(sublist)) { + UsagePageSubType.add( new Option(k, v)); + } + } else { + UsagePageSubType.add( new Option("unsupported")); + } +} + +function UsagePageSubTypeChanged(e) { + console.log(e.target.options[e.target.options.selectedIndex].label); + usage = e.target.options[e.target.options.selectedIndex].value; + console.log(usagePage, usage); +} diff --git a/config/common.ini b/config/common.ini index d760608..d355082 100644 --- a/config/common.ini +++ b/config/common.ini @@ -4,7 +4,7 @@ platform = https://github.com/maxgerhardt/platform-raspberrypi.git board = rpipico board_build.core = earlephilhower framework = arduino -board_build.flash_mode = qio +;board_build.flash_mode = qio ;monitor_filters = default, colorize ;monitor_speed = 115200 debug_speed = 10000 @@ -49,35 +49,34 @@ debug_init_break = tbreak setup board_build.f_cpu = 250000000L ;250Mhz (OC) ;board_build.f_cpu = 270000000L ;270Mhz (OC) (custom) (RAM ONLY) -build_flags = - [pico_common] platform_packages = - ;https://github.com/raspberrypi/openocd.git@^2.12.0 - ;platformio/tool-openocd-raspberrypi@^2.1000.0 + ;toolchain-rp2040-earlephilhower build_flags = ;-fstack-usage - -Waddress - -Wcomment - -Wformat - -Wbool-compare - -Wuninitialized - -Wunknown-pragmas - -Wunused-value - -Wimplicit-fallthrough - -Wunused - -Wpointer-arith - -Wno-missing-braces - -O3 - -fno-exceptions - -D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS +; -Waddress +; -Wcomment +; -Wformat +; -Wbool-compare +; -Wuninitialized +; -Wunknown-pragmas +; -Wunused-value +; -Wimplicit-fallthrough +; -Wunused +; -Wpointer-arith +; -Wno-missing-braces +; -fno-exceptions + -Os +; -D PIO_FRAMEWORK_ARDUINO_ENABLE_EXCEPTIONS -D I2CSPEED=100000 libs = ;https://github.com/IWILZ/PicoSem.git robtillaart/CRC @ ^1.0.2 Adafruit NeoPixel @ ^1.12.0 + ;olikraus/U8g2 @ ^2.35.19 + ;U8g2 diff --git a/config/i2c_node.ini b/config/i2c_node.ini index 170d0a8..63bf723 100644 --- a/config/i2c_node.ini +++ b/config/i2c_node.ini @@ -15,8 +15,6 @@ build_flags = lib_deps = ${pico_common.libs} + debug_build_flags = - ;-O0 - ;-Og - ;-g3 -ggdb \ No newline at end of file diff --git a/config/usb.ini b/config/usb.ini index 8e6b35a..fceea28 100644 --- a/config/usb.ini +++ b/config/usb.ini @@ -25,17 +25,16 @@ build_flags = -D CFG_TUH_MSC=0 -D CFG_TUD_VIDEO=0 -D CFG_TUD_VENDOR=0 - -D CFG_TUD_MSC=0 + -D CFG_TUD_MSC=1 -D CFG_TUD_MIDI=0 lib_deps = ${pico_common.libs} Adafruit TinyUSB Library @ ^3.0.0 -extra_scripts = pre:patch-SDFS.py +extra_scripts = + pre:scripts/patch-SDFS.py + ;pre:scripts/picoasm.py debug_build_flags = - ;-O0 - ;-Og - ;-g3 -ggdb diff --git a/desktop-descriptor.c b/desktop-descriptor.c index caf45cb..fcd0e7b 100644 --- a/desktop-descriptor.c +++ b/desktop-descriptor.c @@ -1,5 +1,6 @@ #define DESKTOP_ONLY #include +#include #include #include #include @@ -18,18 +19,23 @@ typedef struct { uint8_t axis; } input_id; + +bool enableKeyboard = 1; +bool enableMouse = 1; +bool enableMsc = 0; +uint8_t IdCount; +uint8_t MaxDeviceCount = MAX_REPORT_ID; + + uint8_t device_max_button_count = 79; uint8_t device_max_hat_count = 4; uint8_t device_max_axis_count = 9; uint32_t AxisResolution = 11; uint16_t AxisCount = 8; -uint16_t ButtonCount = 65; +uint16_t ButtonCount = 64; uint16_t HatCount = 1; -uint8_t hid_usage_page_val = HID_USAGE_PAGE_DESKTOP; -uint8_t hid_usage_val = HID_USAGE_DESKTOP_JOYSTICK; -uint8_t DeviceCount=0; uint usb_report_size; uint8_t usb_report[MAX_HID_DESCRIPTOR_SIZE]; @@ -39,8 +45,6 @@ uint16_t button_start[MAX_REPORT_ID]; uint16_t hat_start[MAX_REPORT_ID]; uint16_t total_bits[MAX_REPORT_ID]; uint8_t reports[MAX_REPORT_ID][64]; -uint8_t old_reports[MAX_REPORT_ID][64]; -uint8_t readyToUpdate[MAX_REPORT_ID]; uint16_t largest_bits; @@ -88,7 +92,29 @@ void set_hat(uint8_t *report, uint8_t reportid, uint8_t hat, uint8_t value) { } } -void setupUSB() { +void setupDescripor() { + uint16_t maxBuffSize = MAX_HID_DESCRIPTOR_SIZE; + if (enableMouse) + maxBuffSize -= 79; +// MaxDeviceCount -= 1; + if (enableKeyboard) + maxBuffSize -= 67; +// MaxDeviceCount -= 1; + + // reset the connection + memset(inputs_id, 0, sizeof(inputs_id)); + memset(usb_report, 0, sizeof(usb_report)); + memset(button_start,0,sizeof(button_start)); + memset(hat_start,0,sizeof(hat_start)); + memset(axis_start,0,sizeof(axis_start)); + memset(total_bits,0,sizeof(total_bits)); + usb_report_size=0; + IdCount=0; + + //this buffer will be cleared and freed at the end since its a temp buffer + uint8_t* t_buffer = (uint8_t*)malloc(150); + memset(t_buffer,0,150); + uint t_buffer_size; /* Calculate how many "devices" we will need to emulate @@ -96,33 +122,48 @@ void setupUSB() { Calculate how many hats per "device" Calculate how many axis per "device" */ - memset(inputs_id, 0, sizeof(inputs_id)); - memset(usb_report, 0, sizeof(usb_report)); - memset(button_start,0, sizeof(button_start)); - memset(hat_start,0, sizeof(hat_start)); - memset(axis_start,0, sizeof(axis_start)); - usb_report_size=0; - largest_bits=0; - DeviceCount=0; - + for (uint16_t i=ButtonCount, j=1; i>0; i-=MIN(device_max_button_count,i), j++) { inputs_id[j].buttons = i>device_max_button_count ? device_max_button_count:i; - DeviceCount = j>DeviceCount ? j:DeviceCount; + IdCount = j>IdCount ? j:IdCount; } for (uint16_t i=HatCount, j=1; i>0; i-=MIN(device_max_hat_count, i), j++) { inputs_id[j].hats = i>device_max_hat_count ? device_max_hat_count:i; - DeviceCount = j>DeviceCount ? j:DeviceCount; + IdCount = j>IdCount ? j:IdCount; } for (uint8_t i=AxisCount, j=1; i>0;i-=MIN(device_max_axis_count,i), j++) { inputs_id[j].axis = i>device_max_axis_count ? device_max_axis_count:i; - DeviceCount = j>DeviceCount ? j:DeviceCount; + IdCount = j>IdCount ? j:IdCount; + } + for (uint16_t id = 1; id<=IdCount; id++) { + t_buffer_size = 0; + makeJoystickDescriptor(id, AxisResolution, inputs_id[id].axis, inputs_id[id].hats, inputs_id[id].buttons, t_buffer, &t_buffer_size); + if (usb_report_size + t_buffer_size < maxBuffSize) { + memmove(usb_report+usb_report_size, t_buffer, t_buffer_size); + usb_report_size += t_buffer_size; + } + memset(t_buffer, 0, 150); +// makeJoystickDescriptor(id, AxisResolution, inputs_id[id].axis, inputs_id[id].hats, inputs_id[id].buttons, usb_report, &usb_report_size); } - for (uint16_t i = 1; i<=DeviceCount; i++) { - makeDescriptor(i, AxisResolution, inputs_id[i].axis, inputs_id[i].hats, inputs_id[i].buttons, usb_report, &usb_report_size); + if (enableKeyboard) { + t_buffer_size = 0; + makeKeyboardDescriptor(IdCount+1,t_buffer, &t_buffer_size); + if (usb_report_size + t_buffer_size < MAX_HID_DESCRIPTOR_SIZE) { + memmove(usb_report+usb_report_size,t_buffer,t_buffer_size); + usb_report_size += t_buffer_size; + memset(t_buffer,0,150); + } } - for (uint8_t rep = 1; rep < MAX_REPORT_ID; rep++) { - largest_bits = (largest_bits < total_bits[rep]) ? total_bits[rep] : largest_bits; + if (enableMouse) { + t_buffer_size = 0; + makeMouseDescriptor(IdCount+2,t_buffer, &t_buffer_size); + if (usb_report_size + t_buffer_size < MAX_HID_DESCRIPTOR_SIZE) { + memmove(usb_report+usb_report_size,t_buffer,t_buffer_size); + usb_report_size += t_buffer_size; + memset(t_buffer,0,150); + } } + free(t_buffer); } int main(int argc, char **argv) { @@ -134,9 +175,8 @@ int main(int argc, char **argv) { HatCount, ButtonCount ); - setupUSB(); - AxisCount = 0; - setupUSB(); + setupDescripor(); + for (uint8_t i = 0; i <8; i++) { set_axis(reports[1],1,i,(1< #include #include -enum command { - SetConfig = 0B0000000000000010, - GetConfig = 0B0000000000000011, - SetLed = 0B0000000000000100, - GetLed = 0B0000000000000101, - SetAxis = 0B0000000000001000, - GetAxis = 0B0000000000001001, - SetButton = 0B0000000000010000, - GetButton = 0B0000000000010001, - //Set = 0B0000000000100000, - //Get = 0B0000000000100001, - //Set = 0B0000000001000000, - //Get = 0B0000000001000001, - //Set = 0B0000000010000000, - //Get = 0B0000000010000001, - - //Set = 0B0000000100000000, - //Get = 0B0000000100000001, - //Set = 0B0000001000000000, - //Get = 0B0000001000000001, - //Set = 0B0000010000000000, - //Get = 0B0000010000000001, - //Set = 0B0000100000000000, - //Get = 0B0000100000000001, - //Set = 0B0001000000000000, - //Get = 0B0001000000000001, - //Set = 0B0010000000000000, - //Get = 0B0010000000000001, - //Set = 0B0100000000000000, - //Get = 0B0100000000000001, - //Set = 0B1000000000000000, - //Get = 0B1000000000000001, -// |||||||||||||||| -// /||||||||||||||| -// /|||||||||||||| -// /||||||||||||| -// /|||||||||||| -// /||||||||||| -// /|||||||||| -// /||||||||| -// /|||||||| -// /||||||| -// /|||||| -// /||||| -// BUTTON BIT --------------/|||| -// AXIS BIT------------------/||| -// LED BIT -------------------/|| -// CONFIG BIT -----------------/| -// SET/GET BIT -----------------/ - UpdateFirmware = 0B1111111111111111 // ONLY USED FOR MODIFYING THE FIRMWARE -}; -enum ConfigCommands { - AnalogResolution = 0B00000001, - GetAnalog = 0B00000010 -}; - -enum AxisInputs { - X, - Y, - Z, - Rx, - Ry, - Rz, - S, - D, - W, +enum commandType { + Config = 0B00000010, + //Config = 0B00000011, + SetLed = 0B00000100, + GetLed = 0B00000101, + SetAxis = 0B00001000, + GetAxis = 0B00001001, + SetButton = 0B00010000, + GetButton = 0B00010001, + SetMuxId = 0B00100000, + GetMuxId = 0B00100001, + //Set = 0B01000000, + //Get = 0B01000001, + //Set = 0B10000000, + //Get = 0B10000001, }; -enum Buttons { - Button1 = 1, - Button2 , - Button3 , - Button4 , - Button5 , - Button6 , - Button7 , - Button8 , - Button9 , - Button10 , - Button11 , - Button12 , - Button13 , - Button14 , - Button15 , - Button16 , - Button17 , - Button18 , - Button19 , - Button20 , - Button21 , - Button22 , - Button23 , - Button24 , - Button25 , - Button26 , - +enum ConfigCommands { + SetI2cId = 0B0000000000000010, + GetI2cId = 0B0000000000000011, + SetChipId = 0B0000000000000100,// meant for testing only + GetChipId = 0B0000000000000101, + SetWS2812LedPin = 0B0000000000001000, + GetWS2812LedPin = 0B0000000000001001, + SetAnalogResolution = 0B0000000000010000, + GetAnalogResolution = 0B0000000000010001, + SetButtonPin = 0B0000000000100000, + GetButtonPin = 0B0000000000100001, + SetAxisPin = 0B0000000001000000, + GetAxisPin = 0B0000000001000001, + SetHatPins = 0B0000000010000000, + GetHatPins = 0B0000000010000001, + SetMuxPins = 0B0000000100000000, + GetMuxPins = 0B0000000100000001, + SetButtonCount = 0B0000001000000000, + GetButtonCount = 0B0000001000000001, + SetHatCount = 0B0000010000000000, + GetHatCount = 0B0000010000000001, + SetAxisCount = 0B0000100000000000, + GetAxisCount = 0B0000100000000001, + SetMuxCount = 0B0001000000000000, + GetMuxCount = 0B0001000000000001, + SetDigitalPinMode = 0B0010000000000000, + GetDigitalPinMode = 0B0010000000000001, + SetAnalogPinMode = 0B0100000000000000, + GetAnalogPinMode = 0B0100000000000001, + SetInvert = 0B1000000000000000, + GetInvert = 0B1000000000000001, + // ONLY USED FOR MODIFYING THE FIRMWARE SETTINGS + // WILL FORCE A REBOOT AFTERWARDS + ConfigUpdate = 0B1111111111111111 }; - -struct __attribute__((packed, aligned(1))) Input_Config { - uint8_t input_type; - uint8_t input_id; - char input_name; -}; diff --git a/include/HID_Report.h b/include/HID_Report.h deleted file mode 100755 index 5b64bd2..0000000 --- a/include/HID_Report.h +++ /dev/null @@ -1,21 +0,0 @@ -/// HID Gamepad Protocol Report. - - -typedef struct TU_ATTR_PACKED -{ - uint64_t buttons ; ///< Buttons mask for currently pressed buttons - uint8_t hat1 :4 ; ///< Buttons mask for currently pressed buttons in the DPad/hat - uint8_t hat2 :4 ; ///< Buttons mask for currently pressed buttons in the DPad/hat - int16_t x ; ///< Delta x movement of left analog-stick - int16_t y ; ///< Delta y movement of left analog-stick - int16_t z ; ///< Delta z movement of analog right trigger - int16_t rx ; ///< Delta Rz movement of right analog-joystick - int16_t ry ; ///< Delta Rx movement of analog left trigger - int16_t rz ; ///< Delta Rz movement of right analog-joystick - int16_t slider ; ///< Delta Ry movement of analog right trigger - int16_t dial ; ///< Delta Ry movement of analog right trigger - - -}hid_Joystick_report_t; - - diff --git a/include/HID_descriptor.h b/include/HID_descriptor.h index c6087b6..b402d01 100755 --- a/include/HID_descriptor.h +++ b/include/HID_descriptor.h @@ -1,6 +1,6 @@ #include "hid.h" -void makeDescriptor(uint8_t reportID, uint8_t bitsPerAxis, uint8_t axisCount,uint8_t hatCount, uint8_t buttonCount, uint8_t *buffer, uint *bufferSize) { +void makeJoystickDescriptor(uint8_t reportID, uint8_t bitsPerAxis, uint8_t axisCount,uint8_t hatCount, uint8_t buttonCount, uint8_t *buffer, uint *bufferSize) { extern uint16_t axis_start[]; extern uint16_t hat_start[]; extern uint16_t button_start[]; @@ -90,9 +90,117 @@ void makeDescriptor(uint8_t reportID, uint8_t bitsPerAxis, uint8_t axisCount,uin *bufferSize = p - buffer; } -uint8_t *_makeDescriptor(uint8_t reportID, uint8_t bitsPerAxis, uint8_t axisCount,uint8_t hatCount, uint8_t buttonCount,uint *bufferSize) { +uint8_t *_makeJoystickDescriptor(uint8_t reportID, uint8_t bitsPerAxis, uint8_t axisCount,uint8_t hatCount, uint8_t buttonCount,uint *bufferSize) { uint8_t *buffer = (uint8_t*)malloc(MAX_HID_DESCRIPTOR_SIZE); *bufferSize = 0; - makeDescriptor(reportID, bitsPerAxis, axisCount, hatCount, buttonCount, buffer, bufferSize); + makeJoystickDescriptor(reportID, bitsPerAxis, axisCount, hatCount, buttonCount, buffer, bufferSize); return buffer; -} \ No newline at end of file +} +void makeKeyboardDescriptor(uint8_t reportID, uint8_t *buffer, uint *bufferSize) { + extern uint16_t total_bits[]; + uint8_t *p = buffer+*bufferSize; + sp=0; + bits=0; + + hid_usage_page(&p, HID_USAGE_PAGE_DESKTOP); + hid_usage(&p, HID_USAGE_DESKTOP_KEYBOARD); + hid_collection(&p, HID_COLLECTION_APPLICATION); + + hid_report_id(&p, reportID); + + hid_usage_page(&p, HID_USAGE_PAGE_KEYBOARD); + hid_usage_min(&p, 224); + hid_usage_max(&p, 231); + hid_logical_min(&p, 0); + hid_logical_max(&p, 1); + hid_report_count(&p, 8); + hid_report_size(&p, 1); + hid_input(&p, HID_DATA | HID_VARIABLE | HID_ABSOLUTE); + + hid_report_count(&p, 1); + hid_report_size(&p, 8); + hid_input(&p, HID_CONSTANT); + + hid_usage_page(&p, HID_USAGE_PAGE_LED); + hid_usage_min(&p, 1); + hid_usage_max(&p, 5); + hid_report_count(&p, 5); + hid_report_size(&p, 1); + hid_input(&p, HID_DATA | HID_VARIABLE | HID_ABSOLUTE); + + hid_report_count(&p, 1); + hid_report_size(&p, 3); + hid_input(&p, HID_CONSTANT); + + hid_usage_page(&p, HID_USAGE_PAGE_KEYBOARD); + hid_usage_min(&p, 0); + hid_usage_max(&p, 255);// ,2); + hid_logical_min(&p, 0); + hid_logical_max(&p, 255);//,2); + hid_report_count(&p, 6); + hid_report_size(&p, 8); + hid_input(&p, HID_DATA | HID_ARRAY | HID_ABSOLUTE); + hid_collection_end(&p); + + total_bits[reportID] = bits; + *bufferSize = p - buffer; + +} +void makeMouseDescriptor(uint8_t reportID, uint8_t *buffer, uint *bufferSize) { + extern uint16_t total_bits[]; + uint8_t *p = buffer+*bufferSize; + sp=0; + bits=0; + + hid_usage_page(&p, HID_USAGE_PAGE_DESKTOP); + hid_usage(&p, HID_USAGE_DESKTOP_MOUSE); + hid_collection(&p, HID_COLLECTION_APPLICATION); + + hid_report_id(&p, reportID); + + hid_usage(&p, HID_USAGE_DESKTOP_POINTER); + hid_collection(&p, HID_COLLECTION_PHYSICAL); + hid_usage_page(&p, HID_USAGE_PAGE_BUTTON); + hid_usage_min(&p, 1); + hid_usage_max(&p, 5); + hid_logical_min(&p, 0); + hid_logical_max(&p, 1); + hid_report_count(&p, 5); + hid_report_size(&p, 1); + hid_input(&p, HID_DATA | HID_VARIABLE | HID_ABSOLUTE); + + hid_report_count(&p, 1); + hid_report_size(&p, 3); + hid_input(&p, HID_CONSTANT); + + hid_usage_page(&p, HID_USAGE_PAGE_DESKTOP); + hid_usage(&p, HID_USAGE_DESKTOP_X); + hid_usage(&p, HID_USAGE_DESKTOP_Y); + hid_usage_min(&p, 129); + hid_usage_max(&p, 127); + hid_report_count(&p, 2); + hid_report_size(&p, 8); + hid_input(&p, HID_DATA | HID_VARIABLE | HID_RELATIVE); + + hid_usage(&p, HID_USAGE_DESKTOP_WHEEL); + hid_usage_min(&p, 129); + hid_usage_max(&p, 127); + hid_report_count(&p, 1); + hid_report_size(&p, 8); + hid_input(&p, HID_DATA | HID_VARIABLE | HID_RELATIVE); + + hid_usage_page(&p, HID_USAGE_PAGE_CONSUMER); + hid_usage(&p, HID_USAGE_CONSUMER_AC_PAN);//,2); + hid_usage_min(&p, 129); + hid_usage_max(&p, 127); + hid_report_count(&p, 1); + hid_report_size(&p, 8); + hid_input(&p, HID_DATA | HID_VARIABLE | HID_RELATIVE); + + hid_collection_end(&p); + hid_collection_end(&p); + + total_bits[reportID] = bits; + *bufferSize = p - buffer; + +} diff --git a/include/hid_minimal.h b/include/hid_minimal.h index 7a17fa2..cb15989 100644 --- a/include/hid_minimal.h +++ b/include/hid_minimal.h @@ -188,23 +188,6 @@ enum { HID_USAGE_DESKTOP_TABLET_PC_SYSTEM = 0x09, }; -//#define HID_USAGE_PAGE(x) 0x5, x -//#define HID_USAGE(x) 0x9, x -//#define HID_COLLECTION(x) 0xa1, x -//#define HID_COLLECTION_END 0xc0 -//#define HID_REPORT_COUNT(x) 0x95, x -//#define HID_REPORT_SIZE(x) 0x75, x -//#define HID_LOGICAL_MIN(x) 0x15, x -//#define HID_LOGICAL_MAX(x) 0x26, (x & 0xff), (x >> 8) -//#define HID_PHYSICAL_MIN(x) 0x35, x -//#define HID_PHYSICAL_MAX(x) 0x46, (x & 0xff), (x >> 8) -//#define HID_INPUT(x) 0x81, x -//#define HID_PUSH 0xa4 -//#define HID_POP 0xb4 -//#define HID_USAGE_MIN(x) 0x19, x -//#define HID_USAGE_MAX(x) 0x29, x -//#define HID_REPORT_ID(x) 0x85, x - #define HID_DATA (0<<0) #define HID_CONSTANT (1<<0) //1 #define HID_ARRAY (0<<1) diff --git a/include/webInterface.h b/include/webInterface.h index e991212..99a881d 100644 --- a/include/webInterface.h +++ b/include/webInterface.h @@ -588,7 +588,7 @@ void updateHid() { extern void readSystemINI(); if (server.hasArg("devicename")) { String a = server.arg("devicename"); - if (a.length()>0) { + if (a.length()> 0) { DeviceName = a; server.send(200); } else { @@ -691,7 +691,8 @@ void updateHid() { if (server.hasArg("restart")) { server.send(200); writeSystemINI(); - setupUSB(false); + setupDescripor(); + setUSB(false); } } @@ -791,7 +792,7 @@ void setupWifi() { // Get I2C slave list server.on("/listI2C", i2cResult); - server.on("/settings",[](){if(handleFileRead(F("/settings.html"))) {return;}}); + server.on("/settings",[](){if(handleFileRead(F("/settings/settings.html"))) {return;}}); server.on("/settings/updatehid",updateHid); @@ -816,6 +817,7 @@ void setupWifi() { server.on("/edit/index.html", [](){if (!is_authenticated()) {server.send(403);return;} handleGetEdit();}); server.on("/",[](){if(handleFileRead(F("/index.html"))) {return;}}); + server.on("/pinout",[](){if(handleFileRead(F("/pinout/pinout.html"))) {return;}}); // Default handler for all URIs not defined above // Use it to read files from filesystem diff --git a/lib/X52-HOTAS/src/new_x52_common.h b/lib/X52-HOTAS/src/new_x52_common.h new file mode 100644 index 0000000..45d9173 --- /dev/null +++ b/lib/X52-HOTAS/src/new_x52_common.h @@ -0,0 +1,149 @@ +#pragma once + +#ifndef X52_COMMON +#define X52_COMMON + +#include +#include + + +// X52_DEBUG turns serial logging on/off. +// Don't forget to turn it off in your finished "product". +#ifndef X52_DEBUG + #define X52_DEBUG 0 +#endif + +#if X52_DEBUG + #ifndef X52DebugPrint + #define X52DebugPrint(x) Serial.print(x) + #endif + #ifndef X52DebugPrintln + #define X52DebugPrintln(x) {X52DebugPrint(x); X52DebugPrint("\n");} + #endif +#else + #undef X52DebugPrint + #undef X52DebugPrintln + #define X52DebugPrint(x) + #define X52DebugPrintln(x) +#endif + +#ifndef X52_BUSY_WAIT + #define X52_BUSY_WAIT 1 +#endif + + +//namespace x52 { + + +// Direction flag. +// An instance of this type is a bitwise combination of zero or more enum members. +enum Direction { + NoDirection = 0, + Right = 1, + Down = 2, + Left = 4, + Up = 8, + DownLeft = Down | Left, + DownRight = Down | Right, + UpLeft = Up | Left, + UpRight = Up | Right, +}; + + +// The Mode enum defines the possible states of the rotary mode selector on the joystick. +enum Mode { + ModeUndefined, // It's possible to receive ModeUndefined from the joystick! + Mode1, + Mode2, + Mode3, +}; + + +typedef unsigned int uint; +enum Uninitialized { UNINITIALIZED }; + + +// BitField has little endian byte and bit order. +template +class BitField { +public: + BitField() { + memset(m_Bits, 0, sizeof(m_Bits)); + } + + BitField(Uninitialized) {} + + bool Bit(int index) const { + assert(uint(index) < NUM_BITS); + return bool(m_Bits[index>>3] & (1 << (index&7))); + } + + void SetBit(int index, bool bit) { + if (bit) + SetBit(index); + else + ClearBit(index); + } + + void SetBit(int index) { + assert(uint(index) < NUM_BITS); + m_Bits[index>>3] |= 1 << (index&7); + } + + void ClearBit(int index) { + assert(uint(index) < NUM_BITS); + m_Bits[index>>3] &= ~(1 << (index&7)); + } + + uint UInt(int index, int width) const { + assert(uint(index) + uint(width) <= NUM_BITS); + int v = 0; + for (int i=0; i= 0) + return false; + // On AVR the delay is also busy but it might be able + // to save some power on other architectures (ARM). + if (poll_period_micros) + delayMicroseconds(poll_period_micros); + } +} + + +//} // namespace x52 + +#endif diff --git a/lib/X52-HOTAS/src/new_x52_pro.cpp b/lib/X52-HOTAS/src/new_x52_pro.cpp new file mode 100644 index 0000000..3a1e578 --- /dev/null +++ b/lib/X52-HOTAS/src/new_x52_pro.cpp @@ -0,0 +1,621 @@ +#include "new_x52_pro.h" + + +inline void SetUInt(uint32_t b, uint8_t index, uint8_t size, uint8_t value) { + for (uint8_t i = 0; i> i) & 1); +} + +inline void SetBit(uint32_t b, uint8_t index, bool value) { + bitWrite(b, index, value); +} + + +inline void JoystickConfig::SetFromBinary(const Binary& b) { + led_brightness = uint8_t(b.UInt(0, 5)); + pov_1_led_blinking = b.Bit(5); + button_a_led = LEDColor(b.UInt(6, 2)); + pov_2_led = LEDColor(b.UInt(8, 2)); + button_fire_led = !b.Bit(10); + button_b_led = LEDColor(b.UInt(11, 2)); + button_t1_t2_led = LEDColor(b.UInt(13, 2)); + button_t3_t4_led = LEDColor(b.UInt(15, 2)); + button_t5_t6_led = LEDColor(b.UInt(17, 2)); +} + + +inline void JoystickConfig::ToBinary(Binary& b) const { + b.SetUInt(0, 5, led_brightness); + b.SetBit(5, pov_1_led_blinking); + b.SetUInt(6, 2, button_a_led); + b.SetUInt(8, 2, pov_2_led); + b.SetBit(10, !button_fire_led); + b.SetUInt(11, 2, button_b_led); + b.SetUInt(13, 2, button_t1_t2_led); + b.SetUInt(15, 2, button_t3_t4_led); + b.SetUInt(17, 2, button_t5_t6_led); +} + + +inline void JoystickState::SetFromBinary(const Binary& b) { + x = uint16_t(b.UInt(0, 8) | (b.UInt(16, 2) << 8)); + y = uint16_t(b.UInt(8, 8) | (b.UInt(18, 2) << 8)); + z = uint16_t(b.UInt(24, 8) | (b.UInt(22, 2) << 8)); + + switch (b.UInt(32, 4)) { + case 1: pov_1 = Down; break; + case 2: pov_1 = DownRight; break; + case 3: pov_1 = Right; break; + case 4: pov_1 = UpRight; break; + case 5: pov_1 = Up; break; + case 6: pov_1 = UpLeft; break; + case 7: pov_1 = Left; break; + case 8: pov_1 = DownLeft; break; + default: pov_1 = NoDirection; break; + } + + pov_2 = Direction( + (-b.Bit(36) & Up) | + (-b.Bit(37) & Right) | + (-b.Bit(38) & Down) | + (-b.Bit(39) & Left) + ); + + switch (b.UInt(45, 3)) { + case 1: mode = Mode1; break; + case 2: mode = Mode2; break; + case 4: mode = Mode3; break; + default: mode = ModeUndefined; break; + } + + trigger_stage_1 = b.Bit(40); + button_fire = b.Bit(41); + button_a = b.Bit(42); + button_c = b.Bit(43); + trigger_stage_2 = b.Bit(44); + button_b = b.Bit(48); + pinkie_switch = b.Bit(49); + button_t1 = b.Bit(50); + button_t2 = b.Bit(51); + button_t3 = b.Bit(52); + button_t4 = b.Bit(53); + button_t5 = b.Bit(54); + button_t6 = b.Bit(55); +} +inline void JoystickState::SetFromBinary2(const uint64_t b) { + + SetUInt((uint32_t)x,0,8,b); + SetUInt((uint32_t)x,16,2,(b>>15)&3); + + SetUInt((uint32_t)y,8,8,b); + SetUInt((uint32_t)y,18,2,(b>>17)&3); + + SetUInt((uint32_t)z,24,8,b); + SetUInt((uint32_t)z,22,2,(b>>21)&3); + + //x = uint16_t(b.UInt(0, 8) | (b.UInt(16, 2) << 8)); + //y = uint16_t(b.UInt(8, 8) | (b.UInt(18, 2) << 8)); + //z = uint16_t(b.UInt(24, 8) | (b.UInt(22, 2) << 8)); + + uint8_t pov = 0; + SetUInt((uint32_t)pov,32,4,(b>>31)&0xf); + + switch (pov) { + case 1: pov_1 = Down; break; + case 2: pov_1 = DownRight; break; + case 3: pov_1 = Right; break; + case 4: pov_1 = UpRight; break; + case 5: pov_1 = Up; break; + case 6: pov_1 = UpLeft; break; + case 7: pov_1 = Left; break; + case 8: pov_1 = DownLeft; break; + default: pov_1 = NoDirection; break; + } + + pov = 0; + SetUInt((uint32_t)pov,36,4,(b>>35)&0xf); + pov_2 = Direction( + (bitRead(pov,0) & Up) | + (bitRead(pov,1) & Right) | + (bitRead(pov,2) & Down) | + (bitRead(pov,3) & Left) + ); + + //pov_2 = Direction( + // (-b.Bit(36) & Up) | + // (-b.Bit(37) & Right) | + // (-b.Bit(38) & Down) | + // (-b.Bit(39) & Left) + //); + uint8_t m = 0; + SetUInt((uint32_t)m,45,3,(b>>44)&3); + + + switch (m) { + case 1: mode = Mode1; break; + case 2: mode = Mode2; break; + case 4: mode = Mode3; break; + default: mode = ModeUndefined; break; + } + trigger_stage_1 = bitRead(b,40); + button_fire = bitRead(b,41); + button_a = bitRead(b,42); + button_c = bitRead(b,43); + trigger_stage_2 = bitRead(b,44); + button_b = bitRead(b,48); + pinkie_switch = bitRead(b,49); + button_t1 = bitRead(b,50); + button_t2 = bitRead(b,51); + button_t3 = bitRead(b,52); + button_t4 = bitRead(b,53); + button_t5 = bitRead(b,54); + button_t6 = bitRead(b,55); + + //trigger_stage_1 = b.Bit(40); + //button_fire = b.Bit(41); + //button_a = b.Bit(42); + //button_c = b.Bit(43); + //trigger_stage_2 = b.Bit(44); + //button_b = b.Bit(48); + //pinkie_switch = b.Bit(49); + //button_t1 = b.Bit(50); + //button_t2 = b.Bit(51); + //button_t3 = b.Bit(52); + //button_t4 = b.Bit(53); + //button_t5 = b.Bit(54); + //button_t6 = b.Bit(55); +} + + +inline void JoystickState::ToBinary(Binary& b) const { +#if X52_DEBUG + if (x > MAX_X) { + X52DebugPrint("x52::pro::JoystickState.x exceeds the maximum value. x="); + X52DebugPrintln(x); + } + if (y > MAX_Y) { + X52DebugPrint("x52::pro::JoystickState.y exceeds the maximum value. y="); + X52DebugPrintln(y); + } + if (z > MAX_Z) { + X52DebugPrint("x52::pro::JoystickState.z exceeds the maximum value. z="); + X52DebugPrintln(z); + } +#endif + + b.SetUInt(0, 8, x); + b.SetUInt(8, 8, y); + b.SetUInt(24, 8, z); + + b.SetUInt(16, 2, x >> 8); + b.SetUInt(18, 2, y >> 8); + b.SetUInt(20, 2, 0); + b.SetUInt(22, 2, z >> 8); + + uint8_t pov; + switch (pov_1) { + case Down: pov = 1; break; + case DownRight: pov = 2; break; + case Right: pov = 3; break; + case UpRight: pov = 4; break; + case Up: pov = 5; break; + case UpLeft: pov = 6; break; + case Left: pov = 7; break; + case DownLeft: pov = 8; break; + default: pov = 0; break; + } + b.SetUInt(32, 4, pov); + + b.SetBit(36, bool(pov_2 & Up)); + b.SetBit(37, bool(pov_2 & Right)); + b.SetBit(38, bool(pov_2 & Down)); + b.SetBit(39, bool(pov_2 & Left)); + + b.SetBit(40, trigger_stage_1); + b.SetBit(41, button_fire); + b.SetBit(42, button_a); + b.SetBit(43, button_c); + b.SetBit(44, trigger_stage_2); + b.SetBit(45, mode == Mode1); + b.SetBit(46, mode == Mode2); + b.SetBit(47, mode == Mode3); + b.SetBit(48, button_b); + b.SetBit(49, pinkie_switch); + b.SetBit(50, button_t1); + b.SetBit(51, button_t2); + b.SetBit(52, button_t3); + b.SetBit(53, button_t4); + b.SetBit(54, button_t5); + b.SetBit(55, button_t6); +} + +// FakeProThrottle makes it possible to use some of your Arduino pins as a +// connection to the PS/2 socket of an X52 Pro Joystick. +// +// These pin names (C01..C04) were printed on the PCB of my X52 joystick. +// The pinout of the PS/2 connector is the same as that of the X52 non-Pro +// but the protocol is different. +// +// Standard PS/2 (6-pin mini-DIN) female socket pin numbering: +// https://en.wikipedia.org/wiki/Mini-DIN_connector#/media/File:MiniDIN-6_Connector_Pinout.svg +// +// PIN_C01: data output of the throttle (pin #4 of the PS/2 female socket) +// PIN_C02: clock output of the throttle (pin #6 of the PS/2 female socket) +// PIN_C03: data output of the joystick (pin #2 of the PS/2 female socket) +// PIN_C04: clock output of the joystick (pin #1 of the PS/2 female socket) +// +// Pin #3 of the PS/2 female socket is GND. +// Pin #5 of the PS/2 female socket is VCC. +// +// My X52 Pro throttle uses 4.1-4.2V for both power and GPIO but the joystick +// works with 3.3V too. + +FakeProThrottle::FakeProThrottle(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04) { + #if defined(ARDUINO_ARCH_RP2040) + // Find a free SM on one of the PIO's + sm = pio_claim_unused_sm(pio, false); // don't panic + // Try pio1 if SM not found + if (sm < 0) { + pio = pio1; + sm = pio_claim_unused_sm(pio, true); // panic if no SM is free + } + #endif + this->pio = pio; + this->PIN_C01 = PIN_C01; + this->PIN_C02 = PIN_C02; + this->PIN_C03 = PIN_C03; + this->PIN_C04 = PIN_C04; + this->PIN_C01 = PIN_C01; +} + +FakeProThrottle::~FakeProThrottle() {} + +//template +//lass FakeProThrottle { + + +//public: + // Call Setup from the setup function of your Arduino project to initialize + // a FakeProThrottle instance. +void FakeProThrottle::setup() { + pinMode(PIN_C01, OUTPUT); + pinMode(PIN_C02, OUTPUT); + pinMode(PIN_C03, INPUT); + pinMode(PIN_C04, INPUT); + + uint offset = pio_add_program(pio, &throttle_program); + + throttle_program_init(pio, sm, offset, PIN_C01, PIN_C02, PIN_C03, PIN_C04); + + // On the teensy the digitalWrite seems to work only after pinMode. +#if X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION + digitalWrite(PIN_C01, HIGH); +#endif +}; + + +inline bool pio_sm_put_blocking_timeout(PIO pio, uint sm, uint32_t data) { + check_pio_param(pio); + check_sm_param(sm); + uint32_t currentTime = millis(); + while (pio_sm_is_tx_fifo_full(pio, sm)) { + if (millis() - currentTime > 100) + return 0; + } + pio_sm_put(pio, sm, data); + return 1; +} +inline uint32_t pio_sm_get_blocking_timeout(PIO pio, uint sm) { + check_pio_param(pio); + check_sm_param(sm); + uint32_t currentTime = millis(); + while (pio_sm_is_rx_fifo_empty(pio, sm)) { + if (millis() - currentTime > 100) + return 0; + } + return pio_sm_get(pio, sm); +} + +void pio_sm_print_registers(PIO pio, uint sm) { + Serial.printf("ctrl: 0x%x\n\r",pio->ctrl); + Serial.printf("fstat: 0x%x\n\r",pio->fstat); + Serial.printf("fdebug: 0x%x\n\r",pio->fdebug); + Serial.printf("flevel: 0x%x\n\r",pio->flevel); + Serial.printf("DBG_PADOUT: 0x%x\n\r",pio->dbg_padout); + Serial.printf("DBG_PADOE: 0x%x\n\r",pio->dbg_padoe); + Serial.print("\n\r"); + Serial.printf("sm: %d\n\r",sm); + Serial.printf("clkdiv: 0x%x\n\r",pio->sm[sm].clkdiv); + Serial.printf(" INT: %d\n\r",(pio->sm[sm].clkdiv>>16) & 0xffff); + Serial.printf(" FRAC: %d\n\r",(pio->sm[sm].clkdiv>>8) & 0xff); + Serial.print("\n\r"); + Serial.printf("execctrl: 0x%x\n\r",pio->sm[sm].execctrl); + Serial.printf(" EXEC_STALLED: %d\n\r",(pio->sm[sm].execctrl>>31) & 0x1); + Serial.printf(" SIDE_EN: %d\n\r",(pio->sm[sm].execctrl>>30) & 0x1); + Serial.printf(" SIDE_PINDIR: %d\n\r",(pio->sm[sm].execctrl>>29) & 0x1); + Serial.printf(" JMP_PIN: %d\n\r",(pio->sm[sm].execctrl>>24) & 0xf); + Serial.printf(" OUT_EN_SEL: %d\n\r",(pio->sm[sm].execctrl>>19) & 0xf); + Serial.printf(" INLINE_OUT_EN: %d\n\r",(pio->sm[sm].execctrl>>18) & 0x1); + Serial.printf(" OUT_STICKY: %d\n\r",(pio->sm[sm].execctrl>>17) & 0x1); + Serial.printf(" WRAP_TOP: %d\n\r",(pio->sm[sm].execctrl>>12) & 0xf); + Serial.printf(" WRAP_BOTTOM: %d\n\r",(pio->sm[sm].execctrl>>7) & 0xf); + Serial.printf(" RESERVED: %d\n\r",(pio->sm[sm].execctrl>>5) & 0x3); + Serial.printf(" STATUS_SEL: %d\n\r",(pio->sm[sm].execctrl>>4) & 0x1); + Serial.printf(" STATUS_N: %d\n\r",(pio->sm[sm].execctrl>>0) & 0xf); + Serial.print("\n\r"); + Serial.printf("shiftctrl: 0x%x\n\r",pio->sm[sm].shiftctrl); + Serial.printf(" FJOIN_RX: %d\n\r",(pio->sm[sm].shiftctrl>>31) & 0x1); + Serial.printf(" FJOIN_TX: %d\n\r",(pio->sm[sm].shiftctrl>>30) & 0x1); + + Serial.print(" PULL_THRESH: ");Serial.println((pio->sm[sm].shiftctrl>>25) & 0xf,BIN); + //Serial.printf(" PULL_THRESH: %d\n\r",(pio->sm[sm].shiftctrl>>25) & 0xf,BIN); + Serial.printf(" PUSH_THRESH: ");Serial.println((pio->sm[sm].shiftctrl>>20) & 0xf,BIN); + //Serial.printf(" PUSH_THRESH: %d\n\r",(pio->sm[sm].shiftctrl>>20) & 0xf,BIN); + + Serial.printf(" OUT_SHIFTDIR: %d\n\r",(pio->sm[sm].shiftctrl>>19) & 0x1); + Serial.printf(" IN_SHIFTDIR: %d\n\r",(pio->sm[sm].shiftctrl>>18) & 0x1); + Serial.printf(" AUTOPULL: %d\n\r",(pio->sm[sm].shiftctrl>>17) & 0x1); + Serial.printf(" AUTOPUSH: %d\n\r",(pio->sm[sm].shiftctrl>>16) & 0x1); + Serial.printf(" RESERVED: %d\n\r",(pio->sm[sm].shiftctrl>>0) & 0xffff); + Serial.print("\n\r"); + Serial.printf("addr: 0x%x\n\r",pio->sm[sm].addr); + Serial.printf(" RESERVED: %d\n\r",(pio->sm[sm].addr>>5) ); + Serial.printf(" INSTR_ADDR: %d\n\r",(pio->sm[sm].addr>>0) & 0xf); + Serial.print("\n\r"); + Serial.printf("instr: 0x%x\n\r",pio->sm[sm].instr); + Serial.printf(" RESERVED: %d\n\r",(pio->sm[sm].instr>>16) ); + Serial.printf(" INSTR: %d\n\r",(pio->sm[sm].instr>>0) & 0xffff); + Serial.print("\n\r"); + Serial.printf("pinctrl: 0x%x\n\r",pio->sm[sm].pinctrl); + Serial.printf(" SIDESET_COUNT: %d\n\r",(pio->sm[sm].pinctrl>>29) & 0x3); + Serial.printf(" SET_COUNT: %d\n\r",(pio->sm[sm].pinctrl>>26) & 0x3); + Serial.printf(" OUT_COUNT: %d\n\r",(pio->sm[sm].pinctrl>>20) & 0x3f); + Serial.printf(" IN_BASE: %d\n\r",(pio->sm[sm].pinctrl>>15) & 0x1f); + Serial.printf(" SIDESET_BASE: %d\n\r",(pio->sm[sm].pinctrl>>10) & 0x1f); + Serial.printf(" SET_BASE: %d\n\r",(pio->sm[sm].pinctrl>>5) & 0x1f); + Serial.printf(" OUT_BASE: %d\n\r",(pio->sm[sm].pinctrl>>0) & 0x1f); + Serial.print("\n\r"); +} + // PollJoystickState polls the joystick for its state. It creates a frame + // transmission request, waits for the joystick to respond and handles the + // bidirectional data transfer: sends the JoystickConfig and receives the + // JoystickState. Returns zero on success. + // + // A nonzero return value means error and gives the recommended number + // of microseconds to wait before calling PollJoystickState again. + // In that situation the value of the JoystickState is undefined. +unsigned long FakeProThrottle::PollJoystickState(JoystickState& state, const JoystickConfig& cfg, unsigned long wait_micros) { + // PIN_C02 has to be LOW when this function returns. + // + // If X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION==1 + // then PIN_C01 has to be HIGH when this function returns. + + JoystickState::Binary recv_buf; + JoystickConfig::Binary send_buf; + //cfg.ToBinary(send_buf); + + uint32_t sendbuf=0; + SetUInt(sendbuf, 0, 5, cfg.led_brightness); + SetBit(sendbuf, 5, cfg.pov_1_led_blinking); + SetUInt(sendbuf, 6, 2, cfg.button_a_led); + SetUInt(sendbuf, 8, 2, cfg.pov_2_led); + SetBit(sendbuf, 10, !cfg.button_fire_led); + SetUInt(sendbuf, 11, 2, cfg.button_b_led); + SetUInt(sendbuf, 13, 2, cfg.button_t1_t2_led); + SetUInt(sendbuf, 15, 2, cfg.button_t3_t4_led); + SetUInt(sendbuf, 17, 2, cfg.button_t5_t6_led); + + if (!pio_sm_put_blocking_timeout(pio, sm, sendbuf)){ + Serial.println("timeout"); + pio_sm_print_registers(pio, sm); + } + uint64_t a = pio_sm_get_blocking_timeout(pio, sm); + a = (a<<32) | pio_sm_get_blocking_timeout(pio, sm); + if (a>0) + Serial.printf("0x%x\n\r", a); + + + +// // deadline for the whole frame transmission +// unsigned long deadline = micros() + wait_micros; + +// // A frame consists of 76 clock pulses on both C02 and C04. + +// for (int i=0; i<76; i++) { +// #if !X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION +// // This is what the original throttle does but we have a potentially better solution. +// if (i == 1) +// digitalWrite(PIN_C01, HIGH); +// else +// #endif +// if (i >= 57) +// digitalWrite(PIN_C01, send_buf.Bit(i-57)); + +// digitalWrite(PIN_C02, HIGH); + +// // The original joystick samples C01 here between the +// // rising edge of C02 and the rising edge of C04. + +// if (!wait_for_pin_state(PIN_C04, HIGH, deadline)) { +// X52DebugPrint("Error waiting for C04=1. Clock cycle: "); +// X52DebugPrintln(i); +// #if X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION +// if (i >= 57) +// digitalWrite(PIN_C01, HIGH); +// #endif +// digitalWrite(PIN_C02, LOW); +// // Timing out with i==0 means that the joystick didn't respond to our +// // initial C02=1 request within the available time frame defined by `wait_micros`. +// if (i == 0) +// return 1; +// // We are in the middle of a frame (already started talking with the joystick). +// // This means that the joystick will also time out and the throttle should +// // should try to initiate a new frame only after the joystick's timeout. +// return X52_PRO_THROTTLE_UNRESPONSIVE_MICROS; +// } + +// if (i == 0) +// // The original throttle times out if the whole frame isn't +// // transmitted within X52_PRO_THROTTLE_TIMEOUT_MICROS +// // measured from the first falling edge of C02. +// // The previous deadline value was set up using `wait_micros`, +// // that timeout applies only to the first rising edge of C04. +// deadline = micros() + X52_PRO_THROTTLE_TIMEOUT_MICROS; +// else if (i == 56) +// digitalWrite(PIN_C01, LOW); // desync detection: the joystick becomes unresponsive if this isn't LOW +// #if X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION +// // The original throttle doesn't do this but perhaps it should +// // because this makes desync detection more reliable. +// // The last iteration of this for loop ends with C01=HIGH +// // and C01 stays that way until the i==56 of the next frame. +// else if (i >= 57) +// digitalWrite(PIN_C01, HIGH); +// #endif + +// digitalWrite(PIN_C02, LOW); + +// if (!wait_for_pin_state(PIN_C04, LOW, deadline)) { +// X52DebugPrint("Error waiting for C04=0. Clock cycle: "); +// X52DebugPrintln(i); +// return X52_PRO_THROTTLE_UNRESPONSIVE_MICROS; +// } + +// // The original throttle samples C03 here between the +// // falling edge of C04 and the rising edge of C02. +// if (i < JoystickState::NUM_BITS) +// recv_buf.SetBit(i, bool(digitalRead(PIN_C03))); +// } + + state.SetFromBinary2(a); + //state.SetFromBinary(recv_buf); + return 0; +} + +//void FakeProThrottle::PrepareForPoll() { +// digitalWrite(PIN_C02, HIGH); +//} + + +// FakeProJoystick makes it possible to use some of your Arduino pins as a +// connection to the PS/2 socket of an X52 Pro Throttle. +// +// The pin config is the same as that of the FakeThrottle. + +FakeProJoystick::FakeProJoystick(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04) { + this->pio = pio; + this->PIN_C01 = PIN_C01; + this->PIN_C02 = PIN_C02; + this->PIN_C03 = PIN_C03; + this->PIN_C04 = PIN_C04; + this->PIN_C01 = PIN_C01; + #if defined(ARDUINO_ARCH_RP2040) + // Find a free SM on one of the PIO's + sm = pio_claim_unused_sm(pio, false); // don't panic + // Try pio1 if SM not found + if (sm < 0) { + pio = pio1; + sm = pio_claim_unused_sm(pio, true); // panic if no SM is free + } + init = true; + #endif +} +FakeProJoystick::~FakeProJoystick() {} + +//template +//class FakeProJoystick { +//public: + // Call Setup from the setup function of your Arduino project to initialize + // a FakeProJoystick instance. +void FakeProJoystick::setup() { +// uint offset = pio_add_program(pio, &Fake_Throttle_program); + +// Fake_Throttle_program_init(pio, sm, offset, PIN_C01, PIN_C02, PIN_C03, PIN_C04); +} + +// SendJoystickState sends the JoystickState to the throttle and receives +// the JoystickConfig. On the other side there must be a throttle polling/waiting +// for the JoystickState. Returns zero on success. +// +// A nonzero return value means error and gives the number of microseconds +// to wait before calling SendJoystickState again. In that situation the +// value of the JoystickConfig is undefined. +unsigned long FakeProJoystick::SendJoystickState(const JoystickState& state, JoystickConfig& cfg, unsigned long wait_micros) { + // waiting for the throttle's poll + if (!wait_for_pin_state(PIN_C02, HIGH, micros()+wait_micros)) + return 1; + + JoystickConfig::Binary recv_buf; + JoystickState::Binary send_buf; + state.ToBinary(send_buf); + + auto deadline = micros() + X52_PRO_JOYSTICK_TIMEOUT_MICROS; + + // A frame consists of 76 clock pulses on both C02 and C04. + for (int i=0; i<76; i++) { + if (i < JoystickState::NUM_BITS) + digitalWrite(PIN_C03, send_buf.Bit(i)); + else if (i >= 57) + // The original joystick samples C01 here between the + // rising edge of C02 and the rising edge of C04. + recv_buf.SetBit(i-57, bool(digitalRead(PIN_C01))); + + digitalWrite(PIN_C04, HIGH); + + if (!wait_for_pin_state(PIN_C02, LOW, deadline)) { + X52DebugPrint("Error waiting for C02=0. Clock cycle: "); + X52DebugPrintln(i); + digitalWrite(PIN_C04, LOW); + return X52_PRO_JOYSTICK_UNRESPONSIVE_MICROS; + } + +#if X52_PRO_IMPROVED_THROTTLE_CLIENT_DESYNC_DETECTION + if (i >= 1 && i <= 55) { + // This is something that the original joystick doesn't do. + // This method leads to very quick and reliable desync detection. + // It's based on the assumption that the X52 Pro always sends + // ones over C01 while the joystick is sending its state over C03. + if (!digitalRead(PIN_C01)) { + X52DebugPrint("Desync detected: bits 1..55 aren't all ones. Timing out to force a resync. Clock cycle: "); + X52DebugPrintln(i); + digitalWrite(PIN_C04, LOW); + return X52_PRO_JOYSTICK_DESYNC_UNRESPONSIVE_MICROS; + } + } else +#endif + if (i == 56) { + // This is something that the original joystick also does. + if (digitalRead(PIN_C01)) { + X52DebugPrintln("Desync detected: bit 56 isn't zero. Timing out to force a resync."); + digitalWrite(PIN_C04, LOW); + return X52_PRO_JOYSTICK_DESYNC_UNRESPONSIVE_MICROS; + } + } + + digitalWrite(PIN_C04, LOW); + + // The original throttle samples C03 here between the + // falling edge of C04 and the rising edge of C02. + + if (!wait_for_pin_state(PIN_C02, HIGH, deadline)) { + X52DebugPrint("Error waiting for C02=1. Clock cycle: "); + X52DebugPrintln(i); + return X52_PRO_JOYSTICK_UNRESPONSIVE_MICROS; + } + } + + cfg.SetFromBinary(recv_buf); + return 0; +} + +// IsPollInProgress returns true if the throttle is waiting for the +// JoystickState on the other side of the connection. In that situation +// a call to SendJoystickState is less likely to block in a waiting state +// (or fail as a result of wait timeout). +bool FakeProJoystick::IsPollInProgress() { + return bool(digitalRead(PIN_C02)); +} + + + +//} // namespace pro +//} // namespace x52 diff --git a/lib/X52-HOTAS/src/new_x52_pro.h b/lib/X52-HOTAS/src/new_x52_pro.h new file mode 100644 index 0000000..4ba4391 --- /dev/null +++ b/lib/X52-HOTAS/src/new_x52_pro.h @@ -0,0 +1,184 @@ +#ifndef X52_PRO +#define X52_PRO + +// This value is hardcoded into the firmware of my original X52 Pro throttle. +#ifndef X52_PRO_THROTTLE_TIMEOUT_MICROS + #define X52_PRO_THROTTLE_TIMEOUT_MICROS 17000 +#endif + +// This value is hardcoded into the firmware of my original X52 Pro joystick. +#ifndef X52_PRO_JOYSTICK_TIMEOUT_MICROS + #define X52_PRO_JOYSTICK_TIMEOUT_MICROS 23000 +#endif + +#ifndef X52_PRO_THROTTLE_UNRESPONSIVE_MICROS + #define X52_PRO_THROTTLE_UNRESPONSIVE_MICROS (X52_PRO_JOYSTICK_TIMEOUT_MICROS + 5000) +#endif + +// This value is hardcoded into the firmware of my original X52 Pro joystick. +#ifndef X52_PRO_JOYSTICK_UNRESPONSIVE_MICROS + #define X52_PRO_JOYSTICK_UNRESPONSIVE_MICROS 2000 +#endif + +// This value is hardcoded into the firmware of my original X52 Pro joystick. +// It has to be greater than X52_PRO_THROTTLE_TIMEOUT_MICROS. +#ifndef X52_PRO_JOYSTICK_DESYNC_UNRESPONSIVE_MICROS + #define X52_PRO_JOYSTICK_DESYNC_UNRESPONSIVE_MICROS 23000 +#endif + +// Enabling this feature allows the FakeProJoystick to behave differently from +// the original joystick in order to make the desync detection more reliable. +#ifndef X52_PRO_IMPROVED_THROTTLE_CLIENT_DESYNC_DETECTION + #define X52_PRO_IMPROVED_THROTTLE_CLIENT_DESYNC_DETECTION 0 +#endif + +// Enabling this feature allows the FakeProThrottle to behave differently from +// the original throttle in order to make the desync detection more reliable. +#ifndef X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION + #define X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION 0 +#endif + +#ifndef X52_PRO_DEFAULT_POLL_JOYSTICK_STATE_WAIT_MICROS + #define X52_PRO_DEFAULT_POLL_JOYSTICK_STATE_WAIT_MICROS 25000 +#endif + +#ifndef X52_PRO_DEFAULT_SEND_JOYSTICK_STATE_WAIT_MICROS + #define X52_PRO_DEFAULT_SEND_JOYSTICK_STATE_WAIT_MICROS 25000 +#endif + +#include "new_x52_common.h" +#include "throttle-protocol.pio.h" + + + +enum LEDColor { + Amber = 0, // 00 + Green = 1, // 10 + Red = 2, // 01 + Off = 3, // 11 +}; + + +// JoystickState is the data sent by the joystick through the PS/2 cable. +struct JoystickState { + static constexpr uint16_t MAX_X = 1023; + static constexpr uint16_t MAX_Y = 1023; + static constexpr uint16_t MAX_Z = 1023; + + static constexpr uint16_t CENTER_X = 512; + static constexpr uint16_t CENTER_Y = 512; + static constexpr uint16_t CENTER_Z = 512; + + uint16_t x; // valid_range: 0..1023(MAX_X) center: 512(CENTER_X) + uint16_t y; // valid_range: 0..1023(MAX_Y) center: 512(CENTER_Y) + uint16_t z; // valid_range: 0..1023(MAX_Z) center: 512(CENTER_Z) + + Direction pov_1; + Direction pov_2; + Mode mode; + + bool trigger_stage_1: 1; + bool trigger_stage_2: 1; + bool pinkie_switch: 1; + bool button_fire: 1; + bool button_a: 1; + bool button_b: 1; + bool button_c: 1; + bool button_t1: 1; + bool button_t2: 1; + bool button_t3: 1; + bool button_t4: 1; + bool button_t5: 1; + bool button_t6: 1; + + JoystickState() { + memset(this, 0, sizeof(*this)); + } + + // Constructor for the pros who believe they know what they are doing. + JoystickState(Uninitialized) {} + + static constexpr int NUM_BITS = 56; + typedef BitField Binary; + + void SetFromBinary(const Binary&); + void SetFromBinary2(const uint64_t); + void ToBinary(Binary&) const; +}; + + +// JoystickConfig is the data sent by the throttle through the PS/2 cable. +struct JoystickConfig { + static constexpr uint8_t MAX_LED_BRIGHTNESS = 31; + + uint8_t led_brightness; // valid range: 0..31 (MAX_LED_BRIGHTNESS) + bool pov_1_led_blinking; // blinks about 4 times per second + bool button_fire_led; + + LEDColor pov_2_led: 2; + LEDColor button_a_led: 2; + LEDColor button_b_led: 2; + LEDColor button_t1_t2_led: 2; + LEDColor button_t3_t4_led: 2; + LEDColor button_t5_t6_led: 2; + + JoystickConfig() { + led_brightness = MAX_LED_BRIGHTNESS; + pov_1_led_blinking = false; + button_fire_led = true; + pov_2_led = Green; + button_a_led = Green; + button_b_led = Green; + button_t1_t2_led = Green; + button_t3_t4_led = Green; + button_t5_t6_led = Green; + } + + // Constructor for the pros who believe they know what they are doing. + JoystickConfig(Uninitialized) {} + + static constexpr int NUM_BITS = 19; + typedef BitField Binary; + + void SetFromBinary(const Binary&); + void ToBinary(Binary&) const; +}; + +class FakeProThrottle { +private: + int sm; + PIO pio; + int PIN_C01; + int PIN_C02; + int PIN_C03; + int PIN_C04; + bool init = false; + + /* data */ +public: + FakeProThrottle(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04 /* args */); + ~FakeProThrottle(); + void setup(); + unsigned long PollJoystickState(JoystickState& state, const JoystickConfig& cfg, unsigned long wait_micros=X52_PRO_DEFAULT_POLL_JOYSTICK_STATE_WAIT_MICROS); + void PrepareForPoll(); + +}; + +class FakeProJoystick { +private: + int sm; + PIO pio; + int PIN_C01; + int PIN_C02; + int PIN_C03; + int PIN_C04; + bool init = false; +public: + FakeProJoystick(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04 /* args */); + ~FakeProJoystick(); + void setup(); + unsigned long SendJoystickState(const JoystickState& state, JoystickConfig& cfg, unsigned long wait_micros=X52_PRO_DEFAULT_SEND_JOYSTICK_STATE_WAIT_MICROS); + bool IsPollInProgress(); +}; + +#endif diff --git a/lib/X52-HOTAS/src/new_x52_std.cpp b/lib/X52-HOTAS/src/new_x52_std.cpp new file mode 100644 index 0000000..c4c0968 --- /dev/null +++ b/lib/X52-HOTAS/src/new_x52_std.cpp @@ -0,0 +1,289 @@ +#include "new_x52_std.h" + +FakeStdThrottle::FakeStdThrottle(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04) { + pio = this->pio; + PIN_C01 = this->PIN_C01; + PIN_C02 = this->PIN_C02; + PIN_C03 = this->PIN_C03; + PIN_C04 = this->PIN_C04; + PIN_C01 = this->PIN_C01; + #if defined(ARDUINO_ARCH_RP2040) + // Find a free SM on one of the PIO's + sm = pio_claim_unused_sm(pio, false); // don't panic + // Try pio1 if SM not found + if (sm < 0) { + pio = pio1; + sm = pio_claim_unused_sm(pio, true); // panic if no SM is free + } + init = true; + #endif +} + +FakeStdThrottle::~FakeStdThrottle() {} +//template > +//class FakeStdThrottle { +//public: + // Call Setup from the setup function of your Arduino project to initialize + // a FakeStdThrottle instance. + void FakeStdThrottle::setup() { + pinMode(PIN_C01, OUTPUT); + // The value of C01 is allowed to be anything between frames (undefined). + // digitalWrite(PIN_C01, LOW); + pinMode(PIN_C02, OUTPUT); + // On the teensy the digitalWrite seems to work only after pinMode. + digitalWrite(PIN_C02, LOW); + pinMode(PIN_C03, INPUT); + pinMode(PIN_C04, INPUT); + //m_PulseWaiter.Setup(); + } + +// PollJoystickState polls the joystick for its state. It creates a frame +// transmission request, waits for the joystick to respond and handles the +// bidirectional data transfer: sends the JoystickConfig and receives the +// JoystickState. Returns zero on success. +// +// A nonzero return value means error and gives the recommended number +// of microseconds to wait before calling PollJoystickState again. +// In that situation the value of the JoystickState is undefined. +// +// My X52 joystick is willing to send its state at most ~50 times per second. +unsigned long FakeStdThrottle::PollJoystickState(JoystickState& state, const JoystickConfig& cfg, unsigned long wait_micros) { + // Note: PIN_C02 has to be LOW when this function returns. + + { + // deadline for the initial response + unsigned long wait_deadline = micros()+wait_micros; + + if (!wait_for_pin_state(PIN_C04, LOW, wait_deadline)) + return 1; + + // The original joystick's C04 pulse seems to be at least 15us long. + + //auto trigger = [](){ + // digitalWrite(PIN_C02, HIGH); + //}; + //auto wait_res = m_PulseWaiter.WaitForPulse(wait_deadline, trigger); + //if (wait_res != PulseFinished) { + // X52DebugPrintln("Timed out while waiting for the C04 pulse before receiving the joystick state."); + // digitalWrite(PIN_C02, LOW); + // return (wait_res == PulseNotStarted) ? 1 : X52_THROTTLE_UNRESPONSIVE_MICROS; + //} + } + + // deadline for the whole frame transmission + unsigned long deadline = micros() + X52_THROTTLE_TIMEOUT_MICROS; + + JoystickState::Binary recv_buf; + + for (int i=0; i<(JoystickState::NUM_BITS-1); i++) { + + // I don't have an X52 throttle to test this but the throttle must + // be sampling C03 between falling-C04 and falling-C02. + // The other sensible option (between rising-C04 and rising-C02) + // wouldn't work because the joystick often removes the data bit + // from C03 before the rising-C02 edge. + recv_buf.SetBit(i, bool(digitalRead(PIN_C03))); + + digitalWrite(PIN_C02, LOW); + + if (!wait_for_pin_state(PIN_C04, HIGH, deadline)) { + X52DebugPrint("Error waiting for C04=1 while receiving the joystick state. Clock cycle: "); + X52DebugPrintln(i); + digitalWrite(PIN_C02, LOW); + return X52_THROTTLE_UNRESPONSIVE_MICROS; + } + + digitalWrite(PIN_C02, HIGH); + + if (!wait_for_pin_state(PIN_C04, LOW, deadline)) { + X52DebugPrint("Error waiting for C04=0 while receiving the joystick state. Clock cycle: "); + X52DebugPrintln(i); + digitalWrite(PIN_C02, LOW); + return X52_THROTTLE_UNRESPONSIVE_MICROS; + } + } + recv_buf.SetBit(JoystickState::NUM_BITS-1, bool(digitalRead(PIN_C03))); + + // The original joystick's C04 pulse seems to be at least 50us long. + + //auto trigger = [](){ + // digitalWrite(PIN_C02, LOW); + //}; + //auto wait_res = m_PulseWaiter.WaitForPulse(deadline, trigger); + //if (wait_res != PulseFinished) { + // X52DebugPrintln("Timed out while waiting for the C04 pulse before sending the joystick config."); + // return X52_THROTTLE_UNRESPONSIVE_MICROS; + //} + + JoystickConfig::Binary send_buf; + cfg.ToBinary(send_buf); + + for (int i=0; i +//class FakeStdJoystick { +//public: +FakeStdJoystick::FakeStdJoystick(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04) { + pio = this->pio; + PIN_C01 = this->PIN_C01; + PIN_C02 = this->PIN_C02; + PIN_C03 = this->PIN_C03; + PIN_C04 = this->PIN_C04; + PIN_C01 = this->PIN_C01; + #if defined(ARDUINO_ARCH_RP2040) + // Find a free SM on one of the PIO's + sm = pio_claim_unused_sm(pio, false); // don't panic + // Try pio1 if SM not found + if (sm < 0) { + pio = pio1; + sm = pio_claim_unused_sm(pio, true); // panic if no SM is free + } + init = true; + #endif +} + +FakeStdJoystick::~FakeStdJoystick() {} + +// Call Setup from the setup function of your Arduino project to initialize +// a FakeStdJoystick instance. +void FakeStdJoystick::setup() { + pinMode(PIN_C01, INPUT); + pinMode(PIN_C02, INPUT); + pinMode(PIN_C03, OUTPUT); + pinMode(PIN_C04, OUTPUT); + // On the teensy the digitalWrite seems to work only after pinMode. + digitalWrite(PIN_C04, LOW); +} + +// SendJoystickState sends the JoystickState to the throttle and receives +// the JoystickConfig. On the other side there must be a throttle polling/waiting +// for the JoystickState. Returns zero on success. +// +// A nonzero return value means error and gives the number of microseconds +// to wait before calling SendJoystickState again. In that situation the +// value of the JoystickConfig is undefined. +unsigned long FakeStdJoystick::SendJoystickState(const JoystickState& state, JoystickConfig& cfg, unsigned long wait_micros) { + if (!wait_for_pin_state(PIN_C02, HIGH, micros()+wait_micros)) + return 1; + + JoystickState::Binary send_buf; + state.ToBinary(send_buf); + + auto deadline = micros() + X52_JOYSTICK_TIMEOUT_MICROS; + + // The first data bit has to be on C03 before the falling edge of C04 + digitalWrite(PIN_C03, send_buf.Bit(0)); + + // The first C04 pulse that doesn't require an ACK from the throttle + digitalWrite(PIN_C04, HIGH); + // The original joystick uses a >=15us pulse and I don't have a throttle to test shorter pulses. + delayMicroseconds(X52_FIRST_C04_PULSE_MICROS); + digitalWrite(PIN_C04, LOW); + + // The throttle samples C03 for the first data bit here between falling-C04 and falling-C02. + + if (!wait_for_pin_state(PIN_C02, LOW, deadline)) { + X52DebugPrintln("Error waiting for C02=0 while sending the first bit of the joystick state."); + return X52_JOYSTICK_UNRESPONSIVE_MICROS; + } + + // sending the rest of the joystick state + for (int i=1; i=50us pulse and I don't have a throttle to test shorter pulses. + delayMicroseconds(X52_SECOND_C04_PULSE_MICROS); + digitalWrite(PIN_C04, LOW); + + JoystickConfig::Binary recv_buf; + + // receiving the config from the throttle + for (int i=0; i Binary; + + bool SetFromBinary(const Binary&); + void ToBinary(Binary&) const; + + static uint8_t Checksum(const Binary&); +}; + + +// JoystickConfig is the data sent by the throttle through the PS/2 cable. +struct JoystickConfig { + static constexpr uint8_t MAX_LED_BRIGHTNESS = 127; + + uint8_t led_brightness; // valid range: 0..127 (MAX_LED_BRIGHTNESS) + bool pov_1_led_blinking; // blinks about 25 times per second + + JoystickConfig() { + led_brightness = MAX_LED_BRIGHTNESS; + pov_1_led_blinking = false; + } + + // Constructor for the pros who believe they know what they are doing. + JoystickConfig(Uninitialized) {} + + static constexpr int NUM_BITS = 8; + typedef BitField Binary; + + void SetFromBinary(const Binary&); + void ToBinary(Binary&) const; +}; + + +inline void JoystickConfig::SetFromBinary(const Binary& b) { + led_brightness = uint8_t(b.UInt(0, 7)); + pov_1_led_blinking = b.Bit(7); +} + + +inline void JoystickConfig::ToBinary(Binary& b) const { + b.SetUInt(0, 7, led_brightness); + b.SetBit(7, pov_1_led_blinking); +} + + +inline bool JoystickState::SetFromBinary(const Binary& b) { + if (Checksum(b) != b.BufByte(7)) + return false; + + x = uint16_t(b.UInt(0, 8) | (b.UInt(16, 3) << 8)); + y = uint16_t(b.UInt(8, 8) | (b.UInt(19, 3) << 8)); + z = uint16_t(b.UInt(24, 8) | (b.UInt(22, 2) << 8)); + + switch (b.UInt(32, 4)) { + case 1: pov_1 = Up; break; + case 2: pov_1 = UpRight; break; + case 3: pov_1 = Right; break; + case 4: pov_1 = DownRight; break; + case 5: pov_1 = Down; break; + case 6: pov_1 = DownLeft; break; + case 7: pov_1 = Left; break; + case 8: pov_1 = UpLeft; break; + default: pov_1 = NoDirection; break; + } + + pov_2 = Direction( + (-b.Bit(36) & Right) | + (-b.Bit(37) & Down) | + (-b.Bit(38) & Left) | + (-b.Bit(39) & Up) + ); + + switch (b.UInt(54, 2)) { + case 0: mode = b.Bit(47) ? Mode1 : ModeUndefined; break; + case 1: mode = Mode2; break; + case 2: mode = Mode3; break; + default: mode = ModeUndefined; break; + } + + trigger_stage_1 = b.Bit(40); + trigger_stage_2 = b.Bit(41); + button_fire = b.Bit(42); + button_a = b.Bit(43); + button_b = b.Bit(44); + button_c = b.Bit(45); + pinkie_switch = b.Bit(46); + button_t1 = b.Bit(48); + button_t2 = b.Bit(49); + button_t3 = b.Bit(50); + button_t4 = b.Bit(51); + button_t5 = b.Bit(52); + button_t6 = b.Bit(53); + + return true; +} + + +inline void JoystickState::ToBinary(Binary& b) const { +#if X52_DEBUG + if (x > MAX_X) { + X52DebugPrint("x52::std::JoystickState.x exceeds the maximum value. x="); + X52DebugPrintln(x); + } + if (y > MAX_Y) { + X52DebugPrint("x52::std::JoystickState.y exceeds the maximum value. y="); + X52DebugPrintln(y); + } + if (z > MAX_Z) { + X52DebugPrint("x52::std::JoystickState.z exceeds the maximum value. z="); + X52DebugPrintln(z); + } +#endif + + b.SetUInt(0, 8, x); + b.SetUInt(8, 8, y); + b.SetUInt(24, 8, z); + + b.SetUInt(16, 3, x >> 8); + b.SetUInt(19, 3, y >> 8); + b.SetUInt(22, 2, z >> 8); + + uint8_t pov; + switch (pov_1) { + case Up: pov = 1; break; + case UpRight: pov = 2; break; + case Right: pov = 3; break; + case DownRight: pov = 4; break; + case Down: pov = 5; break; + case DownLeft: pov = 6; break; + case Left: pov = 7; break; + case UpLeft: pov = 8; break; + default: pov = 0; break; + } + b.SetUInt(32, 4, pov); + + b.SetBit(36, bool(pov_2 & Right)); + b.SetBit(37, bool(pov_2 & Down)); + b.SetBit(38, bool(pov_2 & Left)); + b.SetBit(39, bool(pov_2 & Up)); + + b.SetBit(47, mode == Mode1); + switch (mode) { + case Mode2: b.SetUInt(54, 2, 1); break; + case Mode3: b.SetUInt(54, 2, 2); break; + default: b.SetUInt(54, 2, 0); break; + } + + b.SetBit(40, trigger_stage_1); + b.SetBit(41, trigger_stage_2); + b.SetBit(42, button_fire); + b.SetBit(43, button_a); + b.SetBit(44, button_b); + b.SetBit(45, button_c); + b.SetBit(46, pinkie_switch); + b.SetBit(48, button_t1); + b.SetBit(49, button_t2); + b.SetBit(50, button_t3); + b.SetBit(51, button_t4); + b.SetBit(52, button_t5); + b.SetBit(53, button_t6); + + b.SetBufByte(7, Checksum(b)); +} + +inline uint8_t JoystickState::Checksum(const Binary& b) { + uint8_t chksum = b.BufByte(0); + for (int i=1; i<7; i++) + chksum ^= b.BufByte(i); + return chksum; +} + + +enum PulseWaitResult { + PulseFinished, + PulseStarted, + PulseNotStarted, + TooManyPulses, +}; + + +// The BitBangPulseWaiter is the naive implementation that can miss a pulse +// especially on a slower MCU. On an Arduino Micro (ATMega32U4 16MHz) this +// implementation can miss pulses every few minutes. That results in desync +// issues for a short period so the joystick is unresponsive for 500-1000ms +// and the LEDs turn off for a fraction of a second. +// +// This bit-banging worked well on my 96MHz teensy 3.2. However, it's better +// to be safe than sorry and use the InterruptPulseWaiter whenever possible. +// That works well on slower MCUs too but it requires a pin that can trigger +// interrupts on falling edges. That isn't a huge requirement. I'd use this +// BitBangPulseWaiter only if I had no interrupt pins available (basically "never"). +// This naive implementation is still useful as a form of documentation because +// the code explains very clearly what we want to achieve with the more +// complicated interrupt based solution. +template +class BitBangPulseWaiter { +public: + void Setup() {} + + template + PulseWaitResult WaitForPulse(unsigned long deadline, PulseTriggerFunc trigger) { + trigger(); + if (!wait_for_pin_state(PIN_C04, HIGH, deadline, 0)) + return PulseNotStarted; + if (!wait_for_pin_state(PIN_C04, LOW, deadline, 0)) + return PulseStarted; + return PulseFinished; + } +}; + + +// The InterruptPulseWaiter attaches an interrupt handler to the C04 pin to be +// able to reliably detect pulses. This whole "waiting for a pulse" problem +// affects only the non-Pro joystick. My X52 Pro uses a different protocol that +// is completely bitbang-friendly even on slower MCUs (as long as they are fast +// enough to transmit the whole frame within 17ms). +template +class InterruptPulseWaiter { +public: + void Setup() { + attachInterrupt(digitalPinToInterrupt(PIN_C04), InterruptHandler, FALLING); + } + + template + PulseWaitResult WaitForPulse(unsigned long deadline, PulseTriggerFunc trigger) { + int c0 = Counter(); + trigger(); + for (;;) { + int c = Counter(); + if (c != c0) + return (c == c0 + 1) ? PulseFinished : TooManyPulses; + // using delta to handle the overflows of micros() + unsigned long micros_left = deadline - micros(); + if (long(micros_left) <= 0) + return digitalRead(PIN_C04) ? PulseStarted : PulseNotStarted; +#if !X52_BUSY_WAIT + delayMicroseconds(min(10, micros_left)); +#endif + } + } + +private: + static int Counter() { + noInterrupts(); + int c = m_Counter; + interrupts(); + return c; + } + + static void InterruptHandler() { + m_Counter++; + } + + static volatile int m_Counter; +}; + +template +volatile int InterruptPulseWaiter::m_Counter = 0; + + +// FakeThrottle makes it possible to use some of your Arduino pins as a +// connection to the PS/2 socket of an X52 (non-Pro) Joystick. +// +// These pin names (C01..C04) were printed on the PCB of my X52 joystick. +// The pinout of the PS/2 connector is the same as that of the X52 Pro +// but the protocol is different. +// +// Standard PS/2 (6-pin mini-DIN) female socket pin numbering: +// https://en.wikipedia.org/wiki/Mini-DIN_connector#/media/File:MiniDIN-6_Connector_Pinout.svg +// +// PIN_C01: data output of the throttle (pin #4 of the PS/2 female socket) +// PIN_C02: clock output of the throttle (pin #6 of the PS/2 female socket) +// PIN_C03: data output of the joystick (pin #2 of the PS/2 female socket) +// PIN_C04: clock output of the joystick (pin #1 of the PS/2 female socket) +// +// Pin #3 of the PS/2 female socket is GND. +// Pin #5 of the PS/2 female socket is VCC. +// +// My joystick claims to be 5V 500mW but works with 3.3V too. +//template < typename PulseWaiter=InterruptPulseWaiter> +class FakeStdThrottle { +private: + //InterruptPulseWaiter m_PulseWaiter; + uint8_t PIN_C01; + uint8_t PIN_C02; + uint8_t PIN_C03; + uint8_t PIN_C04; + PIO pio; + uint8_t sm; + bool init = false; +public: + FakeStdThrottle(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04);//, PulseWaiter InterruptPulseWaiter /* args */); + ~FakeStdThrottle(); + void setup(); + unsigned long PollJoystickState(JoystickState& state, const JoystickConfig& cfg, unsigned long wait_micros=X52_DEFAULT_POLL_JOYSTICK_STATE_WAIT_MICROS); + +}; + +class FakeStdJoystick { +private: + uint8_t PIN_C01; + uint8_t PIN_C02; + uint8_t PIN_C03; + uint8_t PIN_C04; + PIO pio; + uint8_t sm; + bool init = false; +public: + FakeStdJoystick(PIO pio, int PIN_C01, int PIN_C02, int PIN_C03, int PIN_C04);//, PulseWaiter InterruptPulseWaiter /* args */); + ~FakeStdJoystick(); + void setup(); + unsigned long SendJoystickState(const JoystickState& state, JoystickConfig& cfg, unsigned long wait_micros=X52_DEFAULT_SEND_JOYSTICK_STATE_WAIT_MICROS); + bool IsPollInProgress(); +}; + + +#endif \ No newline at end of file diff --git a/lib/X52-HOTAS/src/new_x52_util.h b/lib/X52-HOTAS/src/new_x52_util.h new file mode 100644 index 0000000..7014d94 --- /dev/null +++ b/lib/X52-HOTAS/src/new_x52_util.h @@ -0,0 +1,60 @@ +#pragma once + +#include + + + +template +class RateLogger { +public: + RateLogger(): m_NumUpdates(0), m_PrevLogTime(0) {} + + void OnUpdate() { + m_NumUpdates++; + unsigned long now = millis(); + // using delta to handle the overflows of millis() + unsigned long elapsed = now - m_PrevLogTime; + if (elapsed < LOG_PERIOD_MILLIS) + return; + Serial.print("Updates per second: "); + Serial.println(double(m_NumUpdates) / double(elapsed) * 1000.0); + m_NumUpdates = 0; + m_PrevLogTime = now; + } + +private: + unsigned long m_NumUpdates; + unsigned long m_PrevLogTime; +}; + + +// Rate limiter with a history buffer to be able to deal with some jitter. +// A lower NUM_STORED_TIMESTAMPS value reduces the memory consumption but +// makes the RateLimiter less effective at dealing with jitter. +template +class RateLimiter { +public: + RateLimiter() { + m_Index = 0; + memset(m_UpdateTimes, 0, sizeof(m_UpdateTimes)); + } + + unsigned long MicrosTillNextUpdate() { + unsigned long now = micros(); + // using delta to handle the overflows of micros() + unsigned long micros_left = m_UpdateTimes[m_Index] - now; + if (long(micros_left) > 0) + return micros_left; + m_UpdateTimes[m_Index] = now + g_StoredPeriod; + m_Index = (m_Index + 1) % NUM_STORED_TIMESTAMPS; + m_UpdateTimes[m_Index] = min(m_UpdateTimes[m_Index], now + g_OneUpdatePeriod); + return 0; + } + +private: + int m_Index; + unsigned long m_UpdateTimes[NUM_STORED_TIMESTAMPS]; + + static constexpr unsigned long g_OneUpdatePeriod = (1000000 + MAX_UPDATES_PER_SECOND/2) / MAX_UPDATES_PER_SECOND; + static constexpr unsigned long g_StoredPeriod = (NUM_STORED_TIMESTAMPS*1000000 + MAX_UPDATES_PER_SECOND/2) / MAX_UPDATES_PER_SECOND; +}; diff --git a/lib/X52-HOTAS/src/throttle-protocol.pio b/lib/X52-HOTAS/src/throttle-protocol.pio new file mode 100644 index 0000000..c64fc02 --- /dev/null +++ b/lib/X52-HOTAS/src/throttle-protocol.pio @@ -0,0 +1,63 @@ +.program throttle +.side_set 1 opt + +.wrap_target + mov x, y +read_loop: + wait 1 pin 1 [1] side 1 ; wait for 1 on pin ClockOut and side_set pin 2 to 1 + wait 0 pin 1 [1] side 0 ; wait for 0 on pin ClockOut and side_set pin 2 to 0 + in pins 1 + jmp x-- read_loop + push + + wait 1 pin 1 [1] side 1 + set pins, 1 + wait 0 pin 1 [1] side 0 + set pins, 0 + + set x, 18 ; loop 19 times for writing bits +write_loop: + wait 1 pin 1 [1] side 1 ; wait for 1 on pin ClockOut and side_set pin 2 to 1 + wait 0 pin 1 [1] side 0 ; wait for 0 on pin ClockOut and side_set pin 2 to 0 + out pins, 1 + jmp x-- write_loop +.wrap + +% c-sdk { + +#include "pico/stdlib.h" +#include "hardware/pio.h" +static inline void throttle_program_init(PIO pio, uint sm, uint offset, uint DataOut, uint ClockOut, uint DataIn, uint ClockIn) { + + // Configure state machine + pio_sm_config config = throttle_program_get_default_config(offset); + + sm_config_set_in_shift(&config, false, true, 28); + sm_config_set_out_shift(&config, false, true, 31); + + // Map pins + sm_config_set_out_pins(&config, DataOut, 1); + sm_config_set_set_pins(&config, DataOut, 1); + sm_config_set_in_pins(&config, DataIn); + sm_config_set_sideset_pins(&config, ClockOut); + + // Set pin directions + pio_sm_set_consecutive_pindirs(pio, sm, DataOut, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, ClockOut, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, DataIn, 1, false); + pio_sm_set_consecutive_pindirs(pio, sm, ClockIn, 1, false); + + // Side-set options + sm_config_set_sideset(&config, 1, true, false); // 1 side-set pin, optional, enable side-set + + // Set variable for loop + pio_sm_put(pio, sm, 55); + pio_sm_exec(pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(pio, sm, pio_encode_mov(pio_y,pio_osr)); + + // Initialize state machine + pio_sm_init(pio, sm, offset, &config); + pio_sm_set_enabled(pio, sm, true); +} + +%} \ No newline at end of file diff --git a/lib/X52-HOTAS/src/throttle-protocol.pio.h b/lib/X52-HOTAS/src/throttle-protocol.pio.h new file mode 100644 index 0000000..959926d --- /dev/null +++ b/lib/X52-HOTAS/src/throttle-protocol.pio.h @@ -0,0 +1,69 @@ +// -------------------------------------------------- // +// This file is autogenerated by pioasm; do not edit! // +// -------------------------------------------------- // + +#pragma once + +#if !PICO_NO_HARDWARE +#include "hardware/pio.h" +#endif + +// -------- // +// throttle // +// -------- // + +#define throttle_wrap_target 0 +#define throttle_wrap 3 + +static const uint16_t throttle_program_instructions[] = { + // .wrap_target + 0xa022, // 0: mov x, y + 0x3d21, // 1: wait 0 pin, 1 side 1 [5] + 0x35a1, // 2: wait 1 pin, 1 side 0 [5] + 0x0001, // 3: jmp 1 + // .wrap +}; + +#if !PICO_NO_HARDWARE +static const struct pio_program throttle_program = { + .instructions = throttle_program_instructions, + .length = 4, + .origin = -1, +}; + +static inline pio_sm_config throttle_program_get_default_config(uint offset) { + pio_sm_config c = pio_get_default_sm_config(); + sm_config_set_wrap(&c, offset + throttle_wrap_target, offset + throttle_wrap); + sm_config_set_sideset(&c, 2, true, false); + return c; +} + +#include "pico/stdlib.h" +#include "hardware/pio.h" +static inline void throttle_program_init(PIO pio, uint sm, uint offset, uint DataOut, uint ClockOut, uint DataIn, uint ClockIn) { + // Configure state machine + pio_sm_config config = throttle_program_get_default_config(offset); + sm_config_set_in_shift(&config, false, true, 28); + sm_config_set_out_shift(&config, false, true, 31); + // Map pins + sm_config_set_out_pins(&config, DataOut, 1); + sm_config_set_set_pins(&config, DataOut, 2); + sm_config_set_in_pins(&config, DataIn); + sm_config_set_sideset_pins(&config, ClockOut); + // Set pin directions + pio_sm_set_consecutive_pindirs(pio, sm, DataOut, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, ClockOut, 1, true); + pio_sm_set_consecutive_pindirs(pio, sm, DataIn, 1, false); + pio_sm_set_consecutive_pindirs(pio, sm, ClockIn, 1, false); + // Side-set options + sm_config_set_sideset(&config, 1, true, false); // 1 side-set pin, optional, enable side-set + // Set variable for loop + pio_sm_put(pio, sm, 55); + pio_sm_exec(pio, sm, pio_encode_pull(false, false)); + pio_sm_exec(pio, sm, pio_encode_mov(pio_y,pio_osr)); + // Initialize state machine + pio_sm_init(pio, sm, offset, &config); + pio_sm_set_enabled(pio, sm, true); +} + +#endif diff --git a/lib/X52-HOTAS/src/x52_pro.h b/lib/X52-HOTAS/src/x52_pro.h index 4c2f08e..4710d0d 100644 --- a/lib/X52-HOTAS/src/x52_pro.h +++ b/lib/X52-HOTAS/src/x52_pro.h @@ -28,13 +28,13 @@ #define X52_PRO_JOYSTICK_DESYNC_UNRESPONSIVE_MICROS 23000 #endif -// Enabling this feature allows the ThrottleClient to behave differently from +// Enabling this feature allows the FakeJoystick to behave differently from // the original joystick in order to make the desync detection more reliable. #ifndef X52_PRO_IMPROVED_THROTTLE_CLIENT_DESYNC_DETECTION #define X52_PRO_IMPROVED_THROTTLE_CLIENT_DESYNC_DETECTION 0 #endif -// Enabling this feature allows the JoystickClient to behave differently from +// Enabling this feature allows the FakeThrottle to behave differently from // the original throttle in order to make the desync detection more reliable. #ifndef X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION #define X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION 0 @@ -49,6 +49,15 @@ #endif +inline void SetUInt(uint32_t b, uint8_t index, uint8_t size, uint8_t value) { + for (uint8_t i = 0; i> i) & 1); +} + +inline void SetBit(uint32_t b, uint8_t index, bool value) { + bitWrite(b, index, value); +} + namespace x52 { namespace pro { @@ -104,6 +113,7 @@ struct JoystickState { typedef BitField Binary; void SetFromBinary(const Binary&); + void SetFromBinary2(const uint64_t); void ToBinary(Binary&) const; }; @@ -142,6 +152,7 @@ struct JoystickConfig { typedef BitField Binary; void SetFromBinary(const Binary&); + void SetFromBinary2(const uint64_t); void ToBinary(Binary&) const; }; @@ -281,8 +292,7 @@ inline void JoystickState::ToBinary(Binary& b) const { b.SetBit(55, button_t6); } - -// JoystickClient makes it possible to use some of your Arduino pins as a +// FakeThrottle makes it possible to use some of your Arduino pins as a // connection to the PS/2 socket of an X52 Pro Joystick. // // These pin names (C01..C04) were printed on the PCB of my X52 joystick. @@ -303,11 +313,11 @@ inline void JoystickState::ToBinary(Binary& b) const { // My X52 Pro throttle uses 4.1-4.2V for both power and GPIO but the joystick // works with 3.3V too. template -class JoystickClient { +class FakeThrottle { public: // Call Setup from the setup function of your Arduino project to initialize - // a JoystickClient instance. - void Setup() { + // a FakeThrottle instance. + void setup() { pinMode(PIN_C01, OUTPUT_12MA); pinMode(PIN_C02, OUTPUT_12MA); // On the teensy the digitalWrite seems to work only after pinMode. @@ -337,6 +347,17 @@ class JoystickClient { JoystickConfig::Binary send_buf; cfg.ToBinary(send_buf); + uint32_t sendbuf=0; + uint64_t recvbuf = 0; + SetUInt(sendbuf, 0, 5, cfg.led_brightness); + SetBit(sendbuf, 5, cfg.pov_1_led_blinking); + SetUInt(sendbuf, 6, 2, cfg.button_a_led); + SetUInt(sendbuf, 8, 2, cfg.pov_2_led); + SetBit(sendbuf, 10, !cfg.button_fire_led); + SetUInt(sendbuf, 11, 2, cfg.button_b_led); + SetUInt(sendbuf, 13, 2, cfg.button_t1_t2_led); + SetUInt(sendbuf, 15, 2, cfg.button_t3_t4_led); + SetUInt(sendbuf, 17, 2, cfg.button_t5_t6_led); // deadline for the whole frame transmission unsigned long deadline = micros() + wait_micros; @@ -350,6 +371,7 @@ class JoystickClient { #endif if (i >= 57) digitalWrite(PIN_C01, send_buf.Bit(i-57)); + //digitalWrite(PIN_C01, bitRead(sendbuf,i-57)); digitalWrite(PIN_C02, HIGH); @@ -360,7 +382,7 @@ class JoystickClient { X52DebugPrint("Error waiting for C04=1. Clock cycle: "); X52DebugPrintln(i); #if X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION - if (i >= 57) + if (i >= 57) digitalWrite(PIN_C01, HIGH); #endif digitalWrite(PIN_C02, LOW); @@ -404,9 +426,11 @@ class JoystickClient { // falling edge of C04 and the rising edge of C02. if (i < JoystickState::NUM_BITS) recv_buf.SetBit(i, bool(digitalRead(PIN_C03))); + //bitWrite(recvbuf, i, bool(digitalRead(PIN_C03))); } state.SetFromBinary(recv_buf); + //state.SetFromBinary2(recvbuf); return 0; } @@ -416,16 +440,16 @@ class JoystickClient { }; -// ThrottleClient makes it possible to use some of your Arduino pins as a +// FakeJoystick makes it possible to use some of your Arduino pins as a // connection to the PS/2 socket of an X52 Pro Throttle. // -// The pin config is the same as that of the JoystickClient. +// The pin config is the same as that of the FakeThrottle. template -class ThrottleClient { +class FakeJoystick { public: // Call Setup from the setup function of your Arduino project to initialize - // a ThrottleClient instance. - void Setup() { + // a FakeJoystick instance. + void setup() { pinMode(PIN_C01, INPUT); pinMode(PIN_C02, INPUT); pinMode(PIN_C03, OUTPUT); diff --git a/lib/X52-HOTAS/src/x52_std.h b/lib/X52-HOTAS/src/x52_std.h index 568182b..3acf7a1 100644 --- a/lib/X52-HOTAS/src/x52_std.h +++ b/lib/X52-HOTAS/src/x52_std.h @@ -344,7 +344,7 @@ template volatile int InterruptPulseWaiter::m_Counter = 0; -// JoystickClient makes it possible to use some of your Arduino pins as a +// FakeThrottle makes it possible to use some of your Arduino pins as a // connection to the PS/2 socket of an X52 (non-Pro) Joystick. // // These pin names (C01..C04) were printed on the PCB of my X52 joystick. @@ -364,10 +364,10 @@ volatile int InterruptPulseWaiter::m_Counter = 0; // // My joystick claims to be 5V 500mW but works with 3.3V too. template > -class JoystickClient { +class FakeThrottle { public: // Call Setup from the setup function of your Arduino project to initialize - // a JoystickClient instance. + // a FakeThrottle instance. void Setup() { pinMode(PIN_C01, OUTPUT); // The value of C01 is allowed to be anything between frames (undefined). @@ -490,15 +490,15 @@ class JoystickClient { }; -// ThrottleClient makes it possible to use some of your Arduino pins as a +// FakeJoystick makes it possible to use some of your Arduino pins as a // connection to the PS/2 socket of an X52 (non-Pro) Throttle. // -// The pin config is the same as that of the JoystickClient. +// The pin config is the same as that of the FakeThrottle. template -class ThrottleClient { +class FakeJoystick { public: // Call Setup from the setup function of your Arduino project to initialize - // a ThrottleClient instance. + // a FakeJoystick instance. void Setup() { pinMode(PIN_C01, INPUT); pinMode(PIN_C02, INPUT); diff --git a/patch-SDFS.py b/scripts/patch-SDFS.py similarity index 100% rename from patch-SDFS.py rename to scripts/patch-SDFS.py diff --git a/scripts/picoasm.py b/scripts/picoasm.py new file mode 100644 index 0000000..b8924d8 --- /dev/null +++ b/scripts/picoasm.py @@ -0,0 +1,30 @@ +""" +Custom pioasm compiler script for platformio. +(c) 2022 by P.Z. + +""" +from os.path import join +import glob +import sys +Import("env") + +platform = env.PioPlatform() +PROJ_SRC = env["PROJECT_SRC_DIR"] +PIO_FILES = glob.glob(join(PROJ_SRC, '*.pio'), recursive=True) + +if PIO_FILES: + print("==============================================") + print('PIO ASSEMBLY COMPILER') + try: + PIOASM_DIR = platform.get_package_dir("tool-pioasm-rp2040-earlephilhower") + except: + print("tool-pioasm-rp2040-earlephilhower not found!") + print("please install it using the following command:") + print("pio pkg install -g --tool \"earlephilhower/tool-pioasm-rp2040-earlephilhower@^5.100300.220714\"") + sys.exit() + + PIOASM_EXE = join(PIOASM_DIR, "pioasm") + print("pio files found:") + for filename in PIO_FILES: + env.Execute(PIOASM_EXE + f' -o c-sdk {filename} {filename}.h') + print("==============================================") \ No newline at end of file diff --git a/src/i2c_node.cpp b/src/i2c_node.cpp index 147a049..1070aea 100644 --- a/src/i2c_node.cpp +++ b/src/i2c_node.cpp @@ -4,280 +4,377 @@ #include #include "CRC.h" #include "Wire.h" +#include -//int i2cADDR = calcCRC8((uint8_t*)rp2040.getChipID(), sizeof(rp2040.getChipID())); -int i2cADDR = 0x21; // Buttons // D E I HAT HAT HAT HAT Scroll Scroll RB LB // Axis // ???? #define I2C_SDA 0 #define I2C_SCL 1 -#define BUTTON1 2 -#define BUTTON2 3 -#define BUTTON3 4 -#define HAT1_1 5 -#define HAT1_2 6 -#define HAT1_3 7 -#define HAT1_4 8 -#define SCROLL_1 9 -#define SCROLL_2 10 -#define LMB 12 -#define RMB 11 -#define MUX_SIG A2 -#define MUX_S4 A1 -#define MUX_S3 A0 -#define MUX_S2 22 -#define MUX_S1 21 -#define MUX_EN 20 #define WS2812_PIN NULL #define EXT_NUM_PIXELS 3 +uint8_t maxHats = 8; +uint8_t maxAxis = 32; +uint8_t maxButtons = 32; +uint8_t maxMuxes = 5; - -//Adafruit_NeoPixel ext_pixel(EXT_NUM_PIXELS, WS2812_PIN); - -uint8_t ADCresolution = 11; +//int i2cADDR = calcCRC8((uint8_t*)rp2040.getChipID(), sizeof(rp2040.getChipID())); +struct _config { + int i2cADDR = 0x21; + uint8_t ADCResolution = 11; + uint8_t currentADCResolution; + uint8_t digitalPinCount = 16; + uint8_t analogPinCount = 1; + uint8_t muxCount = 1; + uint8_t axisCount = 3; + uint8_t buttonCount = 7; + uint8_t hatCount = 1; + // pin ids used for reading + uint8_t digitalPins[32]; + uint8_t analogPins[32]; + // pin modes (INPUT, OUTPUT, INPUT_PULLUP, INPUT_PULLDOWN, OUTPUT_2MA, OUTPUT_4MA, OUTPUT_8MA, OUTPUT_12MA) + uint8_t digitalModePins[32]; + uint8_t analogModePins[32]; + + uint8_t buttonPins[32]; + uint8_t bInverted[32]; + uint8_t axisPins[32]; + uint8_t aInverted[32]; + // order is: N,E,S,W + uint8_t hatPins[8][4]; + // order is: EN,S0,S1,S2,S3,SIG + uint8_t muxPins[5][6]; +}; +_config config; +Adafruit_NeoPixel ext_pixel(EXT_NUM_PIXELS, WS2812_PIN); volatile RGBW led; + uint8_t i2cDataSize = 0; -struct __attribute__((packed, aligned(1))) Command{ - uint16_t command_type; - uint16_t id; -}command; uint8_t i2cDataBuf[64]; -void receive(int len); -void request(); -uint16_t readMuxChannel(uint8_t channel); - - -/////////////////////////////////////////////// -// primary core used for noce specific tasks // -/////////////////////////////////////////////// - -//////////////////////////////////////////// -// secondary core used for communications // -//////////////////////////////////////////// - -void setup() { - -#if defined(ARDUINO_RASPBERRY_PI_PICO)||defined(ARDUINO_RASPBERRY_PI_PICO_W) // This changes the SMPS to be less efficient but also less noisy - pinMode(23, OUTPUT); - digitalWrite(23, HIGH); -#endif - - //ext_pixel.begin(); - - analogReadResolution(ADCresolution); - - pinMode(BUTTON1, OUTPUT_2MA); - pinMode(BUTTON2, OUTPUT_2MA); - pinMode(BUTTON3, OUTPUT_2MA); - - pinMode(HAT1_1, OUTPUT_2MA); - pinMode(HAT1_2, OUTPUT_2MA); - pinMode(HAT1_3, OUTPUT_2MA); - pinMode(HAT1_4, OUTPUT_2MA); - pinMode(SCROLL_1, OUTPUT_2MA); - pinMode(SCROLL_2, OUTPUT_2MA); - pinMode(LMB, OUTPUT_2MA); - pinMode(RMB, OUTPUT_2MA); - pinMode(MUX_EN, OUTPUT_2MA); - pinMode(MUX_S1, OUTPUT_2MA); - pinMode(MUX_S2, OUTPUT_2MA); - pinMode(MUX_S3, OUTPUT_2MA); - pinMode(MUX_S4, OUTPUT_2MA); - pinMode(MUX_SIG, INPUT); - - digitalWrite(BUTTON1, HIGH); - digitalWrite(BUTTON2, HIGH); - digitalWrite(BUTTON3, HIGH); - digitalWrite(HAT1_1, HIGH); - digitalWrite(HAT1_2, HIGH); - digitalWrite(HAT1_3, HIGH); - digitalWrite(HAT1_4, HIGH); - digitalWrite(SCROLL_1, HIGH); - digitalWrite(SCROLL_2, HIGH); - digitalWrite(LMB, HIGH); - digitalWrite(RMB, HIGH); - digitalWrite(MUX_EN, HIGH); - digitalWrite(MUX_S1, LOW); - digitalWrite(MUX_S2, LOW); - digitalWrite(MUX_S3, LOW); - digitalWrite(MUX_S4, LOW); - +void readEEPROM() { + //EEPROM.get(0,config); + for (uint8_t i = 0; i T readMuxChannel(uint8_t channel) { - digitalWrite(MUX_S1, ((channel >> 0) & 0x01)); - digitalWrite(MUX_S2, ((channel >> 1) & 0x01)); - digitalWrite(MUX_S3, ((channel >> 2) & 0x01)); - digitalWrite(MUX_S4, ((channel >> 3) & 0x01)); - digitalWrite(MUX_EN, LOW); - delay(1); - T value = analogRead(MUX_SIG); - digitalWrite(MUX_EN, HIGH); +// Reads the analog pin +// muxId is 0 its reading a analog pin directly +// otherwise muxId is used to get the pins from the array for that mux +// and channel is used to select the pin instead +template T readAxis(uint8_t muxId, uint8_t channel) { + T value = 0; + if(muxId>0 && muxId <= config.muxCount) { + digitalWrite(config.muxPins[muxId][1], ((channel >> 0) & 0x01)); + digitalWrite(config.muxPins[muxId][2], ((channel >> 1) & 0x01)); + digitalWrite(config.muxPins[muxId][3], ((channel >> 2) & 0x01)); + digitalWrite(config.muxPins[muxId][4], ((channel >> 3) & 0x01)); + digitalWrite(config.muxPins[muxId][0], LOW); + value = analogRead(config.muxPins[muxId][5]); + digitalWrite(config.muxPins[muxId][0], HIGH); +// digitalWrite(MUX_S1, ((channel >> 0) & 0x01)); +// digitalWrite(MUX_S2, ((channel >> 1) & 0x01)); +// digitalWrite(MUX_S3, ((channel >> 2) & 0x01)); +// digitalWrite(MUX_S4, ((channel >> 3) & 0x01)); +// digitalWrite(MUX_EN, LOW); +// delay(1); +// value = analogRead(MUX_SIG); +// digitalWrite(MUX_EN, HIGH); + } else { + value = analogRead(channel); + } return (T)value; } -uint8_t readButton(uint8_t button) { +bool readButton(uint8_t muxId, uint8_t button) { +// This is since we default our pins high... we pull low when pressed. (this is oposite to how default true/fasle is) bool isPressed = 0; - switch (button) { - case Button1: - { isPressed = digitalRead(BUTTON1); break; } - case Button2: - { isPressed = digitalRead(BUTTON2); break; } - case Button3: - { isPressed = digitalRead(BUTTON3); break; } - case Button4: - { isPressed = digitalRead(HAT1_1); break; } - case Button5: - { isPressed = digitalRead(HAT1_2); break; } - case Button6: - { isPressed = digitalRead(HAT1_3); break; } - case Button7: - { isPressed = digitalRead(HAT1_4); break; } - case Button8: - { isPressed = digitalRead(SCROLL_1); break; } - case Button9: - { isPressed = digitalRead(SCROLL_2); break; } - case Button10: - { isPressed = digitalRead(LMB); break; } - case Button11: - { isPressed = digitalRead(RMB); break; } - case Button12: - { break; } - case Button13: - { break; } - case Button14: - { break; } - case Button15: - { break; } - case Button16: - { break; } - case Button17: - { break; } - case Button18: - { break; } - case Button19: - { break; } - case Button20: - { break; } - case Button21: - { break; } - case Button22: - { break; } - case Button23: - { break; } - case Button24: - { break; } - case Button25: - { break; } - case Button26: - { break; } - default: break; + if(muxId>0 && muxId <= config.muxCount) { + digitalWrite(config.muxPins[muxId][1], ((button >> 0) & 0x01)); + digitalWrite(config.muxPins[muxId][2], ((button >> 1) & 0x01)); + digitalWrite(config.muxPins[muxId][3], ((button >> 2) & 0x01)); + digitalWrite(config.muxPins[muxId][4], ((button >> 3) & 0x01)); + digitalWrite(config.muxPins[muxId][0], LOW); + isPressed = !digitalRead(config.muxPins[muxId][5]); + digitalWrite(config.muxPins[muxId][0], HIGH); + } + else { + isPressed = !digitalRead(config.digitalPins[button]); } - // This is since we default our pins high... we pull low when pressed. (this is oposite to how default true/fasle is) - return !isPressed; + return isPressed; } // function that executes whenever data is received from master // this function is registered as an event, see setup() void receive(int len) { - uint16_t command = Wire.read(); - uint16_t id = Wire.read(); - + uint8_t command = Wire.read(); switch (command) { case SetLed: { - - //led.raw = (uint32_t)command->data; - //ext_pixel.setPixelColor(command->id, led.R, led.G, led.B); - break; - } - case SetConfig: { - - } - case GetConfig: { + //uint8_t id = Wire.read(); + led.raw = Wire.read()|(Wire.read()<<8)|(Wire.read()<<16)|(Wire.read()<<24); + //ext_pixel.setPixelColor(id, led.R, led.G, led.B); break; } case GetLed: { - //((uint32_t*)command.buf)[0] = ext_pixel.getPixelColor(command->id); + //uint8_t id = Wire.read(); + //((uint32_t*)i2cDataBuf)[0] = ext_pixel.getPixelColor(id); //i2cBuffUsed += sizeof(uint32_t); break; } case GetAxis: { - //a = readMuxChannel(command->id); - //command->data = (uint16_t*)a; - - //((uint16_t*)i2cDataBuf)[0] = readMuxChannel(id); - ((uint16_t*)i2cDataBuf)[0] = readMuxChannel(id); - //((uint16_t*)i2cDataBuf)[0] = readMuxChannel(id,uin); + uint8_t id = Wire.read(); + uint8_t muxId = 0; + if (Wire.available()) + muxId = Wire.read(); + ((uint16_t*)i2cDataBuf)[0] = readAxis(muxId, id); i2cDataSize = sizeof(uint16_t); break; } case GetButton: { - i2cDataBuf[0] = readButton(id); + uint8_t id = Wire.read(); + uint8_t muxId = 0; + if (Wire.available()) + muxId = Wire.read(); + i2cDataBuf[0] = readButton(muxId, id); i2cDataSize = sizeof(uint8_t); - - //Serial.print("COMMAND TYPE: "); - //Serial.print(command->command_type); - //Serial.print(" GetButton ID: "); - //Serial.print(command->id); - //Serial.print(" STATE: "); - //Serial.println((bool)command->data); - //Serial.print("BUFF: "); - //for (int i = 0; i < i2cBuffUsed; i++) { - // Serial.print(((uint8_t*)i2cBuff)[i], BIN); - //} - //Serial.println(); - break; } + case Config: { + uint16_t configCommand = Wire.read()|(Wire.read()<<8); + switch (configCommand) { + case ConfigUpdate: { + break; + } + case SetI2cId: { + uint8_t id = Wire.read(); + if (config.i2cADDR != id) { + config.i2cADDR = id; + } + break; + } + case GetI2cId: { + i2cDataBuf[0] = config.i2cADDR; + i2cDataSize = 1; + break; + } + case SetAnalogResolution: { + config.ADCResolution = Wire.read(); + if (config.currentADCResolution != config.ADCResolution) { + analogReadResolution(config.ADCResolution); + config.currentADCResolution = config.ADCResolution; + } + break; + } + case SetButtonPin: { + uint8_t id = Wire.read(); + uint8_t pin = Wire.read(); + if (id < maxButtons) + config.digitalPins[id] = pin; + break; + } + case GetButtonPin: { + uint8_t id = Wire.read(); + i2cDataBuf[0] = config.digitalPins[id]; + i2cDataSize = sizeof(uint8_t); + break; + } + case SetAxisPin: { + uint8_t id = Wire.read(); + uint8_t pin = Wire.read(); + if (id < maxAxis) + config.analogPins[id] = pin; + break; + } + case GetAxisPin: { + uint8_t id = Wire.read(); + i2cDataBuf[0] = config.analogPins[id]; + i2cDataSize = sizeof(uint8_t); + break; + } + case SetHatPins: { + uint8_t id = Wire.read(); + if (id < maxHats) + for (uint8_t i = 0; i< 4; i++) { + config.hatPins[id][i] = Wire.read(); + } + break; + } + case GetHatPins: { + uint8_t id = Wire.read(); + if (id < maxHats) { + memcpy(i2cDataBuf, config.hatPins[id], sizeof(config.hatPins[id])); + i2cDataSize = sizeof(config.hatPins[id]); + } + break; + } + case SetMuxPins: { + uint8_t id = Wire.read(); + if (id < maxMuxes) + for (uint8_t i = 0; i< 6; i++) { + config.muxPins[id][i] = Wire.read(); + } + break; + } + case GetMuxPins: { + uint8_t id = Wire.read(); + if (id < maxMuxes) { + memcpy(i2cDataBuf, config.muxPins[id], sizeof(config.muxPins[id])); + i2cDataSize = sizeof(config.muxPins[id]); + } + break; + } + case SetButtonCount: { + uint8_t count = Wire.read(); + if (count < maxButtons) + config.buttonCount = count; + break; + } + case GetButtonCount: { + i2cDataBuf[0] = config.buttonCount; + i2cDataSize = 1; + break; + } + case SetHatCount: { + uint8_t count = Wire.read(); + if (count < maxHats) + config.hatCount = count; + break; + } + case GetHatCount: { + i2cDataBuf[0] = config.hatCount; + i2cDataSize = 1; + break; + } + case SetAxisCount: { + uint8_t count = Wire.read(); + if (count < maxAxis) + config.axisCount = count; + break; + } + case GetAxisCount: { + i2cDataBuf[0] = config.axisCount; + i2cDataSize = 1; + break; + } + case SetMuxCount: { + uint8_t count = Wire.read(); + if (count < maxMuxes) + config.muxCount = count; + break; + } + case GetMuxCount: { + i2cDataBuf[0] = config.muxCount; + i2cDataSize = 1; + break; + } + case GetAnalogResolution: { + i2cDataBuf[0] = config.currentADCResolution; + i2cDataSize = sizeof(uint8_t); + break; + } + default: + break; + } + } default: break; } - } // function that executes whenever data is received from master // Called when the I2C slave is read from void request() { - Wire.write(i2cDataBuf, i2cDataSize); } -void setup1() { +void setup() { + +// This changes the SMPS to be less efficient but also less noisy +#if defined(ARDUINO_RASPBERRY_PI_PICO)||defined(ARDUINO_RASPBERRY_PI_PICO_W) + pinMode(23, OUTPUT); + digitalWrite(23, HIGH); +#endif + // since its not actually a eeprom we MUST only write to it when we REALLY have to + EEPROM.begin(512); + //////////////////////////// + // EEPROM layout: // + //////////////////////////// + // (1) i2c address // + // (1) adc resolution // + // (1) digital pin count // + // (1) analog pin count // + // (1) mux count // + // (1) rgb led count // + // (1) rgb led pin // + // (32) digital pins // + // (32) analog pins // + // (32) mux pins // + //////////////////////////// + + auto a = sizeof(config); + + uint8_t digitalPins[] = {2,3,4,5,6,7,8,9,10,11,12,20,21,22,26,27}; + uint8_t digitalPinMode[] = {4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4}; + uint8_t analogPins[] = {28}; + uint8_t analogPinMode[] = {0}; + uint8_t buttonPins[] = {2,3,4,5,6,7,8,9,10,11,12,20,21,22,26,27}; + uint8_t axisPins[] = {28}; + uint8_t hatPins[8][4] = {{5,6,7,8}}; + uint8_t muxPins[5][6] = {{20,21,22,26,27,28}}; + + memcpy(config.digitalPins, digitalPins, sizeof(digitalPins)); + memcpy(config.digitalModePins, digitalPinMode, sizeof(digitalPinMode)); + memcpy(config.analogPins, analogPins, sizeof(analogPins)); + memcpy(config.analogModePins, analogPinMode, sizeof(analogPinMode)); + memcpy(config.buttonPins, buttonPins, sizeof(buttonPins)); + memcpy(config.axisPins, axisPins, sizeof(axisPins)); + memcpy(config.hatPins, hatPins, sizeof(hatPins)); + memcpy(config.muxPins, muxPins, sizeof(muxPins)); + + //ext_pixel.begin(); - //if (i2cBuff == NULL) { - // i2cBuff = (uint8_t*)malloc(i2cBuffSize); - // command = (struct Command*)i2cBuff; - //} + analogReadResolution(config.currentADCResolution); - pinMode(D0, OUTPUT_2MA); - pinMode(D1, OUTPUT_2MA); - digitalWrite(D0, HIGH); - digitalWrite(D1, HIGH); + for (uint8_t i = 0; i + +//#include "HID_Report.h" #include #include #include #include "CRC.h" #include "Wire.h" +#include "SPI.h" +//U8G2_ST7528_ERC16064_1_4W_HW_SPI u8g2(U8G2_R0, /* cs=*/ 255, /* dc=*/ 255, /* reset=*/ 255); // Select the FileSystem by uncommenting one of the lines below @@ -40,8 +44,10 @@ LittleFSConfig fileSystemConfig = LittleFSConfig(); #elif defined USE_SDFS #include const char* fsName = "SDFS"; + FS* fileSystem = &SDFS; SDFSConfig fileSystemConfig = SDFSConfig(); + // fileSystemConfig.setCSPin(chipSelectPin); #else #error Please select a filesystem first by uncommenting one of the "-D USE_xxx" lines in the usb.ini file located in "config" folder. @@ -50,6 +56,22 @@ SDFSConfig fileSystemConfig = SDFSConfig(); #include "IniConfig.h" IniConfig ini(&SDFS); +typedef struct { + uint8_t buttons; + uint8_t hats; + uint8_t axis; + +} input_id; + + +typedef union TU_ATTR_PACKED{ + struct { + uint8_t type:2; + uint8_t id:6; + }; + uint8_t option; +}input_type; + bool LED_on; bool LED_breathe; uint8_t LED_brightness; @@ -61,29 +83,25 @@ int report = 1; /*== LED ==*/ Adafruit_NeoPixel pixel; -bool enableKeyboard = 0; -bool enableMouse = 0; -uint8_t DeviceCount; +input_id inputs_id[MAX_REPORT_ID]; + +bool enableKeyboard = 1; +bool enableMouse = 1; +bool enableMsc = 1; +uint8_t IdCount; +uint8_t MaxDeviceCount = MAX_REPORT_ID; uint16_t hue; uint32_t nextScan = millis(); uint32_t nextScanPrint = millis(); uint usb_report_size; uint8_t usb_report[MAX_HID_DESCRIPTOR_SIZE]; -typedef struct { - uint8_t buttons; - uint8_t hats; - uint8_t axis; - -} input_id; -input_id inputs_id[MAX_REPORT_ID]; - uint16_t axis_start[MAX_REPORT_ID]; uint16_t button_start[MAX_REPORT_ID]; uint16_t hat_start[MAX_REPORT_ID]; uint16_t total_bits[MAX_REPORT_ID]; -uint16_t largest_bits; +//uint16_t largest_bits; volatile uint8_t reports[MAX_REPORT_ID][64]; uint8_t old_reports[MAX_REPORT_ID][64]; @@ -105,14 +123,18 @@ uint16_t HatCount = 1; uint8_t hid_usage_page_val = HID_USAGE_PAGE_DESKTOP; uint8_t hid_usage_val = HID_USAGE_DESKTOP_JOYSTICK; -Adafruit_USBD_HID hid_joystick; -Adafruit_USBD_HID hid_kbm; +uint8_t usedIds[128]; +uint8_t slaveCount; -byte i2cIDs[128]; +Adafruit_USBD_HID usb_hid; +//Adafruit_USBD_HID hid_kbm; +Adafruit_USBD_MSC usb_msc; +byte i2cIDs[128]; -void setupUSB(bool begin); +void setupDescripor(); +void setUSB(bool); static bool fsOK; #ifdef ARDUINO_RASPBERRY_PI_PICO_W //todo add more wifi boards (eg esp32) #include "webInterface.h" @@ -150,14 +172,34 @@ uint32_t rainbow(uint16_t _hue, uint8_t saturation, uint8_t brightness, bool gam // this is here since the joystick still works and doesnt need to be replaced (it will be gutted after throttle is properly done) - +//#define USE_NEW +//#define USE_STD #define X52_BUSY_WAIT 0 -#define X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION 0 -#include +#define X52_PRO_IMPROVED_JOYSTICK_CLIENT_DESYNC_DETECTION 1 +#ifndef USE_NEW +#include "x52_pro.h" +#include "x52_util.h" +#else +#ifdef USE_STD +//#include "new_x52_std.h" +//#include "new_x52_util.h" +#else +#include "new_x52_pro.h" +#include "new_x52_util.h" +#endif +#endif #define MAX_UPDATES_PER_SECOND 300 // TODO: Choose your favorite digital pins on your board. -x52::pro::JoystickClient joystick_client; +#ifndef USE_NEW +x52::pro::FakeThrottle fakeThrottle; +#else +#ifdef USE_STD +FakeStdThrottle fakeThrottle(pio0, D20, D26, D21, D22); +#else +FakeProThrottle fakeThrottle(pio0, D20, D26, D21, D22); +#endif +#endif //x52::pro::JoystickConfig cfg; //x52::pro::JoystickState state; @@ -172,6 +214,7 @@ x52::pro::JoystickClient joystick_client; // Return zero will cause the stack to STALL request uint16_t get_report_callback (uint8_t report_id, hid_report_type_t report_type, uint8_t* buffer, uint16_t reqlen) { + memset(buffer,0,reqlen); Serial.print("get_report type: "); Serial.println((int)report_type); Serial.print("get_report id: "); @@ -183,7 +226,16 @@ uint16_t get_report_callback (uint8_t report_id, hid_report_type_t report_type, break; case (hid_report_type_t)HID_REPORT_TYPE_INPUT: - case (hid_report_type_t)HID_REPORT_TYPE_FEATURE: + case (hid_report_type_t)HID_REPORT_TYPE_FEATURE: + if (report_id < 1) { + buffer[0] = IdCount; + } else if (report_id > 0 && report_id <= MAX_REPORT_ID) { + buffer[0] = inputs_id[report_id].buttons; + buffer[1] = inputs_id[report_id].hats; + buffer[2] = inputs_id[report_id].axis; + buffer[3] = AxisResolution; + } else {break;} + return reqlen; } return 0; @@ -206,11 +258,62 @@ void set_report_callback(uint8_t report_id, hid_report_type_t report_type, uint8 break; case HID_REPORT_TYPE_FEATURE: + break; } } +//--------------------------------------------------------------------+ +// SD Card +//--------------------------------------------------------------------+ + +int32_t msc_read_cb (uint32_t lba, void* buffer, uint32_t bufsize) +{ + (void) bufsize; + bool rc=false; +#if SD_FAT_VERSION >= 20000 +// rc = sd->card()->readSectors(lba, (uint8_t*) buffer, bufsize/512); +#else + rc = sd->card()->readBlocks(lba, (uint8_t*) buffer, bufsize/512); +#endif + + return rc ? bufsize : -1; +} + +// Callback invoked when received WRITE10 command. +// Process data in buffer to disk's storage and +// return number of written bytes (must be multiple of block size) +int32_t msc_write_cb (uint32_t lba, uint8_t* buffer, uint32_t bufsize) +{ + bool rc=false; + +#if SD_FAT_VERSION >= 20000 +// rc = sd->card()->writeSectors(lba, buffer, bufsize/512); +#else + rc = sd->card()->writeBlocks(lba, buffer, bufsize/512); +#endif + + return rc ? bufsize : -1; +} + +// Callback invoked when WRITE10 command is completed (status received and accepted by host). +// used to flush any pending cache. +void msc_flush_cb (void) +{ +#if SD_FAT_VERSION >= 20000 +// sd->card()->syncDevice(); +#else + sd->card()->syncBlocks(); +#endif + + // clear file system's cache to force refresh + //sd->cacheClear(); + +// sd_changed = true; +} + + bool getButtonI2cSlave (int slaveID, int inputID) { Wire.beginTransmission(slaveID); Wire.write(GetButton); @@ -327,6 +430,7 @@ void writeSystemINI() { ini.write( "hid report", "DeviceName", DeviceName.c_str()); ini.writeBool("hid report", "enableMouse", enableMouse); ini.writeBool("hid report", "enableKeyboard", enableKeyboard); + ini.writeBool("hid report", "enableMsc", enableMsc); ini.writeInt( "hid report", "UsagePage", hid_usage_page_val); ini.writeInt( "hid report", "Usage", hid_usage_val); ini.writeInt( "hid report", "ButtonCount", ButtonCount); @@ -338,11 +442,12 @@ void writeSystemINI() { void readSystemINI() { ini.open("/config/settings.ini"); - DeviceName = ini.read("hid report", "DeviceName"); + DeviceName = ini.read("hid report", "DeviceName"); hid_usage_page_val = ini.readInt( "hid report", "UsagePage"); hid_usage_val = ini.readInt( "hid report", "Usage"); enableMouse = ini.readBool("hid report", "enableMouse"); enableKeyboard = ini.readBool("hid report", "enableKeyboard"); + enableMsc = ini.readBool("hid report", "enableMsc"); ButtonCount = ini.readInt( "hid report", "ButtonCount"); HatCount = ini.readInt( "hid report", "HatCount"); AxisCount = ini.readInt( "hid report", "AxisCount"); @@ -350,29 +455,57 @@ void readSystemINI() { ADCResolution = ini.readInt( "hid report", "ADCResolution"); } +void setupSD() { + //////////////////////////////// + // FILESYSTEM INIT + fileSystemConfig.setCSPin(17); + fileSystemConfig.setSPISpeed(50*MHZ); + fileSystemConfig.setAutoFormat(false); + fileSystem->setConfig(fileSystemConfig); + fsOK = fileSystem->begin(); + //DBG_OUTPUT_PORT.println(fsOK ? F("Filesystem initialized.") : F("Filesystem init failed!")); +} + +void setupMSC () { + usb_msc.setMaxLun(1); + FSInfo64 fs_info; + fileSystem->info64(fs_info); + usb_msc.setID("test1", "test2", "0.0"); + usb_msc.setCapacity(fs_info.totalBytes/fs_info.blockSize, fs_info.blockSize); + usb_msc.setReadWriteCallback(msc_read_cb, msc_write_cb, msc_flush_cb); + usb_msc.setUnitReady(true); +} + + void setupINI() { - if (!fileSystem->exists("/config")) - fileSystem->mkdir("/config"); if (!fileSystem->exists("/config/settings.ini")) { File file = fileSystem->open("/config/settings.ini","w"); file.close(); - ini.open("/config/settings.ini"); - ini.write( "hid report", "DeviceName", "RP2040-HID"); - ini.writeBool("hid report", "enableMouse", false); - ini.writeBool("hid report", "enableKeyboard", false); - ini.writeInt( "hid report", "UsagePage", HID_USAGE_PAGE_DESKTOP); - ini.writeInt( "hid report", "Usage", HID_USAGE_DESKTOP_JOYSTICK); - ini.writeInt( "hid report", "ButtonCount", 64); - ini.writeInt( "hid report", "HatCount", 1); - ini.writeInt( "hid report", "AxisCount", 8); - ini.writeInt( "hid report", "AxisResolution", 11); - ini.writeInt( "hid report", "ADCResolution", 11); - + DeviceName= "RP2040-HID"; + enableMouse= false; + enableKeyboard=false; + hid_usage_page_val= HID_USAGE_PAGE_DESKTOP; + hid_usage_val= HID_USAGE_DESKTOP_JOYSTICK; + ButtonCount= 64; + HatCount= 1; + AxisCount= 8; + AxisResolution= 11; + ADCResolution= 11; + writeSystemINI(); + } else { + readSystemINI(); } - readSystemINI(); } -void setupUSB(bool begin) { +void setupDescripor() { + uint16_t maxBuffSize = MAX_HID_DESCRIPTOR_SIZE; + if (enableMouse) + maxBuffSize -= 79; +// MaxDeviceCount -= 1; + if (enableKeyboard) + maxBuffSize -= 67; +// MaxDeviceCount -= 1; + // reset the connection memset(inputs_id, 0, sizeof(inputs_id)); memset(usb_report, 0, sizeof(usb_report)); @@ -381,8 +514,12 @@ void setupUSB(bool begin) { memset(axis_start,0,sizeof(axis_start)); memset(total_bits,0,sizeof(total_bits)); usb_report_size=0; - largest_bits=0; - DeviceCount=0; + IdCount=0; + + //this buffer will be cleared and freed at the end since its a temp buffer + uint8_t* t_buffer = (uint8_t*)malloc(150); + memset(t_buffer,0,150); + size_t t_buffer_size; /* Calculate how many "devices" we will need to emulate @@ -393,22 +530,48 @@ void setupUSB(bool begin) { for (uint16_t i=ButtonCount, j=1; i>0; i-=MIN(device_max_button_count,i), j++) { inputs_id[j].buttons = i>device_max_button_count ? device_max_button_count:i; - DeviceCount = j>DeviceCount ? j:DeviceCount; + IdCount = j>IdCount ? j:IdCount; } for (uint16_t i=HatCount, j=1; i>0; i-=MIN(device_max_hat_count, i), j++) { inputs_id[j].hats = i>device_max_hat_count ? device_max_hat_count:i; - DeviceCount = j>DeviceCount ? j:DeviceCount; + IdCount = j>IdCount ? j:IdCount; } for (uint8_t i=AxisCount, j=1; i>0;i-=MIN(device_max_axis_count,i), j++) { inputs_id[j].axis = i>device_max_axis_count ? device_max_axis_count:i; - DeviceCount = j>DeviceCount ? j:DeviceCount; + IdCount = j>IdCount ? j:IdCount; } - for (uint16_t i = 1; i<=DeviceCount; i++) { - makeDescriptor(i, AxisResolution, inputs_id[i].axis, inputs_id[i].hats, inputs_id[i].buttons, usb_report, &usb_report_size); + for (uint16_t id = 1; id<=IdCount; id++) { + t_buffer_size = 0; + makeJoystickDescriptor(id, AxisResolution, inputs_id[id].axis, inputs_id[id].hats, inputs_id[id].buttons, t_buffer, &t_buffer_size); + if (usb_report_size + t_buffer_size < maxBuffSize) { + memmove(usb_report+usb_report_size, t_buffer, t_buffer_size); + usb_report_size += t_buffer_size; + } + memset(t_buffer, 0, 150); +// makeJoystickDescriptor(id, AxisResolution, inputs_id[id].axis, inputs_id[id].hats, inputs_id[id].buttons, usb_report, &usb_report_size); } - for (uint8_t rep = 1; rep < MAX_REPORT_ID; rep++) { - largest_bits = (largest_bits < total_bits[rep]) ? total_bits[rep] : largest_bits; + if (enableKeyboard) { + t_buffer_size = 0; + makeKeyboardDescriptor(IdCount+1,t_buffer, &t_buffer_size); + if (usb_report_size + t_buffer_size < MAX_HID_DESCRIPTOR_SIZE) { + memmove(usb_report+usb_report_size,t_buffer,t_buffer_size); + usb_report_size += t_buffer_size; + memset(t_buffer,0,150); + } } + if (enableMouse) { + t_buffer_size = 0; + makeMouseDescriptor(IdCount+2,t_buffer, &t_buffer_size); + if (usb_report_size + t_buffer_size < MAX_HID_DESCRIPTOR_SIZE) { + memmove(usb_report+usb_report_size,t_buffer,t_buffer_size); + usb_report_size += t_buffer_size; + memset(t_buffer,0,150); + } + } + free(t_buffer); +} + +void setUSB(bool begin) { TinyUSBDevice.detach(); TinyUSBDevice.clearConfiguration(); @@ -422,48 +585,21 @@ void setupUSB(bool begin) { if (begin) SerialTinyUSB.begin(115200); - if (DeviceCount > 0) { - hid_joystick.setPollInterval(1); - hid_joystick.setBootProtocol(HID_ITF_PROTOCOL_NONE); - hid_joystick.setReportDescriptor(usb_report, usb_report_size); - hid_joystick.setReportCallback(get_report_callback, set_report_callback); - if (hid_joystick.isValid()) - TinyUSBDevice.addInterface(hid_joystick); + if (IdCount > 0) { + // char name; + // name = DeviceName.concat("Mouse/Keyboard"); + usb_hid.setPollInterval(1); + usb_hid.setBootProtocol(HID_ITF_PROTOCOL_NONE); + usb_hid.setReportDescriptor(usb_report, usb_report_size); + usb_hid.setReportCallback(get_report_callback, set_report_callback); + /// usb_hid.setStringDescriptor(&name); + if (usb_hid.isValid()) + TinyUSBDevice.addInterface(usb_hid); else - hid_joystick.begin(); + usb_hid.begin(); } - - // HID report descriptor using TinyUSB's template - if(enableMouse || enableKeyboard) { - if(enableMouse && enableKeyboard) { - uint8_t desc_hid_report[] = { - TUD_HID_REPORT_DESC_KEYBOARD( VA_HID_REPORT_ID(1) ), - TUD_HID_REPORT_DESC_MOUSE ( VA_HID_REPORT_ID(2) ), - }; - char name = DeviceName.concat(" Mouse/Keyboard"); - hid_kbm.setStringDescriptor(&name); - hid_kbm.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); - } - if (enableMouse && !enableKeyboard) { - uint8_t desc_hid_report[] = { - TUD_HID_REPORT_DESC_MOUSE ( ), - }; - char name = DeviceName.concat(" Mouse"); - hid_kbm.setStringDescriptor(&name); - hid_kbm.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); - } - if (!enableMouse && enableKeyboard) { - uint8_t desc_hid_report[] = { - TUD_HID_REPORT_DESC_KEYBOARD( ), - }; - char name = DeviceName.concat(" Keyboard"); - hid_kbm.setStringDescriptor(&name); - hid_kbm.setReportDescriptor(desc_hid_report, sizeof(desc_hid_report)); - } - if (hid_kbm.isValid()) - TinyUSBDevice.addInterface(hid_kbm); - else - hid_kbm.begin(); + if (enableMsc) { + usb_msc.begin(); } TinyUSBDevice.attach(); @@ -481,22 +617,16 @@ void setup() { // - mbed rp2040 TinyUSB_Device_Init(0); #endif - fileSystemConfig.setCSPin(17); - - //////////////////////////////// - // FILESYSTEM INIT - fileSystemConfig.setAutoFormat(false); - fileSystem->setConfig(fileSystemConfig); - fsOK = fileSystem->begin(); - //DBG_OUTPUT_PORT.println(fsOK ? F("Filesystem initialized.") : F("Filesystem init failed!")); + setupSD(); + setupMSC(); setupINI(); - setupUSB(true); - + setupDescripor(); + setUSB(true); #ifdef ARDUINO_RASPBERRY_PI_PICO_W setupWifi(); configTime(3 * 3600, 1, "nl.pool.ntp.org", "0.europe.pool.ntp.org"); #endif - +//u8g2.begin(); } @@ -575,12 +705,12 @@ void loop() //} if (TinyUSBDevice.ready()) { if (readyToUpdate[report] ){ - hid_joystick.sendReport(report, (void *)reports[report], total_bits[report]/8); + usb_hid.sendReport(report, (void *)reports[report], total_bits[report]/8); memcpy(old_reports[report],(void *)reports[report],total_bits[report]/8); readyToUpdate[report] = false; } report++; - if (report >= DeviceCount) + if (report >= IdCount) report = 1; } } @@ -634,10 +764,14 @@ void setup1() //setLedI2cSlave(1,0x21,0); //setLedI2cSlave(2,0x21,0); - joystick_client.Setup(); + fakeThrottle.setup(); #if MAX_UPDATES_PER_SECOND +#ifndef USE_NEW static x52::util::RateLimiter rate_limiter; +#else + static RateLimiter rate_limiter; +#endif #endif } @@ -702,23 +836,32 @@ void loop1() // after the delay introduced by the rate limiter. // Without PrepareForPoll it can take up to 1500ms for the joystick // to respond to our PollJoystickState call. - joystick_client.PrepareForPoll(); + //WfakeThrottle.PrepareForPoll(); #if MAX_UPDATES_PER_SECOND +#ifndef USE_NEW static x52::util::RateLimiter rate_limiter; +#else + static RateLimiter rate_limiter; +#endif unsigned long d = rate_limiter.MicrosTillNextUpdate(); if (d == 0) { #endif // Query the state of the X52 Pro joystick via the PS/2 connection and send it to // the PC as the state of the USB joystick emulated by the Arduino-compatible board. +#ifndef USE_NEW x52::pro::JoystickConfig cfg; x52::pro::JoystickState state; +#else + JoystickConfig cfg; + JoystickState state; +#endif cfg.led_brightness = LED; - auto timeout_micros = joystick_client.PollJoystickState(state, cfg); + auto timeout_micros = fakeThrottle.PollJoystickState(state, cfg); if (timeout_micros) { //X52DebugPrintln("PollJoystickState failed"); //delayMicroseconds(timeout_micros); @@ -726,33 +869,55 @@ void loop1() } bool empty = false; - +#ifndef USE_NEW bool p2u = bool(state.pov_2 & x52::Up); bool p2d = bool(state.pov_2 & x52::Down); bool p2l = bool(state.pov_2 & x52::Left); bool p2r = bool(state.pov_2 & x52::Right); - +#else + bool p2u = bool(state.pov_2 & Up); + bool p2d = bool(state.pov_2 & Down); + bool p2l = bool(state.pov_2 & Left); + bool p2r = bool(state.pov_2 & Right); +#endif + bool M1 = 0; bool M2 = 0; bool M3 = 0; switch (state.mode) { - case x52::ModeUndefined: +#ifndef USE_NEW + case x52::ModeUndefined: +#else + case ModeUndefined: +#endif M1 = 0; M2 = 0; M3 = 0; break; - case x52::Mode1: +#ifndef USE_NEW + case x52::Mode1: +#else + case Mode1: +#endif M1 = 1; M2 = 0; M3 = 0; break; +#ifndef USE_NEW case x52::Mode2: +#else + case Mode2: +#endif M1 = 0; M2 = 1; M3 = 0; break; +#ifndef USE_NEW case x52::Mode3: +#else + case Mode3: +#endif M1 = 0; M2 = 0; M3 = 1; @@ -769,7 +934,8 @@ switch (state.mode) { //Serial.println(getAxisI2cSlave(0x21, 5),BIN); if (readyToUpdate[1]== false) { - memset((void *)reports[1], 0, largest_bits/8); + //memset((void *)reports[1], 0, largest_bits/8); + memset((void *)reports[1], 0, sizeof(reports[1])); switch (state.pov_1) { case 0b0001: set_hat(reports[1], 1, 0 ,0b0011); break; case 0b0011: set_hat(reports[1], 1, 0 ,0b0100); break; @@ -793,8 +959,8 @@ if (readyToUpdate[1]== false) { set_button(reports[1], 1, 3, state.button_b); set_button(reports[1], 1, 4, state.button_c); set_button(reports[1], 1, 5, state.pinkie_switch); - set_button(reports[1], 1, 6, getButtonI2cSlave(0x21, Button1)); - set_button(reports[1], 1, 7, getButtonI2cSlave(0x21, Button3)); + set_button(reports[1], 1, 6, getButtonI2cSlave(0x21, 0)); + set_button(reports[1], 1, 7, getButtonI2cSlave(0x21, 2)); set_button(reports[1], 1, 8, state.button_t1); set_button(reports[1], 1, 9, state.button_t2); set_button(reports[1], 1, 10, state.button_t3); @@ -802,25 +968,25 @@ if (readyToUpdate[1]== false) { set_button(reports[1], 1, 12, state.button_t5); set_button(reports[1], 1, 13, state.button_t6); set_button(reports[1], 1, 14, state.trigger_stage_2); - set_button(reports[1], 1, 15, getButtonI2cSlave(0x21, Button10)); + set_button(reports[1], 1, 15, getButtonI2cSlave(0x21, 9)); set_button(reports[1], 1, 16, empty); // scroll up set_button(reports[1], 1, 17, empty); //scroll down - set_button(reports[1], 1, 18, getButtonI2cSlave(0x21, Button11)); + set_button(reports[1], 1, 18, getButtonI2cSlave(0x21, 10)); set_button(reports[1], 1, 19, p2u); set_button(reports[1], 1, 20, p2r); set_button(reports[1], 1, 21, p2d); set_button(reports[1], 1, 22, p2l); - set_button(reports[1], 1, 23, getButtonI2cSlave(0x21, Button7)); - set_button(reports[1], 1, 24, getButtonI2cSlave(0x21, Button6)); - set_button(reports[1], 1, 25, getButtonI2cSlave(0x21, Button5)); - set_button(reports[1], 1, 26, getButtonI2cSlave(0x21, Button4)); + set_button(reports[1], 1, 23, getButtonI2cSlave(0x21, 6)); + set_button(reports[1], 1, 24, getButtonI2cSlave(0x21, 5)); + set_button(reports[1], 1, 25, getButtonI2cSlave(0x21, 4)); + set_button(reports[1], 1, 26, getButtonI2cSlave(0x21, 3)); set_button(reports[1], 1, 27, M1); set_button(reports[1], 1, 28, M2); set_button(reports[1], 1, 29, M3); - set_button(reports[1], 1, 30, getButtonI2cSlave(0x21, Button2)); + set_button(reports[1], 1, 30, getButtonI2cSlave(0x21, 1)); - // 15/14 scroll mfd - // 9/8 pg scroll + // pin 15/14 scroll mfd + // pin 9/8 pg scroll set_button(reports[1], 1, 31, !digitalRead(10)); //function