아!두이노, 라즈베리, 코딩 노가다

ESP32C6_BLE_Gamepad 본문

ESP32

ESP32C6_BLE_Gamepad

아이스뭐라카노 2026. 9. 15. 20:26
반응형

 

/*
 * ESP32-C6 BLE 게임패드 스케치  (핀맵: board5 기준)
 * 보드: ESP32-C6-DevKitC-1
 * 라이브러리: lemmingDev/ESP32-BLE-Gamepad + h2zero/NimBLE-Arduino
 *   ※ ESP32 Arduino core 3.x (C6 지원) + 최신 NimBLE-Arduino 필요
 *
 * ── 배선 요약 ─────────────────────────────────────────────
 * 디지털 버튼 (각 버튼: GPIO ↔ GND, 코드에서 INPUT_PULLUP → 누르면 LOW)
 *   A  = GPIO20   B  = GPIO19   X  = GPIO18   Y  = GPIO9(BOOT)
 *   L1 = GPIO16   R1 = GPIO17   L2 = GPIO15   R2 = GPIO23
 *   Select = GPIO10   Start = GPIO11   L3 = GPIO22   R3 = GPIO21
 *   D-pad: Up=GPIO4  Down=GPIO5  Left=GPIO6  Right=GPIO7
 *
 * 아날로그 스틱 (ADC1 전용, 포텐셔미터 양단 = 3.3V / GND, 와이퍼 = 핀)
 *   왼쪽  X=GPIO0(ADC0)  Y=GPIO1(ADC1)
 *   오른쪽 X=GPIO2(ADC2)  Y=GPIO3(ADC3)
 *
 * ⚠ 부팅 순간 누르지 말 것: Y(GPIO9=BOOT), 그리고 strap 핀 Up/Down(GPIO4/5)·L2(GPIO15)
 * ⚠ 스틱 전원은 반드시 3.3V (5V 금지 — ADC 손상)
 * ─────────────────────────────────────────────────────────
 */

#include <Arduino.h>
#include <BleGamepad.h>

BleGamepad bleGamepad("ESP32-C6 Gamepad", "DIY", 100);

// ===== 디지털 버튼 → GPIO =====
struct BtnMap { uint8_t pin; uint8_t button; };
BtnMap buttons[] = {
    { 20, BUTTON_1  },  // A
    { 19, BUTTON_2  },  // B
    { 18, BUTTON_3  },  // X
    {  9, BUTTON_4  },  // Y  (GPIO9 = BOOT)
    { 16, BUTTON_5  },  // L1
    { 17, BUTTON_6  },  // R1
    { 15, BUTTON_7  },  // L2 (strap)
    { 23, BUTTON_8  },  // R2
    { 10, BUTTON_9  },  // Select
    { 11, BUTTON_10 },  // Start
    { 22, BUTTON_11 },  // L3
    { 21, BUTTON_12 },  // R3
};
const int NUM_BUTTONS = sizeof(buttons) / sizeof(buttons[0]);

// ===== D-pad(햇) → GPIO =====
#define PIN_DPAD_UP     4
#define PIN_DPAD_DOWN   5
#define PIN_DPAD_LEFT   6
#define PIN_DPAD_RIGHT  7

// ===== 아날로그 스틱 → ADC1 핀 =====
#define PIN_LX 0
#define PIN_LY 1
#define PIN_RX 2
#define PIN_RY 3
const int AXIS_CENTER = 16383;  // 0~32767 중앙
const int DEADZONE    = 1500;   // 중앙 흔들림 무시 폭

void setup()
{
    Serial.begin(115200);

    for (int i = 0; i < NUM_BUTTONS; i++)
        pinMode(buttons[i].pin, INPUT_PULLUP);

    pinMode(PIN_DPAD_UP,    INPUT_PULLUP);
    pinMode(PIN_DPAD_DOWN,  INPUT_PULLUP);
    pinMode(PIN_DPAD_LEFT,  INPUT_PULLUP);
    pinMode(PIN_DPAD_RIGHT, INPUT_PULLUP);

    analogReadResolution(12);          // 0~4095
    analogSetAttenuation(ADC_11db);    // ~0~3.3V 풀레인지

    BleGamepadConfiguration cfg;
    cfg.setButtonCount(12);
    cfg.setHatSwitchCount(1);
    cfg.setAutoReport(false);          // loop 끝에서 한 번에 전송
    bleGamepad.begin(&cfg);

    Serial.println("ESP32-C6 BLE Gamepad 시작 - 호스트에서 페어링하세요.");
}

// 아날로그 읽기 + 데드존 → 0~32767
int readAxis(uint8_t pin)
{
    int val = map(analogRead(pin), 0, 4095, 0, 32767);
    if (abs(val - AXIS_CENTER) < DEADZONE) val = AXIS_CENTER;
    return val;
}

