[SOLUCIONADO] "Código lento" joystick con rotary encoders y potenciometros

Buenas! Llevo un tiempo experimentando con un Arduino Leonardo y estoy tratando de fabricar un joystick con 3 rotary encoders (con pulsador), algunos botones y tres potenciometros. Logré armar un código con la librería joystick que cumple su función, pero tiene una pequeña falla.


(Imagen del controlador de dispositivos de Windows)

Los encoders funcionan activando dos “botones” cada uno en el software. El problema es que utilizando el código que dejo debajo de este párrafo los encoders funcionan regularmente si los giro despacio (de 10 pulsos lee la mitad), y si los giro demasiado rápido aparentemente no llega a leer todos esos pulsos y no activa los botones en el tablero de la imagen de arriba. Los probé en un código funcionando por separado y vuelan, lee todos los pulsos por más rápido que gire la perilla.

#include "Joystick.h"
#include <Rotary.h>
// Los true y false de abajo representan cada eje que queremos habilitar o deshabilitar en el joystick
// Los numeros seguidos de joystick_type_Gamped definen la cantidad de botones y hatswitches que queremos en ese orden.
// los ejes se activan en el siguiente orden: eje x, eje y, Eje Z, rotación x, rotación y, rotación Z, acelerador, volante, desconocido 
Joystick_ Joystick(JOYSTICK_DEFAULT_REPORT_ID, JOYSTICK_TYPE_GAMEPAD, 13, 0, false , false, true, true, true, false, false, false, false, false, false);

Rotary rotary1 = Rotary(0, 1);  
Rotary rotary2 = Rotary(2, 3); 
Rotary rotary3 = Rotary(4, 5); 

int gasValue = 0;
int brakeValue = 0;
int clutchValue = 0;
int counter= 0;
long valor;
unsigned long t = 0;
int periodo = 50;

void setup() {
  Joystick.begin();
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  pinMode(9, INPUT_PULLUP);
  pinMode(10, INPUT_PULLUP);
}
const int  pinToButtonMap = 6;
int lastButtonState[2] = {0,0};

void loop() {
  t = millis();
  encoders ();
  flaps ();
  botones();
  pot();
}
void pot (void) { //funcion que aparentemente tiene el conflicto
  Joystick.setZAxis(analogRead(A1));
  Joystick.setRxAxis(analogRead(A2));
  Joystick.setRyAxis(analogRead(A3));
}

void encoders (void) {  //funcion de encoders
            unsigned char result = rotary1.process();
            if (result == DIR_CW) {
            Joystick.setButton(0, 1);
            while(millis() < t+periodo){
            }
            Joystick.setButton(0,0);
            }
    if (result == DIR_CCW) {
            Joystick.setButton(1, 1); 
            while(millis() < t+periodo){
            }
            Joystick.setButton(1, 0);
            }
          unsigned char result2 = rotary2.process();
    if (result2 == DIR_CW) {
            Joystick.setButton(2, 1); 
            while(millis() < t+periodo){
            }
            Joystick.setButton(2,0);
            }
    if (result2 == DIR_CCW) {
            Joystick.setButton(3, 1); 
            while(millis() < t+periodo){
            }
            Joystick.setButton(3,0);
            }
           unsigned char result3 = rotary3.process();
    if (result3 == DIR_CW) {
            Joystick.setButton(4, 1); 
            while(millis() < t+periodo){
            }
            Joystick.setButton(4,0);
            }
    if (result3 == DIR_CCW) {
            Joystick.setButton(5, 1); 
            while(millis() < t+periodo){
            }
            Joystick.setButton(5,0);
            }
}
void botones (void) { //funcion para leer los pulsadores de los encoders y el resto de los botones conectados
      for (int index = 6; index < 11; index++)
  {
    int currentButtonState = !digitalRead(index); 
    if (currentButtonState != lastButtonState[index]) 
    {
      Joystick.setButton(index, currentButtonState); 
      lastButtonState[index] = currentButtonState; 
    }
  }
}
  void flaps (void) { // esta funcion está descartada del problema
    valor = analogRead(A0);
    if (valor > 250 and valor < 750) {
      Joystick.setButton(11, 0);
      Joystick.setButton(12, 0);      
}
  else if (valor < 250) {
      Joystick.setButton(11, 1);
      Joystick.setButton(12, 0);      
}
else if (valor > 750) {
     Joystick.setButton(11, 0);
     Joystick.setButton(12, 1);      
}
  }

Para investigar un poco separé los botones, encoders y potenciometros en funciones para después llamarlos dentro del loop.
Probé por separado la función encoders con todas las demás (flaps y botones) y funcionan bien aún girando los encoders a toda velocidad, pero cuando incluyo pot() que sería la que contiene la lectura de los tres potenciometros (Eje Z, rotación X e Y) dentro del loop parece que ralentiza todo y se empieza a saltear los pulsos de los encoders.
¿Hay algo que estoy haciendo mal y no me dí cuenta, o alguna forma de “agilizar” este código? Espero que se entienda, gracias.

Tu problema es el tamaño de ejecución de programa y los pequeños pulsos de los encoders (en tiempo). Cuando tienes un programa chico (y tiempo de ejecución rápido), no pierdes pulsos (hasta cierto punto). Por eso con los encoders se utilizan interrupciones.

Hace 1 o 2 días sugerí una librería que esta en el IDE y EncoderStepCounter, tmb esta Encoder

este es su ejemplo con interrupciones link

En un UNO/NANO no tienes mas que 2 interrupciones de modo que 3 enconders solo serán posibles en un MEGA usando interrupciones o en otro micro con capacidad para ello.

Como verás acá hay una tabla que pertenece a Teensy y por eso el gran detalle de sus modelos pero solo el MEGA y algun modelo Teensy puede con el desafío.

https://www.pjrc.com/teensy/td_libs_Encoder.html#optimize

Board Interrupt
Teensy 4.0 - 4.1 All Digital Pins
Teensy 3.0 - 3.6 All Digital Pins
Teensy LC 2 - 12, 14, 15, 20 - 23
Teensy 1.0 0, 1, 2, 3, 4, 6, 7, 16
Teensy++ 2.0 0, 1, 2, 3, 18, 19, 36, 37 6
Teensy++ 1.0 0, 1, 2, 3, 18, 19, 36, 37
Arduino Due All Digital Pins
Arduino Leonardo 0, 1, 2, 3
Arduino Mega 2, 3, 18, 19, 20, 21
Sanguino 2, 10, 11
ESP32 All digital pins
ESP8266 All digital pins except GPIO16

Olvidé algo importante, los que pueden con un desafío luego no pueden con el otro que es convertir a tu sistema como algo que enchufas y se convierte en lo que deseas.
Se debe estudiar cada caso.

EDITO:
mas opciones

BOARD DIGITAL PINS USABLE FOR INTERRUPTS
Uno, Nano, Mini, other 328-based 2, 3
Uno WiFi Rev.2, Nano Every all digital pins
Mega, Mega2560, MegaADK 2, 3, 18, 19, 20, 21
Micro, Leonardo, other 32u4-based 0, 1, 2, 3, 7
Zero all digital pins, except 4
MKR Family boards 0, 1, 4, 5, 6, 7, 8, 9, A1, A2
Nano 33 IoT 2, 3, 9, 10, 11, 13, 15, A5, A7
Nano 33 BLE, Nano 33 BLE Sense all pins
Due all digital pins
101 all digital pins (Only pins 2, 5, 7, 8, 10, 11, 12, 13 work with CHANGE)

Creo que tienes demasiadas cosas al mismo tiempo en el void loop, aunque no existe un manual, por experiencia es mejor que cada componente de tu proyecto sea capaz de funcionar por separado con sus propios elementos fijos y cíclicos.

Lo ideal es que cuando la rutina de un componente funcione como quieres, tenga la flexibilidad de copiarla y pegarla en el programa principal sin que altere la funcionalidad de otros componentes. Usa las pestañas que permite administrar el IDE y recurre a funciones individuales:

void componente1()
{
//Código que solo se ejecuta una vez
while(1)
  { 
  //Ciclo de trabajo hasta que se cumpla una condición que retorne al programa principal
  }
}

No tienes tantas líneas en tu proyecto como para que experimentes los fallos que nos compartes, sin embargo al ejecutarse al mismo tiempo sus rutina, terminan por ralentizar al MCU.

Otro aspecto es el uso de cables de prototipo, trata de ir fortaleciendo las zonas que ya funcionan, soldando uniones en algún PCB. A veces los cables son los culpables de tanto dolor de cabeza.

Gracias por todas las respuestas, estuve probando distintas cosas en estos días. En un principio no pensé en utilizar interrupciones porque para otro proyecto similar a este tengo pensado utilizar muchos más encoders, que no sería problema porque mientras no haya que incluir potenciometros van a funcionar igual.
De todas formas estuve intentando hacerlo funcionar con interrupciones para al menos 2 encoders con la intención de enganchar el resto usando la librería EnableInterrupt pero hasta el momento no tuve éxito, si lo logro les comento después.

Por otro lado, me da mucha pena que no pueda funcionar todo junto faltando tan poco. Hay alguna forma de hacer que la función de los analógicos se ejecute una menor cantidad de veces dentro del loop para evitar esa sobrecarga y darle "prioridad" a las demás?

void loop() {
  t = millis();
  static int cont = 0;

  encoders ();
  flaps ();
  botones();
  if(cont == 0) pot(); // ejecuta pot() cada 10 pasadas
  cont++;
  cont %= 10;  // *  resetea contador cada 10 pasadas 
}

Fijate si te sirve, cambiando el valor del divisor en la última instrucción (marcada con *) modificas cada cuantas pasadas se ejecuta, por ejemplo

cont %= 20;

hará que el if sea verdadero cada 20 pasadas y entonces sí, se ejecute pot(), mientras tanto no se ejecuta

Saludos

Nota: uso la función módulo (%), que da el resto de una división entre enteros, por comodidad pero se podría cambiar por

if(cont == 10) cont = 0;

básicamente es lo mismo en este caso en que solo quiero resetear el contador, pero % es 116 bytes mas "barata". :smiley:

Incorporé lo que dijo Gatul en el mensaje de arriba para retrasar las demás funciones y mejoró mucho, estoy muy conforme. Muchas gracias, saludos!

De nada!
Me alegra que te haya servido.

Recuerda que si das por resuelta tu consulta, debes editar tu primer post y agregar "[SOLUCIONADO]" en el título. Gracias

Saludos

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.