Vai al contenuto
PLC Forum


PWM con 16F876A - usando il modulo integrato...


Simons

Messaggi consigliati

Salve a tutti dovrei realizzare un piccolo PWM con il 16f876a e ho notato con gran gioia biggrin.gif che il pic in questione possiede già il modulo PWM integrato!

ho provato a settare i parametri per avere la frequenza 1khz (usando il CCS C COMPILER con il wizard) però ho visto che posso impostare frequenze tipo 1.250hz e simili...

ho visto che il pwm usa l'interrupt del timer2. Per avere una frequenza di 1khz, come posso impostare i vari parametri?

qualcuno mi dà una mano? D:

Link al commento
Condividi su altri siti


Ciao,

Per il pwm , hai due parametri da configurare .

Il periodo che ti da la frequenza del pwm

questo lo regoli con la formula

PWM Period = [(PR2) + 1] • 4 • TOSC • (TMR2 Prescale Value)

Poi devi regolare il duty cycle che in base alla risoluzione del tuo pwm sara

PWM Duty Cycle =(CCPR1L:CCP1CON<5:4>) •TOSC • (TMR2 Prescale Value)

Nota che il registro CCPR1L e i bit 4,5 del CCP1CON diventano un registro a 10 bit.

trovi tutto nel DATASHEET

Se hai dubbi chiedi.

Link al commento
Condividi su altri siti

  • 2 weeks later...

se usi il compilatore CCS avrai sicuramente delle funzioni predisposte

Io avevo le stesse cose nel C30 , ma poi alla fine ho preferito leggermi il datasheet ed i manuali reference e conoscere i registri , i bit ect e farmelo

Fai come ti consiglia DLGCOM

ciao

walter

Link al commento
Condividi su altri siti

vi ringrazio delle risposte...

ho controllato i datasheet del 16f876a e ha 2 moduli ccp però pilotabili entrambi contemporaneamente con la stessa frequenza.

io avrei bisogno di 4 uscite pwm (tutte alla stessa frequenza di 1-2Khz o comunque vicina) però che variano il duty.

suppongo che vada fatto tutto via software...

ora vi chiedo: per fare il pwm sfrutto l'interrupt dei vari timer (ad es il tmr0 o il tmr1 o tmr2 del pic oppure gestisco in altro modo?

utilizzando un quarzo a 20Mhz, la frequenza utile è 5mhz e l'interrupt con prescaler a 1 dovrebbe essere intorno ai 51 microsecondi. Come sfrutto questa cosa a mio vantaggio?

grazie 1000 a tutti smile.gif

Link al commento
Condividi su altri siti

ho provato a sviluppare una routine in CCS C per 3 servi usando l'interrupt del timer2 settato in modo che faccia l'interrupt ogni 10us così contando fino a 100 ho 1khz (giusto? ph34r.gif )

la routine è questa:

ditemi se si può migliorare

T1,T2,T3 sono i dutycycle

i vari txon e off sono i tempi relativi alle varie forme d'onda.

T1,T2 e T3 le imposto dal main e txon e off vengono assegnati da una routine CalcolaDuty che non fa altro che assegnare, ad es, a t1on=T1 e t1off=100-T1

TIMER2_isr()
{
//ogni 10 us fa un interrupt
   

   
   if(t1on>0)
   {
      output_high(SERVO1);
      t1on--;
   }
   else
   {
      if(t1off<=0)
      {
         t1on=T1;
         t1off=100-T1;
      }
      else
      {
         output_low(SERVO1);
         t1off--;
      }

   }
   
   if(t2on>0)
   {
      output_high(SERVO2);
      t2on--;
   }
   else
   {
      if(t2off<=0)
      {
         t2on=T2;
         t2off=100-T2;
      }
      else
      {
         output_low(SERVO2);
         t2off--;
      }
   }
   
   if(t3on>0)
   {
      output_high(SERVO3);
      t3on--;
   }
   else
   {
      if(t3off<=0)
      {
         t3on=T3;
         t3off=100-T3;
      }
      else
      {
         output_low(SERVO3);
         t3off--;
      }
   }
}

grazie 1000

Modificato: da Simons
Link al commento
Condividi su altri siti

Ho provato la routine che avevo scritto prima e funziona. non ho avuto modo di controllare la precisione della frequenza degli impulsi, stasera controllerò anche quella.

l'unico errore è questo:

in ognuna delle mini-routine per ogni servo, c'è da togliere la riga commentata in grassetto altrimenti quando raggiunge lo zero farà un microimpulso della durata dell'interrupt.

if(t1on>0)

{

output_high(SERVO1);

t1on--;

}

else

{

if(t1off<=0)

{

t1on=T1;

t1off=100-T1;

//output_high(SERVO1);

}

else

{

output_low(SERVO1);

t1off--;

}

}

per il resto ho fatto in modo di avere già su T1 il duty settando l'interrupt a 10us così se metto 50 su T1 la frequenza è 1khz con d.c. 50%

saluti smile.gif

Link al commento
Condividi su altri siti

mi sono accorto di un problema.

Siccome nella scheda che sto usando ci sono un display LCD e una tastiera a matrice, quando faccio l'interrupt a 10us col timer2, questo mi impegna tutte le risorse bloccando qualsiasi altra operazione (scrittura LCD, lettura tastiera ecc...)

ho aumentato l'interrupt a 100us però a discapito della precisione del duty cycle. adesso ho 10 passi di regolazione invece che 100. per averne 100 ho provato a lavorare con i float, ma il programma diventa pesante e non gira come dovrebbe.

avete qualche suggerimento utile?

grazie 1000

Link al commento
Condividi su altri siti

ho effettuato le misure con un oscilloscopio dell'onda risultante con l'interrupt a 100us.

la frequenza effettiva è intorno agli 880 hertz e il duty se imposto la variabile a 50 è sempre 40% .. per il problema del duty credo sia perchè fa il conteggio una volta in meno per t1on

per la frequenza sballata: è possibile che con un interrupt a 100us (così mi dice il wizard di CCS compiler) abbia così tanta differenza da 1khz?

qualcuno mi aiuta please?

Link al commento
Condividi su altri siti

Che cosa usi per generare il clock? Quarzo? Risonatore? RC?

Come hai programmato il timer?

Se hai sbaglaito programmazione del timer è normale che succeda il fenomeno che descrivi. Io solitamente lavoro con quarzo a 20mHz e ti grantisco che non ho mai avuto errori sui timer, a meno di errori di programamzione.

Link al commento
Condividi su altri siti

Livio Orsini+29/09/2007, 16:57--> (Livio Orsini @ 29/09/2007, 16:57)

grazie della risposta smile.gif

uso un quarzo a 20Mhz

l'interrupt lo prendo dal timer2 settato con risoluzione 0.2us overflow=50 interrupt period=10 e su ccs compiler veniva scritto "100us"

può dipendere dal fatto che il pic ci mette troppo a fare le istruzioni nella routine di interrupt?

edit: una soluzione alternativa: mettendo la risoluzione del timer0 a 0.4us viene fuori un interrupt di 102.4us se nella routine di interrupt imposto il timer0 a 6 ogni volta fa 6 conti in meno ovvero 6*0.4=2.4us no? c'è la funzione set_timer0(6) che setta il timer a 6.

se riesci ad aiutarmi questo problema te ne sarò molto grato smile.gif

Modificato: da Simons
Link al commento
Condividi su altri siti

// 20 mhz clock, no prescaler, set timer 0

// to overflow in 35us



set_timer0(81);        // 256-(.000035/(4/20000000))

Questo è un esempio trato dal manuale CCS, genera un interrupt ogni 35us. Il timer conta gli impulsi per arrivare a 256 e generare un interrpt, poi riparte da 0 quindi devi ricaricarlo. Nel tuo caso, dovendo contare 500 impulsi e, se ricordo bene, timer2 ha solo un registro da 8 bits, devi fare due conteggi: uno da 256 impulsi e uno da 244 impulsi; quindi carichi 12, attendi interrupt senza ricaricare e sul secondo interrupt ricarichi 12, ottieni 100us.

Attenzione che ogni istruzione asm impiega 0.2us quindi tra un interrupt e l'altro da 100us puoi eseguire complessivamente <500 istruzioni. Io uso asm nelle routines di interupts dei timer, proprio per minimizzare l'impiego di CPU

Link al commento
Condividi su altri siti

Livio Orsini+30/09/2007, 11:57--> (Livio Orsini @ 30/09/2007, 11:57)

grazie.. però pensavo.. l'interrupt che mi serve è 50us e non 100us altrimenti non riesco a generare 1kh ma 500hz

quindi

con il timer settato a 0.2us

se nella routine di interrupt imposto il timer0 a 6 ogni volta fa 6 conti in meno ovvero 6*0.4=1.2us no? quindi 51.2-1.2=50us di interrupt.

in questo modo riesco a suddividere l'onda quadra in 20 "parti" così da avere un duty variabile del 5% anzichè del 10%.

rimane però il problema della durata dell'interrupt. se le istruzioni eccedono il tempo dell'interrupt cosa succede?

Link al commento
Condividi su altri siti

Se imposti timer2 a 6 (tanto per usare il tuo timer) questo conterà 250 impulsi (250+6 = 256), poi genererà l'interrupt di overflow. In questo Interrupt dovrai ricaricare 6, in modo da avere sempre 50us di intervallo (250*0.2us = 50us).

Due consigli.

Primo. leggiti bene il manuale del 16F877 al capitolo dei timer.

Secondo. Nella routine di interrupt, alla prima riga, scrivi #ASM e prosegui con istruzioni in assembler, poi termina con #endasm el'ultima grafa, così sei sicuro di aver minimizzato le istruzioni. Ricorda che tra due interrupt da 50us di intervallo il micro eseguirà solo 250 istruzioni in tutto.

Link al commento
Condividi su altri siti

Livio Orsini+1/10/2007, 12:41--> (Livio Orsini @ 1/10/2007, 12:41)

dunque.. ho cambiato timer. sto usando il timer0...

allora...

inserendo nella routine di servizio l'istruzione set_rtcc(6); il periodo dell'interrupt viene modificato.

addirittura seguendo le righe di codice del manuale CCS l'interrupt non è a 35us ma a 43 e rotti.

settando il timer con RTCC_INTERNAL e RTCC_DIV_1

nell'interrupt eseguo queste semplici operazioni giusto per fare un test:

#int_RTCC

RTCC_isr()

{

set_rtcc(6);

output_high(SERVO2);

output_low(SERVO2);

}

se tolgo set_rtcc(6); l'interrupt è preciso spaccato a 51.2 us

se invece metto set_rtcc(6); l'interrupt si allarga a 57us, quasi come se prima facesse l'interrupt e accodasse ai 256 conteggi altri 6 conteggi.

ho scoperto dal manuale che modificando manualmente il valore del rtcc, il prescaler si resetta. ma non capisco allora cosa ci sta a fare la funzione set_rtcc();

chiedo lumi...

ho i fuses settati così:

#device adc=8

#use delay(clock=20000000)

#fuses NOWDT,HS, NOPUT, NOPROTECT, NODEBUG, BROWNOUT, NOLVP, NOCPD, NOWRT

cosa sbaglio? senzasperanza.gif

non riesco a capire sad.gif

Link al commento
Condividi su altri siti


.....
         setup_counters(RTCC_INTERNAL,RTCC_DIV_4);
         setup_timer_1(T1_INTERNAL);
         set_timer1(0x3caf);   //Caricato 65.535 - 50.000 = 15.535(0x3CAF)
                          //==> 50.000 * 4 * 50nsec = 10msec
......
#int_timer1
void Timer10ms()
    {
            set_timer1(0x3caf);   // Caricato 15535 ==>
                  // 50.000 * 4 * 50nsec = 10msec
                  // 65535 - 50000 = 15535(0x3caf)
....

Questo è il codice di inizializzazione e l'incipit di una routine di interrutpt a tempo che uso io da sempre. Il compilatore è CCS, il quarzo è da 20MHz. Ti garantisco che non ci sono variazioni di tempo apprezzabili. Usando altri timer devi verificare se sono da 8 o da 16 bit.

Link al commento
Condividi su altri siti

mi spiace rompere ancora con questo cavolo di timer...

ho usato la tua routine e l'interrupt lo fa preciso a 10msec

ho fatto un paio di test dimezzando via via il tempo e sono arrivato a 250microsecondi netti.

ho provato a farlo a 100microsecondi settando il timer a:

65535-(100us/(50ns*4))=65035 (FE0B)

con risultato un interrupt a 108microsecondi. ho provato a riportarlo a 500microsecondi e ho ottenuto 508microsecondi. portato a 200 e ho 208microsecondi...

questa la mia routine:

#int_TIMER1

TIMER1_isr()

{

set_timer1(0xFE0B); //Setto il timer

output_high(PIN_A2); //alzo la linea

output_low(PIN_A2); //la abbasso subito

}

non so più dove sbattere la testa... sad.gif sto cominciando a dare di fuori...

vi farò un monumento se risolvo il problema

Link al commento
Condividi su altri siti

Ma tu stai programmando il timer a 400.8 us.

FE0B ==> 65035 ==> 65536 - 65035 = 501 ==> 501*4*200ns = 400.8us

Modificato: da Livio Orsini
Link al commento
Condividi su altri siti

Livio Orsini+9/10/2007, 11:56--> (Livio Orsini @ 9/10/2007, 11:56)

non capisco il conto che hai fatto...

io ho fatto 65535-65035=500

e secondo il conto che hai postato prima: //==> 50.000 * 4 * 50nsec = 10msec

verrebbe 500*4*50nsec = 100us

unsure.gif

Link al commento
Condividi su altri siti

Hai ragione, ho fatto un po' di confusione con il clock, moltiplicando 2 volte per 4 ecomplementando a 0 e non a FFFF; si vede che questa mattina pensavo a chissà che cosa.

Gli 8us in più sembrano 40 istruzioni, però il timer lo ricarichi subito....

Prova, come verifica, a scrivere

#int_TIMER1

TIMER1_isr()

{

set_timer1(0xFE0B); //Setto il timer

if( bit_test(x,y) )

{ //x==porta usata per test, y== bit usato per test

bit_clear(x,y); //abbasso l'uscita

}

else

{

bit_set(x,y); //alzo l'uscita

}

}

Così realizzi un flip-flop con semiperiodo 500us

Per cusiosità come misuri il tempo?

Link al commento
Condividi su altri siti

Livio Orsini+9/10/2007, 16:00--> (Livio Orsini @ 9/10/2007, 16:00)

il tempo lo misuro con un oscilloscopio digitale HP 100Mhz biggrin.gif direi piuttosto preciso perchè riesce a vedere la sovraelongazione di qualche nanosecondo che c'è al momento in cui il pin va alto o basso biggrin.gif

Proverò come mi hai detto e ti farò sapere...

ti ringrazio tantissimo del tempo dedicatomi

Link al commento
Condividi su altri siti

Curiosone, pensa al nuovo lavoro piuttosto biggrin.gif

Come va? Spero bene visto che hai ancora il tempo di curiosare sul forum di elettronica.

Link al commento
Condividi su altri siti

Crea un account o accedi per commentare

Devi essere un utente per poter lasciare un commento

Crea un account

Registrati per un nuovo account nella nostra comunità. è facile!

Registra un nuovo account

Accedi

Hai già un account? Accedi qui.

Accedi ora
×
×
  • Crea nuovo/a...