Wechselstromquelle (Teil 4)

Elektronik AC DAC PWM

Heute schauen wir uns eine dritte Methode an, um eine Sinusschwingung mit dem Arduino zu erzeugen: wir verwenden einen PWM-Ausgang als DAC.

PWM-DAC

Im letzten Teil dieses Projekts haben wir uns mit RC-Filtern befasst und damit, wie sie verwendet werden können, um bestimmte Frequenzkomponenten aus einem Signal zu entfernen. Wir haben einen Tiefpassfilter verwendet, um die Harmonischen höherer Ordnung einer Rechteckschwingung zu entfernen und sie in eine Sinusschwingung umzuwandeln. Sagen wir, wir hatten damit nur begrenzten Erfolg.

Dieses Mal versuchen wir einen etwas anderen Ansatz. Wenn wir eine Rechteckschwingung mit einem Arduino erzeugen, hat sie nicht nur höherfrequente Anteile, sondern auch einen Gleichspannungsanteil. Im letzten Teil dieses Projekts habe ich nichts über diesen Gleichspannungsanteil geschrieben. Der Gleichspannungsanteil ist immer die mittlere Spannung des Signals. Im Falle einer Rechteckschwingung zwischen 0 V und 5 V ist der Mittelwert genau 2,5 V. Alle diese Spannungen sind in Bezug auf die Masse des Arduinos angegeben. Bezogen auf die Masse unseres Wechselstromkreises entspricht dies 0 V und deshalb konnten wir beim letzten Mal den Gleichspannungsanteil ignorieren. Heute interessiert jedoch gerade dieser Gleichspannungsanteil.

Wenn wir ein PWM-Signal erzeugen, hat dieses Signal nicht nur eine bestimmte Frequenz, sondern wir können auch den Tastgrad definieren. Im Falle unserer Rechteckschwingung beträgt der Tastgrad genau 50 %. Der Ausgang ist also 50 % der Zeit HIGH und den Rest der Zeit LOW. Der Knackpunkt ist, dass sich bei einer Änderung des Tastgrads auch die durchschnittliche Spannung des Signals ändert. Dies ist im folgenden Bild zu sehen, das drei PWM-Signale mit unterschiedlichem Tastgrad zeigt. Die mittlere Spannung ist als blaue Linie markiert.

PWM-Signale mit unterschiedlichem Tastgrad und deren mittlere Spannung

Wir können die mittlere Spannung mit einer einfachen Formel mit \(D\) als Tastgrad berechnen:
\(\overline{u} = (U_{max}-U_{min}) \cdot D\)

Theoretisch können wir jede beliebige mittlere Spannung zwischen 0 V und 5 V erzeugen. Der Arduino Uno erlaubt uns, einen Wert zwischen 0 und 255 als Tastgrad anzugeben, was bedeutet, dass der PWM-Ausgang theoretisch als 8-Bit-DAC verwendet werden kann. Aber halt, eine Sache sollten wir nicht vergessen: das ausgegebene Signal ist keine konstante Spannung, sondern ändert sich ständig. Um den PWM-Ausgang wirklich als DAC zu verwenden, müssen wir das Ausgangssignal glätten. Mit anderen Worten, wir müssen alle hochfrequenten Anteile daraus entfernen. Glücklicherweise wissen wir bereits, wie das gemacht werden kann: wir verwenden einen Tiefpassfilter.

Bedeutet das, dass wir auf das gleiche Problem stoßen wie beim letzten Mal, wo wir Probleme hatten, die Signalanteile, die wir behalten wollten, von denen zu trennen, die wir entfernen wollten? Nun, nicht unbedingt. Wenn die PWM-Frequenz hoch genug gewählt wird, gibt es eine klare Unterscheidung zwischen dem gewünschten Ausgangssignal und den hochfrequenten PWM-Frequenzanteilen. Dies macht die Verwendung eines Filters viel einfacher. Legen wir los und schauen, wie das in der Praxis funktioniert.

Ein erster Versuch

Da wir einen Tiefpassfilter benötigen, können wir einfach die gleiche Schaltung wie im letzten Teil dieses Projekts verwenden. Der Tiefpassfilter besteht aus einem 220 Ω Widerstand und einem 10 uF Kondensator und hat damit eine Grenzfrequenz von etwa 72 Hz. Pin 9 des Arduino wird als PWM-Pin verwendet und wie bei den letzten Malen verwenden wir einen Spannungsteiler aus zwei 220 Ω Widerständen, um unsere neue Massereferenz bei 2,5 V zu erzeugen.

RC-Tiefpassfilterschaltung 1. Ordnung

Nichts Neues bei der Schaltung, aber was ist mit dem Code? Nun, ein PWM-DAC unterscheidet sich nicht so sehr von einem normalen DAC, so dass wir den größten Teil des Codes aus dem zweiten Teil dieses Projekts wiederverwenden können sollten. Bevor ich den kompletten Code zeige, lass uns zunächst über die Teile sprechen, die angepasst werden müssen.

Erstens brauchen wir die MCP4725-Bibliothek nicht mehr, da wir dieses DAC-Modul gar nicht nutzen. Infolgedessen kann auch die DAC-Initialisierung entfernt werden. Da wir nicht mehr das DAC Modul verwenden, benötigen wir folglich auch eine neue Methode zur Ausgabe der Datensamples in der loop-Prozedur. Für unseren PWM-DAC ist das denkbar einfach. Wir stellen einfach den Tastgrad über analogWrite ein:

analogWrite(9, voltages[current_step]);

Wenn wir analogWrite verwenden, muss der Tastgrad als Wert zwischen 0 und 255 angegeben werden. Das heißt, wir müssen die Zeile anpassen, in der die Ausgangswerte vorberechnet werden. Der neue Code sieht dann wie folgt aus:

// Precalculate Sine Values (0 - 255)
for(int i = 0; i < steps; i++) {
  voltages[i] = (sin(i*3.14*2/steps) + 1) * 127.5;
}

Zu guter Letzt müssen wir noch das Timing anpassen. Ähnlich wie beim DAC-Modul können wir hier großen Aufwand betreiben, um ein perfektes Timing zu erreichen. Aber, darüber denken wir später nach. Für den Moment stellen wir den usPerCommand auf einen Wert ein, der für die PWM-Ausgabe sinnvoll ist.

Welcher Wert ist sinnvoll? Nun, der Arduino Uno verwendet standardmäßig eine PWM-Frequenz von 490 Hz auf Pin 9. Das bedeutet, dass eine volle Periode etwas über 2 ms dauert. Die maximale Geschwindigkeit, mit der wir den Tastgrad ändern können, ist einmal pro Periode. Lass uns zur Sicherheit einen etwas höheren Wert nehmen und usPerCommand auf 2500 (2,5 ms) setzen. Der komplette Code sieht nun wie folgt aus:

#include <EEPROM.h>

// PWM frequency is 490 Hz so we roughly upate every 2 ms
const int usPerCommand = 2500;

// Precalculated Voltage Buffer
const int BUFFER_SIZE = 256;
unsigned int voltages[BUFFER_SIZE];
unsigned int steps;
unsigned int usPerStep;

unsigned int current_step = 0;
unsigned long start_time;

// Setup frequency
void setup() {
  // Read desired frequency
  Serial.begin(9600);
  Serial.print("Enter Frequency (Hz): ");

  // Wait 10s for input otherwise take stored value
  Serial.setTimeout(10000);
  float frequency = Serial.parseFloat();
  if(frequency == 0) EEPROM.get(0, frequency);
  else EEPROM.put(0, frequency);

  Serial.println(frequency);

  // Calculate number of possible steps
  int possible_steps = 1000000/usPerCommand/frequency;

  // Steps need to be a multiple of 4 to keep the sine form
  steps = (possible_steps  / 4) * 4;
  if(steps > BUFFER_SIZE) steps = BUFFER_SIZE;
  if(steps < 4) steps = 4;

  // Time per Step
  usPerStep = 1000000 / (frequency * steps);
  if(usPerStep < usPerCommand) usPerStep = usPerCommand;

  // Precalculate Sine Values (0 - 255)
  for(int i = 0; i < steps; i++) {
    voltages[i] = (sin(i*3.14*2/steps) + 1) * 127.5;
  }

  Serial.print("Number of output steps: ");
  Serial.println(steps);

  Serial.print("Microseconds per step: ");
  Serial.println(usPerStep);

  Serial.print("Archieved Frequency (Hz): ");
  Serial.println(1000000.0/float(steps)/float(usPerStep));

  // Initially set start time
  start_time = micros();
}

// Output values
void loop() {
  analogWrite(9, voltages[current_step]);
  current_step++;
  if(current_step >= steps) current_step = 0;

  while(micros()-start_time < usPerStep);
  start_time += usPerStep;
}

Im Bild unten ist das Ergebnis zu sehen. Es ist nicht genau das, was wir uns erhofft haben, aber das erzeugte Signal ist einer Sinuskurve zumindest ähnlich. Was ist das Problem und wie können wir das Ergebnis verbessern?

Von unserem Programm erzeugtes Signal

Es gibt zwei Hauptprobleme: die Spannungsspitzen und die Tatsache, dass die Kurve in jeder Periode ein wenig anders aussieht. Die Spannungsspitzen sind einfach zu erklären: jedes Mal, wenn der PWM-Ausgang auf HIGH schaltet, lädt sich der Kondensator auf und die Spannung steigt, und sobald der PWM-Ausgang auf LOW schaltet, sinkt die Spannung wieder. Der Grund, warum dies so deutlich sichtbar ist, ist, dass die PWM-Frequenz von 490 Hz noch recht niedrig ist, um eine 50 Hz Sinusschwingung zu erzeugen. Wenn wir uns die FFT unseres gefilterten Ausgangssignals ansehen, können wir erkennen, dass die Frequenzanteile des PWM-Signals nicht vollständig herausgefiltert werden. Auch nach der Filterung des PWM-Signals schwingt das Ausgangssignal immer noch etwa 500 mV um die eingestellte Ausgangsspannung.

FFT des gefilterten 490 Hz PWM-Signals bei 50% Tastgrad

Das zweite Problem lässt sich dadurch erklären, dass die PWM-Frequenz von 490 Hz kein echtes Vielfaches der Abtastrate ist, die wir zur Vorberechnung und Ausgabe der Sinuswerte verwendet haben. Dies ist im folgenden Bild gut zu erkennen.

Abtastung des Ausgangssignals

Die rote Linie zeigt die mittlere Spannung, die wir mit analogWrite eingestellt haben. Wenn wir jedoch den Tastgrad ändern, passt sich das PWM-Signal nicht sofort an den neuen Wert an. Der neue Tastgrad wird erst geladen, wenn die aktuelle Periode des PWM-Signals zu Ende ist. Die reale Ausgabe entspricht also der blauen Linie, an der wir den gleichen Effekt wie auf dem Oszilloskop sehen können: jede Periode der erzeugten Sinuswelle sieht ein wenig anders aus. Das ist jedoch strenggenommen nicht ganz richtig. Wenn wir das Signal etwas länger beobachten, können wir sehen, dass sich das Muster nach 100 ms wiederholt, was genau 5 Sinuswellenperioden oder 49 PWM-Perioden entspricht. Das bedeutet, dass der Timing-Fehler nicht unendlich ansteigt, aber er ist immer noch schlecht. Wir brauchen einen Weg, um beide Probleme zu beheben: die Spannungsspitzen und die Timing-Fehler.

Verbessern des PWM-DACs

Wie können wir unseren PWM-DAC verbessern? Nun, um die Spannungsspitzen loszuwerden, könnten wir einen Filter höherer Ordnung verwenden, wie wir es im letzten Teil dieses Projekts getan haben. Dies wird jedoch unsere Timing-Probleme nicht lösen. Eine bessere Lösung, die beide Probleme verbessert, ist die Erhöhung der PWM-Frequenz.

Eine höhere PWM-Frequenz bedeutet, dass ihre AC-Frequenzanteile durch den Tiefpassfilter noch stärker gedämpft werden. Es bedeutet aber auch, dass wir eine präzisere Sinuswelle mit mehr Samples und geringeren Timing-Fehlern ausgeben können. Idealerweise sollten wir zusätzlich die PWM-, die Abtast- und die Sinuswellenfrequenz synchronisieren. Dies wäre zwar theoretisch möglich, ist aber unnötig komplex. Wenn die PWM-Frequenz hoch genug ist, sind die verbleibenden Timing-Fehler so klein, dass wir sie einfach ignorieren können.

Zusammenfassend lässt sich sagen, dass eine Erhöhung der PWM-Frequenz die perfekte Lösung für unsere Probleme zu sein scheint. Die Frage ist nun: Wie können wir die PWM-Frequenz erhöhen? Es gibt keine Möglichkeit, eine PWM-Frequenz im analogWrite-Aufruf anzugeben. Fakt ist, dass es keine vordefinierte Funktion gibt, die wir dafür aufrufen könnten. Im Falle des Arduino Uno wird die PWM-Ausgabe von den Hardware-Timern des ATmega328P erzeugt. Die PWM-Ausgabe für Pin 9 und 10 wird von Timer 1 generiert. Es gibt spezielle Arduino-Bibliotheken, wie TimerOne, die uns mehr Kontrolle über diesen Timer erlauben. Mit ihnen könnten wir auch die PWM-Frequenz ändern. Wir können dies aber auch tun, indem wir direkt auf die Timer-Register zugreifen und den Timer neu konfigurieren. Genau das macht die folgende Codezeile:

TCCR1B = (1<<WGM12) | (1<<CS10);

Sie setzt die PWM-Frequenz auf 62,5 kHz. Dies ist die höchste PWM-Frequenz, die wir erreichen können, die mit der analogWrite-Prozedur und einem 8-Bit-Wert für den Tastgrad funktioniert.

Natürlich müssen wir auch die Abtastrate anpassen, die wir bei der Berechnung und Ausgabe der Samples verwenden. Dazu müssen wir die Variable usPerCommand anpassen. Die PWM-Frequenz würde uns erlauben, alle 16 us einen neuen Wert einzustellen. Allerdings benötigt unsere loop-Prozedur mehr Zeit für die Ausführung. Wir könnten genau messen, wie lange es dauert, unsere loop-Prozedur auszuführen oder aber einfach einen sicheren Wert wie 100 us pro Iteration verwenden. Dies ermöglicht eine maximale Abtastfrequenz von 10 kHz. Da wir den Code, den wir für den DAC verwendet haben, beibehalten haben, wird diese Abtastfrequenz an die gewählte Frequenz der Sinuswelle angepasst. Wir werden uns aber, wie gesagt, nicht die Mühe machen, sie mit der PWM-Frequenz zu synchronisieren.

Nach Durchführung der erforderlichen Änderungen sieht der Code jetzt folgendermaßen aus:

#include <EEPROM.h>

// PWM frequency is 62.5 kHz so we can update every 16 us
const int usPerCommand = 100;

// Precalculated Voltage Buffer
const int BUFFER_SIZE = 256;
unsigned int voltages[BUFFER_SIZE];
unsigned int steps;
unsigned int usPerStep;

unsigned int current_step = 0;
unsigned long start_time;

