// Arduino **MEGA** for optical rotary encoder; plus an encoder simulator  Sept 2022 
// WOKWI animation : https://wokwi.com/projects/343001355413094995


/*
The first part of the program receives the quadrature signal from an optical rotary encoder 
and displays its position on the serial monitor. 

---------------------------------------------
If you have a 5 Volts encoder and an Arduino:
---------------------------------------------
Connect the outputs A and B of the 5 Volt encoder to the input pins 2 and 3 of the Arduino
(put resistors of 1K max in series between the pins and the wires A and B, for safety). 
Do not make the connections on pins 11 and 12, nor the 2 buttons on A0 and A3. 
You can delete the second part of the program, which is not used with a real encoder.

------------------------------------
If you don't have the real encoder :
------------------------------------
the second part of the program allows to simulate the quadrature function on pins 11 and 12
(these pins work like an encoder, they are controlled by the buttons).

With a real Arduino:
-------------------
Place resistors of 1K max as shown on the Wokwi diagram, to protect the pins from a programming error: 
output 12 to input 2, and output 11 to input 3; 
and also on the buttons, 1K max between A0 and the button, 1K max between A3 and the other button.
ATTENTION don't forget to set (prevmillis > 20) in the "additionalButtons()" function
(for Wokwi the value is 1, otherwise the reaction time of the buttons is too long).

In simulation with Wokwi:
-------------------------
The resistors are intentionally not connected, it is to indicate how to do with the real Arduino.
In case of blocking, it is sometimes necessary to stop the simulation, and restart it.
You may have noticed that English is not my usual language ;-)
*/




// ENCODER function : quadrature signal decoded with interrupt inputs 2 & 3 ----------------------------

  #define INPUT_ENCOD_A    2                                  // *** MEGA PIN2 (***=adapt with input pin)
  #define INPUT_ENCOD_B    3                                  // *** MEGA PIN3
  #define SignalA          B00010000                          // *** MEGA PIN2 = PORT_E  bit4 = B00010000
  #define SignalB          B00100000                          // *** MEGA PIN3 = PORT_E  bit5 = B00100000
  #define SignalAB         B00110000                          // *** both signals
  volatile int             ISRencodPos;                       // encoder position
  int                      encodLastPos;                      // previous position
  byte                     LastPort8 = SignalA;               // previous A/B state 


void setup(void) {                                            //
  pinMode(INPUT_ENCOD_A, INPUT_PULLUP);                       //
  pinMode(INPUT_ENCOD_B, INPUT_PULLUP);                       //
  attachInterrupt(digitalPinToInterrupt(INPUT_ENCOD_A), ExtInt, CHANGE);
  attachInterrupt(digitalPinToInterrupt(INPUT_ENCOD_B), ExtInt, CHANGE);
  Serial.begin(115200);                                       // fast fast fast !
  Serial.println(F("waiting for encoder rotation..."));       // 
  additionalSetup();   //!!!//                                // (!!! not necessary with real encoder)
}                                                             //


void ExtInt() {                                               /// OPTICAL ENCODER ext interrupt pin X, Y
     byte Port8  =  PINE & SignalAB;                          // *** PINE (PORT INPUT E)  ***for Mega***
      LastPort8 ^=  Port8;                                    //                                      
  if (LastPort8 & SignalA)   ISRencodPos++;                   // Rotation -> {ISRencodPos++; Sense = 1;}
  if (LastPort8 & SignalB)   ISRencodPos--;                   // Rotation <- {ISRencodPos--; Sense = 0;}
  if (    Port8 && (Port8 != SignalAB)) Port8 ^= SignalAB;    //                              (swap A-B)
      LastPort8  =  Port8;                                    //                  mieux vaut faire court
}                                                             //


void loop(void) {                                             /// MAIN LOOP
  noInterrupts();                                             //
    int encodPosition = ISRencodPos;                          //
  interrupts();                                               //

  if (encodLastPos != encodPosition) {                        // when the encoder change,
    encodLastPos = encodPosition;                             //
    Serial.println(encodPosition);                            // print encoder position
  }                                                           //
  additionalButtons();  //!!!//                               // (!!! not necessary with real encoder)
}                                                             //
// END of the REAL ENCODER minimum function ----------------- // END REAL ROTARY ENCODER ---------------

/*
      |
      v


      |
      v


      |
      v 

*/ 

// EXTRA aditional function : BUTTONS & ENCODER SIMULATION---------------------------------------------
  
