Tablero de controles para simulador de vuelo

Buenas gente, les traigo un proyecto casi terminado (me faltan ensamblar cosas) pero de código estoy contento con lo que tengo. Ya llevo casi dos años en esto, entre mi trabajo rotativo y la facultad y mi poco conocimiento de arduino fue lento el tema.

Aun así quiero compartirlo con ustedes ya que @IgnoranteAbsoluto me ofreció que lo comparta con ustedes y entre todos podemos tal vez mejorar el código y ampliar a mas funciones que el programa ofrece.

Una cosa, este programa que utilice no sirve para el comando de superficies de control de vuelo (osea, control de alas, y cola del avión, lo que el piloto hace con el volante y los pedales)

EMPECEMOS
lo dividire en 3 partes:

  • Software
  • Hardware
  • Codigo

POR EL LADO DEL SOFTWARE:

El simulador:
Este programa es solo apto para el Flight Simulator X

El "programa":
Se llama Link2fs (la pagina del creador desaparecio, quedan algunos hilos de este tema en la pagina mycockpit.org) este programa a mi entender, interpreta los códigos que le enviamos por puerto serie con el arduino y manda la función al FSX.

Algunas caracteristicas del link2fs:

  • El mismo permite conectar hasta 5 tarjetas arduino (nunca lo eh probado solo utilizo una)
  • No permite modificar el baudrate del puerto serie esta preseteado a 115200 (primera imagen)
  • Desde arduino se envian codigos por puerto serie para cada función (segunda imagen)
  • Se púeden obtener datos del simulador de vuelo que el mismo link2fs imprime por el puerto serie, estos se pueden elegir cuales queremos recibir para después darle uso con el arduino.

Imagenes del Link2fs:

AHORA EL HARDWARE:
Ah nivel arduino uso un mega por la cantidad de conexiones para lo demás, es a gusto creo yo, como se ve en las imágenes anteriores y muchos datos que se pueden extraer o ingresar al link2fs, pero igual les comparto lo que arme.

se los pongo con fotos y descripciones de ellas.

Esta estructura roja al igual que las palancas la armo mi amigo, futuro piloto y dueño/usuario del simulador (yo soy el de las ideas, programación electrica y electronica del mismo) aca utilize 6 encoders accionados con poleas de teflon conectados con O-rings a cada palanca.



.

Aca esta el comando terminado (siento no tener mas fotos de la construcción de las demás botoneras)
Aun asi les describo las funciones de izquierda a derecha:
Arriba:

  • Palancas negras = acelerador motor 1 y 2;
  • Palancas azules = paso de helice motor 1 y 2;
  • Palancas rojas = Mezcla aire/combustible motor 1 y 2;

Abajo:

  • Llave 2 puntos + led rojo = Freno de estacionamiento + el indicador;
  • Boton azul + llave de dos puntos + 3 leds verds + 1 led rojo = boton azul "test" tren de aterrizaje + llave 2 puntos baja o sube el tren de aterrizaje + leds verdes si encendido tren abajo + led rojo tren en movimiento;
  • Tecla doble sentido + led rojo = Tecla doble sentido baja o sube los flaps + led rojo indica flaps activos


.

Los encoders los compre solos pero estan armados y conexionados como los que se compran para arduino. (los utilizados estan directamente cableados igual que estos y sin la silicona, pero este lo protegi de esa manera para evitar que se corte las soldaduras y tenerlo para pruebas)


.

Bueno y estas placas son parte de una matriz de botones que ira puesta al rededor de uno de los monitores del simulador. Como veran en la primera hice una indiada usando para placa una maderita por no saber hacer placas o no comprar las verdes que termine comprando despues para las otras dos partes jeje


.

Aca tengo todo conectado y probando:

Este es el mueble en el que ira montado todo

AHORA LO LINDO, EL CODIGO:

El codigo final que tengo en funcionamiento hasta llegar a funcionar tuve varias consultas aca en el foro de arduino, de los cuales les dejo los links por si les interesa:

Tenia problemas con los encoders

Necesitaba una matriz de botones de 6x7

Cuando adicione la matriz de botones dejaron de funcionar otra vez los encoders XD

Algo que hice para armar el código cada vez que quería agregar algo fue, en primera el codigo que tenia funcionando, hacerle una copia y ponerla en la carpeta que se llama "para toquetear". Con esto si rompía el código y no sabia como volver atras tenia una copia jeje.

El código a insertar "botonera funciona" (previamente comprobado que funciona solo) en el principal "PG_PH_M_F_FE" , lo abría a la par del original eh iba copiando las partes a los sectores que correspondian.

Una vez verificado en el programa que no habia conflictos si no tenia algo del hardware armado para probarlo completo lo dejaba en otra carpeta hasta poder comprobarlo "PG_PH_M_F_FE_Bot falta probar"

como veran el nombre del archivo es raro pero me servia para identificar las partes ya ensambladas, en este caso significan:
PG = Palanca de Gases PH = Paso de Helice M = Mezcla de motor F = Flaps
FE = Freno de Estacionamiento Bot = Botonera (la matriz de botones)
carpetas

y el codigo:

// Matriz de botones
#include <Arduino.h>

#define BUTTON_COUNT 42
#define KEYPAD_OUTPUT_BEGIN 18
#define KEYPAD_OUTPUT_END 23
#define KEYPAD_INPUT_BEGIN 24
#define KEYPAD_INPUT_END 30

// Variables Globales Encodres
unsigned long time;
char buffer_temporal[10];

// encoder "PGM1" - Palanca de Gases Motor 1
int PGM1_PCLK = 48;//"PCLK" Pin CLK.
int PGM1_PDT = 49;//_"PDT" Pin DT.
int PGM1_RE_P = 1;//_"P" Posicion.
unsigned long PGM1_RE_T;
bool PGM1_RE_A = true;
bool PGM1_RE_B = true;

// encoder "PGM2" - Palanca de Gases Motor 2
int PGM2_PCLK = 52;// "PCLK" Pin CLK.
int PGM2_PDT = 53;// _"PDT" Pin DT.
int PGM2_RE_P = 1;// _"P" Posicion.
unsigned long PGM2_RE_T;
bool PGM2_RE_A = true;
bool PGM2_RE_B = true;

// encoder "PHM1" - Paso de Helice Motor 1
int PHM1_PCLK = 50;//"PCLK" Pin CLK.
int PHM1_PDT = 51;//_"PDT" Pin DT.
int PHM1_RE_P = 1;//_"P" Posicion.
unsigned long PHM1_RE_T;
bool PHM1_RE_A = true;
bool PHM1_RE_B = true;

// encoder "PHM2" - Paso de Helice Motor 2
int PHM2_PCLK = 45;// "PCLK" Pin CLK.
int PHM2_PDT = 44;// _"PDT" Pin DT.
int PHM2_RE_P = 1;// _"P" Posicion.
unsigned long PHM2_RE_T;
bool PHM2_RE_A = true;
bool PHM2_RE_B = true;

// encoder "PMM1" - Palanca de Mezcla Motor 1
int PMM1_PCLK = 42;//"PCLK" Pin CLK.
int PMM1_PDT = 43;//_"PDT" Pin DT.
int PMM1_RE_P = 9;//_"P" Posicion.
unsigned long PMM1_RE_T;
bool PMM1_RE_A = true;
bool PMM1_RE_B = true;

// encoder "PMM2" - Palanca de Mezcla Motor 2
int PMM2_PCLK = 47;// "PCLK" Pin CLK.
int PMM2_PDT = 46;// _"PDT" Pin DT.
int PMM2_RE_P = 9;// _"P" Posicion.
unsigned long PMM2_RE_T;
bool PMM2_RE_A = true;
bool PMM2_RE_B = true;

//Variables Tren de Aterrizaje
int TALN = 41; // _ _ _ _ _ _ _ _ "L" Led, "N" Nariz.
int TALI = 40; // _ _ _ _ _ _ _ _ "L" Led, "I" Izquierda.
int TALD = 38; // _ _ _ _ _ _ _ _ "L" Led, "D" Derecha.
int TALP = 39; // _ _ _ _ _ _ _ _ "L" Led, "P" Peligro.
int TAI = 36; //_ _ _ _ _ _ _ _ _ "I" Interruptor.
int TAPT = 37; // _ _ _ _ _ _ _ _ "P" Pulsador, "T" Test ok.
int TACR; //_ _ _ _ _ _ _ _ _ _ _ "C" Codigo, "R" Recibido: Almacena codigo de Link2FS para TA.
int TAVEI = digitalRead(TAI);// _ "V" Variable, "E" Estado, "I" Interruptor.
int TAVEAI = 0;// _ _ _ _ _ _ _ _ "V" Variable, "E" Estado, "A" Anterior , "I" Interruptor.

//Variables Flaps
int FAL = 33; //_ _ _ _ _ "L" Led.
const int FPD = 34; //_ _ "P" Pulsador, "D" Down.
const int FPU = 35; //_ _ "P" Pulsador, "U" Up.
int FPUV = 0; //_ _ _ _ _ "V" Variable.
int FPUVA = 0; // _ _ _ _ "V" Variable, "A" Anterior.
int FPDV = 0; //_ _ _ _ _ "V" Variable.
int FPDVA = 0; // _ _ _ _ "V" Variable, "A" Anterior.
int FP = 0; // _ _ _ _ _ _"P" Posicion.

//Variables Freno de Estacionamiento
int FELP = 32; // _ _ _ _ _ _ _ _ "L" Led, "P" Peligro.
int FEI = 31; //_ _ _ _ _ _ _ _ _ "I" Interruptor.
int FEVEI = digitalRead(FEI);// _ "V" Variable, "E" Estado, "I" Interruptor.
int FEVEAI = 0;// _ _ _ _ _ _ _ _ "V" Variable, "E" Estado, "A" Anterior , "I" Interruptor.


//------------ Matriz de botones hasta SETUP

uint8_t keypad_button_pressed[BUTTON_COUNT];
uint32_t lastTrigger;

uint32_t math_calc_diff(uint32_t value1, uint32_t value2) {
  if (value1 == value2) {
    return 0;
  }
  if (value1 > value2) {
    return (value1 - value2);
  }
  else {
    // check for overflow
    return (0xffffffff - value2 + value1);
  }
}

void keypad_reset_output() {
  // configure pull ups
  digitalWrite(18, HIGH);
  digitalWrite(19, HIGH);
  digitalWrite(20, HIGH);
  digitalWrite(21, HIGH);
  digitalWrite(22, HIGH);
  digitalWrite(23, HIGH);
}

void clear_buttons() {
  for(int i=0; i < BUTTON_COUNT; i++) {
    keypad_button_pressed[i] = 0;
  }
}

void keypad_setup() {
  // initialize the digital pin as an output:
  pinMode(18, OUTPUT);
  pinMode(19, OUTPUT);
  pinMode(20, OUTPUT);
  pinMode(21, OUTPUT);
  pinMode(22, OUTPUT);
  pinMode(23, OUTPUT);

  keypad_reset_output();

  pinMode(24, INPUT);
  pinMode(25, INPUT);
  pinMode(26, INPUT);
  pinMode(27, INPUT);
  pinMode(28, INPUT);
  pinMode(29, INPUT);
  pinMode(30, INPUT);

  // configure pull ups
  digitalWrite(24, HIGH);
  digitalWrite(25, HIGH);
  digitalWrite(26, HIGH);
  digitalWrite(27, HIGH);
  digitalWrite(28, HIGH);
  digitalWrite(29, HIGH);
  digitalWrite(30, HIGH);
}

// the loop() method runs over and over again,
// as long as the Arduino has power

void keypad_read_buttons() {
  clear_buttons();
  uint8_t y=0;
  for(int i=KEYPAD_OUTPUT_BEGIN; i <= KEYPAD_OUTPUT_END; i++) {
    keypad_reset_output();
    digitalWrite(i, LOW);
    uint8_t x=0;
    for(int j=KEYPAD_INPUT_BEGIN; j <= KEYPAD_INPUT_END; j++) {
      if (digitalRead(j) == LOW) {
        uint8_t index = x+7*y;  //OJO el numero de la ecuacion es la cantidad de columnas
        keypad_button_pressed[index] = 1;
      }
      x++;
    }
    y++;
  }
}

uint8_t keypad_button_is_pressed() {
  for (int i=0; i < BUTTON_COUNT; i++) {
    if (keypad_button_pressed[i]) {
      return 1;
    }
  }
  return 0; // no button pressed
}


String datos[42] = {
  "H25","H09","H22","H20","H23","H21","G14",
  "C26","A56","G18","G09","G13","G12","H01",
  "C25","A55","H17","H18","H19","H20","H21",
  "H24","H23","H22","H21","H20","H19","H18",
  "B12","B11","A57","H14","H15","H16","H17",
  "A58","H37","H38","H39","H40","H41","H42"
  };


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

// SETUP de Encoders
  pinMode (PGM1_PCLK,INPUT);
  pinMode (PGM1_PDT,INPUT);
   
  pinMode (PGM2_PCLK,INPUT);
  pinMode (PGM2_PDT,INPUT);

  pinMode (PHM1_PCLK,INPUT);
  pinMode (PHM1_PDT,INPUT);
   
  pinMode (PHM2_PCLK,INPUT);
  pinMode (PHM2_PDT,INPUT);

  pinMode (PMM1_PCLK,INPUT);
  pinMode (PMM1_PDT,INPUT);
   
  pinMode (PMM2_PCLK,INPUT);
  pinMode (PMM2_PDT,INPUT);

//Setup Tren de aterrizaje
  pinMode(TALN, OUTPUT);
  pinMode(TALI, OUTPUT);
  pinMode(TALD, OUTPUT);
  pinMode(TALP, OUTPUT);
  pinMode(TAI, INPUT_PULLUP);
  pinMode(TAPT, INPUT_PULLUP);

//Setup Flaps
  pinMode(FPU, INPUT_PULLUP);
  pinMode(FPD, INPUT_PULLUP);
  pinMode(FAL, OUTPUT);

//Setup Freno de Estacionamiento
  pinMode(FEI, INPUT_PULLUP);
  pinMode(FELP, OUTPUT);

//Setup matriz
 // init_systicks();
  keypad_setup();

}

void loop() {
time = millis();
//------Palanca de Gases Motor 1
  if (digitalRead(PGM1_PCLK) == LOW){
    PGM1_RE_T = time;
    if (PGM1_RE_A == true){
      PGM1_RE_P ++;
      PGM1_RE_A = false;
      PGM1_RE_B = false;
      PGM1_RE_P = min(10, max(0, PGM1_RE_P));
      sprintf(buffer_temporal, "C56%03u\r\n", PGM1_RE_P*99/10);
      Serial.print(buffer_temporal);
      }
  }
  if (digitalRead(PGM1_PDT)== LOW){
    PGM1_RE_T = time;
    if(PGM1_RE_B == true){
      PGM1_RE_P --;
      PGM1_RE_A = false;
      PGM1_RE_B = false;
    PGM1_RE_P = min(10, max(0, PGM1_RE_P));
      sprintf(buffer_temporal, "C56%03u\r\n", PGM1_RE_P*99/10);
      Serial.print(buffer_temporal);     }
  }
  if (time - PGM1_RE_T>10){
    PGM1_RE_A = true;
    PGM1_RE_B = true;
  }

 //------Palanca de Gases Motor 2
   if (digitalRead(PGM2_PCLK) == LOW){
    PGM2_RE_T = time;
    if (PGM2_RE_A == true){
      PGM2_RE_P ++;
      PGM2_RE_A = false;
      PGM2_RE_B = false;
      PGM2_RE_P = min(10, max(0, PGM2_RE_P));
      sprintf(buffer_temporal, "C57%03u\r\n", PGM2_RE_P*99/10);
      Serial.print(buffer_temporal);
    }
  }
  if (digitalRead(PGM2_PDT)== LOW){
    PGM2_RE_T = time;
    if(PGM2_RE_B == true){
      PGM2_RE_P --;
      PGM2_RE_B = false;
      PGM2_RE_A = false;
      PGM2_RE_P = min(10, max(0, PGM2_RE_P));
      sprintf(buffer_temporal, "C57%03u\r\n", PGM2_RE_P*99/10);
      Serial.print(buffer_temporal);
    }
  }
  if (time - PGM2_RE_T>10){
    PGM2_RE_A = true;
    PGM2_RE_B = true;
  }

//------Paso Helice Motor 1
  if (digitalRead(PHM1_PCLK) == LOW){
    PHM1_RE_T = time;
    if (PHM1_RE_A == true){
      PHM1_RE_P ++;
      PHM1_RE_A = false;
      PHM1_RE_B = false;
      PHM1_RE_P = min(10, max(0, PHM1_RE_P));
      sprintf(buffer_temporal, "C60%03u\r\n", PHM1_RE_P*99/10);
      Serial.print(buffer_temporal);
      }
  }
  if (digitalRead(PHM1_PDT)== LOW){
    PHM1_RE_T = time;
    if(PHM1_RE_B == true){
      PHM1_RE_P --;
      PHM1_RE_A = false;
     PHM1_RE_B = false;
    PHM1_RE_P = min(10, max(0, PHM1_RE_P));
      sprintf(buffer_temporal, "C60%03u\r\n", PHM1_RE_P*99/10);
      Serial.print(buffer_temporal);
     }
  }
  if (time - PHM1_RE_T>10){
    PHM1_RE_A = true;
    PHM1_RE_B = true;
  }

//------Paso Helice Motor 2
   if (digitalRead(PHM2_PCLK) == LOW){
    PHM2_RE_T = time;
    if (PHM2_RE_A == true){
      PHM2_RE_P ++;
      PHM2_RE_A = false;
      PHM2_RE_B = false;
      PHM2_RE_P = min(10, max(0, PHM2_RE_P));
      sprintf(buffer_temporal, "C61%03u\r\n", PHM2_RE_P*99/10);
      Serial.print(buffer_temporal);
    }
  }
  if (digitalRead(PHM2_PDT)== LOW){
    PHM2_RE_T = time;
    if(PHM2_RE_B == true){
      PHM2_RE_P --;
      PHM2_RE_B = false;
      PHM2_RE_A = false;
      PHM2_RE_P = min(10, max(0, PHM2_RE_P));
      sprintf(buffer_temporal, "C61%03u\r\n", PHM2_RE_P*99/10);
      Serial.print(buffer_temporal);
    }
  }
  if (time - PHM2_RE_T>10){
    PHM2_RE_A = true;
    PHM2_RE_B = true;
  }

//------Palanca de Mezcla Motor 1
  if (digitalRead(PMM1_PCLK) == LOW){
    PMM1_RE_T = time;
    if (PMM1_RE_A == true){
      PMM1_RE_P ++;
      PMM1_RE_A = false;
      PMM1_RE_B = false;
      PMM1_RE_P = min(10, max(0, PMM1_RE_P));
     sprintf(buffer_temporal, "C58%03u\r\n", PMM1_RE_P*99/10);
      Serial.print(buffer_temporal);
      }
  }
  if (digitalRead(PMM1_PDT)== LOW){
    PMM1_RE_T = time;
    if(PMM1_RE_B == true){
      PMM1_RE_P --;
      PMM1_RE_A = false;
      PMM1_RE_B = false;
      PMM1_RE_P = min(10, max(0, PMM1_RE_P));
     sprintf(buffer_temporal, "C58%03u\r\n", PMM1_RE_P*99/10);
      Serial.print(buffer_temporal);
     }
  }
  if (time - PMM1_RE_T>10){
    PMM1_RE_A = true;
    PMM1_RE_B = true;
  }
   
//------Palanca de Mezcla Motor 2
   if (digitalRead(PMM2_PCLK) == LOW){
    PMM2_RE_T = time;
    if (PMM2_RE_A == true){
      PMM2_RE_P ++;
      PMM2_RE_A = false;
      PMM2_RE_B = false;
      PMM2_RE_P = min(10, max(0, PMM2_RE_P));
       sprintf(buffer_temporal, "C59%03u\r\n", PMM2_RE_P*99/10);
      Serial.print(buffer_temporal);
    }
  }
  if (digitalRead(PMM2_PDT)== LOW){
    PMM2_RE_T = time;
    if(PMM2_RE_B == true){
      PMM2_RE_P --;
      PMM2_RE_B = false;
      PMM2_RE_A = false;
      PMM2_RE_P = min(10, max(0, PMM2_RE_P));
     sprintf(buffer_temporal, "C59%03u\r\n", PMM2_RE_P*99/10);
      Serial.print(buffer_temporal);
    }
  }
  if (time - PMM2_RE_T>10){
    PMM2_RE_A = true;
    PMM2_RE_B = true;
  }

// Control TA - Tren de Aterrizaje
  if(Serial.available()){
   TACR = getChar(); // _ _ Lectura codigo Link2FS para pocision del TA (ASCII) ? -> 63 Y -> 89
    if(TACR == 63){
     TACR = getChar();
      if(TACR == 89){
       int TAVN = getChar()-48; // _ _ "V" Variable, "N" Nariz. (Se resta 48 pq el 2 en ASCII se representa con el codigo 50)
       int TAVI = getChar()-48; // _ _ "V" Variable, "I" Izquierda. (Se resta 48 pq el 2 en ASCII se representa con el codigo 50)
       int TAVD = getChar()-48; // _ _ "V" Variable, "N" Derecha. (Se resta 48 pq el 2 en ASCII se representa con el codigo 50)          
       if(TAVN == 2){digitalWrite(TALN, HIGH);}else {digitalWrite(TALN, LOW);}
       if(TAVI == 2){digitalWrite(TALI, HIGH);} else {digitalWrite(TALI, LOW);}
       if(TAVD == 2){digitalWrite(TALD, HIGH);} else {digitalWrite(TALD, LOW);}
       if(TAVN+TAVI+TAVD<6 && TAVN+TAVI+TAVD>0){digitalWrite(TALP, HIGH);} else {digitalWrite(TALP, LOW);}
    }
   }
 }
// Envio de codigo a Link2FS acorde a la posicion del interruptor de TA 
  TAVEI = digitalRead(TAI);
  if(TAVEI == 0 && digitalRead(TALN)== LOW && TAVEI != TAVEAI){Serial.println("C02");}
  if(TAVEI != 0 && digitalRead(TALN) == HIGH && TAVEI != TAVEAI){Serial.println("C01");}
  TAVEAI = TAVEI;
  
 int TAVPT = digitalRead(TAPT);//"V" Variable, "P" Pulsador, "T" Test.
  if(TAVPT == 0){
   digitalWrite(TALN, HIGH); digitalWrite(TALI, HIGH); digitalWrite(TALD, HIGH);digitalWrite(TALP, HIGH);delay(500);
   digitalWrite(TALN, LOW); digitalWrite(TALI, LOW); digitalWrite(TALD, LOW);digitalWrite(TALP, LOW);}

// Control F - Flaps
  FPUV = digitalRead(FPU);
  if (FPUV == HIGH && FPUVA != HIGH) {
    Serial.println("C15");
    FP --;
    FP = min(4, max(0, FP));
    }
    delay(20);
    FPUVA = FPUV;
    
  FPDV = digitalRead(FPD);
  if (FPDV == HIGH && FPDVA != HIGH) {
    Serial.println("C14");
    FP ++;
    FP = min(4, max(0, FP));
    }
  FPDVA = FPDV;
    
  if(FP == 0){digitalWrite(FAL, LOW);} else {digitalWrite(FAL, HIGH);}

// Control FE - Freno de Estacionamiento
 FEVEI = digitalRead(FEI);
  if(FEVEI == 0 && digitalRead(TALN)== HIGH && FEVEI != FEVEAI){Serial.println("C040"); digitalWrite(FELP, LOW);}
  if(FEVEI != 0 && digitalRead(TALN) == HIGH && FEVEI != FEVEAI){Serial.println("C041");digitalWrite(FELP, HIGH);}
  FEVEAI = FEVEI;
 
  keypad_read_buttons();

  // allow button processing only every 300ms (30 systicks)
  if (keypad_button_is_pressed() && (math_calc_diff(time, lastTrigger) > 200)) {
    lastTrigger = time;

    for(int i=0; i < BUTTON_COUNT; i++) {
      if (keypad_button_pressed[i]) {
        Serial.println(datos[i]);
      }
    }
   }

}

//FUNCION getChar() Pertenece al Tren de Aterrizaje
char getChar(){
  while(Serial.available()==0);
  return((char)Serial.read());
}

Agradezco toda la ayuda recibida en los demás hilos y mecionar a los que me han encontrado las soluciones: @Surbyte - @IgnoranteAbsoluto - @victorjam - @tauro0221 gracias a todos

y espero podamos mejorar mas esto y a lo mejor alguno mas se anima y lo encara a armar estoy a dispocicion para que me consulten.

@jhonny195 tengo pensado empezar por lo más fácil, los encoders. La idea es aprovechar de tu código la parte que "decodifica" cada encoder e implementarlo en una clase. Así se pueden gestionar los seis encoder sin tener un código tan largo. Además, en el caso de querer añadir alguno más, no hay copiar y pegar tantas líneas.

Pero si bien podría tener el mismo comportamiento que tiene tu código ahora, me surgen algunas dudas que quizás podrían implementarse como "mejoras". La primera es que en tu código defines las variables:


int PGM1_RE_P = 1;//_"P" Posicion.

int PGM2_RE_P = 1;// _"P" Posicion.

int PHM1_RE_P = 1;//_"P" Posicion.

int PHM2_RE_P = 1;// _"P" Posicion.

int PMM1_RE_P = 9;//_"P" Posicion.

int PMM2_RE_P = 9;// _"P" Posicion.

Son las variables que contienen el valor de la posición de los encoders. Lo que me llama la atención es que no las inicializas todas con el mismo valor, sino unas a 1 y otras a 9. Además, los valores permitidos van de 0 a 10 y no utilizas ninguno de esos límites para inicializar estas variables. ¿Por qué las inicializas con esos valores?

Esto me lleva al tema de que, al arrancar el Arduino, no hay forma de saber con certeza en qué posición están las palancas y por ende no se sabe qué valores debería de tener esas variables para que indiquen la posición real de las palancas nada más arrancar. Así que me imagino que lo primero que hacen, al encender el Arduino, es mover las palancas de un extremo a otro, para asegurarse de que los valores de las variables se "sincronizan" con la posición real de las palancas. Esto es debido a que los valores están limitados entre cero y diez. Supongo que el recorrido de las palancas tienen únicamente once posiciones.

¿Pero qué pasa con el simulador mientras se "juega" con las palancas para "calibrar" los encoders? Porque mientras se mueven se está mandando al PC las posiciones de las palancas. Se me ha ocurrido que se puede "silenciar" el envío de la posición de cada una de las palancas mientras no se "calibren". Para ello el Arduino, una vez puesto en marcha, puede esperar a que cada una de las palancas se muevan una primera vez y, transcurrido uno o dos segundos sin moverse la palanca, asume que ya está calibrada. Comenzando a "emitir con normalidad" la posición de cada una de las palancas cuando las de por "calibrada". Eso da la posibilidad de avanzar y retroceder hasta los límites cada una de las palancas, para luego posicionar cada una en el "estado inicial" que se desee. Sin que el simulador se entere de todo ello hasta que estén "posicionadas".

Hay otra cosa sin importancia y es que si trata de superar el límite establecido, el programa emite nuevamente el valor límite. Es una tontería, pero se puede hacer que el software no emita nuevamente la posición "repetida".

Otro detalle es que por lo que he visto, los valores que "emiten", para cada palanca, van desde el 000 hasta el 099. Por lo poco que he visto por internet, se supone que es el porcentaje y que puede llegar hasta el 100%. ¿Por qué no lo programaste para que llegara al 100?

Bueno, resumiendo:

  1. ¿Cómo inicializar/calibrar los valores de los encoders? Si te parece buena idea lo de esperar a que se "calibren" antes de emitir valores. O basta con asignar un valor inicial tal como lo tienes ahora.

  2. ¿"Silenciamos" los valores repetidos al sobrepasar los límites? En teoría no debería de importar si “calibramos en silencio” los encoders. Debido a que sólo se han de poder sobrepasar los límites cuando no se han “calibrado”.

  3. ¿Dejamos el máximo valor a 99 o que sea 100?

Ahora te toca a ti mover ficha. :wink:

@IgnoranteAbsoluto se me habrá pasado en alguna prueba lo de que inician en 9, no hay problema con que inicien en 1, el temas es así, el valor a enviar por puerto serie para los encoders se compone de esta forma: C56xxx (xxx tiene que ser un valor entre 000 y 100)

si ves la imagen por ejemplo el codigo B45xxx asi tiene que ir el codigo.

como los encoders no tienen límite hacia adelante o atrás los limitaba con esto:

    PGM1_RE_P = min(10, max(0, PGM1_RE_P));
      sprintf(buffer_temporal, "C56%03u\r\n", PGM1_RE_P*99/10);
      Serial.print(buffer_temporal); 

Una cosa importante es que el movimiento de las palancas me permiten unos 12 pasos de encoder solamente, por eso establecí el minimo y maximo a 0 y 10 respectivamente. De ahi hice la formula que esta en el sprintf, ademas lo hice *99/10 pq multiplicarlo por 10 directamente me tiraba una falla.

hay que tener en cuenta que tienen que ir las 3 cifras:

esto esta bien C56010, esto esta mal => C5610 (para el valor 10)

el simulador no retroalimenta de la posición, normalmente al iniciar el simulador "sincronizo" las posiciones llevando las palancas al máximo y las vuelvo al mínimo, con eso pongo a punto la posición.

En este código sólo está el control de los encoders. Esta es mi primera versión. El código no está comentado, salvo los parámetros que se pone a cada uno de los encoders. Están configurado los seis que hay en el “código original”, con la diferencia de que llegan al 100% en lugar del 99% (van de 10 en 10) y que no mandan nada hasta que se muevan por primera vez y luego se estén quietos por 2 segundos (2000 milisegundos), es entonces cuando mandan la posición en que han quedado y cada vez que se muevan. Cada uno se calibra por separado. Así que se pueden mover todos a la vez o de uno en uno, para calibrarlos. Está configurado para que si se pasa del mínimo o del máximo, no envíen el mismo valor una y otra vez.

Digo que esta es mi primera versión, porque estoy pensando quitar los límites de “pasos” de los encoders para que sea más fácil usar el código en otros montajes. De esta forma, al mover la palanca de un extremo a otro, se “recuerda” cuántos pasos de recorrido tiene en total y se ajusta automáticamente, sin necesidad de configurarlo en el fuente del programa.

He estado mirando el código del tren de aterrizaje y creo entender cómo funciona. Pero lo de los flaps no sé cómo ha de funcionar ni qué se ha de mandar al simulador.

El freno de estacionamiento, supongo que simplemente ha de mandar un comando cuando se pone y otro cuando se quita. Así como encender el led en uno de los casos. ¿Cual?

@jhonny195, si puedes probar el código, ya me dirás que tal va. Recuerda que sólo son los encoders.

class EncoderFX {
    public:
        // Constructor de la clase
        EncoderFX(int8_t pinCLK, int8_t pinDT, const char* formato, int8_t posicionInicial, int8_t posicionMin, int8_t posicionMax, int valorMin, int valorMax, unsigned long tiempoFinCalibrado) :
            pinCLK(pinCLK),
            pinDT(pinDT),
            posicion(posicionInicial),
            posicionMin(posicionMin),
            posicionMax(posicionMax),
            valorMin(valorMin),
            valorMax(valorMax),
            formato(formato),
            tiempoFinCalibrado(tiempoFinCalibrado),
            instanteAnteriorAntiRebote(0),
            instanteAnterior(0),
            flagA(true),
            flagB(true),
            estado(ESTADO_INICIAL) {
                pinMode(this->pinCLK, INPUT);
                pinMode(this->pinDT, INPUT);
            }
        // Función que se ha de llamar constantemente
        void loop(unsigned long &instanteActual, char* bufferTemporal) {
            int8_t posicionAnterior = this->posicion;
            bool seHaMovido = false;
            if (digitalRead(this->pinCLK) == LOW) {
                this->instanteAnteriorAntiRebote = instanteActual;
                if (this->flagA == true){
                    seHaMovido = true;
                    this->posicion++;
                    this->flagA = false;
                    this->flagB = false;
                    this->posicion = min(posicionMax, max(posicionMin, this->posicion));
                }
            }
            if (digitalRead(this->pinDT) == LOW) {
                this->instanteAnteriorAntiRebote = instanteActual;
                if(this->flagB == true){
                    seHaMovido = true;
                    this->posicion--;
                    this->flagA = false;
                    this->flagB = false;
                    this->posicion = min(posicionMax, max(posicionMin, this->posicion));
                }
            }
            if ((instanteActual - this->instanteAnteriorAntiRebote) >= TIEMPO_ANTI_REBOTE) {
                this->flagA = true;
                this->flagB = true;
            }
            switch (this->estado) {
                case ESTADO_INICIAL :
                    if (this->tiempoFinCalibrado == 0) {
                        this->estado = ESTADO_CALIBRANDO;
                    }
                    else {
                        this->estado = ESTADO_SIN_CALIBRAR;
                    }
                    break;
                case ESTADO_SIN_CALIBRAR :
                    if (!seHaMovido) {
                        break;
                    }
                    this->estado = ESTADO_CALIBRANDO;
                case ESTADO_CALIBRANDO :
                    if (seHaMovido) {
                        this->instanteAnterior = instanteActual;
                        break;
                    }
                    else if ((instanteActual - this->instanteAnterior) >= this->tiempoFinCalibrado) {
                        this->estado = ESTADO_OPERATIVO;
                        seHaMovido = true;
                        posicionAnterior = this->posicion - 1;
                    }
                    else {
                        break;
                    }
                case ESTADO_OPERATIVO :
                    if ((seHaMovido) && (this->posicion != posicionAnterior)) {
                        sprintf(bufferTemporal, this->formato, map(this->posicion, this->posicionMin, this->posicionMax, this->valorMin, this->valorMax));
                        Serial.print(bufferTemporal);
                    }
                    break;
            }
        }

    private:
        static const unsigned long TIEMPO_ANTI_REBOTE = 3; // Tiempo mínimo que ha de trancurrir entre cambios, para evitar "los rebotes"
        int8_t pinCLK;
        int8_t pinDT;
        int8_t posicion;
        int8_t posicionMin;
        int8_t posicionMax;
        int valorMin;
        int valorMax;
        const char* formato;
        unsigned long tiempoFinCalibrado;
        unsigned long instanteAnteriorAntiRebote;
        unsigned long instanteAnterior;
        bool flagA : 1;
        bool flagB : 1;
        enum estado_t {
            ESTADO_INICIAL,
            ESTADO_SIN_CALIBRAR,
            ESTADO_CALIBRANDO,
            ESTADO_OPERATIVO
        } estado;

};


char buffer_temporal[10];

unsigned long time;

// Parámetros de los encoders:
//   Pin al que está conectado el CLK
//   Pin al que está conectado el DT
//   Formato de la cadena que hay que enviar al simulador (la que se utiliza en el priftf)
//   Valor inicial del encoder (1 o 9 en el código original)
//   Valor mínimo del encoder (0 en el código original)
//   Valor máximo del encoder (10 en el código original)
//   Valor mínimo que envía al simulador (0 en el código original)
//   Valor máximo que envía al simulador (99 en el código original, 100 en este código)
//   Tiempo, en milisegundos, que ha de trasncurrir desde el último movimiento de calibrado para que se de por teminada la calibración
EncoderFX encoders[] = {
    {48, 49, "C56%03u\r\n", 1, 0, 10, 0, 100, 2000}, // Palanca de Gases Motor 1
    {52, 53, "C57%03u\r\n", 1, 0, 10, 0, 100, 2000}, // Palanca de Gases Motor 2
    {50, 51, "C60%03u\r\n", 1, 0, 10, 0, 100, 2000}, // Paso de Helice Motor 1
    {45, 44, "C61%03u\r\n", 1, 0, 10, 0, 100, 2000}, // Paso de Helice Motor 2
    {42, 43, "C58%03u\r\n", 9, 0, 10, 0, 100, 2000}, // Palanca de Mezcla Motor 1
    {47, 46, "C59%03u\r\n", 9, 0, 10, 0, 100, 2000}  // Palanca de Mezcla Motor 2
};
const size_t CANTIDAD_ENCODERS = sizeof(encoders) / sizeof(encoders[0]);

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

void loop() {
    time = millis();
    for (size_t i = 0; i < CANTIDAD_ENCODERS; i++) {
        encoders[i].loop(time, buffer_temporal);
    }
}

@IgnoranteAbsoluto probé el código, al inicio y con cada palanca por separado hay una "pausa" hasta que empieza a mandar datos, supongo que ese es el tiempo de calibración, después funcionan perfecto.

Encontré que se puede leer por puerto serie la posición en simulador de las palancas que corresponden a los encoders:
los valores que llegan van entre 000 y 100 (representados por las "x")
=Vxxx posición del acelerador motor 1
=Wxxx posición del acelerador motor 2
<mxxx paso de hélice motor 1
<nxxx paso de hélice motor 2
<oxxx mezcla motor 1
<pxxx mezcla motor 2

se respetan mayúsculas y minúsculas

sobre los flaps:

// Control F - Flaps
  FPUV = digitalRead(FPU);
  if (FPUV == HIGH && FPUVA != HIGH) {
    Serial.println("C15");
    FP --;
    FP = min(4, max(0, FP));
    }
    delay(20);
    FPUVA = FPUV;
    
  FPDV = digitalRead(FPD);
  if (FPDV == HIGH && FPDVA != HIGH) {
    Serial.println("C14");
    FP ++;
    FP = min(4, max(0, FP));
    }
  FPDVA = FPDV;
    
  if(FP == 0){digitalWrite(FAL, LOW);} else {digitalWrite(FAL, HIGH);}

Esto físicamente funciona como dos botones push ( es una tecla de levanta vidrios de auto, la de la derecha de la imagen )

El tema es así, los flaps en el simulador tienen 5 posiciones el código C14 y C15 aumenta o disminuyen la posición de flaps (van solos, sin valor como los encoders)

Un lado de la tecla imprime C14 y suma uno en la variable FP, el otro lado imprime C15 y resta a la variable FP, la variable FP es un contador con máximo y mínimo, y el led rojo se enciende cuando el valor de FP es mayor a cero, para indicar que los flaps están activos.

La posición de simulador de los flaps se puede leer por puerto serie si se quiere y la cadena que llega se compone de <Gxxx donde xxx seria un valor entre 000 y 100 (esto se usa si por ejemplo pusiera una pantalla o varios leds indicadores de posición)

Del tren de aterrizaje:
Lee por puerto serie el estado en el simu con el codigo ?Yxxx donde 000 es levantado y 222 significa abajo (no va contando de 000 a 222, si no que la primer cifra representa la nariz, la segunda el lado izquierdo y el tercero el tren de aterrizaje derecho)
ese codigo para leer el puerto serie lo copie tal cual de otra lado, pero creo que se puede optimizar, y el led rojo lo hago encender cuando la operacion esta en proceso con esta ecuacion:

       if(TAVN+TAVI+TAVD<6 && TAVN+TAVI+TAVD>0){digitalWrite(TALP, HIGH);} else {digitalWrite(TALP, LOW);}

En cuanto al freno de estacionamiento:
Exactamente, si mandas por puerto serie C041 se activa el freno (ahi hago prender el led) y si mando C040 se desactiva). En este caso tambien se puede obtener el estado del freno en el simulador leyendo del puerto serie el codigo <ax (donde la x puede ser 1: activo o 0: desactivado)

para las extracciones de codigos que llegan por puerto serie encontré esto también (que yo no supe usarlo) y tal vez te ayude:

void loop() {
  {INPUTS();}      //Check the Simconnect and "keys" section
  {OTHER();}       // Check for "Other" things to do. (Non extraction stuff)
  if (Serial.available()) {            // checks if anything there
    CodeIn = getChar();            // reads a serial byte
    if (CodeIn == '=') {EQUALS();}      // The first identifier is "="
    if (CodeIn == '<') {LESSTHAN();}  // The first identifier is "<"
    if (CodeIn == '?') {QUESTION();}   // The first identifier is "?"
    if (CodeIn == '/') {SLASH();}           // The first identifier is "/" (Annunciators)
  }  // end of serial read byte
}   // end of void loop

/*Now,, in the above it will shoot off and do anything required in the "INPUTS" void and then come back.
It will then shoot off and do everything in the "OTHER" void and come back.
It will then read 1 charactor from the serial port.
It then checks if that charactor is a "=" or a "<" or "?" or "/" ,, if it is  ,, then it shoots off to the required void ,, does what it's got to do there and comes back and starts all over again at the  "INPUTS" void ,, it just keeps going around and around in the Void loop unless told to go elsewhere.
*/

Como te escribi en los parrafos anteriores, se pueden extraer datos del simulador que vienen codificados (<Gxxx, <ax, =Vxxx, etc..) supongo que lo que hace ahí es leer todo lo que llega y mandar y para analizar el segundo carácter utiliza esto:

void EQUALS(){      // The first identifier was "="
 CodeIn = getChar(); // Get another character
  switch(CodeIn) {// Now lets find what to do with it
    case 'A'://Found the second identifier
       //Do something
    break;
     
    case 'B':
       //Do something
    break;
     
    case 'C':
       //Do something
    break;
    
         //etc etc etc
     }
}

@jhonny195, sí, la idea es que se debe accionar todas las palancas de un extremo al otro del recorrido. Para finalmente posicionar cada una de ellas en donde se quiera "comenzar" y que a partir de entonces sea cuando notifique al simulador la posición de cada palanca. Todo esto es para que no se mande al simulador los movimientos iniciales de "calibración" de las palancas.

Tal como es el montaje actual, no creo que sirva de nada que el simulador informe al Arduino la posición de las palancas, porque las palancas no se pueden ajustar automáticamente a esos valores. Además, esos valores han de ser los mismo que manda el Arduino al simulador y, si fuera necesario, el Arduino puede saber qué es lo que ha mandado.

El que sí que es útil es el valor que de la posición de los flaps. Porque se puede verificar si el simulador da un valor distinto de <G00 para encender el led de los flaps. Incluso, como dices, se podría indicar la posición de los flaps con más de un led o con un servo. Me surge una duda: ¿la información de la posición de los flaps la informa el simulador sólo al cambiar la posición de ellos, las manda cada cierto tiempo o se puede "hacer una petición" para que mande los parámetros. Es para ver si el Arduino puede saber la posición de los flaps sin necesidad de tener que mover los flaps. Si no se puede saber la posición sin tener que accionar los flaps, podría hacer parpadear el leds de los flaps, para indicar que el Arduino no sabe si los flaps están a cero o no.

En cuanto al tren de aterrizaje y los frenos, creo que me ha quedado claro cómo funcionan. Gracias.

¿Me podrías decir si se suele poner en marcha primero el simulador o el Arduino? ¿Al empezar el simulador manda algún tipo de información al Arduino? Para que, en tal caso, el Arduino mande de nuevo al simulador todos los parámetros que controla.

Respecto a la matriz de botones, si se mantiene pulsado uno de ellos, ¿se ha de mandar una y otra vez el “comando” que tienen asociado o sólo se ha de mandar una vez, sin repetir el envío?

Tal como es el montaje actual, no creo que sirva de nada que el simulador informe al Arduino la posición de las palancas, porque las palancas no se pueden ajustar automáticamente a esos valores

@IgnoranteAbsoluto si, despues lo pense a eso, no tiene sentido extraer los datos...

Como se puede apreciar en la imagen donde dice "Serial Coms CARD1" abajo se puede setear la tasa de refresco de los datos que envía el simulador al Arduino.

Ahora, los pasos para iniciar todo son, abro el Link2fs (el programa de la imagen) y de ahí en mas no hay orden, podes iniciar primero el simulador o conectar el Arduino, no genera conflicto, como veras en la imagen para conectarlo, están esos botones grandes amarillos correspondientes a cada tarjeta Arduino ("connect to Com Port")

Una vez que lo conectas (con el botón del programa) y tenes el simulador abierto ya empieza a transmitir y a escuchar el Arduino, mañana te hago un video y lo pongo en mi cuenta de YouTube y pego acá el link para que veas como funciona.

Sobre la matriz de botones, al menos la que esta en el código, creo que la parte que esta al final del código marca el tiempo de escaneo y con eso la velocidad de repetición del comando que a su vez limita el rebote.
Con la corrección que vos me habías dado en el hilo de la consulta de código lento de reemplazar ticks por time, tuve que aumentar ese tiempo si no una pulsación me daba 3 o 4 veces el código.
Pero eso esta bien así, en el simulado justamente cumplen la función de pulsadores también, no como botones on-off.

Igual tranqui con la matriz, que me clave como los mejores, hoy termine de configurar los strings de la matriz de botones y hay códigos del programa que no funcionan en el simulador :sob: :sob: renegué tanto con eso para que al final la mitad de esos códigos asociados a la matriz no me funcionen.
Con este suceso hable con mi amigo y futuro usuario de este proyecto y hablamos de usar las funciones de otra forma, y acá mi pregunta, si yo diseñara una parte de tablero con mas encoders se puede agregar mas? o lo hago con teclas? Me explico, hay funciones de tablero como seleccionar frecuencias de radio o setear la altura de piloto automático o el rumbo del piloto automático que son justamente valores y con una perilla rotativa es mas rápido que apretar 50 veces el mismo botón jaja

Acá te dejo el link del video de conexión entre las partes
simulador - link2fs - Arduino

Como veras, es diferente a la que te describí anteriormente, lo hice justamente par que veas que no exige un orden de conexión.

Se puede:

  • abrir link2fs - conectar (al links2fs) el Arduino - abrir simulador
  • abrir ink2fs - abrir simulador - conectar (al links2fs) el Arduino
  • abrir simulador - link2fs - conectar el Arduino

https://youtu.be/hg0yfEke_xc

@jhonny195 creo que se me ha ido un poco de las manos. Lo que tengo hasta ahora sólo es “el armazón” de una idea para controlarlo "todo". Tiene sus pro y sus contras. Lo bueno es que lo he hecho bastante genérico, con la idea de que se pueda añadir o quitar “elementos” fácilmente, sin necesidad de mucho conocimiento de programación. El inconveniente es que el código que hay “detrás” es demasiado complejo de entender para quienes no sepan de clases y herencias de C++.

Digo que es el armazón porque por ahora sólo controla encoders, leds, pulsadores e interruptores con una lógica muy sencilla. Ejemplos de lo que aún no controla, son el pulsador de test de luces del tren de aterrizaje y el indicador de que está en proceso la operación del tren de aterrizaje. Para esto tendría que hacer un par de clases dedicadas únicamente a ello. Pero si por un casual hay alguna manera de decirle al link2FS que se ha pulsado el botón de test del tren de aterrizaje y este informe que se han de encender todas las luces del tren de aterrizaje, o que el link2FS indique que se ha de encender la luz de indicador de proceso de operación del tren de aterrizaje, en el caso de que así sea, tal vez sería fácil de configurar con lo que hay ahora. ¿Sabes si existen esos parámetros en el link2FS?

Un cambio que he hecho es que no se tenga que poner \r\n al final de la cadena de formato. En lugar de "C56%03u\r\n", basta con poner "C56%03u". Al enviar los datos se hace con un println(). Espero que funcione igualmente y no de problemas. Se tendría que probar con el simulador. Me imagino que si Serial.println("C15") funciona, este cambio también ha de funcionar.

He hecho una especie de “librería” que se encarga de recibir y mandar los datos. Tan sólo hay que añadir o cambiar las llamadas a las funciones link2FS.addEncoder(), link2FS.addPinIn() y ink2FS.addPinOut() en el .ino, que ha quedado casi vacío. He tratado de configurar acorde al montaje que tienes actualmente. Es probable que en algún caso esté mal y se encuentren intercambiados algunos valores o no sean los correctos. Si puedes, revisa y prueba lo que hay.

Los encoders se supone que están configurados tal como los tenías inicialmente. Así que debería de funcionar sin necesidad de cambiar nada. Respecto a los ledes del tren de aterrizaje, como ya he dicho, no funciona ni el test ni el de “operación”, tan sólo los tres que indican que el tren de aterrizaje está desplegado o no. Si te fijas, en el “modelo” de lo que se ha de recibir para encenderlos, hay = para indicar que ese carácter no se ha de tener en cuenta y # para indicar que ese carácter se ha de tener en cuenta para saber qué valor tiene. El led del freno de estacionamiento se enciende según se recibe <a1 o <a0 para ponerse a nivel alto o bajo respectivamente. En el caso de los flaps, si no entendí mal, se ha de encender únicamente cuando el valor es distinto de cero, por lo que está configurado para que se “active” con un LOW cuando el valor es cero. Esto hace que cualquier valor distinto de cero lo “desactive” poniendo la salida a HIGH, encendiendo el led.

Adjunto los ficheros Link2FS.h y Link2FS.cpp que has de incluir junto al siguiente código:

#include "Link2FS.h" // "Librería que se encarga de todo con link2FS

Link2FS link2FS(Serial); // Objeto que se encarga de todo con link2FS. Se le indicua cual es el puerto serie al que está conectado

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

    // Parámetros de addEncoder
    //   Pin al que está conectado el CLK
    //   Pin al que está conectado el DT
    //   Formato de la cadena que hay que enviar al simulador (la que se utiliza en el priftf)
    //   Valor inicial del encoder (1 o 9 en el código original)
    //   Valor mínimo del encoder (0 en el código original)
    //   Valor máximo del encoder (10 en el código original). Si este parámetro y el anterior se ponen a cero, se auto ajusta en el momento del calibrado.
    //   Valor mínimo que envía al simulador (0 en el código original)
    //   Valor máximo que envía al simulador (99 en el código original, 100 en este código)
    //   Tiempo, en milisegundos, que ha de trasncurrir desde el último movimiento de calibrado para que se de por teminada la calibración. En este caso se indica cero porque ya los límites están definido (de 0 a 10)
    link2FS.addEncoder(48, 49, "C56%03u", 0, 0, 10, 0, 100, 0); // Palanca de Gases Motor 1
    link2FS.addEncoder(52, 53, "C57%03u", 0, 0, 10, 0, 100, 0); // Palanca de Gases Motor 2
    link2FS.addEncoder(50, 51, "C60%03u", 0, 0, 10, 0, 100, 0); // Paso de Helice Motor 1
    link2FS.addEncoder(45, 44, "C61%03u", 0, 0, 10, 0, 100, 0); // Paso de Helice Motor 2
    link2FS.addEncoder(42, 43, "C58%03u", 0, 0, 10, 0, 100, 0); // Palanca de Mezcla Motor 1
    link2FS.addEncoder(47, 46, "C59%03u", 0, 0, 10, 0, 100, 0); // Palanca de Mezcla Motor 2

    // Parámetros de addPinInFS:
    //   Pin de entrada
    //   Valor de la lectura del pin cuando está "activado"
    //   Indica si se utiliza la resistencia pull up interna o no (INPUT_PULLUP o INPUT)
    //   Cadena que hay que enviar si el pin está "activado"
    //   Cadena que hay que enviar si el pin está "desactivado"
    //   Inicio de la cadena que se recibe con el valor que confirma la recepción del dato
    //   Tiempo que espera antes de volve a comunicar el estado, si no recibe la cadena de "confirmación". Si es cero, no lo reenvía
    link2FS.addPinIn(31, LOW, INPUT_PULLUP, "C041", "C040", "?G", 2000); // Interruptor freno de estacionamiento
    link2FS.addPinIn(36, LOW, INPUT_PULLUP, "C02", "C01", "?Y", 2000); // Interruptor tren de aterrizaje
    link2FS.addPinIn(34, LOW, INPUT_PULLUP, "C15", NULL, NULL, 0); // Pulsador de flaps DOWN
    link2FS.addPinIn(35, LOW, INPUT_PULLUP, "C14", NULL, NULL, 0); // Pulsador de flaps UP

    // Parámetros de addPinOutFS:
    //   Pin de salida
    //   Valor de la salida del pin cuando está "activado"
    //   Modelo de la cadena que se recibe con el dato
    //   Valor numerico del dato para "activar" la salida
    link2FS.addPinOut(41, HIGH, "?Y#==", 2); // Led tren de aterrizaje nariz
    link2FS.addPinOut(40, HIGH, "?Y=#=", 2); // Led tren de aterrizaje izquierda
    link2FS.addPinOut(38, HIGH, "?Y==#", 2); // Led tren de aterrizaje derecha
    link2FS.addPinOut(32, HIGH, "<a", 1); // Led del freno de estacionamiento
    link2FS.addPinOut(33, LOW, "?G", 0); // Led de los flaps
}

