/*################### 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
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-
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