[Solved] Lettura valori encoder Nema23 + HBS57

Salve a tutti, spero di essere nella giusta sezione.

Ho un'arduino mega che sto usando per pilotare un motore Nema23 tramite il driver HBS57 che ho tirato via da un vecchio progetto.
Dove sto usando adesso il tutto necessita di movimenti fatti sia "a mano" quando il motore è disabilitato dal pin enable e di movimenti fatti dal motore quindi programmati.
Per quelli fatti dal motore non ci sono problemi il tutto funziona a meraviglia, ma ho la necessità di leggere l'encoder del nema per sapere quanta rotazione ed il verso di essa viene effettuata durante la fase manuale. Purtroppo non sono a conoscenza del modello di encoder in uso, ma si collega coi due canali e 4 cavi + 2 di alimentazione all'HBS57 (EA-, EA+, EB-, EB+)

Non so se è possibile leggere questi valori dal driver, quindi penso si debba fare un ponte per portare quei canali sia al driver che all'arduino.

Grazie a tutti, ovviamente chiedete pure se servono altre informazioni.

Personalmente vedo due soluzioni possibili:

  1. Ti scarichi il software ProTuner dal sito della Leadshine e ti procuri il cavetto di connessione, dopodiché il valore di impulsi giro te lo leggi da li.

2)Ti cerchi su internet, o cerchi il mio secondo post sul forum. Ho postato uno sketch per la lettura encoder e come hai detto tu colleghi in parallelo il GND del drive e dell'Arduino.
Ti porti i segnali encoder sugli ingressi che usa lo sketch per la lettura encoder (prendi solo EA+ ed EB+ tanto sono segnali a 5V massimo).
Fai fare al motore un giro esatto e conti gli impulsi. La risoluzione in impulsi giro dell'encoder sarà pari ad 1/4 del valore che leggi e molto probabilmente sarà 1000 o 1024Imp/giro.

Per quanto riguarda la lettura dal driver via seriale, se lo fa il 'ProTuner' lo puoi sicuramente fare anche tu, il problema è che, non si trova il protocollo seriale usato dal driver. Io in passato avevo provato anche a richiederlo via mail alla Leadshine, ma non mi hanno mai risposto. Se riesci ad averlo fammelo avere per favore

vedi qui: http://www.cncshop.cz/PDF/HBS/HBSprotunermanual.pdf

Ciao tecno67 e grazie per la risposta.

Riguardo la soluzione da adottare penso sia più comodo optare per la seconda.
Quindi collego la gnd output dell'hbs (quella che alimenta l'encoder) e quella della mega insieme e poi vado a mettere EA+ ed EB+ sui pin di interrupt e li leggo come un normalissimo encoder rotativo e dovrebbe funzionare?
Se così provo in giornata.

Riguardo comunque al protocollo della leadshine non penso che lo rilasceranno mai, però posso consigliarti di scaricare uno dei tanti software che si trovano in rete anche free per analizzare il traffico di una porta seriale e ricostruirlo da li. Lo ho fatto in passato con un software, per creare un device personalizzato ma riconosciuto da quel software.
In genere non sono mai dei protocolli molto complicati.

Update,
Ho provato così per come ha detto prima tecno.
Ma c'è una divergenza che non mi spiego.

Mettendo il motore in stato disabled, e girando manualmente l'albero in un giro conto circa 2000 impulsi, qualcosina di più, Sia verso sinistra che verso destra ritorno a 0 quando sono nella stessa posizione.

Se invece faccio svolgere lo stesso processo al motore programmandolo ho una forte discrepanza quando vado verso destra o verso sinistra.
Quale potrebbe essere il problema?

Non ho capito esttamente cosa fai nel secondo caso. A che velocità lo fai girare il motore? Considera che stai comunque sempre facendo una lettura in Software. Prova a ripetere la cosa con velocità piuttosto basse.

Tieni conto che se hai usato il programma per 3 encoder e ne hai usato solo uno, la ISR che effettua la lettura dura circa 130cicli di clock ovvero con una frequenza di 16MHz puoi leggere teoricamente una frequenza di più di 100KHz. Ma questo sarebbe vero se nessun altro nel micro usasse interrupt. Cosa assolutamente non veritiera. Le funzioni di temporizzazione li usano, la seriale, che è impegnata nel programma pure ....

