Last active
June 27, 2025 20:40
-
-
Save ChaitanyaKatti/e43e07d745836d1bf31e8e4a669dbd7c to your computer and use it in GitHub Desktop.
ESP32 + FlyFky Transmiter, PPM to BLE Gamepad for simulation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // 8 Channel PPM output from flysky fs-i6X tranmitter, | |
| // Ch1/2/3/4 - Default | |
| // SwA - Ch5 | |
| // SwB - Ch6 | |
| // SwC - Ch7 | |
| // SwD - Ch8 | |
| #define numChannels 8 | |
| #define axisMin 0 | |
| #define axisMax 32767 // Seems that this should be set to 127 for Android devices | |
| #define PPMmin 590 // Minimimum width on a HIGH pulse in PPM, in milliseconds, ideally shoudl be 600 | |
| #define PPMmax 1610 // Maximum ... | |
| #define PPMthreshold 1100 // Threshold for switches, above which the button is pressed | |
| #define PPMin 4 // PPM input pin | |
| #include <BleGamepad.h> | |
| #include <Arduino.h> | |
| BleGamepad bleGamepad("FlySky FS-i6X", "ESP", 100); | |
| int channel[numChannels]; | |
| void setup() { | |
| Serial.begin(115200); | |
| pinMode(PPMin, INPUT); | |
| pinMode(LED_BUILTIN, OUTPUT); | |
| BleGamepadConfiguration bleGamepadConfig; | |
| bleGamepadConfig.setAxesMin(axisMin); | |
| bleGamepadConfig.setAxesMax(axisMax); | |
| bleGamepadConfig.setButtonCount(4); | |
| bleGamepadConfig.setHatSwitchCount(1); | |
| bleGamepad.begin(&bleGamepadConfig); | |
| } | |
| void PPM() { | |
| // Waits ultil synchronize arrives ~ 4 miliseconds pulse | |
| // Min channel width : 0.6ms | |
| // Max channel width : 1.6ms | |
| // Gap between channels : 0.4ms | |
| // sync pulse width : 4 ms | |
| // Total time for 8 channels : (1.6 + 0.4) * 8 = 16ms | |
| // Full width of 8 channel PPM signal: 16 + 4 = 20ms | |
| // Wait until sync pulse arrives | |
| // If all channels are fully active, then sync pulse shirnks to 3.6ms instead of 4ms, experimentally determined. | |
| while (pulseIn(PPMin, HIGH) < 3600){ | |
| // Do nothing | |
| } | |
| for (int i = 1; i <= numChannels; i++) { | |
| channel[i - 1] = pulseIn(PPMin, HIGH); | |
| } | |
| // Debugging | |
| // for (int i = 1; i <= numChannels; i++) | |
| // { | |
| // Serial.print(channel[i - 1]); | |
| // Serial.print("\t\t"); | |
| // } | |
| // Serial.println(); | |
| } | |
| // roll : channel 1 : rightThumbX | |
| // pitch : channel 2 : rightThumbY | |
| // throttle : channel 3 : leftThumbY | |
| // yaw : channel 4 : leftThumbX | |
| // bleGamepad.setAxes(Left Thumb X, Left Thumb Y, Right Thumb X, Right Thumb Y, Left Trigger, Right Trigger, Slider 1, Slider 2); | |
| // What Gamepad tester recognizes | |
| // https://hardwaretester.com/gamepad | |
| // Axis 0 LeftThumbX yaw | |
| // Axis 1 LeftThumbY thr | |
| // Axis 2 RightThumbX roll | |
| // Axis 3 LeftTrigger SwA | |
| // Axis 4 RightTrigger SwB | |
| // Axis 5 RightTriggerY pitch | |
| // Axis 6 Slider2 SwD | |
| // Axsi 7 | |
| // Axis 8 | |
| void setAxes(){ | |
| // Set axes data | |
| bleGamepad.setRightThumb( | |
| map(channel[0], PPMmin, PPMmax, axisMin, axisMax), // roll | |
| map(channel[1], PPMmin, PPMmax, axisMin, axisMax)); // pitch | |
| bleGamepad.setLeftThumb( | |
| map(channel[3], PPMmin, PPMmax, axisMin, axisMax), // yaw | |
| map(channel[2], PPMmin, PPMmax, axisMin, axisMax)); // throttle | |
| bleGamepad.setLeftTrigger( | |
| map(channel[4], PPMmin, PPMmax, axisMin, axisMax)); // SwA | |
| bleGamepad.setRightTrigger( | |
| map(channel[5], PPMmin, PPMmax, axisMin, axisMax)); // SwB | |
| bleGamepad.setSliders( | |
| map(channel[6], PPMmin, PPMmax, axisMin, axisMax), // SwC // ------------- doesn't work --------------- // | |
| map(channel[7], PPMmin, PPMmax, axisMin, axisMax)); // SwD | |
| // This also works, but order layout is dfferent | |
| // bleGamepad.setAxes( | |
| // map(channel[0], PPMmin, PPMmax, axisMin, axisMax), // roll | |
| // map(channel[1], PPMmin, PPMmax, axisMin, axisMax), // pitch | |
| // map(channel[2], PPMmin, PPMmax, axisMin, axisMax), // throttle | |
| // map(channel[3], PPMmin, PPMmax, axisMin, axisMax), // yaw | |
| // map(channel[4], PPMmin, PPMmax, axisMin, axisMax), // SwA | |
| // map(channel[5], PPMmin, PPMmax, axisMin, axisMax), // SwB | |
| // map(channel[6], PPMmin, PPMmax, axisMin, axisMax), // SwC // ------------- doesn't work --------------- // | |
| // map(channel[7], PPMmin, PPMmax, axisMin, axisMax) // SwD | |
| // ); | |
| } | |
| void setButtons(){ | |
| if (channel[4] > PPMthreshold) bleGamepad.press(BUTTON_1); | |
| else bleGamepad.release(BUTTON_1); | |
| if (channel[5] > PPMthreshold) bleGamepad.press(BUTTON_2); | |
| else bleGamepad.release(BUTTON_2); | |
| if (channel[6] > PPMthreshold) bleGamepad.press(BUTTON_3); | |
| else bleGamepad.release(BUTTON_3); | |
| if (channel[7] > PPMthreshold) bleGamepad.press(BUTTON_4); | |
| else bleGamepad.release(BUTTON_4); | |
| } | |
| void loop() { | |
| if (bleGamepad.isConnected()) { | |
| digitalWrite(LED_BUILTIN, HIGH); | |
| PPM(); | |
| setAxes(); | |
| setButtons(); | |
| } | |
| else { | |
| // Blink LED | |
| digitalWrite(LED_BUILTIN, HIGH); delay(300); | |
| digitalWrite(LED_BUILTIN, LOW); delay(300); | |
| } | |
| } |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Check out Gamepad Tester : https://hardwaretester.com/gamepad
This code somehow produces different output with Gamepad tester when tested on Android vs Windows PC
Also seems like Gamepad tester doesn't recognize Slider2 set by bleGamepad