PWM via software

Salve a tutti.
Mi sono trovato di fronte ad un progetto di tesi in qui si utilizza un atmega 32L quindi con funzionalità praticamente identiche al microcontrollore usato da arduino. L'atmega 32L in questione si trova installato sopra ad un prototipo di veivolo alimentato da celle fotovoltaiche. Il microcontrollore tra le altre cose deve controllare in PWM 10 motori 7mm micro dc motor da (1,5 V, 10500 rpm, 15 mA) raggruppati in 4 gruppi, usando 10 MOS INFINEON BS0200N03S. Essendo le uscite PWM del microcontrollore usate anche per regolare il dutty cicle degli alimentatori, il numero di uscite PWM offerte dall'atmega 32L non sufficienti a gestire sia motori che alimentatori. E' nata dunque l'esigenza di implementare canali PWM via software, ma non so come farlo.
Sono quindi acettati consigli.

federico

Ciao!

Potresti usare usare un TCL5940 per espandere le porte PWM, viene usato per i led quindi non so se possa andare bene anche per i motori,
di sicuro l'ho visto usare per controllare servi.

Il TLC5940 può fornire al max 120 mA su tutti i pin di out a 5V, la corrente è impostabile con un'unica resistenza. Fai un po' di conti per vedere si sono sufficienti.

Purtroppo sono vincolato ad usare il solo microcontrollore senza supporti hardware aggiuntivi devo necessariamente implementarlo via software. Ho cercato un po' su google ed ho trovato una soluzione abbastanza fattibile che sto cercando di provare su arduino.
In pratica dice di usare:
-un timer ad esempio TCNT0 (timer0) come semplice contatore da cui dipende la frequenza del segnale PWM
-un timer ad esempio TCNT2 (timer2) impostato a lanciare un'interruzione ogni volta che va in overflow
-una variabile contenente il dutty cicle del PWM
-una funzione, svolta dalla routine di risposta all'interruzione lanciata dall'overflow del timer2, dove si confronta il valore attuale di TCNT0 con il valore di dutty cicle che si vuole impostare su un determinato pin. Se TCNT0 >= dutty cicle allora pin alto, altrimenti pin basso.

In questa maniera posso modificare durante il "loop" il valore del dutty cicle desiderato (stando attendo a non modificarlo quando viene usato dalla routine). Però devo capir meglio come posso impostare la frequenza PWM anche in funzione del clock del quarzo e in più devo stare attento perchè entrambi i timer vengono utilizzati anche per il PWM hardware.
Ora provo su arduino uno cosa può succedere pilotando in pwm sul pin 6 un led, utilizzando il timer corrispondente, ed utilizzando lo stesso timer anche per lanciare un interruzione di overflow e il timer 0 come semplice contatore per implementare il canale pwm software sul pin 13.

federico

Se non ti interessa perdere il PWM dell'Arduino, potresti fare tutto con un singolo timer.
Il timer puoi programmarlo come più ti piace, e puoi variare il PWM direttamente da codice. Imposti un interrupt sul timer in modo da intercettare la condizione di overflow del contatore e da lì attivi i pin. C'è da chiedersi però che influenza abbia la gestione delle porte sul tempo di esecuzione della routine di intercettazione dell'interrupt.

PS: TCNTx non sono timer, sono i registri contatori del timer :wink:

leo72:
PS: TCNTx non sono timer, sono i registri contatori del timer :wink:

Per essere precisi si :slight_smile:

Ho fatto diverse prove e questo codice funziona.

byte dutty_cicle13=0;

//routine di risposta all'interruzione causata dall'overflow del timer2
ISR(TIMER2_OVF_vect)
{
  dutty_cicle13++;
}

void setup()
{
  pinMode(11,OUTPUT);
  
  //abilita interruzioni globali
  SREG |=0x80;

  //abilita interuzzione di overflow del timer 2
  TIMSK2 |=0x01;

  //imposta il timer 2 ad operare in fast PWM sul pin 11 ed imposta un fattore di prescale 64
  TCCR2A=0x83;
  TCCR2B=0x04;
}

void loop()
{
  OCR2A=dutty_cicle;
}

Il risultato è un effetto fadding su di un led collegato al pin 11. Il segnale generato ha una frequenza di 976,5625Hz dato dalla formula 16MHz/64/256.
Così non ho ancora implementato un PWM via software ma ho capito concetti utili.

Cio che ho pensato di fare è di utilizzare un'altro timer ad esempio il timer 1 come contatore (in realtà lo impostero in modalità fast PWM per utilizzarlo anche per il PWM hardware e contemporaneamente funge anche da contatore). Nella routine lanciata dall'overflow del timer 2, vado a leggere il registro contatore del timer 1 (TCNT1) ]:D, e lo confronto con i vari dutty cicle (tanti quanti sono i pwm software; ..ma non troppi.. per ora uno) e imposto il pin 13 alto o basso a seconda del confronto.

Quindi ho provato questo codice:

byte dutty_cicle13=0;

ISR(TIMER1_OVF_vect)
{
  byte COUNTER=TCNT1; 
  if(dutty_cicle13>=COUNTER)
    digitalWrite(13,HIGH);
  else
    digitalWrite(13,LOW);
}

void setup()
{
  pinMode(13,OUTPUT);
  pinMode(11,OUTPUT);
  pinMode(9,OUTPUT);
  
  //imposta il timer 1 ad operare in fast PWM sul pin 9 con fattore di prescale 64
  TCCR1A |=0x81;
  TCCR1B |=0x0C;

  //abilita interruzioni globali
  SREG |=0x80;
  //abilita interuzzione di overflow del timer 2
  TIMSK2 |=0x01;
  
   //imposta il timer 1 ad operare in fast PWM sul pin 11 con fattore di prescale 64
  TCCR2A=0x83;
  TCCR2B=0x04;
}

void loop()
{
 OCR2A=50;
  OCR1A=130;
  dutty_cicle13=0;
  delay(5000);
  OCR2A=130;
  OCR1A=255;
  dutty_cicle13=50;
  delay(5000);
  OCR2A=255;
  OCR1A=0;
  dutty_cicle13=130;
  delay(5000);
  OCR2A=0;
  OCR1A=50;
  dutty_cicle13=255;
  delay(5000);
}

cioè pwm hardware sul pin 9 (timer 1) ed 11 (timer 2) e software sul pin 13.
Non funziona: sembra che non ci sia ritorno dalla routine, infatti i led collegati a pin 9,11 e 13 rimangono sempre nello stesso stato.

federico

Fai tutto con un unico timer. Ti sfuggono le modalità di azione dei timer, ad esempio la FastPWM.

Per avere frequenze più elevate basta "giocare" con i registri OCRxA/B per impostare i limiti minimi e max.

Utilizzando un solo timer sarebbe molto meglio ma credo sia possibile utilizzarlo a costo di un tempo di elaborazione più lungo e meccanismi più complessi. Almeno credo. Si ho visto quel link ma ho visto che il diagramma del fast PWM è diverso da quello del datasheet dell'atmega: varia OCxA (OCxB) anche quando il conteggio torna a zero; mi sembra scorretto.

EDIT: correggo c'ho che ho detto, il diagramma dell'atmega e quello del link sono coerenti.

federico

Ciao
Sono arrivato ad una soluzione accettabile 2 pwm hardware (pin 6 e 11) e un pwm software sul pin 13.
Per implementare il pwm software (LO HAI MAI REALIZZATO CON UN PIC? - Una semplice tecnica di PWM software - ElectroYou):
-ho impostato il timer 0 in modalità fast pwm canale A utilizzabile, contemporaneamente, sia per il pwm hardware sul pin 6 sia come contatore per il pwm software sul pin 13;
-ho impostato il timer 2 in modalità fast pwm canale A e l'ho abilitato a lanciare un interruzione ogni qual volta si verifica un evento di overflow (mentre realizza il pwm hardware sul pin 11)
-ho implementato la routine di risposta all'interruzione di overflow del timer 2 così:
-legge il registro contatore del timer 0 (TCNT0);
-lo confronta con il valore di dutty cicle desiderato per il pwm software-pin 13, se TCNT0 è minore del dutty cicle, il pin 13 viene resettato altrimenti settato.

byte dutty_cicle13;
byte COUNTER;

//routine che va in esecuzione con una frequenza pari a 
//16MHz/64/256=976.5625Hz che è anche la frequenza del 
//segnale pwm software (all'incirca)
ISR(TIMER2_OVF_vect)
{ 
  COUNTER=TCNT0;
  if(dutty_cicle13>COUNTER)
  { 
    //digitalWrite(13,HIGH);
    //porta il pin 13 di arduino (pin 5 della PORTA B dell'atmega)
    //alto lasciando gli altri invariati
    PORTB |=0x20;
  }
  else
  {
    //digitalWrite(13,LOW);
    //porta il pin 13 di arduino (pin 5 della PORTA B dell'atmega) 
    //basso lasciando invariati gli altri
    PORTB &=0xDF;
  }
}

void setup()
{
  //pinMode(6,OUTPUT);
  //imposta il pin 6 di arduino (pin 6 della PORTA D dell'atmega) 
  //come output lasciando gli altri invariati
  DDRD |=0x40;

  //pinMode(13,OUTPUT);
  //imposta il pin 13 di arduino (pin 5 della PORTA B dell'atmega) 
  //come output lasciando gli altri invariati
  DDRB |=0x20;

  //pinMode(11,OUTPUT);
  //imposta il pin 11 di arduino (pin 3 della PORTA B dell'atmega) 
  //come output lasciando gli altri invariati
  DDRB |=0x08;

  //imposta il timer 0 in modalità fast pwm sul pin 6 (non invertito)
  TCCR0A =0x83;
  //fattore di prescale 64
  TCCR0B =0x04;
  
  //abilita interruzioni globali
  SREG |=0x80;
  //abilita interuzzione di overflow del timer 2
  TIMSK2 |=0x01;
  
  //imposta il timer 2 in modalità fast pwm sul pin 11 (non invertito)
  TCCR2A=0x83;
  //fattore prescale 64 per il timer 2
  TCCR2B=0x04;
}

void loop()
{
  //imposta dutty cicle per il canale A del timer 2 (pin 11)
  OCR2A=50;

  //imposta dutty cicle per il canale A del timer 0 (pin 6)
  OCR0A=130;

  //imposta dutty cicle per il canale pwm software
  dutty_cicle13=0;
  delay(2000);

  OCR2A=130;
  OCR0A=255;
  dutty_cicle13=50;
  delay(2000);

  OCR2A=255;
  OCR0A=0;
  dutty_cicle13=130;
  delay(2000);

  OCR2A=0;
  OCR0A=50;
  dutty_cicle13=255;
  delay(2000);
}

in questa maniera il tempo impiegato per eseguire la routine è in pratica il tempo necessario ad eseguire il confronto tra TCNT0 e il dutty cicle ma non so quanti cilci macchina si abbisognano per un confronto di byte e quindi quanto possa influire sul segnale pwm sofrware. Ci vorrebbe un oscilloscopio XD.. Ovviamente più canali pwm software si vogliono implementare maggiore sarà il tempo "perso" nella routine. Un'altro limite di questa tecnica e che la frequenza del pwm software sul pin 13 è necessariamente la stessa del segnale pwm generato sul pin 11.
Eventuali miglioramenti sono ben accetti.

federico

Non ho neanch'io un oscilloscopio per cui non posso aiutarti.

Comunque ricorda che il valore di un registro TCNTx puoi impostarlo a mano, dando quindi un valore di partenza, che equivale a fare un check a metà dell'opera. Oppure puoi effettuare un confronto con un valore predefinito (il famoso registro OCRxA/B).
PS:
duty cicle, non dutty :stuck_out_tongue:

leo72:
Comunque ricorda che il valore di un registro TCNTx puoi impostarlo a mano, dando quindi un valore di partenza, che equivale a fare un check a metà dell'opera. Oppure puoi effettuare un confronto con un valore predefinito (il famoso registro OCRxA/B).

Si potrebbe essere una tecnica utile nel caso in cui non si usassero i timer contemporaneamente per più scopi. Di fatti si possono implementare tecniche diverse a seconda delle situazioni per esempio abilitare il timer 2 a lanciare un interruzione non solo quando va in overflow ma anche quando i valori dei registri TCNT2 e OCR2A/B coincidono, potendo così sfruttare la routine corrispondente per modificare ulteriormente il duty cilce dei canali pwm software, variando quindi la frequenza del segnale pwm software rispetto alla frequenza imposta dal timer 2.

leo72:
PS:
duty cicle, non dutty :stuck_out_tongue:

..dai l'importante è capirsi :blush:..
grazie comunque..

federico