PWM VELOCE

?R:
ogni volta che parli di modificare l'ISR non capisco mai dove è il codice nel quale devo andare a mettere le mani... :sweat_smile:

Beh, avendo passato un pò di ore a guardare tutti i file del core di Arduino, poi le cose so dove cercarle :wink:

ho un'altra domanda: se io vario la frequenza di un pwm di conseguenza vario anche quella di tutti i pwm legati a quel timer? o dipende come lo vario?

Partiamo a monte. C'è un timer. Un timer è un circuito che contiene un contatore aggiornato da un circuito collegato ad un prescaler, ossia un divisore di clock posto sull'ingresso. Grazie a questo divisore, posso variare il segnale d'ingresso per cui posso diminuire a mio piacimento la frequenza con la quale viene aggiornato il contatore. Il contatore poi è un registro in RAM per cui posso anche variarne il contenuto. In questo modo posso ottenere overflow molto rapidi oppure molto lenti, a seconda di come imposto il tutto.

Ora vediamo come possiamo usare il timer. Lo possiamo usare essenzialmente in 3 modi: per generare un segnale di interrupt, per cambiare lo stato di un pin oppure per sollevare entrambi gli eventi.
Se lo uso solo per sollevare un interrupt, il timer diventa un semplice contatore. Se invece lo collego materialmente ad un pin esso diventa un generatore di segnale PWM, la cui frequenza e duty cicle varia in base a come lo impostiamo (se in modalità FastPWM, Phase Correct PWM, con controllo sul valore massimo e/o sul valore minimo ecc.... ci sono una dozzina di modi differenti per sistemarlo).

E' ovvio che il timer 0 è "delicato", nel senso che essendo già stato impostato dal core di Arduino per determinate funzioni, se vai a toccare il prescaler, il valore del contatore, il duty cicle o la modalità di generazione del segnale PWM, alteri tutto quello che c'è a valle.

Fin qui è chiaro, no?

Adesso come possiamo variarlo? Se noi inseriamo una piccola routine alla fine della funzione che intercetta l'overflow del timer 0, che sull'Arduino è usata per la gestione del contatore di millisecondi, possiamo fargli eseguire delle operazioni. Ad esempio, possiamo variare lo stato di un pin a nostro piacimento, alternandolo LOW/HIGH con una frequenza dimezzata rispetto a quella del timer. Per far ciò basta cambiare lo stato di questo pin ogni 2 chiamate della ISR così che se la ISR lavora a 976 Hz il nostro generatore di PWM lavori ad una frequenza di (976/2)=488 MHz.

Fai questo test. Apri il file /arduino-1.0.2/hardware/arduino/cores/arduino/wiring.c
Intorno alla posizione 44 la funzione che intercetta l'overflow del contatore del timer (che usa una parola chiave, SIGNAL, che sui compilatori avr-gcc sta andando in "deprecato", sostituita appunto dalla parola chiave ISR)

Il codice è questo:

#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
SIGNAL(TIM0_OVF_vect)
#else
SIGNAL(TIMER0_OVF_vect)
#endif
{
	// copy these to local variables so they can be stored in registers
	// (volatile variables must be read from memory on every access)
	unsigned long m = timer0_millis;
	unsigned char f = timer0_fract;

	m += MILLIS_INC;
	f += FRACT_INC;
	if (f >= FRACT_MAX) {
		f -= FRACT_MAX;
		m += 1;
	}

	timer0_fract = f;
	timer0_millis = m;
	timer0_overflow_count++;
}

Tu modificalo così:

#if defined(__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__)
SIGNAL(TIM0_OVF_vect)
#else
SIGNAL(TIMER0_OVF_vect)
#endif
{
	volatile static uint8_t _tempBitStatus = 0; //MOD
	
	// copy these to local variables so they can be stored in registers
	// (volatile variables must be read from memory on every access)
	unsigned long m = timer0_millis;
	unsigned char f = timer0_fract;

	m += MILLIS_INC;
	f += FRACT_INC;
	if (f >= FRACT_MAX) {
		f -= FRACT_MAX;
		m += 1;
	}

	timer0_fract = f;
	timer0_millis = m;
	timer0_overflow_count++;
	
	if (_tempBitStatus & 1) { //MOD
		PORTD |= (1<<7); //MOD
	} else { //MOD
		PORTD &= ~(1<<7); //MOD
	} //MOD
	_tempBitStatus++; //MOD
}

