/*################### File version: 4.0 ##################*/
/*########################################################*/
/* Controllo stepmotor per il nastro trasportatore a      */
/* scopo didattico (il docente dovrà spiegarne il         */
/* funzionamento).                                        */
/* vengono utilizzati tre pulsanti:                       */
/*  - pulsante rosso P1 per halt e stop del motore;       */
/*  - pulsante verde P2 per controllare il senso          */
/*    di rotazione;                                       */
/*  - pulsante grigio per controllare il microstepping    */
/*    (quindi la velocità) del motore.                    */
/* Il programma utilizza per il tasto P1 tre funzioni     */
/* logiche con cui si tenta di simulare il comportamento  */
/* di un debounce hardware basato su flip flop; per il    */
/* tasto P2 la chiamata a funzione dove con tre funzioni  */
/* logiche simili a quelle per il tasto P1; per il tasto  */
/* P2 l'interrupt del microcontrollore.                   */
/*                                                        */
/*     driver A4988 e DRV8825                             */
/*         ,-----------,                                  */
/*  ENABLE |o         o| V_MOTORE                         */
/*     MS1 |o ____    o| GND                              */
/*     MS2 |o|    |   o| 2B                               */
/*     MS3 |o|____|   o| 2A                               */
/*   RESET |o         o| 1A                               */
/*   SLEEP |o     _   o| 1B                               */
/*    STEP |o    |_|  o| VDD                              */
/*     DIR |o         o| GND                              */
/*         `-----------'                                  */
/*                                                        */
/*    ==========================================          */
/*     MS1 | MS2	|  MS3 |  stepLevel resolution          */
/*    -----|------|------|----------------------          */
/*    Low	 | Low	| Low	 |  Full step                     */
/*    High | Low	| Low	 |  1/2 step                      */
/*    Low	 | High |	Low	 |  1/4 step                      */
/*    High | High |	Low	 |  1/8 step                      */
/*    High | High |	High |	1/16 step                     */
/*    =========================================           */
/*########################################################*/
// https://wokwi.com/projects/426024956545802241
#include <TimerOne.h>  // Libreria per gestire interrupt basati su timer
#define dirPin 6 //2
#define stepPin 3
#define button_ForwardReverse 5
#define button_HaltStart 4
#define button_Microstep 2 //6
#define  DEBOUNCETIME 50

volatile bool dirChange = false;
volatile bool directionChange = false;  // Flag per prevenire step durante cambio dir
volatile bool direction = true;
volatile bool stato = false;
volatile bool stepChange = false;
volatile int count1 = 0;
uint16_t speed;
uint8_t startStopPin = 0;
uint8_t stepLevel = 0;
uint16_t stepInterval = 1200; // Microsecond (600µs HIGH + 600µs LOW)

// Variabili per gestione non bloccante del cambio di direzione
unsigned long dirChangeStartTime = 0;
bool waitingForDirChange = false;

int MS[3] = {9, 8, 7}; // pin microstepper MS1=MS[0]->9, MS2=MS[1]->8, MS3=MS[2]->7
boolean microstepArray[5][3] = {
 {LOW, LOW, LOW},
 {HIGH, LOW, LOW},
 {LOW, HIGH, LOW},
 {HIGH, HIGH, LOW},
 {HIGH, HIGH, HIGH} 
};

enum resolution{
	FULL,
	HALFT,
	QUARTER,
	EIGHTH,
	SIXTEENTH
};

/**
 * Struttura Button per gestione avanzata del debounce
 * 
 * Questa implementazione rileva immediatamente il primo cambio di stato di un pulsante
 * e poi inibisce il rilevamento di ulteriori cambiamenti per un periodo configurabile.
 * 
 * Funzionamento:
 * 1. Al primo cambiamento di stato: rileva immediatamente e attiva debState
 * 2. Durante il periodo di debounce (debState = true): ignora tutti i cambiamenti
 * 3. Dopo il periodo di debounce: ripristina lo stato di riferimento e riabilita il rilevamento
 */
struct Button {
  uint8_t pin;           // Pin di Arduino collegato al pulsante
  uint8_t val0;          // Ultimo valore letto
  unsigned long debtime; // Tempo di debounce in millisecondi
  uint8_t val;           // Valore attuale letto
  uint8_t val00;         // Ultimo stato stabile
  unsigned long last;    // Timestamp ultimo cambiamento
  bool debState;         // Flag per stato debounce (true = in debounce)
  
  /**
   * Verifica se c'è stato un cambiamento valido dello stato del pulsante.
   * Rileva solo il fronte iniziale del cambiamento, non la fine del debounce.
   * 
   * @return true solo al rilevamento del primo fronte, false altrimenti
   */
  bool changed() {
    bool chg = false;    // Default: nessun cambiamento rilevato
    val = digitalRead(pin);
    
    if(!debState) {      // STATO IDLE: attesa di un cambiamento
      if(val != val0) {  // Rilevato cambiamento
        debState = true;  // Attiva stato di debounce
        last = millis();  // Memorizza timestamp
        chg = true;       // Segnala cambiamento (SOLO QUI)
        val0 = val;       // Aggiorna ultimo valore letto
        val00 = val;      // Memorizza stato valido
      } 
    } else {              // STATO DEBOUNCE: filtraggio rimbalzi
      if(val != val0) {   // Se lo stato continua a cambiare
        last = millis();  // Reset del timer di debounce
	      val0 = val;         // Aggiorna ultimo valore letto
      }else if((unsigned long)(millis() - last) > debtime) {
        debState = false;  // Torna a stato stabile
        val0 = val00;      // Ripristina stato di riferimento
        // chg rimane false quando il debounce termina
      }
    }
    
    return chg;  // Ritorna true solo sul fronte iniziale
  }
};

// Inizializzazione dei pulsanti con tempi di debounce
Button buttonStart = {button_HaltStart, LOW, 50};      	  // Debounce standard per start/stop
Button buttonDir = {button_ForwardReverse, LOW, 50};      // Debounce standard per direzione
Button buttonStep = {button_Microstep, LOW, 50};          // Debounce standard per microstepping

/**
 * Imposta la risoluzione del microstepping
 * 
 * @param level Livello di microstepping (0-4)
 * @return true se il livello è valido, false altrimenti
 */
bool setResolution(uint8_t level){
  if (level < 0 || level > 4) return false;
  
  Serial.print("Res: ");
  for (int i = 0; i < 3; i++) {
    digitalWrite(MS[i],microstepArray[level][i]);
    Serial.print(microstepArray[level][i]);
  }
  Serial.println();
  return true;
}

/**
 * Funzione di interrupt per controllare gli step del motore
 * Viene eseguita a ogni interrupt del timer
 */
void stepMotor() {
  static bool stepState = false;
  
  if (directionChange) {
    return;  // Non generare step durante il cambio di direzione
  }
  
  stepState = !stepState;
  digitalWrite(stepPin, stepState);
}

void setup() {
  Serial.begin(115200);
 
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
  pinMode(MS[0], OUTPUT);
  pinMode(MS[1], OUTPUT);
  pinMode(MS[2], OUTPUT);
  pinMode(button_ForwardReverse, INPUT);
  pinMode(button_HaltStart, INPUT);
  pinMode(button_Microstep, INPUT);
   
  setResolution(FULL);
  stato = false;
  speed = 600;
  direction = true;
  directionChange = false;
  
  // Configura l'interrupt di timer per gli step del motore
  Timer1.initialize(stepInterval/2);  // Metà del periodo totale
  Timer1.attachInterrupt(stepMotor);
  Timer1.stop();  // Ferma il timer finché non è necessario
}

void loop() {
  // Gestione non bloccante del cambio di direzione
  if (waitingForDirChange && ((unsigned long) millis() - dirChangeStartTime >= 1000)) {
    waitingForDirChange = false;
    digitalWrite(dirPin, buttonDir.val);
    directionChange = false;
    
    // Se il motore era in movimento prima del cambio direzione, riprendi
    if (stato) {
      Timer1.resume();
    }
  }
  
  // Controlla il pulsante di microstepping
  if(buttonStep.changed()) {
    if(buttonStep.val) {
      stepLevel++;
      stepLevel = stepLevel % 5;
      setResolution(stepLevel);
    }
  }

  // Controlla il pulsante di direzione
  if(buttonDir.changed()) {
    directionChange = true;
    Serial.print("Direction: ");
    Serial.println(buttonDir.val);
    
    // Ferma il motore durante il cambio di direzione
    if (stato) {
      Timer1.stop();
    }
    
    // Avvia il cambio di direzione non bloccante
    waitingForDirChange = true;
    dirChangeStartTime = millis();
  }
    
  // Controlla il pulsante di avvio/arresto
  if(buttonStart.changed()) {
    if(buttonStart.val) {
      stato = true;
      Timer1.start();
      Serial.println("Timer started");
    } else {
      stato = false;
      Timer1.stop();
      Serial.println("Timer stopped");
    }
  }
  
  delay(1); // Breve pausa per stabilità
}
// https://wokwi.com/projects/426024956545802241
$abcdeabcde151015202530354045505560fghijfghij
nano:12
nano:11
nano:10
nano:9
nano:8
nano:7
nano:6
nano:5
nano:4
nano:3
nano:2
nano:GND.2
nano:RESET.2
nano:0
nano:1
nano:13
nano:3.3V
nano:AREF
nano:A0
nano:A1
nano:A2
nano:A3
nano:A4
nano:A5
nano:A6
nano:A7
nano:5V
nano:RESET
nano:GND.1
nano:VIN
nano:12.2
nano:5V.2
nano:13.2
nano:11.2
nano:RESET.3
nano:GND.3
stepper1:A-
stepper1:A+
stepper1:B+
stepper1:B-
A4988
drv2:ENABLE
drv2:MS1
drv2:MS2
drv2:MS3
drv2:RESET
drv2:SLEEP
drv2:STEP
drv2:DIR
drv2:GND.1
drv2:VDD
drv2:1B
drv2:1A
drv2:2A
drv2:2B
drv2:GND.2
drv2:VMOT
btn1:1.l
btn1:2.l
btn1:1.r
btn1:2.r
btn2:1.l
btn2:2.l
btn2:1.r
btn2:2.r
vcc1:VCC
gnd1:GND
btn3:1.l
btn3:2.l
btn3:1.r
btn3:2.r
r4:1
r4:2
r5:1
r5:2
r6:1
r6:2
led1:A
led1:C
r7:1
r7:2
r3:1
r3:2