Hallo allerseits,
bei folgender Aufgabe stoße ich momentan an meine grenzen, oder sehe nur
wieder mal den Wald vor lauter Bäumen nicht:
ATmega328P mit 16 MHz (vorgegeben, nicht änderbar)
Mit Timer 0 müsste ich zwei unabhängige PWM-Signale erzeugen, allerdings
mit 25 kHz. Auf einen anderen Timer auszuweichen ist nicht möglich
(alles schon belegt)
Hardware-PWM wäre zwar super, aber mit 16MHz und den wenigen Prescalern
komme ich nicht in die Gegend von 25kHz
Derzeitige Lösung: Timer 0 läuft mit Prescaler 8 (also 2MHz), die 25kHz
erreiche ich ziemlich genau indem ich bis 80 zähle. Also je ein
Interrupt auf Output Compare A und B, im Interrupt setze/lösche ich den
Ausgang, und erhöhe OCR0A/B um die Low/High Zeit (also z.B. bei 20% Duty
um 16 bzw. 64)
Funktioniert eigentlich perfekt, nur in den "Randbereichen" mit sehr
kleiner Low- oder High-Zeit verhaspelt er sich. Ich gehe davon aus dass
sich der Timer selbst überholt, bzw. der Counter-Wert schon weiter
gestiegen ist als mein Inkrement des OCR.
Ursache ist mit ziemlicher Sicherheit eine (wenn auch geringe) last
anderer Interrupts, und ein paar Routinen (Software-SPI) die mit
gesperrten Interrupts laufen (wenn auch nur sehr kurz).
Vermutlich wird der OC-Int ausgelöst, ISR nicht gleich angesprungen,
TCNT zählt brav weiter, sobald er in die ISR kommt erhöhe ich zwar brav
den OCR-Wert, aber TCNT hat schon drüberweggezählt. bei einem PWM-Wert
von 5 und Prescaler 8 sind das 40 takte, die können schon mal
zusammenkommen...
Timer:Setup:
1 | TCCR0A = (0 << COM0A1) | (0 << COM0A0) | (0 << COM0B1) | (0 << COM0B0) | (0 << WGM01) | (0 << WGM00); // normal operation
|
2 | TCCR0B = (0 << FOC0A) | (0 << FOC0B) | (0 << WGM02) | (0 << CS02) | (1 << CS01) | (0 << CS00); // Prescaler 8
|
3 | OCR0A = 80; // PWM 1 = Fan#1
|
4 | OCR0B = 80; // PWM 2 = Fan#2
|
5 | TIMSK0 = (1 << OCIE0B) | (1 << OCIE0A) | (0 << TOIE0); // Output Compare Match A & B Interrupt Enable;
|
Eine der ISR sieht so aus (die zweite ganz analog)
1 | // Timer 0 compare match A handler (Fan #1)
|
2 | ISR(TIMER0_COMPA_vect)
|
3 | {
|
4 | uint8_t pwm = Fan1_PWM; // cache volatile
|
5 |
|
6 | if (pwm == 0) {
|
7 | PORTD &= ~_BV(6); // always off
|
8 | OCR0A += 80; // setup next event
|
9 | } else if (pwm >= 80) {
|
10 | PORTD |= _BV(6); // always on
|
11 | OCR0A += 80; // setup next event
|
12 | } else {
|
13 | if (PIND & _BV(6)) {
|
14 | // currently on => turn off
|
15 | PORTD &= ~_BV(6);
|
16 | OCR0A += (80 - pwm); // setup next event
|
17 | } else {
|
18 | // currently off => turn on
|
19 | PORTD |= _BV(6);
|
20 | OCR0A += pwm; // setup next event
|
21 | }
|
22 | }
|
23 | }
|
"Knistern" tuts so im bereich 10% / 90%, bei 5% ists ganz vorbei, da
zieht er "Ehrenrunden".
Eine pragmatische Lösung wäre, den Arbeitsbereich auf 20%..80% zu
beschränken, <20% als "ganz aus" und >80% als "ganz ein" zu behandeln,
wobei man in der Praxis vermutlich nicht mal einen Unterschied bemerken
würde.
Da ich notorisch neugierig und unzufrieden bin, hätte mich interessiert
obs auch eine saubere Lösung gäbe?
Eine Variante wie mir einfiele wäre statt OCR zu inkrementieren, OCR auf
TCNT+Offset zu setzen, der Preis dafür wäre eine nicht mehr stimmende
PWM-Frequenz.
Andere Ideen?
Wie gesagt, Timer 0 und 16 MHz sind gesetzt.
Danke, Michi