Skip to content

Instantly share code, notes, and snippets.

@ChaitanyaKatti
Last active June 27, 2025 20:40
Show Gist options
  • Select an option

  • Save ChaitanyaKatti/e43e07d745836d1bf31e8e4a669dbd7c to your computer and use it in GitHub Desktop.

Select an option

Save ChaitanyaKatti/e43e07d745836d1bf31e8e4a669dbd7c to your computer and use it in GitHub Desktop.
ESP32 + FlyFky Transmiter, PPM to BLE Gamepad for simulation
// 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);
}
}
@ChaitanyaKatti
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment