PWM-Messung mit einem PIC Mikrocontroller
über
Obwohl die Programmierung eines Arduino einfacher ist als die eines PICs, erweist sich letzterer aufgrund seiner Architektur als präziser bei der Berechnung des Timings eines (PWM)-Signals. Die zu bewältigenden Herausforderungen sind sicherlich schwieriger, aber letztendlich werden sie die Erfahrung derjenigen bereichern, die gerne programmieren. Das vorgeschlagene Gerät kann gleichzeitig hohe und niedrige Pulsdauern und auch die Periode messen, alles mit der Auflösung von einer Mikrosekunde für Pulsdauern von 10 µs bis 65.535 µs für PWM-Signale mit Frequenzen von 7,63 Hz bis 50 kHz.
Die Technik der zeitlichen Variation der Pulsdauer eines Rechtecksignals wird Pulsweitenmodulation (Pulse-Width Modulation, PWM) genannt. Diese Modulationsart, die ursprünglich aus der Telekommunikation stammt, findet heutzutage auch in der digital-analogen Welt Anwendung, um eine gemittelte Spannung oder den Strom zu variieren, deren Wert vom Verhältnis der Dauer des positiven Impulses zur Dauer der gesamten Periode abhängt. Dieser Zusammenhang wird als Tastverhältnis oder englisch Duty-cycle bezeichnet.
PWM wird zur Spannungsregelung in Schaltnetzteilen, zur Motorsteuerung, für Servoaktuatoren und sogar für das Ausgangssignal einiger Sensoren verwendet. Aus diesen Gründen verfügen die meisten modernen Mikrocontroller über einen oder mehrere PWM-Ausgänge. Einige Wandler, zum Beispiel akustische Abstandssensoren, besitzen einen digitalen Impulsausgang, dessen Dauer proportional zur Amplitude des Signals ist. Der Zweck dieses Projekts ist also nicht die Erzeugung von PWM-Signalen, sondern die präzise Messung ihrer Impulsdauer.
In diesem Artikel beschreibe ich eine Schaltungsversion mit dem PIC16F628, um die Dauer des High- und des Low-Anteils des Rechtecksignals und damit auch die Periodendauer zu messen, wie im Titelbild zu sehen. Wenn Sie auch das Tastverhältnis in Prozent berechnen und anzeigen wollen, benötigen Sie einen PIC16F648, da dies eine zusätzliche Division erfordert. Das Programm benötigt dann zwar nur ein paar Befehle mehr, aber der Code überschreitet die 2K-Marge, und der F628 hat nur einen 2K Flash-Speicher. Quellprogramme und kompilierte HEX-Dateien stehen aber für beide Versionen unter zur Verfügung. Der Rest der Hardware bleibt exakt gleich.
Der PIC-Mikrocontroller
Warum fiel die Wahl auf einen PIC-Mikrocontroller? Nun, ich hatte ein solches Gerät schon zu einer Zeit entwickelt, als ich hauptsächlich PICs in meinen Projekten verwendete, und dann später zur Entwicklung mit der Arduino-IDE und MicroPython wechselte. Bei PIC-Mikrocontrollern ist der interne Takt ein Viertel der Frequenz des Quarzoszillators, und alle Anweisungen werden – mit Ausnahme von Programmverzweigungen – in einem Takt ausgeführt. Mit einem 16-MHz-Quarz beträgt der interne Takt 4 MHz, so dass die Ausführung eines Befehls nur 0,25 µs dauert.
Der PIC16F628 von Microchip ist ein Mikrocontroller mit so genannter RISC-Architektur (Reduced Instruction Set Computer), der mit 8-Bit-Daten arbeitet, dessen 35 Befehle aber jeweils ein 14-Bit-Wort belegen. In unserem Fall passen 2K Worte in den Flash-Speicher. Dieser PIC verfügt neben seinen zahlreichen I/O-Funktionen auch über Event Capture am Pin CCP1 (RB3), indem er den Inhalt von Timer1 in den beiden Registern CCPR1L und CCPR1H speichert. Etwas Ähnliches gibt es auch beim ATmega328P des Arduino UNO, aber die Wahl eines PICs führt zu einem viel einfacheren, kompakteren System mit viel geringerem Stromverbrauch.
Natürlich werden diejenigen, die es gewohnt sind, mit Arduino zu arbeiten, größere Schwierigkeiten haben, etwas mit PICs zu entwickeln. Es ist nicht so einfach, vorgefertigte Boards oder die vielen für Arduino verfügbaren Bibliotheken zu finden, um die verwendete Peripherie zu verwalten. Meiner Meinung nach kann man bei der Arbeit mit Arduino nicht besonders tief in die Materie eindringen und weiß nicht einmal genau, was man überhaupt tut.
Viele Arduino-Benutzer tun nichts anderes, als die Verbindungen der Projekte, die aus in Fritzing- und Breadboard-Bildern angeordneten Komponenten bestehen, zu replizieren, und wissen dabei gar nicht, wie das alles funktioniert (wenn es wegen unsicherer oder falscher Verbindungen oft gar nicht funktioniert).
Der für PICs verwendete Compiler ist weniger benutzerfreundlich als die Arduino-IDE und die verfügbaren Bibliotheken sind viel spärlicher: Zum Beispiel gibt es keine pulsein()-Funktion, um die Dauer eines Impulses zu messen. Aus diesen Gründen muss sich der Entwickler eingehender mit der Funktionsweise von Mikrocontrollern befassen, wie ich es auch bei diesem Projekt getan habe, wo ich das Datenblatt studieren musste, um die Funktionsweise von Timern und CCP-Capture zu verstehen.
Der mikroPascal-Compiler, den ich für die Programmentwicklung auf PICs verwende, ist nur für Programme kostenlos, deren Code kleiner als 2K Wörter (1 Wort = 14 Bits) ist. Diese Grenze konnte in diesem Fall auch gar nicht überschritten wurde, da der Flash-Speicher des verwendeten PICs ohnehin nicht größer ist. Der Arbeitsspeicher, das RAM, ist bei diesem PIC-Modell nur 224 Bytes groß. Mit diesen Grenzen kann man nicht viel anfangen, denn wenn man zum Beispiel eine Float-Division durchführt, wird die Speichergrenze leicht überschritten. Aber die Kompilierung verläuft viel schneller als bei Arduino, und natürlich ist der erzeugte Code auch kompakter.
Das Programm wurde in einem leistungsfähigen Pascal-Cross-Compilers für Windows-PCs namens mikroPascal PRO für PIC, Version 7.6.0 entwickelt. Eine Vollversion kann von der MIKROE-Website heruntergeladen werden.
In dieser ersten Version des Programms ist der Code kürzer, so dass Sie ihn ohne Probleme kompilieren können. Wer mit C oder Basic besser vertraut ist, kann entsprechende Compiler herunterladen und das Programm in die neue Sprache übersetzen. Wenn man mit dem vorgegebenen Programm zufrieden ist und keine Änderungen vornehmen möchte, spart sich die Arbeit in einem Compiler ganz und benötigt nur einen PIC-Programmierer, der die bereits kompilierte Hex-Datei PWMmeter.hex auf den Controller lädt.
Eine weitere Version des Programms zur Berechnung der Einschaltdauer wurde ebenfalls entwickelt, benötigt aber wegen der Fließkommadivision mehr Flash-Speicher und einen PIC16F648. In diesem Fall müssen Sie, wenn Sie die Compiler-Lizenz nicht erworben haben, den Chip mit der HEX-Datei dieses Projekts programmieren. Diese beiden Mikrocontroller sind Pin-zu-Pin-kompatibel.
Messung der Impulsdauer
Einige Mikrocontroller mit eingebautem BASIC-Interpreter wie die BASIC-Stamp, Picaxe oder BasicX verwenden eine spezielle Routine (PULSIN) zur Messung der Pulsdauer, können aber kurze Pulse nicht messen, weil Interpreter zu langsam sind. Arduino-Boards verwenden die Funktion pulsein(pin, level, timeout), um die Dauer eines Pulses (High- oder Low-Level) mit einer Auflösung in Mikrosekunden zu messen. Für längere Zeiten gibt es die Funktion pulseInLong(), die Interrupts verwendet und Zeiten von 10 µs bis 3 min misst. Beide Funktionen geben einen unsigned long zurück, einen Long-Wert ohne Vorzeichen.
Das Programm verwendet zwei Interrupts: Timer0 für die Abtastzeit, während die zweite Interrupt-Quelle bei Capture/Compare/PWM oder kurz CCP-Erfassungsereignissen an der steigenden und fallenden Flanke des Pulses generiert wird und die entsprechenden Zeiten in Timer1 gezählt werden.
Zeitsteuerung mit Timer0
Timer0 erzeugt einen Interrupt, mit dem alle 50 Ereignisse, das bedeutet etwa 0,5 s Messwerte abgetastet und auf dem LCD angezeigt werden. Timer 0 ist ein 8-Bit-TMR0-Zähler, dem ein programmierbarer 8-Bit-Teiler, der sogenannte Vorteiler, vorangestellt ist.
Das Programm wählt den internen Taktgeber mit einer Frequenz, die einem Viertel der Quarzfrequenz entspricht: 16 MHz / 4 = 4 MHz. Wenn wir den Vorteiler auf den höchstmöglichen Wert (1:256) einstellen, hat Timer0 als Eingang eine Frequenz von f1 = 4 MHz / 256 = 15,625 kHz mit einer Periode von 64 µs; das Laden von 100 in den Zähler führt zu einem Interrupt bei allen 256-100 = 156 Impulsen der Frequenz f1, was einer Periode von 156 × 64 = 9984 µs (etwa 100 Hz) entspricht.
Der Timer0-Interrupt wird erzeugt, wenn der Timer/Zähler im TMR0-Register von 0xFF auf 0x00 überläuft, wodurch das T0IF-Bit gesetzt wird. Dieser Interrupt erzeugt den Takt, mit dem die Messwerte abgetastet und auf der LCD-Anzeige dargestellt werden, und zwar alle 50 Ereignisse, was etwa 0,5 s entspricht.
Ereigniserfassung mit Timer1
Das Programm wechselt nach der steigenden Flanke am Pin RB3 in den Erfassungsmodus für die fallende Flanke und umgekehrt. Ein Impuls kann Low-High-Low-Pegel aufweisen, was wir als LoHiLo bezeichnen wollen, oder invertiert sein (HiLoHi), wie in Bild 1 gezeigt.
Bei Impulsen vom Typ LoHiLo setzt das Programm das Register CCP1CON := $05 auf eine Erfassung bei steigender Flanke. Wenn dieses Ereignis eintritt, speichert die Interrupt-Routine den Inhalt des Registerpaars CCPR1L und CCPR1H, der dann an t1 übertragen wird. Zur Erfassung auf die fallende Flanke wird CCP1CON := $04 gesetzt, bei deren Eintreten das Registerpaar CCPR1L und CCPR1H erneut gelesen wird, was dann nach t2 übertragen wird, wie in Bild 1 zu sehen ist. Die pulsew-Zeit ergibt sich aus der Differenz t2-t1.
Bei Impulsen des Typs HiLoHi verhält es sich ähnlich, aber man beginnt mit der fallenden Flanke (t2) und endet mit der steigenden Flanke (t1). In diesem Fall ist die pulsew-Zeit = t1-t2. Timer1 hat einen 16-Bit-Zähler, dem ein 2-Bit-Prescaler vorangestellt ist.
Auch hier wird der interne Takt von 4 MHz verwendet. Mit dem Vorteiler = 1:1 ergibt sich eine Auflösung von 0,25 µs, was bei einem normalen Quarz zu knapp ist, so dass ein Verhältnis 1:4 mit einer Auflösung von 1 µs verwendet wurde. Daher misst das System Impulse mit einer Dauer von etwa 10 µs bis 65.535 µs. Unter 10 µs funktioniert es nicht, weil es mehrere Anweisungen in der Interrupt-Routine gibt und dies eine gewisse Ausführungszeit erfordert. Wenn Sie längere Zeiten mit einer Auflösung von 2 µs messen wollen, müssen Sie das Verhältnis 1:8 verwenden. In diesem Fall sollten die Zeiten mit zwei multipliziert werden, nachdem die Variablen in lange Ganzzahlen umgewandelt wurden.
Eine maßgeschneiderte neue und größere Version des Originalprogramms kann die beiden Dauern gleichzeitig messen; daher können Sie die PWM-Periode und auch das Tastverhältnis berechnen. In diesem Fall wählt das Programm die Erfassung für die steigende Flanke, speichert die Zeit in t1, hebt das firstcre-Flag auf high, um diese Flanke von der letzten zu unterscheiden, setzt dann für die fallende Flanke und speichert die Zeit in t2, und setzt für die nächste steigende Flanke, die es in t3 speichert, wie in Bild 2 zu sehen. Bei der dritten Flanke setzt die Interrupt-Routine das datok-Flag, um anzuzeigen, dass die Zeiten t1, t2 und t3 gemessen wurden. Und berechnet:
pulsewHi = t2-t1, pulsewLo = t3-t2, period = pulsewHi + pulsewLo
Das Tastverhältnis erfordert Operationen mit float-Variablen (real in Pascal), die die Grenzen des Flash-Speichers des 16F628 überschreiten, aber wir können sie schnell mit einem Taschenrechner durchführen:
Periode: T = pulsewHi + pulsewLo
Tastverhältnis: D = pulsewHi / T × 100 [%]
Vergleich mit Arduino pulseIn()
Zum Vergleich, nur für einen LoHiLo-Puls, habe ich auch ein kleines Programm für den Arduino Uno geschrieben:
// program pulseintest
#define pulsein 4
void setup() {
Serial.begin(115200);
pinMode(pulsein, INPUT);
}
void loop() {
unsigned long duration = pulseIn(pulsein, HIGH);
Serial.print("Pulse High [us] = ");
Serial.println(duration);
delay(500);
}
Um die Zeiten zu überprüfen, habe ich mein eigenes quarzbasiertes Gerät mit programmierbarem Teiler und Rechteckausgang als Referenz verwendet. Tabelle 1 vergleicht die Ergebnisse mit denen meines PIC-Systems. Wie man sehen kann, ist das PIC-System sehr genau.
Das Schaltbild
Bild 3 zeigt den Schaltplan des Systems. Bei einem Spannungsregler LM7805 (ein 78L05 tut es auch) liegt der Eingangsspannungsbereich zwischen 7 V (optimaler Wert) und 12 V. Das Gerät benötigt aber (ohne Hintergrundbeleuchtung) weniger als 10 mA, sodass die Verlustleistung des Reglers vernachlässigt werden kann. Viel bequemer macht man es sich mit einer Versorgung durch drei gewöhnliche 1,5-V-Batterien. Kritisch ist in diesem Fall vielleicht das LCD mit einer Nennversorgungsspannung von 5 V, aber solche Displays funktionieren normalerweise auch mit 4,5 V.
Das Display habe ich ein handelsübliches zweizeiliges 16-Zeichen-LCD mit einer HD44780-kompatiblen Parallelschnittstelle verwendet. Der Jumper W1 verhindert Konflikte mit der Stromversorgung und muss entfernt werden, wenn Sie den Chip mit dem PICKIT programmieren. Jumper (oder Schalter) W2 wird verwendet, um die Hintergrundbeleuchtung abzuschalten und Strom zu sparen.
Der Prototyp
Bild 4 zeigt die Anordnung der Bauteile auf der Lochrasterplatine des Prototyps. Die LCD-Anzeige wurde entfernt, um die darunter angebrachten Bestandteile der Schaltung zu zeigen.
Oben ist der kleine Schalter W2 zu sehen, der die Hintergrundbeleuchtung des Displays ausschaltet, während links ein Cinch-Stecker für den PWM-Eingang angebracht ist und unten ein 6-poliger Steckverbinder für die Chip-Programmierung, der mit dem PICkit-Programmiergerät kompatibel ist. Wenn Sie ein PIC-Programmiergerät mit einer ZIF-Fassung haben, ist dieser Anschluss nicht erforderlich. Sie müssen dann aber den PIC aus seiner Fassung ziehen, um ihn zu programmieren, was oft mit verbogenen Beinchen verbunden ist, wenn man nicht aufpasst. Neben dem Verbinder, immer noch an der Unterseite, ist der Stromanschluss zu sehen. Die Platine wurde aus einem älteren Projekt recycelt, zu dem auch die Buchsenleiste rechts gehört und die hier keine Funktion hat.
Das Programm
Der größte Teil des Programms wurde bereits oben beschrieben. Die Ausgaben auf dem LCD sind grundlegender als die Funktionen der Arduino-Bibliothek LiquidCrystal, und die Meldungen müssen Strings oder Arrays von Zeichen sein, so dass es eine notwendige Aufgabe des Programms sein muss, die Variablen in Strings umzuwandeln und alle führenden Leerzeichen zu entfernen.
Die wichtigste Arbeit wird von der Interrupt-Routine erledigt. Im ersten Teil prüft sie, ob die Unterbrechung durch einen Timer0-Überlauf verursacht wird. In diesem Fall setzt INTCON, T0IF = 1 das int0flg-Flag high, das für das Timing verwendet wird. Anschließend wird die Zahl 100 in den Zähler geladen, so dass er nach 156 Impulsen einen Interrupt auslöst und die Interrupt-Flags INTCON und T0IF auf 0 zurücksetzt.
Der zweite, komplexere Teil ist die Erfassung der steigenden und fallenden Flanken und die Speicherung der Inhalte der beiden Erfassungsregister CCPR1L und CCPR1H in den Variablen t1, t2 und t3, die der ersten steigenden Flanke, der fallenden Flanke und der zweiten steigenden Flanke entsprechen. Wenn alle drei Zeiten erfasst wurden, wird das datok-Flag gesetzt, das vom Programm zur Berechnung der Dauern und der Periode verwendet wird.
Die in Mikrosekunden ausgedrückten Zeiten für den High-Anteil der PWM, gekennzeichnet durch _-_ , und für den Low-Anteil, der mit -_- gekennzeichnet ist, werden in der ersten Displayzeile ausgegeben. Die PWM-Periode, also die Summe der beiden Zeiten, ist in der zweiten Zeile angegeben.
Wenn kein Signal anliegt, zeigt das Display nichts an. Der vollständige Code ist in Listing 1 zu sehen. Dieses Programm belegt 1.236 Wörter des Flash-Speichers (65 %) und 74 Byte des RAM (37 %), und die Kompilierung wurde in 187 ms durchgeführt.
Programm mit Tastverhältnis-Anzeige
In diesem Fall benötigen Sie einen PIC16F648A, der dem F628 ähnlich ist, aber 4K Worte Flash-Speicher besitzt. Wie bereits erwähnt, müssen Sie zum Kompilieren dieser Version die nicht ganz billige Programmlizenz erwerben, die die 2K-Wort-Begrenzung aufhebt. Die bereits kompilierte Hex-Datei für den PIC16F648 ist jedoch im Paket für diesen Artikel enthalten und kann direkt in den PIC geladen werden.
Das Programm gibt pulsewHi [µs], pulsewLo [µs], period [µs] und dutyc [%] auf dem Display aus.
Leider hat mir die Fließkommadivision bei der Kompilierung Speicherprobleme bereitet, da auch der PIC16F648 dafür nicht besonders geeignet ist. Ich habe die Unannehmlichkeiten überwunden, indem ich die prozentuale Einschaltdauer in eine ganze Zahl umgewandelt habe, wobei natürlich der Dezimalteil verloren ging.
Zum Schluss ein Vergleich
Bild 5 und Bild 6 zeigen einen Vergleich zwischen Messungen mit einem Oszilloskop und denen des PIC16F648-Systems. In diesem Fall wurde ein TTL-Pulsgenerator verwendet.
Wie Sie sehen können, sind die Messungen sehr ähnlich.
PIC16F648 PWMmeter Program Listing
Das Programm selbst ist dem vorherigen weitgehend ähnlich, mit Ausnahme der LCDprint-Routine. Es wurden Anweisungen zur Berechnung und zur Ausgabe des Tastverhältnisses hinzugefügt. Die geänderte Routine ist in Listing 2 dargestellt.
Der geänderte Code belegt 2.266 Wörter des Flash-Speichers (55 %) und 92 Byte des RAM (62 %), und die Kompilierung wurde in 47 ms durchgeführt.
Haben Sie Fragen oder Kommentare?
Haben Sie technische Fragen oder Kommentare zu diesem Artikel? Bitte kontaktieren Sie die Elektor-Redaktion unter redaktion@elektor.de.
Diskussion (0 Kommentare)