void loop()
{
    if (!bleGamepad.isConnected()) { delay(50); return; }

    // --- 버튼 (풀업이라 LOW = 눌림) ---
    for (int i = 0; i < NUM_BUTTONS; i++) {
        if (digitalRead(buttons[i].pin) == LOW)
            bleGamepad.press(buttons[i].button);
        else
            bleGamepad.release(buttons[i].button);
    }

    // --- D-pad → 8방향 햇 ---
    bool up    = digitalRead(PIN_DPAD_UP)    == LOW;
    bool down  = digitalRead(PIN_DPAD_DOWN)  == LOW;
    bool left  = digitalRead(PIN_DPAD_LEFT)  == LOW;
    bool right = digitalRead(PIN_DPAD_RIGHT) == LOW;

    signed char hat = HAT_CENTERED;
    if      (up   && right) hat = HAT_UP_RIGHT;
    else if (up   && left)  hat = HAT_UP_LEFT;
    else if (down && right) hat = HAT_DOWN_RIGHT;
    else if (down && left)  hat = HAT_DOWN_LEFT;
    else if (up)            hat = HAT_UP;
    else if (down)          hat = HAT_DOWN;
    else if (left)          hat = HAT_LEFT;
    else if (right)         hat = HAT_RIGHT;
    bleGamepad.setHat1(hat);

    // --- 아날로그 스틱 ---
    int lx = readAxis(PIN_LX);
    int ly = readAxis(PIN_LY);
    int rx = readAxis(PIN_RX);
    int ry = readAxis(PIN_RY);
    // setAxes(x, y, z, rx, ry, rz, slider1, slider2)
    bleGamepad.setAxes(lx, ly, 0, rx, ry, 0, 0, 0);

    bleGamepad.sendReport();   // 모아둔 상태 한 번에 전송
    delay(10);                 // ~100Hz
}

 

 

https://github.com/lemmingDev/ESP32-BLE-Gamepad

 

GitHub - lemmingDev/ESP32-BLE-Gamepad: Bluetooth LE Gamepad library for the ESP32

Bluetooth LE Gamepad library for the ESP32. Contribute to lemmingDev/ESP32-BLE-Gamepad development by creating an account on GitHub.

github.com


"strap"은 **strapping pin(스트래핑 핀)**을 줄여 쓴 말입니다. 앞의 핀맵에서 GPIO4·5·15 같은 핀에 "strap"이라고 붙여둔 게 이것입니다.

의미는 이렇습니다. ESP32 계열 칩은 **전원이 켜지는 바로 그 순간(리셋이 풀리는 시점)**에 특정 핀 몇 개의 전압이 HIGH인지 LOW인지를 읽어서, 부팅 방식을 결정합니다. 예를 들면 "일반 실행 모드로 부팅할지, 펌웨어 다운로드 모드로 들어갈지", "플래시 전압을 몇 V로 쓸지" 같은 설정을 이 핀들의 초기 상태로 정합니다. 이렇게 부팅 옵션을 정하는 데 쓰이는 핀들을 스트래핑 핀이라고 부릅니다. "strap"은 원래 회로에서 핀을 특정 레벨로 '묶어둔다(고정한다)'는 뜻에서 온 말입니다.

중요한 건 **읽는 시점이 부팅 순간뿐**이라는 겁니다. 부팅이 끝나고 나면 그 핀들은 다시 평범한 일반 GPIO로 쓸 수 있습니다. 그래서 스트래핑 핀에 버튼이나 센서를 연결해도 대부분 문제없이 동작합니다.

문제가 되는 건 딱 한 경우입니다. **전원을 켜는(또는 리셋하는) 바로 그 순간에 그 핀이 원치 않는 레벨로 눌려 있으면**, 칩이 부팅 옵션을 잘못 읽어서 엉뚱한 모드로 들어갑니다. 게임패드로 치면, 스트래핑 핀에 물린 버튼을 누른 채로 전원을 켜면 부팅이 안 되거나 다운로드 모드로 빠질 수 있다는 뜻입니다. 그래서 앞에서 "strap 핀에 연결된 버튼은 부팅 순간에 누르지 마세요"라고 안내드린 것입니다.

ESP32-C6의 스트래핑 핀은 GPIO4·5·8·9·15입니다. 이 중 GPIO9는 BOOT 버튼과 연결돼 있어서(다운로드 모드 진입용) 가장 민감하고, GPIO8은 부팅 시 HIGH여야 하며 온보드 RGB LED와도 엮여 있습니다. 나머지 GPIO4·5·15는 JTAG/플래시 관련 설정에 쓰이는데 일반 게임패드 용도에는 영향이 거의 없어, 켤 때만 안 눌리면 사실상 문제없습니다.

'ESP32' 카테고리의 다른 글

ESP32 C6 보드 핀맵  (0) 2026.09.15
ESP32를 출시별 정리  (0) 2026.09.04