#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Joystick.h>
// =========================================================
// HID_Joystick (RP2040 core) — API corect
// Constructorul nu primeste argumente, axele se seteaza
// prin metode separate, fara sendState explicit
// =========================================================
// =========================================================
// MAPARE BUTOANE
// =========================================================
// B0 = Master1 (pin 14)
// B1 = Master2 (pin 15)
// B2 = Misc S2 (pin 2)
// B3 = Misc S3 (pin 3)
// B4 = Misc S4 (pin 4)
// B5 = Misc S5 (pin 5)
// B6 = Misc S6 (pin 6)
// B7 = Misc S7 (pin 7)
// B8 = Misc S8 (pin 8)
// B9 = Misc S9 (pin 9)
// B10 = Magneto OFF
// B11 = Magneto R
// B12 = Magneto L
// B13 = Magneto BOTH
// B14 = Magneto START
// B15 = Display page toggle (encoder SW)
// =========================================================
// PINS
// =========================================================
const int master1Pin = 14;
const int master2Pin = 15;
const int miscPins[8] = {2, 3, 4, 5, 6, 7, 8, 9};
const int encClkPin = 11;
const int encDtPin = 12;
const int encSwPin = 13;
const int joyVertPin = 26;
const int joyHorzPin = 27;
// =========================================================
// ENCODER — tabel tranzitii
// =========================================================
const int8_t ENC_TABLE[4][4] = {
{ 0, -1, 0, +1},
{+1, 0, -1, 0},
{ 0, +1, 0, -1},
{-1, 0, +1, 0}
};
int magnetoStep = 0;
int lastEncState = 0;
int lastMagnetoBtn = 0;
const char* magnetoNames[5] = {"OFF", "R", "L", "BOTH", "START"};
// =========================================================
// DISPLAY
// =========================================================
LiquidCrystal_I2C lcd(0x27, 20, 4);
int displayPage = 0;
bool lastEncSwState = HIGH;
unsigned long lastEncSwPress = 0;
unsigned long lastLcdUpdate = 0;
// =========================================================
// STARI BUTOANE
// =========================================================
bool lastM1 = HIGH;
bool lastM2 = HIGH;
bool lastMisc[8];
// =========================================================
// ALTELE
// =========================================================
bool gearDown = false;
String camDir = "CENTER";
// =========================================================
// HELPER LCD
// =========================================================
void lcdField(int col, int row, const char* text, int width) {
lcd.setCursor(col, row);
int len = strlen(text);
for (int i = 0; i < width; i++)
lcd.print(i < len ? (char)text[i] : ' ');
}
void lcdField(int col, int row, String text, int width) {
lcdField(col, row, text.c_str(), width);
}
// =========================================================
// UPDATE ENCODER
// =========================================================
void updateEncoder() {
int clk = digitalRead(encClkPin);
int dt = digitalRead(encDtPin);
int state = (clk << 1) | dt;
if (state == lastEncState) return;
int delta = ENC_TABLE[lastEncState][state];
lastEncState = state;
if (delta == 0) return;
int newStep = magnetoStep + delta;
if (newStep < 0) newStep = 0;
if (newStep > 4) newStep = 4;
if (newStep == magnetoStep) return;
// Elibereaza butonul anterior
Joystick.setButton(10 + lastMagnetoBtn, 0);
magnetoStep = newStep;
lastMagnetoBtn = magnetoStep;
// Apasa butonul nou
Joystick.setButton(10 + magnetoStep, 1);
}
// =========================================================
// UPDATE LCD
// =========================================================
void updateLcd(bool m1, bool m2, int miscOn) {
char pgBuf[4];
snprintf(pgBuf, sizeof(pgBuf), "[%d]", displayPage + 1);
lcdField(17, 0, pgBuf, 3);
switch (displayPage) {
case 0:
lcdField(0, 0, "=== FLIGHT === ", 17);
lcdField(0, 1, m1 == LOW ? "M1: ON " : "M1: OFF ", 8);
lcdField(9, 1, m2 == LOW ? "M2: ON " : "M2: OFF ", 8);
lcdField(0, 2, "Magneto: ", 9);
lcdField(9, 2, magnetoNames[magnetoStep], 6);
lcdField(0, 3, " ", 20);
break;
case 1:
lcdField(0, 0, "=== GEAR/MAG === ", 17);
lcdField(0, 1, "Gear: ", 6);
lcdField(6, 1, gearDown ? "DOWN " : "UP ", 5);
lcdField(0, 2, "Magneto: ", 9);
lcdField(9, 2, magnetoNames[magnetoStep], 6);
lcdField(0, 3, " ", 20);
break;
case 2:
lcdField(0, 0, "=== CAMERA === ", 17);
lcdField(0, 1, "Dir: ", 5);
lcdField(5, 1, camDir, 12);
lcdField(0, 2, " ", 20);
lcdField(0, 3, " ", 20);
break;
case 3:
lcdField(0, 0, "=== MISC === ", 17);
{
char buf[8];
snprintf(buf, sizeof(buf), "Act:%d/8", miscOn);
lcdField(0, 1, buf, 8);
}
for (int i = 0; i < 4; i++) {
lcd.setCursor(i * 5, 2);
lcd.print("S"); lcd.print(i + 2); lcd.print(":");
lcd.print(digitalRead(miscPins[i]) == LOW ? "1" : "0");
lcd.print(" ");
}
for (int i = 4; i < 8; i++) {
lcd.setCursor((i - 4) * 5, 3);
lcd.print("S"); lcd.print(i + 2); lcd.print(":");
lcd.print(digitalRead(miscPins[i]) == LOW ? "1" : "0");
lcd.print(" ");
}
break;
case 4:
lcdField(0, 0, "=== ALL === ", 17);
{
char r1[20], r2[20];
snprintf(r1, sizeof(r1), "M1:%-3s M2:%-3s G:%-2s",
m1 == LOW ? "ON" : "OFF",
m2 == LOW ? "ON" : "OFF",
gearDown ? "DN" : "UP");
snprintf(r2, sizeof(r2), "Mag:%-5s Mis:%d/8",
magnetoNames[magnetoStep], miscOn);
lcdField(0, 1, r1, 20);
lcdField(0, 2, r2, 20);
}
lcdField(0, 3, "Cam: ", 5);
lcdField(5, 3, camDir, 12);
break;
}
}
// =========================================================
// SETUP
// =========================================================
void setup() {
pinMode(master1Pin, INPUT_PULLUP);
pinMode(master2Pin, INPUT_PULLUP);
for (int i = 0; i < 8; i++) {
pinMode(miscPins[i], INPUT_PULLUP);
lastMisc[i] = HIGH;
}
pinMode(encClkPin, INPUT_PULLUP);
pinMode(encDtPin, INPUT_PULLUP);
pinMode(encSwPin, INPUT_PULLUP);
Wire.setSDA(0);
Wire.setSCL(1);
Wire.begin();
lcd.init();
lcd.backlight();
lastEncState = (digitalRead(encClkPin) << 1) | digitalRead(encDtPin);
// HID_Joystick begin — fara argumente
Joystick.begin();
// Buton magneto initial apăsat (OFF = B10)
Joystick.setButton(10, 1);
lastMagnetoBtn = 0;
lcd.clear();
lcd.setCursor(3, 0); lcd.print("COCKPIT v2.1");
lcd.setCursor(1, 1); lcd.print("HID Gamepad Ready");
lcd.setCursor(0, 2); lcd.print("Enc: magneto steps");
lcd.setCursor(0, 3); lcd.print("Enc SW: page toggle");
delay(2000);
lcd.clear();
}
// =========================================================
// LOOP
// =========================================================
void loop() {
// --- Master 1 (B0) ---
bool m1 = digitalRead(master1Pin);
if (m1 != lastM1) {
Joystick.setButton(0, m1 == LOW ? 1 : 0);
lastM1 = m1;
}
// --- Master 2 (B1) ---
bool m2 = digitalRead(master2Pin);
if (m2 != lastM2) {
Joystick.setButton(1, m2 == LOW ? 1 : 0);
lastM2 = m2;
}
// --- Misc switches (B2..B9) ---
int miscOn = 0;
for (int i = 0; i < 8; i++) {
bool s = digitalRead(miscPins[i]);
if (s == LOW) miscOn++;
if (s != lastMisc[i]) {
Joystick.setButton(2 + i, s == LOW ? 1 : 0);
lastMisc[i] = s;
}
}
// --- Encoder rotire → magneto ---
updateEncoder();
// --- Encoder SW → schimba pagina LCD ---
bool encSw = digitalRead(encSwPin);
if (encSw == LOW && lastEncSwState == HIGH) {
if (millis() - lastEncSwPress > 250) {
displayPage = (displayPage + 1) % 5;
lastEncSwPress = millis();
}
}
lastEncSwState = encSw;
// --- Joystick axe (0..4095 -> -32767..+32767) ---
int rawX = analogRead(joyHorzPin);
int rawY = analogRead(joyVertPin);
int axisX = map(rawX, 0, 4095, -32767, 32767);
int axisY = map(rawY, 0, 4095, -32767, 32767);
// HID_Joystick foloseste X() si Y() pentru axe
Joystick.X(axisX);
Joystick.Y(axisY);
// Deadzone pentru display
bool right = axisX > 10000;
bool left = axisX < -10000;
bool up = axisY < -10000;
bool down = axisY > 10000;
if (up && right) camDir = "UP-RIGHT";
else if (up && left) camDir = "UP-LEFT";
else if (down && right) camDir = "DOWN-RIGHT";
else if (down && left) camDir = "DOWN-LEFT";
else if (up) camDir = "UP";
else if (down) camDir = "DOWN";
else if (right) camDir = "RIGHT";
else if (left) camDir = "LEFT";
else camDir = "CENTER";
// --- LCD refresh ---
if (millis() - lastLcdUpdate > 150) {
updateLcd(m1, m2, miscOn);
lastLcdUpdate = millis();
}
delay(5);
}