Con 500 Impulsi giro ed adottando un fattore prudenziale di 1/3 sulla frequenza (e non è detto che basti) già sopra i 1000Rpm la lettura non sarebbe più affidabile.

Un'altra ipotesi potrebbe essere che col motore in moto gli ingressi dell'ARDUINO captino rumore.

Per quanto riguarda gli impulsi mi sarei aspettato 4000 o 4096 impulsi. Molti di quei motori usano quella risoluzione, tanto che alcuni cloni orientali simili all'HBS alla voce impulsi encoder prevedono solo un bit di selezione tra 1000 oppure 1024 impulsi giro.

In ogni caso in un applicazione definitiva, sarebbe meglio portare tutti e quattro i segnali fino all'Arduino convertire da Line-Driver a TTL o CMOS e poi entrare sugli ingressi. Il piu noto e comune degli integrati che potresti usare è il classico MAX485. ma ne dovresti usare due. Cerca su internet che ne trovi parecchi. Quello che ti ho fatto fare, in effetti va bene per una provetta così sul banco, non certo per una applicazione operativa. Come poi ti ho spiegato sopra, se i tuoi motori fanno più di qualche centinaio di giri, saresti già oltre le possibilità di Arduino con degli Encoder a così alto numero di giri.

Scusami tecno ma sono stato alquanto impegnato, comunque attualmente sto usando del codice mio, che forse è il caso che posti, lo farò da PC appena possibile.

Ho anche portato tutti e 4 gli ingressi all'arduino perché cercando online ho finalmente un'idea di come funzioni questo tipo di encoder, modificando di conseguenza le isr.

Riguardo al secondo caso, ho praticamente programmato il motore (sull'hbs sono su 800 pulsazioni a giro) e facendone fare in realtà qualcuna in più (una cinquantina) faccio un giro completo di 360°.
Attualmente non sto usando una libreria per controllare il motore, anzi se ne avete da consigliare tanto meglio.

Sto usando un busy waiting (non delay) di 500 microsecondi tra un passaggio e l'altro di High low per dare gli impulsi.

Infine non credo che il problema stia nelle isr perché girando manualmente a motore disabilitato come dicevo funziona alla perfezione.

Due cose soltanto:

-800 impulsi, ovvero 200Impulsi giro per l'encoder, ci potrebbe anche stare, ma 850 ovvero (850/4=812,5) impulsi giro, mi pare davvero strana!

E' molto probabile che ti stia perdendo qualcosa.

-Il fatto di leggere anche i due segnali complementari, a mio parere, complica molto le cose ma non risolve il problema di incompatibilità tra un segnale per cui è significativo il valore di tensione verso il GND (CMOS o TTL) ed uno (LINE-DRIVER) dove è significativo il verso della differenza di potenziale tra le due line (EA- ed EA+ oppure EB- ed Eb+) e non tanto il loro livello assoluto verso il GND. Problema che è da risolvere in HW a mio parere.

Ultima cosa il fatto che tu ci chieda una libreria per comandare il motore, mi fa supporre che tu ora lo stia pilotando con una frequenza fissa senza alcuna forma di rampa di partenza/arresto. Se è così, è altamente probabile che il motore non compia esattamente il numero di step che tu gli hai richiesto e questo potrebbe spiegare perché apparentemente i conti non tornano. Oppure tu dai gli impulsi al driver che essendo Closed Loop provvede ad un esatto controllo del motore? (Sempre che le resistenze di PULL_UP interne all'Arduino non rompano le scatole agli ingressi del tuo driver e gli facciano perdere qualcosa ogni tanto)?

P.S.:Posso chiederti, se non sono indiscreto, cosa stai cercando di realizzare, magari vi sono altre strade più semplici e/o economiche per raggiungere l'obiettivo.

Inoltre conoscere il contesto applicativo di una cosa (te lo dico per esperienza) spesso aiuta ad identificare meglio e più velocemente eventuali errori o sviste che talvolta fanno impazzire, ma che invece erano li sotto i nostri occhi, mentre noi concentrati su altro, semplicemente non vedevamo.

Ciao tecno, per intanto posto il mio codice, in cui ho anche trovato un piccolo errore (dichiaravo l'interrupt due volte per lo stesso pin (copia ed incolla andato a male) >:( , ora effettivamente il valore letto in un giro per l'encoder è di circa 4000 valori come ipotizzavi tu.

#include <Arduino.h>
#define pulsePin PA10
#define dirPin PA9
#define enaPin PA8

#define EAP PA15 //EA+
#define EBP PB3 //EB+
#define EAM PB6 //EA-
#define EBM PB4 //EB-

volatile int value = 0;
int i = 0;
unsigned long actime = millis(), acttimer2 = micros();

boolean left = LOW, right = HIGH;

void motor_enable() {
  digitalWrite(enaPin, LOW);
}

void motor_disable() {
  digitalWrite(enaPin, HIGH);
}

void motor_left() {
  digitalWrite(dirPin, HIGH);
}

void motor_right() {
  digitalWrite(dirPin, LOW);
}

void set_direction(bool dir) {
  if(dir)
    motor_right();
  else
    motor_left();
}


void ISRA() {
  bool eap = digitalRead(EAP), eam = digitalRead(EAM);
  bool ebp = digitalRead(EBP), ebm = digitalRead(EBM);

  if(eap == HIGH && eam == LOW) {
    if(ebp == LOW && ebm == HIGH) {
      value++;
    }
    else if(ebp == HIGH && ebm == LOW) {
      value--;
    }
  }
  else if(eap == LOW && eam == HIGH){
    if(ebp == HIGH && ebm == LOW) {
      value++;
    }
    else if(ebp == LOW && ebm == HIGH){
      value--;
    }
  }
}

void ISRB() {
  bool eap = digitalRead(EAP), eam = digitalRead(EAM);
  bool ebp = digitalRead(EBP), ebm = digitalRead(EBM);

  if(ebp == HIGH && ebm == LOW) {
    if(eap == HIGH && eam == LOW) {
      value++;
    }
    else if(eap == LOW && eam == HIGH){
      value--;
    }
  }
  else if(ebp == LOW && ebm == HIGH){
    if(eap == LOW && eam == HIGH) {
      value++;
    }
    else if(eap == HIGH && eam == LOW){
      value--;
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(pulsePin, OUTPUT);
  pinMode(dirPin, OUTPUT);
  pinMode(enaPin, OUTPUT);
  pinMode(EAP, INPUT_PULLUP);
  pinMode(EBP, INPUT_PULLUP);
  pinMode(EAM, INPUT_PULLUP);
  pinMode(EBM, INPUT_PULLUP);

  attachInterrupt(digitalPinToInterrupt(EAP), ISRA, CHANGE);
  attachInterrupt(digitalPinToInterrupt(EBP), ISRB, CHANGE);
  
  motor_disable();
  i = 0;
}

void mov(int gradi, bool direction) {
  set_direction(direction);
  int pulses = (int)(((float)(850*gradi))/360);
  motor_enable();
  for(int k = 0; k < pulses; k++) {
    digitalWrite(pulsePin, HIGH);
    acttimer2 = micros();
    while(micros() - acttimer2 < 1500) {}
    digitalWrite(pulsePin, LOW);
    acttimer2 = micros();
    while(micros() - acttimer2 < 1500) {}
  }
  motor_disable();
}

void test_movement() {
  if(millis() % 6500 == 0) {
    if(i) {
      mov(360, left);
      i = 0;
    }
    else {
      mov(360, right);
      i = 1;
    }
  }
}

void loop() {
  test_movement();
  if(millis() % 256 == 0) {
    Serial.println(value);
  }
}

Il risultato però non è cambiato, ho i miei 4k valori mentre ruoto l'asse in entrambi i versi a motore spento, mentre a motore acceso ricevo pochi impulsi quando il motore gira a sinistra in particolare.

Per quanto riguarda l'HW avendo a casa anche una bluepill (STM32F103 col bootloader arduino) ho provato anche con quella ma con gli stessi risultati. Il collegamento è i 2 canali con tutti e 4 le uscite ai pin arduino, GND arduino ed encoder (USCITA GND HBS) collegati insieme.

Riguardo gli impulsi in più, si io sto pilotando a frequenza fissa, per come puoi vedere dal codice penso che tu intenda questo.

Scusa The_Hawk, ma mi sembra che ci sia bisogno di un pò di chiarezza. Io ho postato una routine di lettura per 3 encoder in quadratura basata su di una ISR scritta in assembly AVR scrivendo chiaramente che è adatta ad un Arduino UNO R3 che è basato su di un microcontrollore AVR ATMega328P ad 8 bit, che ovviamente non può funzionare con l'Hardware su cui stai operando ora.
Tu non l'hai impiegata, ovviamente, ma non hai nemmeno riprodotto il meccanismo di lettura che io ho implementato nella mia e sinceramente quella scritta da te, per quello che leggo (salvo mia incomprensione) non dovrebbe nemmeno poter funzionare. In un encoder in quadratura tu devi incrementare/decrementare quando hai determinate transizioni tra uno stato precedente degli ingressi che devi aver memorizzato da qualche parte ed uno attuale. Io non vedo nulla di tutto ciò.
Se vuoi ti posso spiegare meglio l'algoritmo da me impiegato, ma non puoi chiedere a me lumi sul mancato funzionamento di una cosa che non ho scritto io e che per giunta non mi torna proprio.

Per chiarire meglio se tu hai due bit che ti rappresentano lo stato attuale dei canali A e B, se:

Stato attuale: a=0 e b=0: il successivo può essere A=1 e B=0 se l'encoder ha avanzato
A=0 e B=1 se l'encoder ha arretrato

in pratica gli stati sono: 00 poi 10 poi 11 poi 01 ed infine ancora 00 se giri in avanti
se giri indietro 00, 01, 11, 10.

Ma per decidere se incrementare devi sempre comparare lo stato attuale col precedente e non mi sembra che tu lo faccia.

Io per inciso faccio questo in una maniera più criptica ma equivalente:

Se tu metti in una tabella tutte le transizioni possibili segnando a sinistra la coppia di bit che rappresenta lo stato precedente ed a destra lo stato attuale, avrai questa situazione:

Incremento: Decremento: Nessun cambiamento: Doppio avanzamento:

prec. Attuale prec. Attuale prec. Attuale prec. Attuale
ab AB ab AB ab AB ab AB
00 ---> 10 00 ---> 01 00 ---> 00 00 ---> 11
10 ---> 11 01 ---> 11 01 ---> 01 01 ---> 10
11 ---> 01 11 ---> 10 11 ---> 11 11 ---> 00
01 ---> 00 10 ---> 00 10 ---> 10 10 ---> 01

Se ora nelle colonne dello stato attuale inverti tra loro i bit e poi esegui un XOR tra stato attuale e precedente, potrai vedere che in tutti icasi di incremento avrai: 01, in tutti i casi di decremento avrai 10, con nessun cambiamento 00 e con doppio avanzamento 11. Quindi se il bit0=1 incrementi, se il bit1=1 decrementi se hai 00 non fai nulla, se hai 11 è una condizione di errore di lettura. Il micro non è abbastanza veloce in rapporto alla frequenza degli impulsi oppure ci sono dei disturbi che devi eliminare in HW.

Spero di averti chiarito qualche dubbio.

Luca

Scusa ma nell'inviare il messggio mi ha scobinato tutta l'impaginazione, ma credo si capisca ugualmente.

Bene tecno, ovviamente capisco i tuoi dubbi, ma in realtà facciamo la stessa cosa in modi diversi.
Essendo scritta in ASM la tua soluzione potrebbe sicuramente essere più efficiente ma l'approccio è identico.
Ti spiego così da farti capire il mio ragionamento.

Nel mio codice lo stato precedente è dato da quale ISR viene richiamata. Essendo gli encoder in quadratura, i segnali non cambiano nello stesso istante ma sfalsati a metà rispetto l'onda quadra generata dai due canali.

Quindi supposto venga richiamata l'ISR relativa al canale A (ISRA sul mio codice) allora rispetto allo stato precedente
B = B' perchè non è ancora cambiato. Mentre A != A' ovvero è cambiato da 0 a 1 o viceversa.

Supponiamo infatti A sia cambiato da A' = 1 ad A = 0 e che B = B' = 1. Allora lo stato precedente è 11 mentre lo stato attuale è 01 => sono nella condizione di un incremento come è anche possibile vedere dalla tua stessa tabella.
Se invece B fosse stato 0 allora ci saremmo trovati nel caso precedente 10 e stato attuale 00 e quindi in un decremento.
E così via è possibile valutare tutti i possibili casi.

Ho aggiunto sui controlli dentro le due ISR però che se A+ = 0 allora A- = 1 e viceversa e stessa cosa con B perchè su ciò che ho trovato riguardo questo tipo di encoder, correggimi se sbaglio, lo stato si inverte tra i due pin dello stesso canale encoder per evitare le interferenze.

Ripeto la tua soluzione potrebbe essere più efficiente e penso proprio che lo sia, però la mia dovrebbe essere perfettamente valida, soprattutto perchè usando gli interrupt non dovrebbe perdersi per strada dei passaggi.

Al più sto pensando ci possano essere delle interferenze a motore attivo, potrei provare ad allontanare HBS e motore, anche perchè a motore spento le letture seppure fatte molto velocemente corrispondono.

Infine mi sono accorto solo ora, che ho postato il codice (identico) relativo alla prova che ho fatto sull'STM32 per capire se fosse il micro troppo lento o se ci fossero problemi legati a quello e quindi i nomi dei pin ovviamente corrispondono a quella board li, ma ripeto sulla mega uso lo stesso codice cambiando i pin di interrupt (uso i pin 18 e 19), scusami per l'errore.

Spero di essere stato chiaro, eventualmente chiedi pure.
Grazie.

Si in effetti sintatticamente sembra corretto, ma attenzione, aggiungendo la valutazione del segnale complementare, a mio parere le cose sono peggiorate, perché se dato un eventuale sia pur minimo sfasamento tra il cambio di stato sulla linea positiva e su quella negativa (la contemporaneità è solo teorica, in realtà non l'avrai mai), la doppia condizione che tu verifichi, potrebbe anche non esserci ancora quando tu entri nella ISR (A oppure B) e quindi nessuna condizione è verificata, ma soprattutto ti perdi un passo e tutte le considerazioni fatte da te cadono.
Altra cosa da dire è: A quanti step/giro ai selezionato l'HBS? Coincidono con la risoluzione encoder?
Ed infine: Tu muovi il motore con uno step/rate di circa 1 impulso ogni 3mSec, ma la visualizzazione la fai ogni 256mSec.
Per fare 850Step impieghi 850*3= 2550 Secondi ovvero in un ciclo fai 9.96 aggiornamenti della visualizzazione. Un po pochini per vedere cosa avviene effettivamente e soprattutto per via dei resti (2550/256) potresti avere una percezione errata. Oltretutto c'è il ritardo di trasmissione sulla seriale. Quanto impieghi a trasmettere tutti i caratteri ascii che formano il numero? E' vero che hai dichiarato la seriale a 115200 baud, ma con tutte le interruzioni dell'encoder non ce l'hai proprio quella velocità, perché i caratteri potrebbero non essere spediti uno subito dietro l'altro, ma avere delle grosse pause tra un carattere e l'altro.

Volendo verificare, io farei una cosa più semplice:

Lasciando la visualizzazione come l'hai fatta tu.

Usa una 'tone(pin, frequency, duration)' con una frequenza di 283Hz ed una durata di 3004ms. Equivale ad emettere esattamente 850 impulsi (1 ogni 3mSec) senza dover scrivere tutto il ciclo che hai fatto tu.

Fai un movimento in una direzione, ti fermi per 5 secondi per avere tempo di leggere il valore del conteggio e poi lo fai nella direzione opposta, nuova pausa di 5 secondi x leggere e via di nuovo nella direzione iniziale. Se tutto è ok, dovresti leggere, durante le pause, alternativamente due valori che sono circa 850 l'uno più dell'altro.

Oppure esegui un certo numero di oscillazioni avanti e indietro della stessa lunghezza, senza pause, poi fermi tutto. Se il valore finale è uguale a quello iniziale, non hai perso impulsi, altrimenti c'è qualcosa che non va.

Mi viene in mente anche un'altra cosa che a mio parere non è corretta nel tuo programma.

La variabile value che tu usi per il conteggio è un int, quindi a due Byte e viene modificata dalle ISR. Potrebbe benissimo accadere che mentre la Serial.print la sta maneggiando per trasmetterla dopo aver preso/valutato un byte, prima di operare sull'altro la ISR modifica la variabile ed il valore utilizzato dalla Serial.print a quel punto non ha più senso perché i due byte della variabile non sono coerenti. Sarebbe meglio copiare il valore della variabile in una variabile di appoggio dentro un blocco atomico e poi dare questa seconda variabile in pasto alla Serial.print. Se vedi la mia lettura x tre encoder io faccio proprio questo, sia in lettura che in scrittura quando faccio il reset.

tecno67:
... Sarebbe meglio copiare il valore della variabile in una variabile di appoggio dentro un blocco atomico e poi dare questa seconda variabile in pasto alla Serial.print. ....

Esatto !

Non si sa mai quando scatta un interrupt e ... potrebbe essere proprio nel bel mezzo della lettura di una variabile da lui aggiornata per cui occorre fare:

...
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
   variabile_appoggio = variabile_ISR;
}
...
Serial.print(variabile_appoggio);
...

Guglielmo

Grazie Guglielmo! Fa piacere ricevere una conferma da te.

Siccome anche se ho scritto un pezzo di codice in Assembly, sono un neofita totale di Arduino ed anche se sarebbe Off_Topic, posso approfittare dell'occasione per chiedere se c'è qualche posto dove si possa reperire un elenco delle periferiche e degli interrupt impiegati, sempre e comunque dall'Arduino UNO base.

Intendo dire, non funzioni introdotte con librerie aggiunte (per quelle devo analizzare il relativo codice), ma proprio quelli di sistema e/o quelli attivati dai comandi base di Arduino? Magari, se non è troppo, una lista del numero di cicli macchina massimo che impiegano nel peggiore dei casi.

Per intenderci una cosa del tipo: comando xyz() - Usa questa e questa periferica e questo interrupt (se ne usa). L'interrupt impiega max 'tot' cicli macchina.

Questo perché volendo valutare la latenza massima della mia lettura è chiaro che essa non dipende solo dal codice che io ho scritto, ma anche da quanto dura una eventuale ISR che fosse già in esecuzione al momento dell'attivazione del PinChange_interrupt che ho impiegato io. Che se non si impiegano gli INT_Ext int0 e int1, è quello di priorità maggiore.

Ringrazio e mi scuso con The_Hawk per avergli sporcato il post.

Luca

tecno67:
... chiedere se c'è qualche posto dove si possa reperire un elenco delle periferiche e degli interrupt impiegati, sempre e comunque dall'Arduino UNO base. ...

No, purtroppo non esiste tale documentazione ... :confused:
... tocca aprire il singolo "core" e, modulo per modulo, vedere cosa fa, che interrupt usa, ecc. ecc. facendo poi sempre riferimento al datasheet della MCU scelta.

Guglielmo

Puoi dettagliare meglio la cosa? Oppure indicarmi dove posso trovare info? Come ho detto sono un newbie totale riguardo a questo. Dove trovo i "core".
Un altro mio limite è che comprendo abbastanza bene il 'C', mentre il C++ ed in particolar modo classi, metodi, ereditarietà ecc. mi sono sempre state indigeste.
Ad esempio ancora non so come visionare il codice Assembly prodotto dal compilatore. Dove lo piazza l'IDE?
Forse perché dopo una gioventù da studente ITIS in cui stavo attaccato al PC notte e giorno, mi sono perso il periodo della programmazione ad oggetti. Ed ora a 52 anni i neuroni sono un po arrugginiti. :slight_smile:

Vai in arduino\hardware\arduino\avr\cores e trovi tutti i moduli che compongono il "core" degli Arduino AVR di base (UNO, MEGA, Leonardo, ...). :slight_smile:

Guglielmo

Per quanto riguarda il codice assembler ... NON viene di base generato, ma lo puoi ricavare ...

Nelle preferenze devi mettere il segno di spunta al dettaglio sia della compilazione che del caricamento. Quando compili ti darà molte più informazioni e alla fine della compilazione ti indicherà dove ha messo (in una directotory temporanea) il file che termina con .elf ... è quello che a te occorre.

Recuperato il .elf devi usare da console il comando avr-objdump.exe con la seguente sintassi:

arduino\hardware\tools\avr\bin\avr-objdump.exe -S nome_del_elf >  disassemblato.txt

... in pratica gli dai in ingresso il file .elf che hai recuperato e gli dici di reindirizzare l'uscita su un file di testo con il nome che vuoi.

Guglielmo

Grazie! Ci provo.