Ottimizzazione codice - 4 motori passopasso lentissimi

Ciao ragazzi, intanto buona sera a tutti, connessi e non connessi.
Allego il codice in un file txt perché troppo lungo per essere inserito qui nel post direttamente.

È da anni che non mettevo le mani sul mio UNO e mi è mancato tantissimo.
Avendo perso la dimestichezza con la scrittura di codice, ma non il pensiero di massima, ho creato uno sketch funzionante ma non ottimizzato. Ed è essenziale che lo sia.

Venendo al dunque, sto controllando 4 motori passo-passo bipolari 24V 2A drylin della Igus (ciascuno col proprio A4988) tramite seriale, in particolare tramite caratteri, lettere, inviate dal PC ad Arduino.
Ciascuna lettera fa girare il rispettivo motore in una direzione.

I motori (che nella pratica sono delle slitte) nel codice li ho indicati come S1, S2, S3, S4.

Finché ne controllavo solo 1 tutto era fantastico, poco poco rumoroso, potevo modificare la velocità a mio piacimento.

Quando, in questi giorni ho integrato gli altri 3, è andato tutto allo scatafascio. Il codice funziona, fa quello che deve fare, ma non lo fa affatto bene.

La gestione di questi 4 motori ha rallentato incredibilmente la loro velocità.
Mea culpa, chiaramente, ho usato una marea di IF nidificati che tengono Arduino impegnatissimo (credo sia questo il problema).

Sono notti che dormo 3 ore e di giorno non faccio altro che pensare a come sistemarlo, ma purtroppo non penso di avere le conoscenze, o se le ho sono molto arrugginite.

Chiedo a qualche super eroe di aiutarmi ad alleggerire il tutto, ripristinando la velocità originale a tutti i motori.. ogni minuto è prezioso..

P.S. sono "pazzo" abbastanza da spedire birra a chi mi aiuta. Sono davvero alla frutta...

4stepSerial.txt (8.86 KB)

Buonasera,
essendo il tuo primo post, nel rispetto del regolamento (… punto 13, primo capoverso), ti chiedo cortesemente di presentarti IN QUESTO THREAD (spiegando bene quali conoscenze hai di elettronica e di programmazione ... possibilmente evitando di scrivere solo una riga di saluto) e di leggere con MOLTA attenzione il su citato REGOLAMENTO ... Grazie. :slight_smile:

Guglielmo

P.S.: Qui una serie di link utili, NON necessariamente inerenti alla tua domanda:
- serie di schede by xxxPighi per i collegamenti elettronici vari: ABC - Arduino Basic Connections
- pinout delle varie schede by xxxPighi: Pinout
- link generali utili: Link Utili

Non sono per niente esperto per quanto riguarda Arduino e quindi non posso darti la conferma che il problema sia dovuto solo da quello (anzi, probabilmente non lo è), ma è un inizio...

Allora, prima di tutto mi sfugge il senso degli if annidati all'interno del ciclo while, come tu stesso hai intuito, e soprattutto di quegli or.
Per snellire un attimo il codice prima di tutto fai uno switch-case

switch (comando) {
    case 'a':
        S1_abilitato = true;
        S1_direzione = true;
    break;
    case 'b':
        S1_abilitato = true;
        S1_direzione = false;
    break;
    case 'c':
        S1_abilitato = false;
    break;
    case 'd':
        S2_abilitato = true;
        S2_direzione = true;
    break;
    case 'e':
        S2_abilitato = true;
        S2_direzione = false;
    break;
    case 'f':
        S2_abilitato = false;
    break;
    case 'g':
        S3_abilitato = true;
        S3_direzione = true;
    break;
    case 'h':
        S3_abilitato = true;
        S3_direzione = false;
    break;
    case 'i':
        S3_abilitato = false;
    break;
    case 'l':
        S4_abilitato = true;
        S4_direzione = true;
    break;
    case 'm':
        S4_abilitato = true;
        S4_direzione = false;
    break;
    case 'n':
        S4_abilitato = false;
    break;
    case 't':
        InizializzazioneFUNCT();
    break;
  }

Come seconda cosa... mi sembra di aver capito che i comandi che arrivano sulla seriale dovrebbero, in teoria, essere ricevuti soltanto una tantum per spegnere e accendere i motori, o per cambiare direzione.
Dico bene?

E a questo punto, mi viene il dubbio che in realtà la seriale stia bufferizzando molti più comandi di quanto ti aspetti, e quindi il programma passa molto tempo all'interno del while a impostarsi le variabili abilitato e direzione, finendo nella seconda parte del loop (quella che effettivamente muove i motori) molto più di rado di quanto ti aspetti.