void loop() {
    link2FS.loop(); // Se ha de realizar constantemente esta llamada para que se controle todo
}

Los “interruptores” están configurados para que envíen una y otra vez la información de su posición cada 2 segundos, si no recibe la “respuesta” asociada a su función. Es por si el Arduino se pone en marcha antes que la aplicación link2FS. Esta funcionalidad es posible utilizarla cuando se lee de un pulsador, si se quiere que emita repetidamente los datos si el pulsador se mantiene presionado. Para ello basta con indicar cada cuánto tiempo lo ha de reenviar y poner un NULL en el campo donde se indica la cadena que “confirma la recepción”.

En cuanto a poner más encoders, en principio no hay problema, salvo que el código tenga “muchas cosas que hacer” entre lectura y lectura de los encoders y estos se muevan demasiado rápido. Se podría probar y ver que tal funciona. En el peor de los casos creo que siempre se podría poner otro MEGA para controlar parte de los dispositivos. Por lo que he visto en los pantallazos que has puesto, parece que se pueden conectar otros MEGA al simulador. Si no se pudiera conectar otros megas, creo que también se podrían “conectar en cascada”, usando otro puerto serie del MEGA, para comunicar con el siguiente.

Así que creo que sí se podría poner más encoders para cosas como establecer la altura y rumbo del piloto automático o la frecuencia de la radio. Pero supongo que, además del encoder, se necesitaría algún tipo de display que muestre el valor que tiene y no sé si un botón para establecer/confirmar el valor. Puede que, en lugar de tener un encoder, display y botón para cada cosa, se prefiera que con uno de cada se pueda controlar varios parámetros, teniendo un botón para seleccionar el parámetro para ver o establecer.

Además, supongo que, por ejemplo, para establecer la altura, no querrán que sea de pie en pie, sino de cien en cien pies, o algo por el estilo. Como siempre, necesitaría saber cómo funciona el simulador. Qué información aporta, cómo se le establece los valores, rango de valores y cosas por el estilo. Así como si el simulador indica si está puesto o quitado el piloto automático o cosas por el estilo. Cuanto más información de el simulador y conozca cómo funciona, mejor.

Las cosas que aún quedan por hacer, para que haga lo mismo que tienes hasta ahora, son el control de la luz de operación del tren de aterrizaje y el test de luces (si no hay forma de que el simulador lo notifique, que sería lo mejor). También falta controlar la lectura del teclado matricial como si fueran un pulsador más.

Como mejora, me gustaría poder controlar displays de siete segmentos con el tm1640 o algún display LCD, para mostrar valores. Así como lo que has propuesto de establecer valores de altura, rumbo, etc con los encoders.

@jhonny195 se me olvidó adjuntar los ficheros Link2FS.h y Link2FS.cpp que has de poner junto al código. Los adjunto aquí. Perdón. :angel:
Link2FS.h (8.1 KB)
Link2FS.cpp (9.7 KB)

Primero lo primero... no se como apreciar todo el trabajo que te estas mandando, no se si es porque para mis ojos es se ve inalcanzable hacer algo así, pero es sorprendente todo esto. gracias una y otra vez.

Ahora tratare de contestar las preguntas y dudas que pusiste, despues lo pruebo y te respondo que tal se comporta.

El botón de test de tren fue algo mío, no hay extracción de datos de eso, asi como no hay codigo de envio, si lo viste en mi código era encender unos segundos los leds y luego apagarlos. Si se puede agregar asi para darle uso bien, si no no hay drama lo utilizo para otra cosa jaja

 int TAVPT = digitalRead(TAPT);//"V" Variable, "P" Pulsador, "T" Test.
  if(TAVPT == 0){
   digitalWrite(TALN, HIGH); digitalWrite(TALI, HIGH); digitalWrite(TALD, HIGH);digitalWrite(TALP, HIGH);delay(500);
   digitalWrite(TALN, LOW); digitalWrite(TALI, LOW); digitalWrite(TALD, LOW);digitalWrite(TALP, LOW);}

sobre el indicador de transición, y los leds de cada rueda, el codigo ?Yxxx que te llega por serie te indica el estado, de tren de aterrizaje. Aca te lo detallo:
?Y (código a leer)
x - primer cifra - indica estado de tren de nariz
x - segunda cifra - indica estado de tren Izquierdo
x - tercera cifra - indica estado de tren derecho

los 3 varían entre 0 1 2, para cada cifra respectivamente.
0 tren arriba - 1 en transición - 2 tren abajo

aca te hago un ejemplo:
Avión en tierra (tren abajo obvio) llega ?Y222
Rn vuelo, activo el interruptor para subir y pasan todos a 1, llega ?Y111 (seria la transición)
Al final en algo de 2 segundos o mas (depende del avion) llega ?Y000

También se puede llegar a leer por ejemplo ?Y010 o ?Y001 (eso es que el tren correspondiente al 1 todavía esta en transición y los demas ya esta arriba)

Lo mismo a la inversa, por serie llega ?Y000, bajas interruptor y se ponen en 1 osea ?Y111 (transición) cuando cada tren cumple el ciclo se ponen en 2.

Ahora sobre el serial.println()
Ese buffer temporal fue una solución (dada por un profesor de mi universidad) al que tantos serial.printin() me generaban retardo en el código y por lo tanto fue el primer problema que tuve con la lectura de los encoders. a lo mejor con tu código al ser mas corto no aparece tal falla.

En cuanto a esto:

Esta muy bueno, así no hay que "sincronizar" manualmente las cosas como hacia yo jaja

Sobre mi consulta de mas encoders y la función que llevarian, con esto de las nuevas restricciones pude viajar a mi pueblo a reunirme con mi amigo (el piloto, y usuario de esto) para saber que funciones le servirán, pq por mensajes es engorroso. Ni bien pueda te cuento que se podria agregar que sea util.

Bueno ahora me pongo a conectar todo y probar.

Bueno, estuve probando, te cuento, de la parte de instrucciones que se envían funciona todo, (todos los encoders, freno de estacionamiento , interruptor de tren de aterrizaje, pulsador de flaps).
lo único que hice fue modificarte el orden de los códigos porque funcionaba al revés jaja pero funciono todo bien eso.

Por el lado de los indicadores te modifique uno, que por error mío te di mal el dato, de freno de estacionamiento te lo di como <a y era <q (en el programa esta amontonado y no se ve la pata de la letra jeje) y de flap hay dos opciones una es ?Xxxx y la otra es ?Gxxx (osea con 3 cifras atrás, puede ponerse en grados por porcentaje te da la opción)

Después de hacer esos cambios cargue todo, probé y como te conté al inicio funciona solo lo que se envía, y probé a agregar (por lo que te entendí de los # y los = ) que también tenia que leer el valor que llegaba entonces se lo agregue pero aun así los leds no encendieron, los leds de tren de aterrizaje que están tal cual los mandaste vos tampoco funcionaron.

    link2FS.addPinIn(31, LOW, INPUT_PULLUP, "C040", "C041", "<q", 2000); // Interruptor freno de estacionamiento
    link2FS.addPinIn(36, LOW, INPUT_PULLUP, "C02", "C01", "?Y", 2000); // Interruptor tren de aterrizaje
    link2FS.addPinIn(34, LOW, INPUT_PULLUP, "C14", NULL, NULL, 0); // Pulsador de flaps DOWN
    link2FS.addPinIn(35, LOW, INPUT_PULLUP, "C15", NULL, NULL, 0); // Pulsador de flaps UP

    // Parámetros de addPinOutFS:
    //   Pin de salida
    //   Valor de la salida del pin cuando está "activado"
    //   Modelo de la cadena que se recibe con el dato
    //   Valor numerico del dato para "activar" la salida
    link2FS.addPinOut(41, HIGH, "?Y#==", 2); // Led tren de aterrizaje nariz
    link2FS.addPinOut(40, HIGH, "?Y=#=", 2); // Led tren de aterrizaje izquierda
    link2FS.addPinOut(38, HIGH, "?Y==#", 2); // Led tren de aterrizaje derecha
    link2FS.addPinOut(32, HIGH, "<q#", 1); // Led del freno de estacionamiento
    link2FS.addPinOut(33, LOW, "<G==#", 0); // Led de los flaps
}

Controle los números de pines de cada cosa por las dudas también y están perfectos.

Acá te adjunto una captura mientras de mientras probé, ahí se aprecia la transición de el tren de aterrizaje de ?Y000 hasta ?Y222 como va cambiando, y la posición de flaps ahí estaba en grados (el código que inicia <G, elegí ese tenia mas probabilidad de ser mayor a 0 para que cumpla con el código y encender el led ) y el <q0, <q1 es la del freno por eso probe a agregar el #

Eso si lo que modifique de toqueton, fue porque interprete que se adapta a cumplir la función si llega el parámetro entre las comillas, si no podia tocar perdon :point_right: :point_left:

@IgnoranteAbsoluto te pregunto esto por que no se que es mas simple... jaja

Hacer esto pero como un joystick (osea que la pc lo vea como tal) es mas complicado que lo que estamos haciendo ahora? por que a lo mejor te hago romper la cabeza con todo este sistema y como joystick es mas facil.
Se que intercomunican arduinos, se que el leonardo (tengo uno) se puede activar como joystick. con eso en mi cabeza giro armar todo en el mega, que se lo comunique al leonardo y el leonardo comanda la PC.
Si me decis deja de pensar boludeces sin saber y ponete a leer lo entendere jajaja :yum: :grinning_face_with_smiling_eyes:

@jhonny195 toca todo lo que quieras. Para eso dispones del código del programa. Y si no se toquetea no se aprende :grinning_face_with_smiling_eyes:. Los caracteres = y # son para poder indicar qué dígitos son los que hay que tener en cuenta, en el caso de no ser todos. Es por ello que sólo hace falta para el tren de aterrizaje. Para el resto no hace falta indicar nada, pues asume que son todos los dígitos que vienen después del "la cabecera" que se le indica.

Lo único que se me ocurre que pueda estar pasando, es que el link2FS tal vez no esté mandando un retorno de carro o avance de línea al final de cada "respuesta". O que hay algo entre los números y el retorno de carro. Por si es esto último, prueba ha hacer un cambio el fichero Link2FS.cpp. Añade la línea:

        if ((*cadena < '0') || (*cadena > '9')) continue;

En la función DecodificaDatoFS::getCantidad(const char* cadena) para que quede así:

int32_t DecodificaDatoFS::getCantidad(const char* cadena) const {
    int32_t valor = 0;
    cadena += this->posicionValor;
    for (size_t i = 0 ; (i < this->longitudValor) && (*cadena != 0); i++, cadena++) {
        if ((*cadena < '0') || (*cadena > '9')) continue;
        valor *= 10;
        valor += *cadena - '0';
    }
    return valor;
}

Además, en ese mismo fichero la línea 10 que pone:

        if ((caracter == 10) || (caracter == 13)) {

Cámbiala por:

        if ((caracter <= 32) || (caracter > 125)) {

Prueba a ver si con esos dos cambios funciona. Y si quieres, prueba con el monitor serie de Arduino y manda al MEGA algo como ?Y222 a ver si se te encienden las luces del tren de aterrizaje.

Respecto a utilizar un Leonardo para simular un joystick, la verdad es que ahora mismo no sé cómo sería ni lo que esa simulación podría ser capaz de hacer. ¿Sabes si hay algún código por ahí que no sólo mande datos al simulador, sino que también reciba la información del estado de las cosas como la posición del tren de aterrizaje, los flaps, etc?

Bueno te voy contando:
Hice la prueba con el código original que mandaste (solo con las correcciones de letras, sin mis toqueteos de # y = ) y con los cambios tuyos nuevos en el monitor serie del IDE y funcionan los leds perfectamente. Pero sigue sin funcionar en el simu.

Estuve escarbando en los .txt que tiene el programa para ver si decía algo de la lectura por puerto serie pero nada que sirva.

Aca te dejo el link del programita, a lo mejor te sirve, (pero para que te muestre datos creo que tenes que tener el simulador tambien. Si queres lo cargo a mi drive y te lo mando tambien.
Link2fs

PD: te acordas que una vez me preguntaste por que unos encoders los iniciaba en 9? hoy lo recordé mientras probaba. cuando arrancas el simulador los motores están el marcha, si la palanca de mezcla de los motores inicia en 0 se te detienen los motores y tenes que volver a iniciarlos, en tu codigo nuevo cambie el valor inicial a 10 (que es el valor máximo) por esta cuestion.

@jhonny195 ¿Podrías cargar este programa en el Arduino y conectarlo al link2fs? El programa sólo hace eco hacia el puerto serie de lo que le llega por el puerto serie, pero mostrando el valor hexadecimal de los caracteres “especiales”. De esta forma se supone que en la pestaña “monitor” del link2fs se verá, en la zona “From card”, lo que manda link2fs al Arduino. Por ver si hay algo que no he tenido en cuenta. Una vez mande unos cuantos valores, haz una captura y ponla aquí, para ver qué es todo lo que manda link2fs al Arduino. Gracias.

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

bool saltar = false;

void loop() {
    if (Serial.available()) {
        byte c = Serial.read();
        if ((c > 32) && (c < 127)) {
            if (saltar) {
                Serial.println();
                saltar = false;
            }
            Serial.print((char)c);
        }
        else {
            Serial.println();
            Serial.print('[');
            if (c < 0xf) {
                Serial.print('0');
            }
            Serial.print(c, HEX);
            Serial.print(']');
            saltar = true;
        }
    }
}

Hola @IgnoranteAbsoluto ya sin hacerlo sabia lo que pasaría (porque me ocurrio jaja), pero igual trate.

Si abro el monitor serie, no puedo conectar el arduino al link2fs, y si conecto el arduino al link2fs, el monitor serie tambien me dice que no se puedo conectar.

En resumen no puedo conectar las dos cosas al mismo tiempo.

Idea 1==> Tengo este display, se puede imprimir acá lo que llega al arduino por serie? y lo anoto en papel o lo grabo con el cel (si es muy rapido) y despues lo copio. O los datos no saldrán igual?

Idea 2==> Grabar los datos y luego extraerlos? no se si por tiempo (10 segs ) o con un botón que yo pulse y "grabe". Después desconecto el link2fs y lo saco por monitor serie con otro botón o te mando el codigo con lo que haya grabado.

@IgnoranteAbsoluto me puse a toquetear el codigo que me mandaste y logre hacerlo imprimir en el LCD.

aca te dejo el codigo por si meti la pata y la informacion no te sirve:


#include <LCD_I2C.h>

LCD_I2C lcd(0x27); // Default address of most PCF8574 modules, change according
int pulsador = 37;

void setup(){
    lcd.begin(); // If you are using more I2C devices using the Wire library use lcd.begin(false)
                 // this stop the library(LCD_I2C) from calling Wire.begin()
    lcd.backlight();
    Serial.begin(115200);
     pinMode(pulsador, INPUT_PULLUP);
}

bool saltar = false;

void loop(){
     if (Serial.available()) {
        byte c = Serial.read();
        if ((c > 32) && (c < 127)) {
            if (saltar) {
                //Serial.println();
                //lcd.print();
                saltar = false;
            }
            //Serial.print((char)c);
            lcd.print((char)c);
        }
        else {
            //Serial.println();
            //Serial.print('[');
           // lcd.print();
            lcd.print('[');
            if (c < 0xf) {
                //Serial.print('0');
                lcd.print('0');
            }
            //Serial.print(c, HEX);
            //Serial.print(']');
            lcd.print(c, HEX);
            lcd.print(']');
            saltar = true;
        }
    }
    int varbot = digitalRead(pulsador);
    if (varbot == 0) {
    lcd.clear();
  } 
    delay(50);
}

Esta imagen es lo que salió tal cual con el link2fs:

Acá te mando un foto de lo mismo pero hecho con el monitor serie del IDE, escribí jhonny195 envie con enter agrega [0A] al final y si apretó enter sin escribir nada también llega esto solo [0A] :

Con esto interpreto que como dijiste no llega un "enter" al final de cada código del link2fs si no que llega todo en un solo chorro constante de caracteres. (perdón mi lenguaje rustico jaja)

espero te sirva esto. saludos!!

PD: jugando con el lcd me di cuenta que si hago un espacio entre palabras también genera un código entre corchetes el [20] osea que tampoco envía espacios entre códigos

@IgnoranteAbsoluto FUNCIONAN LAS EXTRACCIONES!!!! :grinning: :grinning: :grinning:

Ves donde esta el puntero del mouse? hay un tilde de una opción que dice: "include CR/LF at the end" recordé que el enter es un line feed y lo asocie con eso, lo active y funciona.

Algo por lo que no lo había tocado antes es por que mas abajo en todo ese texto en ingles dice que no hay que utilizarlo con Arduino (no explica el porque :confused: ) .

Bueno ahora podes seguir renegando con los demás jaja ahora sabemos que anda, solo tengo que recordar activar eso.

@IgnoranteAbsoluto :smiley: te estoy llenando de mensajes para que leas jaja, te cuento que me puse a probar agregar entradas al Arduino (botón y encoder) te tengo un par de consultas.
Ahora agregue 1 encoder y dos botones, pero por lo que hable con mi amigo, necesitaría agregar 4 encoders mas y botones también.

Hay limite en agregar renglones de los 3 tipos? encoders, botones, extracciones?
por la parte de botones suponiendo que sean mas de 16 botones, se podrá hacer con matriz manual? algo como lo que había mandado @Surbyte (aca te dejo el link => Matriz de botones)