#define Phase1     B00100000                                  // SIMUL_ENCODER  bit 6 & 5 of Mega PORTB
#define Phase2     B01000000                                  // SIMUL_ENCODER  for direct PORT write,
#define Phase1_2   B01100000                                  // SIMUL_ENCODER  but not used
byte               Quadra = Phase1;                           // SIMUL_ENCODER  (digitalWrite used)


#define INPUT_buttonLeft    A0                                // PIN
#define INPUT_buttonRight   A3                                // PIN
#define OUTPUT_simulEncodA  12                                // PIN
#define OUTPUT_simulEncodB  11                                // PIN


#define buttonLeft      B00000001                             // BUTTON   PF0   (A0)   ***for Mega***
#define buttonRight     B00001000                             // BUTTON   PF3   (A3)   ***for Mega***
#define buttonAll       B00001001                             // BUTTON   
#define maskAutoRepeat  B00001001                             // BUTTON   auto-repeat define here concerned keys 
#define tempoRepeatSlow 60                                    // BUTTON   auto-repeat first tempo (multiple 20ms)
#define tempoRepeatFast  6                                    // BUTTON   auto-repeat second tempo(multiple 20ms)
unsigned long     prevmillis;                                 // BUTTONS  debounce period
byte              actionStat;                                 // BUTTONS  state    buttons
byte              actionDown;                                 // BUTTONS  pressed  buttons
byte              actionUp;                                   // BUTTONS  released buttons
byte              actionMemo;                                 // BUTTONS  memorised states buttons
byte              tempoAutoRepeat = tempoRepeatSlow;          // BUTTONS  auto-repeat double-timer



void additionalSetup(){                                       // setup
  pinMode(OUTPUT_simulEncodA, OUTPUT);                        // SIMUL_ENCODER
  pinMode(OUTPUT_simulEncodB, OUTPUT);                        // SIMUL_ENCODER
  pinMode(INPUT_buttonLeft,  INPUT_PULLUP);                   // BUTTON
  pinMode(INPUT_buttonRight, INPUT_PULLUP);                   // BUTTON
  digitalWrite(OUTPUT_simulEncodA,  Quadra & Phase2);         // initialise quadrature signal
  digitalWrite(OUTPUT_simulEncodB,  Quadra & Phase1);         // on pins 11, 12 
}                                                             // 


void additionalButtons(){                                     // 1 .. 8 BUTTONS, 16 with word instead byte
  if (millis() - prevmillis > 1) {    //! ATTENTION //       // Wokwi=1,real Mega=20 (ms debounce period)
    prevmillis = millis();                                    //
    byte iii   = ~PINF & buttonAll;                           // PORTF bit 0 & 3  (A0  A3)  ***for Mega***
    byte jjj   = actionStat;                                  //    
    actionStat = iii & actionMemo;                            // buttons status                 (state)
    actionDown = (jjj ^ actionStat) & actionStat;             // buttons is (are) just pressed  (front)
    actionUp   = (iii ^ jjj) & jjj;                           // buttons is (are) just released (front)
    actionMemo = iii;                                         // buttons configuration stored for next use

    if (actionStat & maskAutoRepeat) {                        // autoRepeat only on buttons in the mask
      tempoAutoRepeat--;                                      // 
      if (tempoAutoRepeat == 0) {                             // 
        tempoAutoRepeat = tempoRepeatFast;                    // autorepeat short tempo after 1st stroke
        actionDown = actionStat;                              // re-triggs buttons that are kept pressed
    } } else tempoAutoRepeat = tempoRepeatSlow;               // autoRepeat long tempo for first stroke


    
    if (buttonLeft & actionDown) {                            // ENCODER SIMULATION 
      if (Quadra && (Quadra != Phase1_2)) Quadra ^= Phase2;   // left button pressed
      else                                Quadra ^= Phase1;   // make quadrature signal
      digitalWrite(OUTPUT_simulEncodA,    Quadra &  Phase2);  // write quadrature signal
      digitalWrite(OUTPUT_simulEncodB,    Quadra &  Phase1);  // on pin 11,12
    }                                                         //
    
    if (buttonRight & actionDown) {                           // right button pressed
      if (Quadra && (Quadra != Phase1_2)) Quadra ^= Phase1;   // make quadrature signal
      else                                Quadra ^= Phase2;   //
      digitalWrite(OUTPUT_simulEncodA,    Quadra &  Phase2);  // write quadrature signal
      digitalWrite(OUTPUT_simulEncodB,    Quadra &  Phase1);  // on pin 11,12
    }                                                         //
  }                                                           //
}                                                             //