Magari puoi fare un prova mettendoti un Serial.println(Serial.available()); all'interno del while.

Ciao,
il problema credo che stia nel fatto che non utilizzi correttamente la libreria (credo tu stia utilizzando airspayce.com AccelStepper).
Leggiti bene come funziona, non capisco perché abiliti e disabiliti tu il pin dell'enable del driver, fallo fare alla libreria se è proprio necessario! (vedi setEnablePin()) se il driver necessita di enable invertito dillo alla libreria (vedi setPinsInverted), è superfluo che setti tu i pin ad OUTPUT, lo fa la libreria, poi utilizzi la variabile S1_escursione che non ho ben capito a cosa serve, perché dopo 300 iterazioni della funzione loop() disabiliti il driver? (300 iterazioni è un tempo brevissimo, sicuro sia quello che vuoi fare?), poi i motori vanno frenati con la funzione stop() non bloccati disabilitando il driver altrimenti non utilizzerai mai le funzioni di accelerazione e decelerazione (abilitare il driver con un segnale di step già avviato potrebbe far perdere passi al motore se la frequenza è abbastanza alta), la funzione runSpeed() va chiamata sempre all'interno del loop non solo se si verificano certe condizioni, sempre per il fatto che le accelerazioni e decelerazioni non vengono calcolate se non chiami quella funzione.
Forse dovresti anche studiarti un po' della teoria dei motori passo-passo per capire come funzionano, ti consiglio, almeno per capire le basi, di leggerti il tutorial di VincenzoV è semplice da capire e spiega sufficientemente bene le basi dei motori passo-passo.

Dino

DgDev91:
Per snellire un attimo il codice prima di tutto fai uno switch-case

Grazie per il suggerimento ma non ha risolto il problema di fondo: quando girano più motori rallenta paurosamente.
Direi però abbia alleggerito un minimo.
Video: https://www.youtube.com/watch?v=63ZN2a23i9o

DgDev91:
Come seconda cosa... mi sembra di aver capito che i comandi che arrivano sulla seriale dovrebbero, in teoria, essere ricevuti soltanto una tantum per spegnere e accendere i motori, o per cambiare direzione.
Dico bene?

E a questo punto, mi viene il dubbio che in realtà la seriale stia bufferizzando molti più comandi di quanto ti aspetti, e quindi il programma passa molto tempo all'interno del while a impostarsi le variabili abilitato e direzione, finendo nella seconda parte del loop (quella che effettivamente muove i motori) molto più di rado di quanto ti aspetti.

Magari puoi fare un prova mettendoti un Serial.println(Serial.available()); all'interno del while.

Mi restituisce solo il valore 0 una volta, ovvero quando gli mando un comando da seriale.
Quindi quel ciclo funziona correttamente, ci entra solo quando gli arrivano comandi. E restituisce niente.

dinodf:
Ciao,
il problema credo che stia nel fatto che non utilizzi correttamente la libreria...

Ciao Dino, ti ringrazio per le dritte, ho appurato le mie lacune nell'uso della libreria. Quello che dovrò fare è studiarmela a modo. Il punto è che come molti principianti fanno, ho trovato uno sketch iniziale adatto al mio progetto, poi l'ho ampliato secondo le mie necessità in modo non ottimo .
Quei 300 cicli li avevo impostati per un motivo, e la variabile S1_escursione l'avevo aggiunta per usarla al posto di inserire 300 manualmente, poi non ho continuato ed è rimasta lì.
Ho paura effettivamente che perda dei passi, ma non so come appurarlo.

Mi documenterò il più possibile su queste cose e spero di saltarci fuori.. in ogni caso, per la parte del seriale cosa pensi? In particolare sull'usare lo Switch case.
Gli if successivi rimangono comunque, giusto?

Ema.

Ciao, per il case guarda il codice postato da DgDev91, dovrebbe essere ok (ho controllato velocemente), se usi il case perché poi vuoi anche controllare con gli if? semplicemente una volta settate tutte le variabili (dopo la lettura della seriale per intenderci) richiami le funzioni corrette passando le variabili che hai appena settato.

D

Buongiorno ragazzi, buon lunedì a tutti.
Dopo qualche notte passata al PC o a rigirarmi nel letto, sono riuscito a gestire in modo appropriato (a parere mio) i 4 passo-passo usando la lib AccelStepper.
I motori non sono rallentati quando in funzione contemporaneamente, e si fermano dopo tot giri.

