Vai al contenuto
PLC Forum


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


Messaggi consigliati

Inserito:

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:


Inserita:

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.

  • 2 weeks later...
Inserita:

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

Inserita:

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

Inserita:

utilizzo CCS C Compiler (PCW)

non è che potreste postare qualche routine di esempio per più di 2 pwm?

grazie 1000

Inserita: (modificato)

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
Inserita:

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

Inserita:

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

Inserita:

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?

Inserita:

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.

Inserita: (modificato)

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
Inserita:

// 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

Inserita:
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?

Inserita:

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.

Inserita:
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

Inserita:

ho provato a sostituire le funzioni setup_rtcc() e le altre con le corrispettive in assembler, ma non è cambiato nulla...

suggerimenti?

Inserita:


.....
         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.

Inserita:

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

Inserita: (modificato)

Ma tu stai programmando il timer a 400.8 us.

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

Modificato: da Livio Orsini
Inserita:
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

Inserita:

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?

Inserita:
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

Inserita:

Curiosone, pensa al nuovo lavoro piuttosto biggrin.gif

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

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...