Wir haben uns eine funktionierende, aber recht komplexe Wechselstromquelle gebaut. Unsere Lösung ist aber nicht die einzig mögliche.
Auch wenn unser vorheriges Design einer Wechselstromquelle mit dem MCP4725 und dem PWM-DAC gut funktioniert hat, ist die Schaltung doch recht komplex geworden. Wir benötigten ziemlich viele Bauteile für die drei Teile unserer Schaltung: Signalerzeugung, Verstärkung mit dem Operationsverstärker und die externe Push-Pull-Stufe. Unsere Lösung war wie folgt: Wir haben ein schwaches analoges Signal erzeugt, das dann durch den Operationsverstärker und die Push-Pull-Stufe verstärkt wurde. Wenn wir PWM zur Signalerzeugung verwenden, können wir eine andere Methode anwenden. Wir können das digitale PWM-Signal direkt verstärken. Das verstärkte Ausgangssignal wird dann erst anschließend mit einem LC-Filter in eine Sinuswelle umgewandelt. Lass uns loslegen und schauen, wie diese alternative Lösung funktioniert!
Hier ist die neue Schaltung:
Diese Schaltung sieht zwar ähnlich aus wie die Push-Pull-Stufe, die wir im vorherigen Teil dieses Projekts verwendet haben, aber das Arbeitsprinzip dieser Schaltung ist anders. In dieser Schaltung werden die Transistoren als Schalter und nicht als Verstärker für ein analoges Signal verwendet. Die Schaltung wird nun direkt durch das PWM-Signal angesteuert. Der LC-Tiefpass-Filter ist es, der das digitale Signal in eine Sinuswelle umwandelt. Ein LC-Filter hat einen ähnlichen Effekt wie ein RC-Filter, mit dem Unterschied, dass sowohl die Induktivität als auch der Kondensator die hochfrequenten Anteile des Signals unterdrücken.Ein LC-Filter ist ein Filter zweiter Ordnung und hat eine steilere Frequenzkurve mit einer Steigung von -40 dB/Dekade anstelle von nur -20 dB/Dekade. In der untenstehenden Grafik ist der Frequenzgang beider Filtertypen für eine Grenzfrequenz von 100 Hz dargestellt. Die rote Linie zeigt den Frequenzgang des RC-Filters 1. Ordnung, die blaue Linie zeigt den Frequenzgang eines LC-Filters.
In der Schaltung werden eine 1 mH Spule und ein 10 uF Keramikkondensator verwendet. Dieser Filter hat die folgende Grenzfrequenz:
\(f_c = {1 \over 2 \pi \sqrt{L C}} = {1 \over 2 \pi \sqrt{1 mH \cdot 10 uF}} \approx 1,6 kHz\)
Der Vorteil dieses Filtertyps ist, dass es einfacher ist, einen Filter mit einem vergleichsweise geringen Gleichstromwiderstand zu bauen. Dies ist wichtig, da das Signal nun nach der Verstärkung gefiltert wird und somit das Filter leistungsstark genug für die angeschlossene Last sein muss. Der von mir verwendete LC-Filter hat einen Gleichstromwiderstand von 5 Ω, was recht hoch ist. Es ist jedoch möglich, einen LC-Filter mit einem kleineren Widerstand und höheren Ausgangsleistungen zu entwerfen, wenn man eine entsprechende Spule nutzt.
Zusätzlich zum LC-Filter befindet sich ein 10 uF Kondensator zwischen 5 V
und GND
. Dieser Kondensator stabilisiert die Versorgungsspannung des Arduinos, während die Transistoren schalten. Durch die Spule können beim Schalten hohe Stromspitzen auftreten, insbesondere während das Magnetfeld zusammenbricht. In einer professionelleren Schaltung würde man Rücklaufdioden einsetzen. Ich habe mich dagegen entschieden, um die Schaltung so einfach wie möglich zu gestalten. In unserem Fall ist das kein großes Problem, da Induktivität und Spannung vergleichsweise klein sind.
Aber schauen wir uns mal an, wie die Transistorschaltung selbst funktioniert. Wenn der PWM-Ausgang D9
auf HIGH
und der PWM-Ausgang D10
auf LOW
steht, werden nur der obere linke und der untere rechte Transistor über den Transistor T5
eingeschaltet. Die linke Seite der Last ist mit 5 V verbunden, während die rechte Seite mit Masse verbunden ist. Wie im Bild unten gezeigt, fließt ein Strom durch die beiden Transistoren und die Last. Der Stromfluss ist in der konventionellen Stromrichtung dargestellt.
Wenn D10
HIGH
und D9
LOW
ist, fließt der Strom in die entgegengesetzte Richtung. Auf diese Weise erhalten wir eine negative Spannung.
Die Transistorschaltung ist auch als H-Brückenschaltung bekannt und wird häufig zur Steuerung von Motoren verwendet. Bei Motoren ermöglicht sie uns, den Motor in beide Richtungen drehen zu lassen. Ein weiterer Anwendungsfall für diese Schaltung sind sogenannte Wechselrichter, die eine niedrige Gleichspannung wie 12 V in 230 V Wechselspannung umwandeln. Unsere Schaltung arbeitet im Prinzip genauso wie diese Wechselrichter. Sie ist jedoch nur für eine kleine Ausgangsspannung und Leistung ausgelegt.
Kommerzielle Wechselrichter sind wesentlich ausgereifter und beinhalten Sicherheitsmaßnahmen wie Überstrom- und Übertemperaturschutz. Bitte versuche nicht, diese Schaltung mit hohen Spannungen oder hohen Ausgangsleistungen zu verwenden.
Die endgültige Ausgangsspannung nach der Filterung hängt vom Tastgrad ab. Genau wie in unserer vorherigen Lösung können wir eine Sinuswelle erzeugen, indem wir den Tastgrad entsprechend der Sinuswerte ändern.
Beim Arbeiten mit H-Brücken ist es sehr wichtig, dass D9
und D10
nie gleichzeitig eingeschaltet sind. Wenn beide Transistoren in einem Bein der H-Brücke gleichzeitig eingeschaltet sind, erzeugt das einen Kurzschluss, der zur Zerstörung der H-Brücke führen kann.
Das vollständige Programm sieht wie folgt aus:
#include <EEPROM.h>
// PWM update frequency is 31.25 kHz (~32 us)
const int usPerCommand = 100;
// Dead Time to prevent shoot through
const int deadTime = 5;
// 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-5 V
for(int i = 0; i < steps; i++) {
int value = sin(i*3.14*2/steps) * (126.5-deadTime) + 127.5;
voltages[i] = constrain(value, 1, 254);
}
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
// choose phase correct mode and invert output 10
TCCR1A = (1<<WGM10)|(1 << COM1B0);
TCCR1B = (1<<CS10);
TIMSK1 |= (1<<TOIE1);
// Initially set start time
start_time = micros();
}
ISR(TIMER1_OVF_vect) {
analogWrite(9, voltages[current_step]-deadTime);
analogWrite(10, voltages[current_step]+deadTime);
}
// Output values
void loop() {
current_step++;
if(current_step >= steps) current_step = 0;
while(micros()-start_time < usPerStep);
start_time += usPerStep;
}
Insgesamt ist dieses Programm dem für unsere vorherige Lösung sehr ähnlich. Es gibt jedoch eine sehr wichtige Änderung. Die folgende Code-Zeile invertiert den PWM-Ausgang D10
und aktiviert den sogenannten phasenkorrekten PWM-Modus:
TCCR1A = (1<<WGM10)|(1 << COM1B0);
Diese Funktionen werden von dem auf dem Arduino Uno-Board verwendeten ATmega328P-Mikrocontroller bereitgestellt. Die Details hierzu kannst du in dessen [Datenblatt] nachlesen (https://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf).
Da sich die Hardware um die Invertierung des Ausgangssignals an D10
kümmert, können wir einfach das gleiche Tastverhältnis für beide PWM-Pins einstellen:
analogWrite(9, voltages[current_step]-deadTime);
analogWrite(10, voltages[current_step]+deadTime);
Es gibt jedoch mehrere Probleme mit dieser Methode, um die wir uns kümmern müssen. Das erste: Die Arduino-Core-Bibliotheken sind nicht für den Fall konzipiert, dass der Ausgang an D10
invertiert wird. Dies wird ein Problem für die Werte 0
und 255
. Für beide Werte schaltet die Arduino-Core-Bibliothek, die die Funktion analogWrite
bereitstellt, die PWM-Funktionalität ab und setzt den Pin auf HIGH
oder LOW
. Da sie nicht weiß, dass wir den Ausgang an D10
invertiert haben, werden beide Pins, D10
und D9
, auf LOW
gesetzt, wenn der Wert 0
ist und auf HIGH
, wenn der Wert 255
ist. Das ist nicht das, was wir wollen. Um dieses Problem zu lösen, enthält der Code einen Workaround, der sicherstellt, dass die Werte 255 oder 0 nicht auftreten. Dies wird durch die folgenden Codezeilen erreicht:
// Precalculate Sine Values 0-5 V
for(int i = 0; i < steps; i++) {
int value = sin(i*3.14*2/steps) * (126.5-deadTime) + 127.5;
voltages[i] = constrain(value, 1, 254);
}
Zunächst wird die Amplitude so gewählt, dass 0 und 255 nicht vorkommen. Die zusätzliche Verwendung der Funktion constrain
stellt sicher, dass auch bei einem Wert von 0
stattdessen 1
verwendet wird. Ähnlich bei Werten über 255
, hier wird stattdessen der Wert 254
verwendet. Dieser Aufruf ist nicht notwendig, verhindert aber, dass man beim Experimentieren und Anpassen des Codes aus Versehen einen Kurzschluss erzeugt. Es ist quasi eine Art Safe-Guard. Durch die Konvertierung von float
nach int
könnte z.B. versehentlich ein Wert von 0
oder 255
entstehen, wenn die Werte anders gewählt werden.
Das zweite Problem ist, dass die Invertierung des Signals allein nicht ausreicht, um sicherzustellen, dass die beiden Ausgänge niemals gleichzeitig aktiviert sind. Das Problem ist im folgenden Bild dargestellt:
Auch wenn wir ein digitales Ausgangssignal haben, benötigt der Wechsel zwischen HIGH
und LOW
aufgrund von Kapazitäten in der Schaltung immer ein wenig Zeit. Während der Umschaltzeit ist es möglich, dass beide Transistoren gleichzeitig eingeschaltet sind. Um wirklich sicherzugehen, dass das nicht passiert, muss man deshalb eine sogenannte Totzeit einführen. Eine zusätzliche Zeit, in der beide Transistoren ausgeschaltet sind. Dazu verkürzen wir die Einschaltzeiten in den PWM-Signalen. Da der Ausgang an D10
invertiert ist, müssen wir in diesem speziellen Fall die Totzeit addieren.
analogWrite(9, voltages[current_step]-deadTime);
analogWrite(10, voltages[current_step]+deadTime);
Das letzte Problem, das gelöst werden muss, ist ein bisschen knifflig. Wenn wir Pech haben, könnte der Timer den Tastgrad zwischen unseren beiden analogWrite
Aufrufen aktualisieren. Wenn das passiert, könnte das Tastverhältnis für D9
aktualisiert worden sein, während das Tastverhältnis für D10
nicht aktualisiert wurde. In diesem Fall können wir wieder auf das Problem stoßen, dass beide Transistoren zur gleichen Zeit eingeschaltet werden. Das zugrunde liegende Problem ist, dass die Aufrufe von analogWrite
nicht mit dem Timer synchronisiert sind. Wenn wir diese Werte synchron zum Timer aktualisieren wollen, müssen wir sie innerhalb der sogenannten Interrupt Service Routine (ISR) für Timer 1 aktualisieren. Falls du dich gefragt hast, warum die analogWrite
jetzt in einer anderen Methode mit dem seltsamen Namen ISR(TIMER1_OVF_vect)
sind, kennst du jetzt die Antwort. Diese Methode ist genau die ISR, die ich zuvor erwähnt habe. Um die ISR nutzen zu können, müssen wir noch den zugehörigen Interrupt aktivieren. Dies geschieht durch die folgende Codezeile in setup
:
TIMSK1 |= (1<<TOIE1);
Schauen wir uns das Ergebnis an. Ohne den Lastwiderstand erhalten wir eine Sinuswelle mit 5 V Amplitude. Sehr schön.
Sobald wir jedoch den Lastwiderstand zuschalten, verringert sich die Amplitude der Sinuswelle. Das folgende Bild zeigt das Ausgangssignal mit angeschlossenem 51 Ω Lastwiderstand:
Der Hauptgrund dafür ist der Gleichstromwiderstand der Spule. Die Spule und der Lastwiderstand bilden einen Spannungsteiler. Dies bewirkt, dass die Amplitude des Signals vom Lastwiderstand abhängig ist. Mit steigendem Strom nimmt außerdem der Spannungsabfall über den Transistoren zu. Beide Effekte bewirken eine geringere Amplitude bei größeren Lasten.
In diesem Beispiel beträgt die Ausgangsleistung 150 mW. Das ist nicht viel, aber für kleine Schaltungen ausreichend. Mit anderen Transistoren und einer anderen Spule sind leicht höhere Ausgangsleistungen möglich. Zusätzlich ist es möglich, die H-Brücke an eine höhere Gleichspannung als die 5 V vom Arduino anzuschließen. Anstatt sie an 5 V
anzuschließen, kann man sie an Vin
anschließen und dann den Arduino mit einer höheren Spannung versorgen.
Wie sieht es mit dem Wirkungsgrad aus? Nun, es ist nicht einfach, den Wirkungsgrad für einen Schaltkreis zu berechnen, der mit einer hohen Frequenz schaltet. Ich habe jedoch einige Messungen bei 5 V mit einer 51 Ω Last durchgeführt. Bei diesen Parametern liegt der Wirkungsgrad bei etwa 25 %. Der Grund für diesen geringen Wirkungsgrad ist der Spannungsabfall über der Spule und die Tatsache, dass in diesem Beispiel der Basisstrom für die Transistoren fast so hoch ist wie der Ausgangsstrom. Bei höheren Ausgangsspannungen steigt der Wirkungsgrad an. Theoretisch können H-Brücken-Wechselrichter einen hohen Wirkungsgrad von bis zu 99 % haben. Mit einer eigenen Schaltung und bei niedrigen Spannungen ist es jedoch nicht ohne weiteres möglich, solch hohe Wirkungsgrade zu erreichen. Strebt man einen hohen Wirkungsgrad an, ist es zudem ratsam, die bipolaren Sperrschichttransistoren durch MOSFETs zu ersetzen. Im nächsten Teil dieser Serie werden wir uns mit diesem Thema befassen und unsere selbstgebaute H-Brückenschaltung durch ein H-Brücken-Modul ersetzen, das MOSFETs nutzt.