Advarsel: Forfærdeligt langt indlæg, uha-uha-uha...Tusind tak. her er de links jeg har kigget på
http://winavr.scienceprog.com/comment/52
http://blog.roderickmann.org/2006/01/atmega32-pwm/
http://www.societyofrobots.com/member_tutorials/node/231
Den øverste kan jeg ikke helt greje om den er i c
De er alle 3 tutorials til en at mega men det er vel bare et spørgsmål om hvordan der addresseres

Sådan set er alle 3 i C, ikke assembler. =)
Jeg kan ikke selv afprøve noget kode, for jeg har ikke en servo-motor.
Men det, som er vigtigt, er at du finder ud af at regne frekvensen ud korrekt.
Derefter er det forholdsvis enkelt at sætte en timer op; så vidt jeg husker, valgte vi at have en 16-bit timer fri til PWM, hvilket vil sige at der er gode muligheder for rimelig præcise frekvenser.
Du skal altså have en rutine der regner frekvensen ud, når denne rutine får en eller anden form for input.
Du skal så også have en rutine, som starter PWM'en.
Hvis du kigger på
det 3. eksempel du sendte, vil du se den nederste kode-blok.
Han har skrevet koden til ATmega8, så hvis vi kigger i ATmega8's datablad, vil vi kunne finde ud af hvad det er han sætter op.
Det første han gør, er at slå PWM fra, mens det sættes op, det gør han her...
TCCR1A = 0;
Med andre ord: Han sætter disse bits til 0 i TCCR1A:
COM1A1, COM1A0, COM1B1, COM1B0, FOC1A, FOC1B, WGM11, WGM10
At han sætter alle COM1xx bit til 0, betyder at COMpare- (sammenlignings-) registrene slåes fra.
Jeg mener der er en bedre måde at gøre dette på, nemlig at sætte prescaleren til off (timer stopped), men det kommer lidt an på om man ønsker at timeren kører hele tiden.
Det næste han gør, er:
Dette sætter i hans tilfælde TOP-grænsen hvis man bruger Fast PWM (mode 14).
Han sætter så sin timer op til at køre Fast PWM:
TCCR1A = (1 << WGM11); /* WGM10 er her samtidig blevet sat til 0 */
TCCR1B = (1 << WGM13) | (1 << WGM12) | (1 << CS10);
Han sætter så samtidig CS10 til 1, og alle andre CSxx til 0, dvs...
CS12=0, CS11= 0, CS10=1: clk/1 (no prescaling)
Derefter sætter han PB1 og PB2 til at være outputs.
...Dette kan skrives lidt mere forståeligt:
DDRB |= (1 << DDB1) | (1 << DDB2);
(DDB1 betyder Data Direction for pin B1, DDB2 betyder så Data Direction for pin B2)
Efter det, enabler han COM1xx registrene:
TCCR1A |= 2 << 6;
TCCR1A |= 2 << 4;
Dette kunne skrives mere forståeligt sådan:
TCCR1A |= (1 << COM1A1) | (1 << COM1B1);
Så indstiller han sine Output Compare Registers...
OCR1A = ICR1 * 2 / 20;
OCR1B = ICR1 * 2 / 20;
Det ene outputben (OC1A) får så resultatet af OCR1A's indstilling i forhold til ICR1
Det andet outputben (OC2A) får så resultatet af OCR2A's indstilling i forhold til ICR1
Efter det, så lader han sit program gå i loop med en
-Timeren ordner resten.
Man kan lave en slags grafisk illustration på denne måde:
+------------------------------------------------+
^ ^ ICR (top)
OCR1A (40% 'duty cycle' af ICR)
Så resultatet af ovenstående illustration kunne være...
____________________ (on)
______________________________ (off)
Se, det du så ønsker er...
1: At kunne beregne hvilken duty-cycle du vil have, altså forholdet mellem 'tændt' og 'slukket'.
Man kunne fx. sige du giver et tal i procent, så ville formlen være....
OCRxx = (TOP * procent) / 100;
Eller hvis du hellere vil give en byte...
OCRxx = (TOP * byte) / 256;
(kan skrives om til)...
OCRxx = (TOP * byte) >> 8; /* at bitskifte til venstre 8 gange er det samme som at dividere med 256, bare hurtigere og kortere */
-Find eventuelt selv på flere.

2: At indstille Timer1. Prøv at se på databladet, side 85, "Sektion 12. 16-bit Timer/Counter1".
-Der står en hel masse som du ikke gider læse lige nu.
Bladrer vi frem til side 106, finder vi beskrivelse af TCCR1A. Her er et lille overblik over dens bits:
COM1A1, COM1A0, COM1B1, COM1B0, -, -, WGM11, WGM10
Godt at huske til om lidt.
TCCR1B bliver beskrevet på side 108. På denne side står også lidt omkring de forskellige modes. Det er lige det vi skal bruge.
Vi vil gerne have det ligesom ham gutten i den tutorial; vi sætter selv vores TOP-værdi, og vi bestemmer selv hvor vores PWM skal tænde og hvor den skal slukke.
Mode 14 ser ud til at være fin til det brug; det virker til at det er samme mode som han brugte i sin tutorial.
Så kigger vi på hvad vi skal indstille WGM tll. Det er i nabo-kolonnen til Mode. Der står WGM1[3:0] hvilket betyder at rækkefølgen er WGM13, WGM12, WGM11, WGM10.
Dvs. for at få mode 14, skal vi sætte , WGM13=1, WGM12=1, WGM11=1 og WGM10=0.
Godt. Vi skal også indstille ICR1 til at være top-værdi.
WGM11 og WGM10 indstilles i TCCR1A, mens WGM13 og WGM12 indstilles i TCCR1B (fjollet, men det kan vi ikke lave om på).
Vi skal også finde en passende prescaler værdi. Om du vil køre 8MHz eller 1MHz, det ved jeg ikke lige nu, men vi kan prøve at starte med 1MHz.
På side 109 finder vi en oversigt over prescaler værdierne:
CS12=0, CS11=0, CS10=1 giver en prescaler på clk/1, dette giver 8MHz.
CS12=0, CS11=1, CS10=0 giver en prescaler på cllk/8, dette giver 1MHz.
OK, så godt, så langt.

På side 107, ser vi en oversigt over hvad Timer1 kan gøre med OC1A og OC1B benene.
Jeg fortalte dig vidstnok noget tidligere, med at det ville være praktisk at sætte servoen på et OCxx-ben, desværre tror jeg at jeg fik overbevist dig til at bruge OC0x og ikke OC1x.
Det kan derfor være du 'bliver nødt til' at flytte servoen til fx. ben 7 som er OC1A, eller ben 8, som er OC1B.
Det kan være at servo-motoren går amok, når du så programmerer chippen, men det må den så gøre.
Altså på side 107 er der 2 tabeller. Den første er "non-PWM", den vil vi ikke kigge på.
Den anden tabel, "Table 12-3. Compare Output Mode, Fast PWM" viser noget vi kan bruge.
Vi er nok mest interesseret i at tænde OC1A først, derefter slukke den når vi rammer sammenlignings-værdien som står i OCR1A. Det ser ud til at COM1A[1:0] så skal være 11.
(Det gør ikke noget, hvis vi samtidig sætter COM1B, så kan vi flytte servo-motoren derover hvis vi har lyst).
Det bliver nogenlunde til følgende kode, som kunne ligge i PWM.c:
#define PWM_TOP_VALUE 65535 /* choose any value that suits you. */
void initPWM()
{
/* First we stop our timer: */
TCCR1B = 0; /* CS12=0, CS11=0, CS10=0: No clock source (Timer/Counter stopped). */
ICR1 = PWM_TOP_VALUE;
if(SERVO_ENABLED == GPIOR2)
{
TCCR1A = (1 << WGM11) | (1 << WGM10) | (1 << COM1A1) | (COM1A0) | (1 << COM1B1) | (1 << COM1B0);
}
TCCR1B = 0; /* safety, to ensure PWM is not started by accident during RESET */
if(SERVO_ENABLED == GPIOR2)
{
TCCR1B = (0 << CS12) | (1 << CS11) | (0 << CS10) | (1 << WGM13) | (1 << WGM12);
}
/* PWM is now running! */
DDRA |= (1 << DDA6) | (1 << DDA5); /* set both OC1A and OC1B to be output pins */
}
Et eller andet sted i programmet kan du så...
OCR1A = (PWM_TOP_VALUE * 17) / 100; /* 17% on, 83% off */
OCR1B = (PWM_TOP_VALUE * 61) / 100; /* 61% on, 39% off */
Dette kan skrives om til noget lidt mere fleksibel kode, som kunne ligge i PWM.c:
- (void)setDutyCyclePercentage(uint8_t aPercentage)
{
OCR1A = (PWM_TOP_VALUE * aPercentage) / 100;
}
-Men bemærk: Dette er sikkert slet ikke en præcis nok beregning.
Du vil sikkert gerne have nogle forud-definerede (forud-beregnede) 16-bit værdier, fx. én værdi til 'load' og én værdi til 'unload'.
Jeg ved dette indlæg ikke er udtømmende, men det skulle kunne give dig en nogenlunde pejling på hvordan PWM sættes op.
Hvis du har datablad på servoen, der fortæller dig noget om hvordan duty-cycle/frekvenserne beregnes, så kan det være til hjælp.
Det kan være, at det ikke er nok, kun at ændre OCR1A, men at du også er nødt til at kunne ændre ICR1 når du styrer servoen.