Sono però bloccato su come ottenere lo stop sul relativo finecorsa.
Il motore S1 va in un senso, diciamo CW, per una durata stabilita da me, e va in CCW finché il suo finecorsa non mette LOW il relativo pin, che a sua volta usa la libreria per disattivare il pin Enable sul driver (quindi sgancia l'alimentazione al motore quando fermo, che è essenziale per evitare surriscaldamento).

Il punto è che non riesco a dirgli: "ok, se stai andando in CCW continua finché pinSXSTOP==HIGH, altrimenti fermati". Devo differenziale le variabili delle velocità per ogni motore? Al momento ce n'è una unica, velocita, che varia a seconda del carattere ricevuto.

#include <Bounce2.h>
#include <AccelStepper.h>

const int pinS1Dir = 2;
const int pinS1Step = 3;
const int pinS1Enable = 4;
const int pinS2Dir = 5;
const int pinS2Step = 6;
const int pinS2Enable = 7;
const int pinS3Dir = 8;
const int pinS3Step = 9;
const int pinS3Enable = 10;
const int pinS4Dir = A0;
const int pinS4Step = A1;
const int pinS4Enable = A2;
const int pinS1STOP = 11;
const int pinS2STOP = 12;
const int pinS3STOP = A4;
const int pinS4STOP = A5;

const int debounceMs = 10;

const float velocita = 1000;
const float accelerazione = 300;
const float escursione = 1000;

boolean S1_abilitato, S1_direzione;
boolean S2_abilitato, S2_direzione;
boolean S3_abilitato, S3_direzione;
boolean S4_abilitato, S4_direzione;

AccelStepper S1(AccelStepper::DRIVER, pinS1Step, pinS1Dir);
AccelStepper S2(AccelStepper::DRIVER, pinS2Step, pinS2Dir);
AccelStepper S3(AccelStepper::DRIVER, pinS3Step, pinS3Dir);
AccelStepper S4(AccelStepper::DRIVER, pinS4Step, pinS4Dir);

Bounce S1STOP = Bounce();
Bounce S2STOP = Bounce();
Bounce S3STOP = Bounce();
Bounce S4STOP = Bounce();

void setup() {
 Serial.begin(57600);

 S1_abilitato = S2_abilitato = S3_abilitato = S4_abilitato = false;
 Serial.println("Motori fermi");
 delay(500);
 
 const int S1_posizione = 0;
 const int S2_posizione = 0;
 const int S3_posizione = 0;
 const int S4_posizione = 0;
 Serial.println("Posizioni azzerate");
 delay(500);

 pinMode(pinS1STOP, INPUT);
 pinMode(pinS2STOP, INPUT);
 pinMode(pinS3STOP, INPUT);
 pinMode(pinS4STOP, INPUT);
 Serial.println("Pin settati");
 delay(500);

 digitalWrite(pinS1STOP, HIGH);
 digitalWrite(pinS2STOP, HIGH);
 digitalWrite(pinS3STOP, HIGH);
 digitalWrite(pinS4STOP, HIGH);
 Serial.println("Pullup settati");
 delay(500);

 S1.setEnablePin(pinS1Enable);
 S1.setPinsInverted(false, false, true);
 S2.setEnablePin(pinS2Enable);
 S2.setPinsInverted(false, false, true);
 S3.setEnablePin(pinS3Enable);
 S3.setPinsInverted(false, false, true);
 S4.setEnablePin(pinS4Enable);
 S4.setPinsInverted(false, false, true);

 S1.setMaxSpeed(velocita);
 S1.setAcceleration(accelerazione);
 S2.setMaxSpeed(velocita);
 S2.setAcceleration(accelerazione);
 S3.setMaxSpeed(velocita);
 S3.setAcceleration(accelerazione);
 S4.setMaxSpeed(velocita);
 S4.setAcceleration(accelerazione);
 Serial.println("Velocità e accelerazioni settate");
 delay(500);  

 S1STOP.attach(pinS1STOP);
 S1STOP.interval(debounceMs);
 S2STOP.attach(pinS2STOP);
 S2STOP.interval(debounceMs);
 S3STOP.attach(pinS3STOP);
 S3STOP.interval(debounceMs);
 S4STOP.attach(pinS4STOP);
 S4STOP.interval(debounceMs);
 Serial.println("Finecorsa inizializzati");
 delay(500);

 //ResetPosizioneFUNCT();

 Serial.println("");
 Serial.println("* * * * P R O N T O * * * *");
}

void loop() {
 //refresh stato finecorsa
 S1STOP.update();
 S2STOP.update();
 S3STOP.update();
 S4STOP.update();
 
 //leggi valore dei pulsanti
 int valS1STOP = S1STOP.read();
 int valS2STOP = S2STOP.read();
 int valS3STOP = S3STOP.read();
 int valS4STOP = S4STOP.read();

 //Nel caso arrivi un comando da seriale:
 while (Serial.available() > 0){
   char comando = Serial.read();
   switch (comando){
     case 'a':        
       S1.enableOutputs();
       S1.move(escursione);
       S1.setSpeed(velocita);
       Serial.println("A");
     break;
     case 'b':
       S1.enableOutputs();
       S1.setSpeed(-velocita);
       Serial.println("B");
     break;
     case 'c':
       S1.stop();
       S1.disableOutputs();
       Serial.println("C");     
     break;
     case 'd':
       S2.enableOutputs();
       S2.move(escursione);
       S2.setSpeed(velocita);
       Serial.println("D");
     break;
     case 'e':
       S2.enableOutputs();
       S2.setSpeed(-velocita);
       Serial.println("E");
     break;
     case 'f':
       S2.stop();
       S2.disableOutputs();
       Serial.println("F");
     break;
     case 'g':
       S3.enableOutputs();
       S3.move(escursione);
       S3.setSpeed(velocita);
       Serial.println("G");
     break;
     case 'h':
       S3.enableOutputs();
       S3.setSpeed(-velocita);
       Serial.println("H");
     break;
     case 'i':
       S3.stop();
       S3.disableOutputs();
       Serial.println("I");
     break;
     case 'l':
       S4.enableOutputs();
       S4.move(escursione);
       S4.setSpeed(velocita);
       Serial.println("L");
     break;
     case 'm':
       S4.enableOutputs();
       S4.setSpeed(-velocita);
       Serial.println("M");
     break;
     case 'n':
       S4.stop();
       S4.disableOutputs();
       Serial.println("N");
     break;
     //case 't':
       //ResetPosizioneFUNCT();
     //break;
   }
 }
 S1.runSpeed();
 S2.runSpeed();
 S3.runSpeed();
 S4.runSpeed();
 if(S1.distanceToGo() == 0){
   S1.disableOutputs();
 }
}

L'ultimo IF, se creato per ogni motore, rallenta e incasina di molto la prima istanza di movimento dei motori, quindi non va bene.

Le condizioni che devo inserire, quindi, sono:

.muovi i motori CW finché non hanno raggiunto la posizione impostata;
.muovi i motori CCW finché il relativo finecorsa non chiama stop(); e disabilita l'Enable;
.nel caso il finecorsa sia attivo (quindi dopo CCW) e sia richiesto il movimento CW, fallo, fino alla posizione impostata;

Ciao,
se spieghi meglio cosa vuoi fare forse riusciamo a trovare la soluzione migliore.

Secondo me si potrebbe azzerare il contatore dei singoli motori all'avvio del sistema (sempre che sia possibile), praticamente appena acceso il sistema in sequenza vengono fatti girare uno alla volta i motori in senso antiorario (CCW) finché il relativo fine-corsa non viene chiuso, appena chiuso va posto a zero il contatore del motore (setCurrentPosition), così facendo d'ora in poi non dovrai più controllare il finecorsa ma solo la posizione del motore.
A questo punto se invii via seriale il comando per muovere il motore basta che dici al motore di portarsi alla posizione corretta (che devi calcolare in base a cosa vuoi fare perché non ho capito proprio cosa stai costruendo) (moveTo) ed alla velocità che desideri;
quando poi vorrai riportare il motore alla posizione di zero basterà che gli dici "vai a zero", non servirà che controlli nuovamente che il finecorsa venga chiuso perché (se il sistema è dimensionato correttamente e quindi non perdi passi) la posizione sarà quella per sempre, a 0 il motore va a chiudere il finecorsa sempre.
Non è necessario che disabiliti ogni volta il driver, se hai impostato correttamente la corrente per ogni fase il motore non si rovinerà, è fisiologico che i motori stepper da fermi scaldino, è buona norma controllare la corrente di fase (finché riesci a mantenere la mano appoggiata al motore senza sentire troppo caldo da doverla togliere il motore non soffre, 40-60°C dipende dai motori sono normali, se scaldano un po' troppo abbassa un pochino la corrente, fino a circa il 70-75% della corrente nominale la coppia da fermo solitamente è sufficiente), se proprio vuoi puoi (dato che mi pare di ricordare che i driver A4988 non hanno la riduzione automatica della corrente) puoi impostare un timer che ad esempio dopo 30 secondi di inattività spegne il driver. Ti ricordo che la procedura corretta per fermare un motore NON è togliere l'enable, meglio se utilizzi la funzione che comanda la posizione o usi stop().

Dino

Rieccomi qui, dopo giorni di apprendimento, test, e deprivazione di tranquillità mentale :slight_smile:

Sono riuscito a ottimizzare la gestione dei motori, semplicemente usando come si deve la lib AccelStepper.

Ho scritto il codice in base a quello che volevo facessero, e a loro volta fanno quello che gli dice il codice: che bella sensazione :'D

Bando alle ciance.
@dinodf, grazie per il supporto, mi hai aiutato a far quadrare la situazione e la gestione dei motori.
Ho isolato i sensori di finecorsa perché complicavano la gestione, e ho utilizzato invece il conto dei passi effettuati, per fargli capire le rispettive posizioni (aperto o chiuso).
I finecorsa sono posizionati in modo che quando eccitati, il motore non è nella posizione giusta. E non mi è possibile spostare e regolare questi finecorsa. Quando usavo il primo sketch condiviso ero riuscito a inserire un mini ciclo FOR per farlo continuare di tot giri dopo che quello venisse eccitato. Funzionava, era l'unica parte che effettivamente muoveva i motori nel giusto modo, ovvero richiamando runSpeed() senza ritardi.

Cambiando approccio ed essendo costretto a riscrivere il codice per utilizzare al meglio AccelStepper, ho eliminato la lettura dei finecorsa perché appunto, sapendo i passi compiuti in apertura, sa anche quelli che deve fare per chiudersi.

Inizialmente ero preoccupato nel lasciarli sempre in tensione perché pensavo di non aver regolato a modo la corrente sul driver A4988, ma poi, facendo altre prove su altri motori, ho visto che la posizione del pot che la regola -sui motori effettivi usati nel progetto- è a circa l'80% dell'escursione massima.
I motori hanno una corrente max di 1.8A e il driver una corrente max di 2A. Direi siano perfetti così. I driver si scaldano, ma non mi preoccupano, adesso.

Mi rimane solo una cosa, da risolvere: quando Arduino viene inizializzato, con il setup dei vari pin, i motori si muovono per un attimo (2-3 secondi) facendo un casino dell'ostia perché sono già a finecorsa e quindi non potendo muoversi, saltano passi.
Come posso fare per evitare che durante l'inizializzazione si muovano come vogliano loro?

Bonus: alla fine del codice ho inserito 4 IF (uno per ogni motore) che fanno disabilitare i driver nel caso in cui il rispettivo stepper sia fermo o non stia andando da alcuna parte. Però non va, non capisco perché. Se riuscissi a disabilitarli una volta fermati -tramite moveTo- il driver non si scalderebbe mai, dato che l'escursione di questi motori è davvero corta, nel loro utilizzo. Ma come detto prima non mi preoccupa il calore, se non altro è per allungargli la vita tenendoli il più freddi possibile.

P.S. dinodf se vuoi pm il tuo indirizzo ti spedisco una birra da bere alla mia :')

v05_AS_testinviato_disableoutputs.ino (7.36 KB)

v05_AS_testinviato_disableoutputs.txt (7.62 KB)

ciao

emanuell0:
Mi rimane solo una cosa, da risolvere: quando Arduino viene inizializzato, con il setup dei vari pin, i motori si muovono per un attimo (2-3 secondi) facendo un casino dell'ostia perché sono già a finecorsa e quindi non potendo muoversi, saltano passi.
Come posso fare per evitare che durante l'inizializzazione si muovano come vogliano loro?

Nei driver A4988 gli ingressi DIR e STEP hanno bisogno di una resitenza di PULL-UP (o PULL-DOWN) altrimenti "vanno dove vogliono".

ciao
pippo72

emanuell0:
Bonus: alla fine del codice ho inserito 4 IF (uno per ogni motore) che fanno disabilitare i driver nel caso in cui il rispettivo stepper sia fermo o non stia andando da alcuna parte. Però non va, non capisco perché.

In realtà vanno è che alla successiva chiamata di runSpeedToPosition() credo vengano riabilitati, una soluzione sarebbe quella di controllare se distanceToGo() != 0.0 chiamare la funzione runSpeedToPosition() altrimenti disableOutputs()

emanuell0:
P.S. dinodf se vuoi pm il tuo indirizzo ti spedisco una birra da bere alla mia :')

Grazie mille, come accettato! ne berrò una alla tua alla prima occasione :wink: