Jag vet inget annat sätt än att plöja datablad. Jag tycker atmels datablad är ganska tunga att läsa.
I just detta fallet så tror jag att du kan nöja dig med timern som den är och "bara" ändra på OCR1A och ICR1, även om jag vet att det är frustrerande att inte ha koll på vad som händer under skalet.
ATmega328 har tre timrar 0, 1 och 2. 0 och 2 är 8-bitars timrar, 1 är 16 bitar.
Fördelen med timrar är att man avlastar CPU. Arduino-sättet brukar ta ganska mycket CPU i anspråk.
Arduino använder själv timer0 till millis() och micros().
Jag valde timer1 för att det finns ganska mycket frihet med just den.
Timern är inställd på "Fast PWM", med top = ICR1. Det ställs in med registren TCCR1A och TCCR1B.
- Kod: Markera allt
TCCR1A &= ~(1 << WGM10);
TCCR1A |= (1 << WGM11);
//TCCR1B
TCCR1B |= (1 << WGM12);
TCCR1B |= (1 << WGM13);
TCCR1B |= (1 << CS10); //prescaler = 1
Här sker lite programmeringsmagi, båda register är 8-bitar och varje bit har en mening.
TCCR1A |= (1<<WGM11) är ett kortare sätt att skriva TCCR1A = TCCR1A | 0b00000010.
WGM11 är en lagrad konstant som är 1.
(1<<1) betyder att man tar skiftar en 1 ett steg åt vänster, binärt skulle man skriva 0b00000010
(1<<0) = 0b00000001
(1<<2) = 0b00000100
osv.
| är samma sak som en OR-grind om det är mer bekant.
Tanken är att man tar registret TCCR1A och låter alla bitar vara oförändrade förutom bit 1 som skall vara 1.
För att skriva en nolla så använder man "and" istället TCCR1A &= ~(1 << WGM10), ~ betyder invertera bitvis. WGM10 = 0.
Så (1<<WGM10) = 0b00000001, ~(1<<WGM10) = 0b11111110.
Vet inte om du blir klokare av detta, jag tycker att det är ett snyggt sätt att skriva för koden blir ganska lättläst.
Jag minns dock att jag tyckte att det var lite svårt att greppa i början, så jag lägger lite extra tid på att förklara.
WGM (Waveform generation mode) bitarna ställer in typ av vågform. Jag lyckas inte ladda upp bilder, stjäl en från internet.
I koden ser du att WGM10 "och-as" ner och WGM11, WGM12 och WGM13 "or-as" upp, det blir nr 14 i tabellen.
Fast PWM med TOP ICR1 betyder att timern räknar från 0 till ICR1 (ställbart 0-65535) och börjar sedan om.
Om den passerar OCR1A (eller OCR1B) kan ett interrupt ske. Interrupt måste konfigureras att släppas fram. Det sker med TIMSK1.
I koden:
- Kod: Markera allt
//TIMSK1
//Interruptkontroll för timer 1,
TIMSK1 |= (1 << OCIE1A);
TIMSK1 |= (1 << TOIE1);
TOIE1 = Timer1 overflow interrupt enable, det interruptet som sker vid ICR1.
OCIE1A =
Output Compare A Match Interrupt Enable (timer1).
Nu kommer interrupt ske vid dessa båda tillfällen.
När ett interrupt sker så kommer CPUn att i princip stanna mitt i vad den håller på med.
Den sparar ner lite värden på stacken så att den vet precis vad den höll på med när den avbröt och hoppar sedan till minnesadressen som interruptet pekar på.
I C-koden är det
- Kod: Markera allt
ISR(TIMER1_OVF_vect)
alternativt
- Kod: Markera allt
ISR(TIMER1_COMPA_vect) {
Då CPUn som sagt avbryter vad den gör så ska man alltid försöka hålla interrupt-rutinerna så korta som man någonsin kan.
En stor fördel (även om den inte är jätteviktig i detta fallet) med timrar är att timingen alltid blir rätt.
Gör man ett program med delayer så är det svårt att veta hur lång tid saker tar, t.ex. analogRead vi pratade om tidigare.
Att använda sig av millis() eller micros() är faktiskt ett sätt att använda timrarna fast indirekt (och med lite sämre tidnoggrannhet).
Det enda jag använt timern och interruptet är att styra den högfrekventa signalen på minus-sidan, plus-sidan styr jag likadant som tidigare.
Varje fas är plus i 9,9 ms, sedan dödtid på 0,1 ms, negativ i 9,9 ms, dödtid 0,1 ms.
Jag tycker 100 us är ganska generöst med dödtid, men det är bara att ändra på
- Kod: Markera allt
int ontime = 9900;
om du vill ha mer eller mindre.
En sak att notera är att i ifsatserna för minus-utgångarna så släcker man utgången när pulsen tar slut, men man rör inte utgången när pulsen börjar. Det är för att säkerställa så inte den negativa pulsen kan fortsätta.
Det går inte att ladda upp bilder verkar det som, jag får försöka förklara.
När den negativa pulsen ska ta slut så sätter man variablen fasxmaAktiv till 0. Då kommer interruptet
- Kod: Markera allt
ISR(TIMER1_OVF_vect) {
if (fas1mAktiv == 1)
digitalWrite(fas1m, HIGH);
if (fas2mAktiv == 1)
digitalWrite(fas2m, HIGH);
if (fas3mAktiv == 1)
digitalWrite(fas3m, HIGH);
}
Låta bli att sätta utången hög.
MEN
Om utgången blev satt hög precis innan actTime blev tillräckligt hög så kommer hela PWM-perioden att gå färdigt.
Med 4 kHz så är periodtiden 0,25 ms med en duty cycle på över 40% så kommer både fasXp och fasXm leda samtidigt.
Därför ser jag till att släcka utgången manuellt.
Jag har lagt fasXmAktiv = 0 innan digitalWrite(fasXm, LOW) för att det annars kan hända att interruptet sker mellan dessa två rader.
Mycket text blev det, hoppas något blir klarare.
Har du bra gate-drivare? Det är ganska enkelt att öka på frekvensen ännu mer om man vill.
Jag har funderat på om man skulle använda även timer2 och tror att jag skulle lyckas ändra duty-cycle under varje period om du vill ha mer sinus-form på strömmen.
Kan dessutom vara en bra idé att hålla nere komplexiteten på koden.
/Kristoffer
Vet dock inte om det ger något?