Adesso carica questo sketch sul tuo Arduino:

void setup() {
}

void loop() {
    //accende e spenge il PWM sul pin 7 ogni 5 sec
    pinMode(7, OUTPUT);
    delay(5000);
    pinMode(7, INPUT);
    delay(5000);
}

Controlla il segnale sul pin 7: avrai un PWM con frequenza dimezzata rispetto a quella solita generata dal timer 0, che viene accesa e spenta ogni 5 secondi.

ok grazie mille per la spiegazione! :wink: ora ho capito :slight_smile:

nella prova pratica però ho dei dubbi, premetto che ho sistemato i codice perchè io ho solo un arduino mega e il pin 7 della porta D è il pin 38, comunque la frequenza è giusta, circa 476Hz, ho provato col mio oscilloscopio... ma c'è una cosa che non capisco: quando metto com input resta comunqe acceso un po' il led... :roll_eyes:
ma così non posso variare il pwm, no?

?R:
nella prova pratica però ho dei dubbi, premetto che ho sistemato i codice perchè io ho solo un arduino mega

Scusa, ma hai finito i pin PWM Per caso?
Perché altrimenti non capisco per quale motivo vuoi alterare il timer 0.
Lo sai, vero, che a parte il timer 0 gli altri timer sono impostati per avere una frequenza di ~488 Hz e che solo il timer 0 è impostato per avere ~976 Hz? Se il tuo problema era avere un PWM a bassa frequenza, allora è un "non problema". :wink:

e il pin 7 della porta D è il pin 38, comunque la frequenza è giusta, circa 476Hz, ho provato col mio oscilloscopio...

Non avevi specificato della Mega, per cui ho preso per buono il fatto di una Arduino Uno/2009. Comunque il tutto è facilmente adattabile, basta modificare il bit ed il registro della porta.

ma c'è una cosa che non capisco: quando metto com input resta comunqe acceso un po' il led... :roll_eyes:

Mettendo il pin in input, nel momento in cui viene scritto il segnale HIGH si accende la pull-up interna, che viene spenta quando il segnale viene messo su LOW.
Come ti avevo spiegato, per ridurre al minimo l'impatto sui calcoli del tempo, ho messo una routine più semplice possibile.
Volendo si può rivedere il codice affinché stacchi proprio il segnale dal pin quando lo metti in input. Però si torna a quanto ti ho chiesto prima, e cioè: ma hai finito i pin PWM?

ma così non posso variare il pwm, no?

Col codice che ti ho dato no. E' minimale che di più non si può. Fa solo ON/OFF con frequenza dimezzata rispetto a quella impostata dal timer, tutto qui.
Per alterare il PWM devi alterare le impostazioni del timer, e si torna al discorso che ti facevo nel precedente post.

ok ok no problem, grazie delle spiegazioni
purtroppo non pensavo di avere questi problemi con le frequenze dei pwm e avendo il circuito su pcb non mi resta che mettere mano al software :frowning:
allora se non è possibile modificare la frequenza del pwm del timer 0 devo fare in modo che anche gli altri pwm abbiano lo stesso comportamento, cioè raddoppio le frequenze degli altri pwm che mi interessano, e questo dovrei riuscire a farlo da solo :wink:
non mi è chiara ancora una cosa, se io nel timer 2 voglio che un pwm vada a 980hz e un'altro a 18KHz, si può o devo fare una cosa tipo quella che suggerivi di fare per il timer 0?
Grazie :wink:

?R:
allora se non è possibile modificare la frequenza del pwm del timer 0 devo fare in modo che anche gli altri pwm abbiano lo stesso comportamento,

In teoria il timer 0 puoi modificarlo come ti pare. Ricordati solo che poi non hai più le funzioni temporali dell'Arduino.
Se la cosa non è fondamentale, puoi farlo.

cioè raddoppio le frequenze degli altri pwm che mi interessano, e questo dovrei riuscire a farlo da solo :wink:

Ma il problema è avere un PWM con frequenza bassa o alta? :sweat_smile:
Prima dici che vuoi una frequenza alta, poi dici che i 976 Hz del timer 0 sono troppi e vuoi dimezzarli, ora invece vuoi raddoppiare la frequenza degli altri timer... deciditi :stuck_out_tongue_closed_eyes:
Spiega per bene cosa vuoi fare, vediamo se riesco a darti una mano concreta :wink:

non mi è chiara ancora una cosa, se io nel timer 2 voglio che un pwm vada a 980hz e un'altro a 18KHz, si può o devo fare una cosa tipo quella che suggerivi di fare per il timer 0?
Grazie :wink:

Non si può fare. O vai a 18 KHz o vai a 980 Hz.
Però puoi usare un "trucco" come quello che ti ho illustrato per inserire un prescaler software ed avere su un pin un segnale con una frequenza inferiore.

ok, il problema è che ho un motore che ha un comportamento diverso dagli altri 3...
però non ho chiaro quale sia la souluzine ideale, vorrei che sul pin 2 ci fosse un pwm con una frequenza di quasi 20KHz, mentre sul 3,4,5 e 6 un pwm di frequenza arbitraria uguale per tutti questi 4 pin... ma da questo link SobiSource.com is for sale | HugeDomains vedo che i pin 2, 3 e 5 sono tutti collegati al timer 3 e quindi mi crea problemi :stuck_out_tongue_closed_eyes:
oggi volevo provare a fare lo sweep per vedere a che frequenza taglia ma non mi va più lo schermo del robot e sto ancora lavorando su quello... :stuck_out_tongue_closed_eyes: in un altro topic mi suggerivi di andare a fare un giro a Lourdes, forse sarebbe proprio il caso...

Facciamo il punto delle "certezze":

  1. hai un PCB già fatto per cui non puoi modificare le piste
  2. hai necessità di un PWM sul pin 2 diverso da quello che il timer 3 ti genera

Allora ho una buona notizia per te :wink:
Un pin genera un segnale PWM corrispondente alle impostazioni del timer solo se lo agganci al timer stesso. Se lo lasci sganciato, è un pin normale e lo puoi usare per generare un segnale PWM usando la tecnica bit-banging via SW che ti ho illustrato io.
Quindi, se tu piloti il pin 2 via SW senza agganciare il pin al timer usando l'analogWrite, puoi generare sul pin 2 un segnale PWM con una frequenza differente (solo minore, ovviamente) rispetto a quella generata sugli altri 2 pin agganciati al timer :wink:
Che ne dici? :smiley:

un'altra certezza è che i pwm sui pin 3,4,5 e 6 devono avere la stessa frequenza e di duty cycle indipendenti fra loro...
poi ci sarebbe che siccome il pwm del pin 2 può essere di frequenza molto più alta mi piacerebbe sfruttare questa cosa, però per le frequenze precise prima devo fare dei test per vedere a che frequenze taglia
nei prossimi giorni faccio dei test, poi ci risentiamo, intanto grazie mille :wink:

Scrivi e vediamo se si tira fuori qualcosa.

allora sul pin 2 posso arrivare tranquillamente a 20KHz di frequenza con duty cycle variabile, mentre sui pin 3,4,5 e 6 avrei bisognpo di una frequenza molto bassa, sui 200Hz...
per i pin con il pwm "lento" si possono usare gli altri timer che non sono direttamente collegati ai pin indicati per fare questi pwm andando a modificare l'ISR come proponevi di fare con il timer 0?
per il pwm "veloce" invece mi basta andare a mettere le mani sui registri come mi avevi già spiegato

Nonostante siano in sequenza, i pin 2,3,4,5,6 sono agganciati a diversi timer:
timer 3: pin 2, 3, 5
timer 0: pin 4
timer 4: pin 6

Il timer 3 lo possiamo perciò impostare come vogliamo, e possiamo avere sul pin 2 i tuoi 20 kHz con duty cicle variabile.
Sui pin 3 e 5 potremmo usare la tecnica del bit-banging sul timer 3, dividendo la frequenza del timer di un fattore 100 per avere proprio 200 Hz. Avresti però lo stesso duty cicle, se non è un problema.

Il timer 0 non lo possiamo modificare altrimenti si alterano le funzioni temporali per cui il pin 4 lo dobbiamo pilotare col bit-banging, magari agganciandolo al timer 4, che pilota il pin 6.

Che ne dici?

sul pin 2 siamo d'accordo, sugli altri se non si riesce a variare il duty cycle bisogna pensare ad una alternativa...

Curiosità, la funzione tone si basa sul timer 0, fa con la stessa tecnica?

?R:
sul pin 2 siamo d'accordo, sugli altri se non si riesce a variare il duty cycle bisogna pensare ad una alternativa...