// Setup frequency
void setup() {
  // Read desired frequency
  Serial.begin(9600);
  Serial.print("Enter Frequency (Hz): ");

  // Wait 10s for input otherwise take stored value
  Serial.setTimeout(10000);
  float frequency = Serial.parseFloat();
  if(frequency == 0) EEPROM.get(0, frequency);
  else EEPROM.put(0, frequency);

  Serial.println(frequency);

  // Calculate number of possible steps
  int possible_steps = 1000000/usPerCommand/frequency;

  // Steps need to be a multiple of 4 to keep the sine form
  steps = (possible_steps  / 4) * 4;
  if(steps > BUFFER_SIZE) steps = BUFFER_SIZE;
  if(steps < 4) steps = 4;

  // Time per Step
  usPerStep = 1000000 / (frequency * steps);
  if(usPerStep < usPerCommand) usPerStep = usPerCommand;

  // Precalculate Sine Values (0 - 255)
  for(int i = 0; i < steps; i++) {
    voltages[i] = (sin(i*3.14*2/steps) + 1) * 127.5;
  }

  Serial.print("Number of output steps: ");
  Serial.println(steps);

  Serial.print("Microseconds per step: ");
  Serial.println(usPerStep);

  Serial.print("Archieved Frequency (Hz): ");
  Serial.println(1000000.0/float(steps)/float(usPerStep));

  // Enable the fastest possible frequency for timer 1
  TCCR1B = (1<<WGM12) | (1<<CS10);

  // Initially set start time
  start_time = micros();
}

// Output values
void loop() {
  analogWrite(9, voltages[current_step]);
  current_step++;
  if(current_step >= steps) current_step = 0;

  while(micros()-start_time < usPerStep);
  start_time += usPerStep;
}

Da wir mit dieser Lösung eine recht hohe Abtastfrequenz erreichen können, sollten wir darüber nachdenken, ob wir den Tiefpassfilter anpassen wollen, um höhere Frequenzen für unsere Sinusschwingung zu ermöglichen. Bei einer Abtastfrequenz von 10 kHz könnten wir eine Sinusschwingung mit einer Frequenz von 1 kHz und mehr erzeugen. Die Cutoff-Frequenz des Filters begrenzt uns jedoch auf 72 Hz bei -3 dB Verstärkung. Bei höheren Frequenzen wird nicht nur das PWM-Signal, sondern auch unser Sinussignal immer mehr abgeschwächt, bis es nicht mehr als solches erkennbar ist. Wenn wir Frequenzen über 50 Hz zulassen wollen, müssen wir den Filter anpassen. Dazu können wir z. B. den 10 uF-Kondensator gegen einen 1 uF-Kondensator austauschen. Dadurch ändert sich die Grenzfrequenz des Filters auf grob 720 Hz.

Wie im Bild unten gezeigt, kann man noch einen kleinen 100 nF-Kondensator parallel zum größeren Elko hinzufügen. Ich werde hier nicht ins Detail gehen, warum wir diesen zusätzlichen Kondensator brauchen. Die Kurzversion: er hilft, das hochfrequente Rauschen im Ausgangssignal zu reduzieren. Er wird benötigt, weil ein realer Kondensator sich nicht wie ein idealer Kondensator verhält und immer auch eine gewisse Induktivität hat. Wenn du an den Details interessiert bist, kannst du nach der sogenannten äquivalenten Serieninduktivität (ESL) von Kondensatoren suchen. Verbesserte Schaltung für eine höhere PWM-Frequenz

Das Ergebnis

Schauen wir uns das neue Ergebnis an. Mit angepasstem Programm und verbesserter Schaltung erhalten wir nun eine saubere 50-Hz-Sinuswelle.

Erzeugte Sinuswelle nach den Verbesserungen

Genau wie beim DAC-Modul hat das Ausgangssignal eine Amplitude von 2,5 V und genau wie beim DAC-Modul kann diese Lösung nicht viel Leistung für eine AC-Schaltung bereitstellen. Beide Lösungen sind nahezu gleichwertig. Wie jedoch im vorherigen Teil, in dem wir ebenfalls ein digitales Ausgangssignal verwendet haben, angesprochen, ist uns die Arbeit mit einem digitalen Ausgangssignal vertrauter und erlaubt die Verwendung von Methoden zur Verstärkung, die bei analogen Signalen nicht so einfach angewendet werden könnten. In den nächsten Teilen dieses Projekts werden wir uns näher mit dem Thema Verstärkung befassen und auch hierbei verschiedene Methoden genauer anschauen.

Vorheriger Beitrag Nächster Beitrag