Codice: Seleziona tutto
#include <AccelStepper.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <Keypad.h>
#include <EEPROM.h>
#include <math.h>
// ============================================================================
// CONFIGURAZIONE DIREZIONE ASSI
// ============================================================================
#define INVERTI_DIR_X true // Inversione rotazione asse X
#define INVERTI_DIR_Y false // Mantenuto false per Y
// ============================================================================
// HARDWARE PINOUT
// ============================================================================
#define BTN_UP 2 // Comando A -> Menu DATI
#define BTN_RIGHT 3 // Comando B -> Menu IMPOSTAZIONI
#define BTN_DOWN 4 // Comando C -> Ritorno Posizione Iniziale (0,0)
#define BTN_LEFT 5 // Comando D -> STOP / EMERGENZA
#define BTN_JOY 8 // Pulsante Centrale Joystick -> START Ciclo Automatico
#define JOY_X A1 // Analogico X Joystick
#define JOY_Y A0 // Analogico Y Joystick
// Driver Stepper
#define X_STEP 45
#define X_DIR 43
#define Y_STEP 41
#define Y_DIR 39
AccelStepper stepperX(AccelStepper::DRIVER, X_STEP, X_DIR);
AccelStepper stepperY(AccelStepper::DRIVER, Y_STEP, Y_DIR);
// Keypad 4x4
const byte ROWS = 4;
const byte COLS = 4;
char keys[ROWS][COLS] = {
{'1','2','3','A'},
{'4','5','6','B'},
{'7','8','9','C'},
{'*','0','#','D'}
};
byte rowPins[ROWS] = {22, 24, 26, 28};
byte colPins[COLS] = {30, 32, 34, 36};
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
// LCD 20x4 I2C
LiquidCrystal_I2C lcd(0x27, 20, 4);
// ============================================================================
// STRUTTURE DATI
// ============================================================================
struct Impostazioni {
float riduzione_X_giri_per_mm;
float riduzione_Y;
int backlash_X_decimi_mm;
int backlash_Y_decimi_mm;
int microstep_X;
int microstep_Y;
} setupData;
struct DatiLavoro {
float diametro_fresa_mm;
float lunghezza_fresa_mm;
int numero_eliche;
float angolo_eliche_gradi;
} jobData;
enum StatoMacchina {
STATO_IDLE,
MENU_DATI,
MENU_IMPOSTAZIONI,
AUTO_INIT,
AUTO_DISIMPEGNO_1,
AUTO_POSIZIONAMENTO,
AUTO_ANDATA,
AUTO_BACKLASH_RITORNO, // Stato per recupero gioco a fine andata
AUTO_RITORNO,
AUTO_DISIMPEGNO_2,
AUTO_ROTAZIONE_Y,
AUTO_REIMPEGNO,
EMERGENCY_STOP
};
StatoMacchina statoAttuale = STATO_IDLE;
int elicaAttuale = 0;
long targetPassiX = 0;
long targetPassiY = 0;
long quotaBaseY = 0;
long disimpegnoPassiX = 0;
long backlashPassiX = 0; // Variabile per i passi reali di backlash X
// Variabili per la gestione del Joystick
float currentSpeedX = 0.0;
float currentSpeedY = 0.0;
const float maxJoystickSpeed = 500.0;
unsigned long lastJoystickUpdate = 0;
// ============================================================================
// PROTOTIPI FUNZIONI
// ============================================================================
void gestisciPulsantiShield();
void gestisciComandiTastiera();
void gestisciJoystickManuale();
void eseguiCicloAutomatico();
void apriMenuDati();
void apriMenuImpostazioni();
void ritornaPosizioneIniziale();
bool leggiValoreKeypad(const char* titolo, float &valore, const char* unita);
void mostraMessaggio(String r1, String r2);
void aggiornaDisplayIdle();
float calcolaPassiPerMM_X();
float calcolaPassiPerGrado_Y();
void calcolaInterpolazioneSincronizzata();
// ============================================================================
// SETUP
// ============================================================================
void setup() {
pinMode(BTN_UP, INPUT_PULLUP);
pinMode(BTN_RIGHT, INPUT_PULLUP);
pinMode(BTN_DOWN, INPUT_PULLUP);
pinMode(BTN_LEFT, INPUT_PULLUP);
pinMode(BTN_JOY, INPUT_PULLUP);
stepperX.setPinsInverted(INVERTI_DIR_X, false, false);
stepperY.setPinsInverted(INVERTI_DIR_Y, false, false);
stepperX.setMaxSpeed(1000.0);
stepperY.setMaxSpeed(1000.0);
stepperX.setMinPulseWidth(10);
stepperY.setMinPulseWidth(10);
lcd.init();
lcd.backlight();
mostraMessaggio("CNC AFFILATRICE", "Inizializzazione...");
EEPROM.get(0, setupData);
if (isnan(setupData.riduzione_X_giri_per_mm) || setupData.riduzione_X_giri_per_mm <= 0) {
setupData.riduzione_X_giri_per_mm = 0.5;
setupData.riduzione_Y = 5.0;
setupData.backlash_X_decimi_mm = 0;
setupData.backlash_Y_decimi_mm = 0;
setupData.microstep_X = 8;
setupData.microstep_Y = 8;
EEPROM.put(0, setupData);
}
EEPROM.get(sizeof(Impostazioni), jobData);
if (isnan(jobData.diametro_fresa_mm) || jobData.diametro_fresa_mm <= 0) {
jobData.diametro_fresa_mm = 16.0;
jobData.lunghezza_fresa_mm = 30.0;
jobData.numero_eliche = 4;
jobData.angolo_eliche_gradi = 30.0;
EEPROM.put(sizeof(Impostazioni), jobData);
}
delay(800);
aggiornaDisplayIdle();
}
// ============================================================================
// LOOP PRINCIPALE
// ============================================================================
void loop() {
if (digitalRead(BTN_LEFT) == LOW) {
statoAttuale = EMERGENCY_STOP;
}
switch (statoAttuale) {
case STATO_IDLE:
gestisciJoystickManuale();
gestisciPulsantiShield();
gestisciComandiTastiera();
break;
case AUTO_INIT:
case AUTO_DISIMPEGNO_1:
case AUTO_POSIZIONAMENTO:
case AUTO_ANDATA:
case AUTO_BACKLASH_RITORNO:
case AUTO_RITORNO:
case AUTO_DISIMPEGNO_2:
case AUTO_ROTAZIONE_Y:
case AUTO_REIMPEGNO:
eseguiCicloAutomatico();
break;
case EMERGENCY_STOP:
stepperX.stop();
stepperY.stop();
mostraMessaggio("! ARRESTO EMERGENZA !", "Premere Reset HW");
while (1);
break;
default:
break;
}
}
// ============================================================================
// CALCOLI CINEMATICI
// ============================================================================
float calcolaPassiPerMM_X() {
return setupData.riduzione_X_giri_per_mm * setupData.microstep_X * 200.0;
}
float calcolaPassiPerGrado_Y() {
return (setupData.riduzione_Y * setupData.microstep_Y * 200.0) / 360.0;
}
void calcolaInterpolazioneSincronizzata() {
float passiMM_X = calcolaPassiPerMM_X();
float passiGrado_Y = calcolaPassiPerGrado_Y();
targetPassiX = lround(jobData.lunghezza_fresa_mm * passiMM_X);
disimpegnoPassiX = lround(10.0 * passiMM_X);
// Calcolo dei passi di backlash basato sui decimi di mm inseriti nel menu
float mmBacklash = (float)setupData.backlash_X_decimi_mm / 10.0;
backlashPassiX = lround(mmBacklash * passiMM_X);
// Calcolo passo con angolo rispetto all'asse longitudinale: P = (pi * D) / tan(angolo)
float angoloRadianti = jobData.angolo_eliche_gradi * (M_PI / 180.0);
float passo_elica_mm_giro = (M_PI * jobData.diametro_fresa_mm) / tan(angoloRadianti);
float gradiRapportoY = (jobData.lunghezza_fresa_mm / passo_elica_mm_giro) * 360.0;
targetPassiY = lround(gradiRapportoY * passiGrado_Y);
float vMaxBase = 800.0;
float accelBase = 500.0;
if (abs(targetPassiX) > 0) {
float rapporto = (float)abs(targetPassiY) / (float)abs(targetPassiX);
stepperX.setMaxSpeed(vMaxBase);
stepperX.setAcceleration(accelBase);
stepperY.setMaxSpeed(fmax(100.0, vMaxBase * rapporto));
stepperY.setAcceleration(fmax(100.0, accelBase * rapporto));
}
}
// ============================================================================
// LOGICA CICLO AUTOMATICO
// ============================================================================
void eseguiCicloAutomatico() {
stepperX.run();
stepperY.run();
switch (statoAttuale) {
case AUTO_INIT:
stepperX.setCurrentPosition(0);
stepperY.setCurrentPosition(0);
quotaBaseY = 0;
elicaAttuale = 0;
calcolaInterpolazioneSincronizzata();
mostraMessaggio("AUTO - DISIMPEGNO", "Elica 1/" + String(jobData.numero_eliche));
stepperX.setMaxSpeed(800.0);
stepperX.setAcceleration(500.0);
stepperX.moveTo(-disimpegnoPassiX);
stepperY.moveTo(quotaBaseY);
statoAttuale = AUTO_DISIMPEGNO_1;
break;
case AUTO_DISIMPEGNO_1:
if (stepperX.distanceToGo() == 0 && stepperY.distanceToGo() == 0) {
mostraMessaggio("AUTO - POSIZIONA", "Punto di Partenza");
stepperX.moveTo(0);
stepperY.moveTo(quotaBaseY);
statoAttuale = AUTO_POSIZIONAMENTO;
}
break;
case AUTO_POSIZIONAMENTO:
if (stepperX.distanceToGo() == 0 && stepperY.distanceToGo() == 0) {
mostraMessaggio("AUTO - ANDATA", "Elica " + String(elicaAttuale + 1) + "/" + String(jobData.numero_eliche));
stepperX.moveTo(targetPassiX);
stepperY.moveTo(quotaBaseY + targetPassiY);
statoAttuale = AUTO_ANDATA;
}
break;
case AUTO_ANDATA:
if (stepperX.distanceToGo() == 0 && stepperY.distanceToGo() == 0) {
if (backlashPassiX > 0) {
mostraMessaggio("AUTO - BACKLASH", "Recupero Gioco X");
stepperX.moveTo(targetPassiX + backlashPassiX);
statoAttuale = AUTO_BACKLASH_RITORNO;
} else {
mostraMessaggio("AUTO - RITORNO", "Ritorno Carrello");
stepperX.moveTo(0);
stepperY.moveTo(quotaBaseY);
statoAttuale = AUTO_RITORNO;
}
}
break;
case AUTO_BACKLASH_RITORNO:
if (stepperX.distanceToGo() == 0) {
mostraMessaggio("AUTO - RITORNO", "Ritorno Carrello");
stepperX.moveTo(0);
stepperY.moveTo(quotaBaseY);
statoAttuale = AUTO_RITORNO;
}
break;
case AUTO_RITORNO:
if (stepperX.distanceToGo() == 0 && stepperY.distanceToGo() == 0) {
mostraMessaggio("AUTO - DISIMPEGNO", "Disimpegno Sicurezza");
stepperX.moveTo(-disimpegnoPassiX);
stepperY.moveTo(quotaBaseY);
statoAttuale = AUTO_DISIMPEGNO_2;
}
break;
case AUTO_DISIMPEGNO_2:
if (stepperX.distanceToGo() == 0 && stepperY.distanceToGo() == 0) {
elicaAttuale++;
if (elicaAttuale < jobData.numero_eliche) {
mostraMessaggio("AUTO - ROTAZIONE Y", "Indice Elica " + String(elicaAttuale + 1));
float gradiDivisione = 360.0 / jobData.numero_eliche;
quotaBaseY += lround(gradiDivisione * calcolaPassiPerGrado_Y());
stepperY.moveTo(quotaBaseY);
statoAttuale = AUTO_ROTAZIONE_Y;
} else {
mostraMessaggio("LAVORAZIONE OK", "Ciclo Completato!");
delay(2000);
statoAttuale = STATO_IDLE;
aggiornaDisplayIdle();
}
}
break;
case AUTO_ROTAZIONE_Y:
if (stepperY.distanceToGo() == 0) {
mostraMessaggio("AUTO - REIMPEGNO", "Reimpegno X");
stepperX.moveTo(0);
statoAttuale = AUTO_REIMPEGNO;
}
break;
case AUTO_REIMPEGNO:
if (stepperX.distanceToGo() == 0) {
calcolaInterpolazioneSincronizzata();
mostraMessaggio("AUTO - POSIZIONA", "Elica " + String(elicaAttuale + 1) + "/" + String(jobData.numero_eliche));
stepperX.moveTo(0);
stepperY.moveTo(quotaBaseY);
statoAttuale = AUTO_POSIZIONAMENTO;
}
break;
default:
break;
}
}
// ============================================================================
// MANOVRABILITÀ E COMANDI MANUALI
// ============================================================================
void gestisciPulsantiShield() {
if (digitalRead(BTN_UP) == LOW) {
apriMenuDati();
} else if (digitalRead(BTN_RIGHT) == LOW) {
apriMenuImpostazioni();
} else if (digitalRead(BTN_DOWN) == LOW) {
ritornaPosizioneIniziale();
} else if (digitalRead(BTN_JOY) == LOW) {
statoAttuale = AUTO_INIT;
delay(200);
}
}
void gestisciComandiTastiera() {
char key = keypad.getKey();
if (key == 'A') {
apriMenuDati();
} else if (key == 'B') {
apriMenuImpostazioni();
} else if (key == 'C') {
ritornaPosizioneIniziale();
}
}
void ritornaPosizioneIniziale() {
mostraMessaggio("RITORNO HOME", "Spostamento...");
stepperX.setMaxSpeed(800.0);
stepperX.setAcceleration(500.0);
stepperY.setMaxSpeed(1000.0);
stepperY.setAcceleration(600.0);
stepperX.moveTo(0);
stepperY.moveTo(0);
while (stepperX.distanceToGo() != 0 || stepperY.distanceToGo() != 0) {
if (digitalRead(BTN_LEFT) == LOW) {
statoAttuale = EMERGENCY_STOP;
return;
}
stepperX.run();
stepperY.run();
}
mostraMessaggio("RITORNO HOME", "Quota 0 Raggiunta!");
delay(1000);
aggiornaDisplayIdle();
}
void gestisciJoystickManuale() {
if (millis() - lastJoystickUpdate >= 10) {
lastJoystickUpdate = millis();
int valX = analogRead(JOY_X);
int valY = analogRead(JOY_Y);
float targetSpeedX = 0.0;
if (valX < 400) {
targetSpeedX = -maxJoystickSpeed;
} else if (valX > 620) {
targetSpeedX = maxJoystickSpeed;
}
float targetSpeedY = 0.0;
if (valY < 400) {
targetSpeedY = -maxJoystickSpeed;
} else if (valY > 620) {
targetSpeedY = maxJoystickSpeed;
}
float stepRampa = 4.0;
if (targetSpeedX == 0.0) {
currentSpeedX = 0.0;
} else {
if (currentSpeedX < targetSpeedX) {
currentSpeedX += stepRampa;
if (currentSpeedX > targetSpeedX) currentSpeedX = targetSpeedX;
} else if (currentSpeedX > targetSpeedX) {
currentSpeedX -= stepRampa;
if (currentSpeedX < targetSpeedX) currentSpeedX = targetSpeedX;
}
}
if (targetSpeedY == 0.0) {
currentSpeedY = 0.0;
} else {
if (currentSpeedY < targetSpeedY) {
currentSpeedY += stepRampa;
if (currentSpeedY > targetSpeedY) currentSpeedY = targetSpeedY;
} else if (currentSpeedY > targetSpeedY) {
currentSpeedY -= stepRampa;
if (currentSpeedY < targetSpeedY) currentSpeedY = targetSpeedY;
}
}
stepperX.setSpeed(currentSpeedX);
stepperY.setSpeed(currentSpeedY);
}
stepperX.runSpeed();
stepperY.runSpeed();
}
// ============================================================================
// MENÙ CONFIGURAZIONE KEYPAD (con riga 4 a 20 caratteri)
// ============================================================================
bool leggiValoreKeypad(const char* titolo, float &valore, const char* unita) {
String buffer = "";
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(titolo);
lcd.setCursor(0, 1);
lcd.print("Attuale: ");
lcd.print(valore, 1);
lcd.print(" ");
lcd.print(unita);
lcd.setCursor(0, 2);
lcd.print("Nuovo: ");
lcd.setCursor(0, 3);
lcd.print("OK:# C:Del *=. D:Esc");
while (true) {
if (digitalRead(BTN_LEFT) == LOW) {
statoAttuale = EMERGENCY_STOP;
return false;
}
char key = keypad.getKey();
if (key != NO_KEY) {
if (key == 'D') {
return false;
} else if (key >= '0' && key <= '9') {
if (buffer.length() < 8) {
buffer += key;
lcd.setCursor(7, 2);
lcd.print(buffer);
}
} else if (key == '*') {
if (buffer.indexOf('.') == -1) {
if (buffer.length() == 0) buffer += "0";
buffer += ".";
lcd.setCursor(7, 2);
lcd.print(buffer);
}
} else if (key == 'C') {
if (buffer.length() > 0) {
buffer.remove(buffer.length() - 1);
lcd.setCursor(7, 2);
lcd.print(" ");
lcd.setCursor(7, 2);
lcd.print(buffer);
}
} else if (key == '#') {
if (buffer.length() > 0) {
valore = buffer.toFloat();
}
return true;
}
}
}
}
void apriMenuDati() {
statoAttuale = MENU_DATI;
if (!leggiValoreKeypad("1/4 DIAMETRO FRESA", jobData.diametro_fresa_mm, "mm")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
if (!leggiValoreKeypad("2/4 LUNGH. FRESA", jobData.lunghezza_fresa_mm, "mm")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
float tempEliche = jobData.numero_eliche;
if (!leggiValoreKeypad("3/4 NUMERO ELICHE", tempEliche, "nr")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
jobData.numero_eliche = (int)tempEliche;
if (!leggiValoreKeypad("4/4 ANGOLO ELICA", jobData.angolo_eliche_gradi, "deg")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
EEPROM.put(sizeof(Impostazioni), jobData);
mostraMessaggio("DATI SALVATI!", "Salvato in EEPROM");
delay(1000);
statoAttuale = STATO_IDLE;
aggiornaDisplayIdle();
}
void apriMenuImpostazioni() {
statoAttuale = MENU_IMPOSTAZIONI;
if (!leggiValoreKeypad("1/6 RIDUZIONE X", setupData.riduzione_X_giri_per_mm, "g/mm")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
if (!leggiValoreKeypad("2/6 RIDUZIONE Y", setupData.riduzione_Y, "ratio")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
float tempBlX = setupData.backlash_X_decimi_mm;
if (!leggiValoreKeypad("3/6 BACKLASH X", tempBlX, "0.1mm")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
setupData.backlash_X_decimi_mm = (int)tempBlX;
float tempBlY = setupData.backlash_Y_decimi_mm;
if (!leggiValoreKeypad("4/6 BACKLASH Y", tempBlY, "0.1mm")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
setupData.backlash_Y_decimi_mm = (int)tempBlY;
float tempMsX = setupData.microstep_X;
if (!leggiValoreKeypad("5/6 MICROSTEP X", tempMsX, "step")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
setupData.microstep_X = (int)tempMsX;
float tempMsY = setupData.microstep_Y;
if (!leggiValoreKeypad("6/6 MICROSTEP Y", tempMsY, "step")) { statoAttuale = STATO_IDLE; aggiornaDisplayIdle(); return; }
setupData.microstep_Y = (int)tempMsY;
EEPROM.put(0, setupData);
mostraMessaggio("SETUP SALVATO!", "Salvato in EEPROM");
delay(1000);
statoAttuale = STATO_IDLE;
aggiornaDisplayIdle();
}
// ============================================================================
// INTERFACCIA LCD
// ============================================================================
void mostraMessaggio(String r1, String r2) {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(r1);
lcd.setCursor(0, 1);
lcd.print(r2);
}
void aggiornaDisplayIdle() {
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Stato: PRONTO (IDLE)");
lcd.setCursor(0, 1);
lcd.print("A:Dati B:Setup C:Hom");
lcd.setCursor(0, 2);
lcd.print("JOYSTICK: START");
lcd.setCursor(0, 3);
lcd.print("Pulsante SX: STOP");
}