Si può fare a livello di codice ma diventa complicato con la tecnica del bit-banging.
Si potrebbe usare un ulteriore timer non usato ed agganciare i pin 3, 4 e 5 ad esso (es. timer 2). Però tutti e 3 i pin avrebbero stessa frequenza e stesso duty cicle.

Curiosità, la funzione tone si basa sul timer 0, fa con la stessa tecnica?

No, viene fatto fare in HW, impostando il timer a seconda del tipo e della frequenza da generare. Il timer usato cambia a seconda del microcontrollore. Sull'Atmega1280/2560 viene usato il timer 2. Ma questa scelta può essere cambiata, basta modificare il file Tone.cpp del core.

ok ma a me servirebbe avere almeno 3 livelli di duty cycle tra cui scegliere e che i 4 canali possano essere controllati individualmente :~
però se mettiamo il duty cycle sempre al 50%, a frequenza di circa 200Hz, si risce ad avere un controllo di quei 4 pin in modo indipendente tra loro? cioè se c'è il pwm al 50% poi anche se faccio digitalWrite il pin non resta sempre a livello alto o basso, no? è un casino... :stuck_out_tongue_closed_eyes:

?R:
ok ma a me servirebbe avere almeno 3 livelli di duty cycle tra cui scegliere e che i 4 canali possano essere controllati individualmente :~

Non è impossibile ma si complica un po' il codice da scrivere. Ammettendo 3 livelli di duty cicle al 25/50/75% (poi c'è lo 0% che possiamo far corrispondere ad un segnale LOW fisso, ed il 100%, che possiamo far corrispondere ad un segnale HIGH fisso), si hanno un po' di switch..case o di if..else.
Tutto fattibile, ma con un po' di codice in più.

però se mettiamo il duty cycle sempre al 50%, a frequenza di circa 200Hz, si risce ad avere un controllo di quei 4 pin in modo indipendente tra loro?

Beh, cerchiamo di accendere/spengere il PWM sui pin indicati in modo indipendente.
Si può creare una funzione activatePwm(pin, dutyCicle) che attivi il PWM software sul pin indicato col duty cicle indicato.
Tutto si può fare, va vista la complessità del resto del codice per capire le risorse a disposizione e se c'è qualcosa che va in conflitto con qualcos'altro.
Allo stato attuale, per far ciò che ti ho detto, serve avere libero accesso ad almeno 3 timer: t2, T3 e t4.

cioè se c'è il pwm al 50% poi anche se faccio digitalWrite il pin non resta sempre a livello alto o basso, no? è un casino... :stuck_out_tongue_closed_eyes:

Il finto PWM surclasserebbe il digitalWrite. Cioè se attivi un PWM al 25% su un pin, e poi su quello stesso pin ci fai un digitalWrite, il segnale imposto col digitalWrite dura fino al successivo richiamo dell'interrupt del timer che controlla quel pin. Poi il codice nella ISR reimposterebbe il pin secondo le sue impostazioni, quindi il digitalWrite se ne va. Va prima disattivato il segnale PWM software e poi si può riutilizzare il pin col digitalWrite.

per semplificare ulteriormente a me andrebbe bene anche solo 0% 50% e 100%, basta che siano indipendenti tra loro... :slight_smile:

?R:
per semplificare ulteriormente a me andrebbe bene anche solo 0% 50% e 100%, basta che siano indipendenti tra loro... :slight_smile:

OK.
Allora, riassumiamo cosa vuoi e su quali pin:
pin 2: 20 KHz
pin 3, 4, 5, 6: 200 Hz

Duty cicle (ogni pin indipendente):
0-50-100%

Timer che utilizzerò: 3 (forse anche il 4)

OK? Se va bene, domani inizio a scrivere qualcosa.

si, e sul pin 2 a 256 livelli impostabili
grazie infinite per l'aiuto e scusa per il disturbo, fai pure con calma che non ho fretta :wink:

?R:
si, e sul pin 2 a 256 livelli impostabili

Sì, ovvio. Essendo un pin pilotato direttamente dal timer, puoi avere i 256 livelli ddi duty cicle possibili.

grazie infinite per l'aiuto e scusa per il disturbo, fai pure con calma che non ho fretta :wink:

OK.

grazie mille! :wink:
ormai mi sento in debito con te per tutte le volte che mi hai aiutato :sweat_smile: