Verwenden Sie PWM und ISR gleichzeitig auf dem AVR

Ist es möglich, AVR-PWM-Ausgänge und ISR-Interrupts gleichzeitig zu verwenden? Ich habe ein Projekt, das ich auf einem ATMega328P durchführen möchte, und ich brauche 3 PWM-Ausgänge, aber ich muss AUCH in der Lage sein, ISR-Interrupts von zwei verschiedenen Timern zu verwenden, um andere Multiplex- und Tastenhandhabungen durchzuführen (oh ja, muss auch verwenden Sie die externen Interrupts INT0 und INT1).

Gibt es eine Möglichkeit, beides zu tun?

Update zur Klarstellung: Hier ist das vollständige Setup. Ich habe 3 RGB-LEDs, für die ich PWM für jeden ihrer Kanäle benötige. Diese PWM kann mit der gleichen Frequenz laufen, benötigt aber unabhängige Arbeitszyklen für jeden Kanal, damit ich jede Farbe erzeugen kann, die ich brauche. Da der ATMega328P keine 9 unabhängigen PWMs hat, muss ich ihn fälschen. Also war mein Plan, die PWM zu multiplexen. Stellen Sie im Grunde PWM für RGB1 ein, schalten Sie auf RGB2 um und dann RGB3 alle auf> 400 Hz. Auf diese Weise brauche ich nur 3 PWM-Kanäle auf dem Chip und kann einfach umschalten, welche LED geerdet wird (sie sind gemeinsame Kathode). Also brauche ich einen ISR-Interrupt, um das Multiplexen selbst zu handhaben, und dann verwende ich normalerweise einen ISR-Interrupt mit niedrigerer Frequenz (~ 100 Hz), um Tastendrücke zu handhaben (im Grunde meine Art, Entprellen zu machen, ich finde es ziemlich effektiv). Wie Sie sehen können, brauche ich also 3 PWM-Kanäle und 2 ISRs.

Ja, ganz einfach, aber die Effektivität hängt alles davon ab, mit welchen Geschwindigkeiten Sie jedes davon arbeiten müssen ... weitere Details darüber, was Sie versuchen zu tun?
Genauer gesagt, welche Frequenz (oder welcher Frequenzbereich) muss jeder der PWM-Ausgänge haben? Welche Frequenz wollten Sie mit den ISRs für die Tastenhandhabung verwenden?
Siehe Aktualisierung des ursprünglichen Beitrags.

Antworten (2)

Es ist ziemlich einfach, eigene PWM-Kanäle in der Software zu erstellen, insbesondere wenn sie nicht sehr schnell sind - wie das, was Sie versuchen. Denken Sie beim Herumspielen mit den Timern daran, dass dies wirklich nur ein von der CPU-Uhr herunterskalierter Zähler ist, der zurückgesetzt wird, wenn er einen bestimmten TOP- Wert erreicht . Sie können einen einzigen Timer verwenden, um viele verschiedene Dinge zu tun.

Timer-Übersicht

Der ATmega328 hat drei Timer - zwei 8-Bit- und einen 16-Bit-Timer. Der einzige Grund, den 16-Bit-Timer zu verwenden, ist, wenn Sie die zusätzliche Auflösung benötigen - genauer auf Ihre idealen Frequenz- und Arbeitszykluswerte. Der 8-Bit-Timer 0 verbraucht im eingeschalteten Zustand am wenigsten Strom, daher empfehle ich, ihn so oft wie möglich zu verwenden. Jeder Timer verfügt über unabhängige Interrupt-Quellen und dedizierte Ausgänge, die so eingestellt werden können, dass sie zu bestimmten Zeiten automatisch auf LO oder HI gehen, wodurch eine automatische Hardware-PWM erzeugt wird. Alles, was Sie tun müssen, ist, die Arbeitszykluszeiten zu aktualisieren, und die Hardware erledigt den Rest für Sie. Sie können dies jedoch einfach in der Software tun, um viel mehr PWM-Kanäle zu erstellen.

Hardware-Überlegungen

Zu Beginn gehe ich davon aus, dass Sie Folgendes versuchen:

RGB-Multiplexing

Es gibt drei RGB-LEDs. Eine einzelne Leitung steuert jede der drei internen LEDs, und diese Leitung wird von jeder LED mit der gleichen Farbe geteilt. Eine weitere Steuerleitung ist für die Erdung dieser gemeinsamen Kathoden-LEDs verantwortlich, insgesamt sechs Steuerleitungen. Es leuchtet jeweils nur eine RGB-LED, aber die Bodensteuerleitungen werden schnell genug gemischt, dass es niemandem auffällt. Ich habe eine "Black Box" als Bodensteuerung aufgenommen, weil ich nicht weiß, wie Sie den LED-Strom senken. Wenn Sie sich für Option 1 entscheiden, geht ein I / O-Pin auf LO, um den Strom für eine LED zu senken. Denken Sie dabei daran, dass der gesamte I/O-Pin-Strom 40 mA beträgt, sodass jede interne LED nur (40/3) = 13 mA verwenden kann. Option 2 überwindet dieses Problem, indem ein Transistor verwendet wird, um den Strom zu senken (ein BJT mit einem Basiswiderstand funktioniert ebenfalls). Dadurch wird jedoch die Steuerung von aktiv LO auf aktiv HI umgeschaltet. Ansteuern der Basis / des Gates, um den LED-Strom durch den Emitter / die Quelle zu senken. An jeder Steuerleitung kann ein einzelner Widerstand verwendet werden, um den LED-Strom zu begrenzen, da nur eine der drei angeschlossenen LEDs gleichzeitig eingeschaltet ist.

Beachten Sie, dass beim Multiplexen auf diese Weise jede LED maximal 33,33 % der Zeit eingeschaltet ist. Die Helligkeit jeder LED ist geringer als erwartet, und die RGB-Farbe wird ebenfalls beeinflusst.

PWM-Überlegungen

Ich kann Ihnen nicht genau sagen, was Sie tun sollen, weil Sie nicht gesagt haben, wie hoch Ihre Systemtaktrate war. Ich weiß, für den Arduino sind es 16 MHz. AVR-Chips verwenden jedoch standardmäßig den internen 8-MHz-Oszillator, der auf 1 MHz herunterskaliert ist. Dieser Systemtakt-Vorteiler kann auch in der Software geändert werden. Dies wird im Datenblatt unter Abschnitt 9.11 „System Clock Prescaler“ besprochen.

Sie müssen sich um zwei Frequenzen kümmern: die RGB-Farbschaltfrequenz (wie schnell jede einzelne LED gepulst wird) und die RGB-Ground-Control-Schaltfrequenz (wie schnell die Steuerung von einer RGB-LED zur nächsten umgeschaltet wird). Eine dieser Frequenzen sollte um einiges höher sein als die andere. Ich würde eine schnellere Pulsfrequenz vorschlagen.

Zum Beispiel: Das Schalten der LED-Steuerung alle 2 ms bedeutet eine Gesamtsteuerungsschaltperiode von 6 ms, wodurch eine Frequenz von 167 Hz erzeugt wird. Die Impulsfrequenz muss innerhalb der 2 ms erfolgen, in denen jede LED aktiviert ist. Wie viele in diese 2 ms passen, ist Ihnen überlassen, aber ich würde sagen mindestens 5. Daher sollte eine Pulsfrequenz von 2,5 kHz verwendet werden (5 / 2 ms = 2,5 kHz). Auf diese Weise durchläuft jede LED während der 2 ms, die sie durch die Massesteuerleitung aktiviert ist, 5 volle Impulszyklen. Sie können mit diesen Zahlen spielen, um zu sehen, was passiert ...

Softwaresteuerung

Sobald Sie herausgefunden haben, wie schnell alles wirklich sein muss, ist die Softwaresteuerung relativ einfach, aber es gibt ein paar verschiedene Möglichkeiten, dies zu tun.

Option 1

Sie könnten einen einzelnen Timer im CTC-Modus mit dem TOP-Wert einrichten, der durch Vergleichsvergleich A festgelegt wurde, um die schnellere Impulsfrequenz zu erzeugen. In unserem Beispiel sollten dies 2,5 kHz sein. Jedes Mal, wenn diese ISR ausgelöst wird, sollten die LED-Farbregler alle auf HI eingestellt sein. Eine Variable in dieser ISR könnte auch zählen, wie oft sie ausgelöst wird. Schalten Sie beim fünften Mal die Steuerung von einer LED zur nächsten und setzen Sie den ISR-Zähler zurück. Dies erzeugt einen 2-ms-Timer innerhalb des 2,5-kHz-ISR.

Wenn der Vergleichsvergleich A so eingestellt ist, dass er zu Beginn jedes LED-Impulszyklus auslöst, verwenden Sie den Vergleichsvergleich B, um zu triggern, wenn eine LED ausgeschaltet werden soll (die Farbsteuerleitung wird gelöscht). Dieser Wert müsste innerhalb der Vergleichsanpassung B ISR für die nächste LED, die ausgeschaltet werden muss, aktualisiert werden. Die "Auszeit" kann in main aktualisiert werden.

Ich habe das viele Male gemacht und weiß, dass es gut funktioniert, aber es wird einige Überlegungen erfordern, wie man weiß, welche Farb-LED abgeschaltet werden soll, und wie man den nächsten Vergleichswert B einstellt. ISR B sollte jedes Mal, wenn ISR A auslöst, dreimal auslösen (einmal für jede Farbsteuerleitung), um einen neuen PWM-Zyklus zu starten. Ich lasse Sie diese Details herausfinden ...

Pseudocode:

// Every 1 ISR iteration is start of new PWM cycle
// Every 5 ISR iterations is time to switch control to next RGB LED
ISR_A{

  // Time to switch control from one RGB LED to the next
  if(++cycle_count == 5){
  TURN_OFF(ctrl_R | ctrl_G | ctrl_B); // make sure the previous LED is OFF
  cycle_cnt = 0;                      // reset count

  // Switch from LED 1 to LED 2
  if(ctrl_gnd1){
    TURN_OFF(ctrl_gnd1);
    TURN_ON(ctrl_gnd2);
    R_LED_time = R_LED2_ON;
    G_LED_time = G_LED2_ON;
    B_LED_time = B_LED2_ON;
  }
  // Switch from LED 2 to LED 3
  else if(ctrl_gnd2){
    TURN_OFF(ctrl_gnd2);
    TURN_ON(ctrl_gnd3);
    R_LED_time = R_LED3_ON;
    G_LED_time = G_LED3_ON;
    B_LED_time = B_LED3_ON;
  }
  // Switch from LED 3 to LED 1
  else{
    TURN_OFF(ctrl_gnd3);
    TURN_ON(ctrl_gnd1);
    R_LED_time = R_LED1_ON;
    G_LED_time = G_LED1_ON;
    B_LED_time = B_LED1_ON;
  }
  // This is the start of a new PWM cycle, turn on the color control lines
  TURN_ON(ctrl_R | ctrl_G | ctrl_B);
}

// This ISR will trigger when the next LED color control line should be turned off
ISR_B{

// ISR Trigger at RED LED duty cycle
if(COMPARE_MATCH_B = R_LED_time)
  TURN_OFF(ctrl_R);

// ISR Trigger at Green LED duty cycle
if(COMPARE_MATCH_B = G_LED_time)
  TURN_OFF(ctrl_G);

// ISR Trigger at Blue LED duty cycle
if(COMPARE_MATCH_B = B_LED_time)
  TURN_OFF(ctrl_B);

// Need to compare color control times to determine when the next ISR_B should trigger.
// ... Your Algorithm here...
}

Option 2

Dies ist möglicherweise die einfachere Option, erfordert jedoch mehr Zeit zum Arbeiten, was bedeutet, dass die Systemuhr schneller laufen muss ... Nachdem Sie wissen, wie schnell der LED-Impuls sein muss, bestimmen Sie, welche Auflösung der Farbsteuerung Sie wünschen. Das heißt, um die Helligkeit einer einzelnen LED zu ändern, erhöhen Sie ihr Tastverhältnis um 0,5 %, 1 %, 10 % usw. Der Einfachheit halber verwende ich eine Auflösung von 1 %. Das bedeutet, dass Sie den Timer (im CTC-Modus) auf das 100-fache der Pulsfrequenz einstellen müssen ...

Diese ISR wird jetzt bei 250 kHz ausgelöst. Sie werden einen variablen Zähler darin einfügen, der die ISR-Trigger zählt. Bei 0 (oder 100, wie auch immer Sie es möchten) ist das der Beginn / das Ende des PWM-Zyklus, also setzen Sie alle Farbsteuerleitungen auf HI. Wenn dieser Zähler eine bestimmte Zahl erreicht, löschen Sie die Farbkontrolllinie mit diesem bestimmten Arbeitszyklus. Es sollten 3 Sätze von Zeitwerten verwendet werden, wobei der verwendete bestimmt wird, durch welchen RGB gesteuert wird. Diese Steuerung schaltet alle 100 * 5 ISR-Trigger um, da diese ISR 100-mal häufiger auftritt als die von Option 1.

Pseudocode:

// Every 100 ISR iterations equals 1 PWM cycle 
// Every 500 ISR iterations is time to switch control to next RGB LED
ISR_A{

  // Time to switch control from one RGB LED to the next
  if(++cycle_count == 500){
    TURN_OFF(ctrl_R | ctrl_G | ctrl_B); // make sure the previous LED is OFF
    cycle_cnt = 0;                      // reset count

    // Switch from LED 1 to LED 2
    if(ctrl_gnd1){
      TURN_OFF(ctrl_gnd1);
      TURN_ON(ctrl_gnd2);
      R_LED_time = R_LED2_ON;
      G_LED_time = G_LED2_ON;
      B_LED_time = B_LED2_ON;
      break;
    }
    // Switch from LED 2 to LED 3
    else if(ctrl_gnd2){
      TURN_OFF(ctrl_gnd2);
      TURN_ON(ctrl_gnd3);
      R_LED_time = R_LED3_ON;
      G_LED_time = G_LED3_ON;
      B_LED_time = B_LED3_ON;
      break;
    }
    // Switch from LED 3 to LED 1
    else{
      TURN_OFF(ctrl_gnd3);
      TURN_ON(ctrl_gnd1);
      R_LED_time = R_LED1_ON;
      G_LED_time = G_LED1_ON;
      B_LED_time = B_LED1_ON;
      break;
    }
  }

  // Time to turn OFF the red LED control line
  if(++pulse_count == R_LED_time)
    TURN_OFF(ctrl_R);

  // Time to turn OFF the green LED control line
  if(pulse_count == G_LED_time)
    TURN_OFF(ctrl_G);

  // Time to turn OFF the blue LED control line    
  if(pulse_count == B_LED_time)
    TURN_OFF(ctrl_B);

  // This is the start of a new PWM cycle, turn on the color control lines
  if(pulse_cnt == 100){
    TURN_ON(ctrl_R | ctrl_G | ctrl_B);
    pulse_cnt = 0;
  }
}

Das Knifflige an dieser Methode ist das ISR-Timing im Vergleich zur Systemuhr. Bei 250 kHz mit einem Systemtakt von 16 MHz liegen zwischen jedem ISR-Trigger nur 64 Systemtaktzyklen. Der Code muss rechtzeitig ausgeführt werden, sonst funktioniert er nicht richtig, ohne die Impulsfrequenz oder die Arbeitszyklusauflösung erheblich zu reduzieren. Deshalb ist Option 1 besser.

Möglichkeit 3

Wenn Ihnen all die verschiedenen Zählvariablen verwirrend erscheinen, könnten Sie etwas ganz anderes ausprobieren. Verwenden Sie den 16-Bit-Timer 1, der 4 Interrupt-Möglichkeiten hat: Overflow, OCR1A, OCR1B und Input Capture ICR1. Wenn sich der Timer im normalen Modus befindet, verwenden Sie den größten verfügbaren Vorteiler (1024), um die Systemtaktfrequenz so niedrig wie möglich zu senken - dies würde 15,6 kHz aus einem 16-MHz-Systemtakt oder etwa 1 kHz aus einem 1-MHz-Systemtakt machen. Der Überlauf ISR ist der Beginn des PWM-Zyklus. Verwenden Sie dies, um alle Farbsteuerleitungen auf HI zu setzen, und zählen Sie diese ISR-Zyklen, um die Steuerung von der LED zur nächsten umzuschalten. Verwenden Sie dann jede der verbleibenden drei ISR-Quellen als Arbeitszyklus für die farbigen LEDs: OCR1A für Strg-R, OCR1B für Strg-G und ICR1 für Strg-B. Diese Zeitwerte können sich jederzeit ändern (im Haupt- oder einem anderen ISR) und werden im Normalmodus sofort aktualisiert.

Dies sind nicht die einzigen (oder besten) Möglichkeiten, dies zu tun, aber sie sind genau das, was ich in der Vergangenheit erfolgreich angewendet habe. Was auch immer Sie tun, dies ist die LED-Antwort, die Sie erwarten sollten:

RGB-Multiplexsignale

Beachten Sie, dass jede LED ihren eigenen Arbeitszyklus hat, aber alle mit der gleichen Frequenz arbeiten. Jedes RGB ist nur 1/3 der Gesamtzeit aktiviert. Sie können auch Ihre Tasten innerhalb des Impuls-ISR überprüfen. Verwenden Sie einfach einen anderen variablen Zähler, um einen 10-ms-Timer (Sie erwähnten 100 Hz) innerhalb von ISR A zu erstellen, ähnlich dem Timer, der in den Multiplex-Bodensteuerleitungen verwendet wird.

Deine Frage macht wenig Sinn. Das ist ungefähr so, als würde ich sagen, kann mein Nachbar seinen Rasen wässern und ich gleichzeitig meine Hose anziehen? Es hängt davon ab, ob.

328 hat 3 Timer: 0, 1, 2. Wenn Ihre 3 PWMs alle von derselben Zeitbasis stammen, dh dieselbe Frequenz, aber unterschiedliche Arbeitszyklen, dann ja. Sie würden einen Timer verwenden, um die 3 PWMs zu generieren, und die anderen 2 würden Überlauf-ISRs generieren.

INT0, INT1 können unabhängig von den Timern verwendet werden, daher sollten diese verwendbar sein, wenn diese Pins frei sind.

Ich dachte, dass jeder der 3 Timer 2 zugeordnete PWM-Ausgänge hat? Ist es tatsächlich möglich, 3 PWM-Ausgänge von nur einem der Hardware-Timer zu machen? Das wäre perfekt, da es die anderen 2 Timer für was auch immer frei lassen würde.
@AdamHaile Ich vermute, der Vorschlag hier ist die Verwendung von Software-PWM. Hardware-PWM hat die Beschränkung auf zwei PWM-Kanäle, die an bestimmte Pins gebunden sind und auf jedem Timer-Kanal mit derselben Frequenz laufen.
Ok... das dachte ich mir. Aber kann ich immer noch die PWM von Timer1 verwenden, während ich auch ISR-Interrupts verwende? Ich kann wahrscheinlich damit arbeiten, dass beide mit der gleichen Frequenz laufen: P
Sie können PWM so konfigurieren, dass es umschaltet, wenn der Zähler einen Vergleichswert erreicht. Dies steuert Ihren Arbeitszyklus. Der „TOP“-Wert und Zählerpreskalar steuert Ihre Frequenz. Sie können aktivieren, dass ein Überlauf-Interrupt ausgelöst wird, wenn Ihr Zähler „TOP“ erreicht. Daher können Sie Überlauf-ISRs verwenden, wenn die gewünschte Periode gleich 1 / (pwm-Frequenz) ist.