Forum: Mikrocontroller und Digitale Elektronik PWM-Signal mit STM32 messen


von Olli Z. (z80freak)


Lesenswert?

Gibt es beim STM32F103C8(Blue Pill) (ich verwende derzeit die Arduino 
IDE Toolchain von STM32duino) die Möglichkeit den Dutycycle eines 3,3V 
PWM-Signals mit "bliebiger" Frequenz (irgendwas zwischen 100 Hz und 1 
kHz) direkt als Messwert zu erhalten, oder muss man das selbst 
programmieren?

Bei letzterem, wie wäre der Ansatz? Über die steigende/fallende Flanke 
eines Interrupt-Pins eine ISR auslösen lassen und dazwischen jeweils die 
CPU-Zyklen zählen um ein Ratio zu erhalten? Und dann eine Messreihe 
integrieren um einen stabilen Wert zu erhalten?

Am Ende hätte ich gern einen DC von 0-100% als Integer Wert. Besondere 
Präzision ist nicht gefragt, 10% Messfehler sind kein Problem.

: Bearbeitet durch User
von pegel (Gast)


Lesenswert?

Es gibt ein Beispielprojekt unter:
~/STM32Cube/Repository/STM32Cube_FW_F1_V1.8.3/Projects/STM32F103RB-Nucle 
o/Examples_MIX/TIM/TIM_PWMInput

von pegel (Gast)


Lesenswert?

Oh, Ardudingens habe ich übersehen, Sorry!

von Kevin M. (arduinolover)


Lesenswert?

Die elegante Variante wäre über einen Timer, allerdings bezweifle ich, 
dass es da für Arduino was Out of the Box gibt. Im Arduinoumfeld wird 
das einfachste vermutlich ein Pinchange Interrupt sein, allerdings kann 
ich dir nicht sagen wie genau das ganze ist. Der Arduino Kram hat doch 
recht viel Overhead.

Grundsätzlich bringt der STM alles mit um das mit hoher Präzision 
komplett in Hardware zu machen, aber da wirst du selbst was schreiben 
müssen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

mit Pin Interrupt und freien Timer spielt der "Arduino Kram" keine 
Rolle.
Hat der STM keinen speziellen Messtimer?

von Jan H. (jan_h74) Flattr this


Lesenswert?

Das wird gemacht mit ein Timer und ein "Input capture". Bei den Pin 
change wird die actuelle Timercounter wert gespeichert und ein Interrupt 
getriggert. So ist die "latenz"nur noch abhangig von Timerfrequenz.

von Stefan F. (Gast)


Lesenswert?

Veit D. schrieb:
> Hat der STM keinen speziellen Messtimer?

Ja schon (jeder Timer kann das), aber Arduino nutzt sie nicht so.

von Olli Z. (z80freak)


Lesenswert?

Also aufs falsche Pferd gesetzt?! I wurde bislang mit STMCube oder Mbed 
nicht so richtig warm... bzw. die Lernkurve ist sehr steil für den 
Basteleinsatz.

Aber unabhängig davon, vielleicht kann mir einer der Herren doch nochmal 
etwas genauer erklären wie das mit einem Timer funktioniert? Das habe 
ich bei der kurzen Erklärung noch nicht ganz verstanden.

Wer mag, gern auch ein kleines Codebeispiel zum besseren Verständnis :-)

von Stefan F. (Gast)


Lesenswert?

Olli Z. schrieb:
> Also aufs falsche Pferd gesetzt?! I wurde bislang mit STMCube oder Mbed
> nicht so richtig warm

Niemand zwingt dich dazu, überhaupt ein Framework zu verwenden. Es geht 
auch ohne. Hier hats du was für den Anfang: 
http://stefanfrings.de/stm32/stm32f1.html

Das Reference Manul ist dein Freund und Helfer.

> Aber unabhängig davon, vielleicht kann mir einer der Herren doch
> nochmal etwas genauer erklären wie das mit einem Timer funktioniert?
> Das habe ich bei der kurzen Erklärung noch nicht ganz verstanden.

Das kann man auch nicht aml eben kurz erklären. Dafür sind die Timer zu 
komplex. Du musst die Beschreibung schon selbst lesen und verstehen. Es 
hilft nichts, wenn wenn dir hier das entsprechende Kapitel des Reference 
Manual abschreiben.

Du brauchst unabhängig davon ein Lösungskonzept. Wie kann man mit einem 
Timer die Pulsbreite messen? Zum Beispiel so:

Du lässt eine Timer mit einer festen Taktfrequenz (z.B. 100kHz) hoch 
zählen. Den Timer konfigurierst du so, dass er bei jeder Flanke des PWM 
Signals einen Interrupt auslöst und sich den dann gerade aktuellen 
Zählerstand merkt (input capture mode).

In der ISR fragst du ab, ob das gerade die steigende oder fallende 
Flanke war. Daraus ergibt sich, ob er gerade die HIGH oder LOW Phase des 
PWM Signals gemessen hat. Außerdem subtrahierst du den vorherigen 
capture-Wert vom aktuellen, um die Breite des PWM Pulses zu ermitteln.

Nun hast du zwei Zahlen: Breite des LOW-Anteils und Breite des 
HIGH-Anteils. Diese dividierst du, dann hast du den Faktor im Bereich 
0-1. Multipliziert mit hundert ergibt den gewünschten Wert in Prozent.

von Herr Bert (Gast)


Lesenswert?

So, wie das hier vielfach vorgeschlagen wird, produziert das aber einen 
großen Fehler. Der Abtastfehler kommt 2x rein, der Jitter des 
abtastenden Taktes auch und vor allem hat man das Problem eine 
jitternden Eingangssignals und das Verhalten des Pins nicht im Griff.

Sauber ist es die Phasen der jeweils korrespondierenden Flanken zu 
messen und den sich bildenen Versatz über mehrere Phasen zu mitteln und 
zufiltern. Dann kriegt man den Abtastfehler und Jitter nur 1x rein und 
hat zudem mit N Phasen den Taktfehler des Eingangs um den Faktor 1/N 
reduziert.

von Stefan F. (Gast)


Lesenswert?

Man könnte es auch pragmatisch angehen und das PWM Signal mittels R/C 
Filter in eine analoge Spannung umwandeln, die man dann wiederum mit 
Arduino ganz einfach auslesen kann. Den digitalen Filter kann man sich 
damit auch gleich einsparen.

Kostet halt 20 Cent für Bauteile.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Olli Z. schrieb:
> die Möglichkeit den Dutycycle eines 3,3V PWM-Signals mit "bliebiger"
> Frequenz (irgendwas zwischen 100 Hz und 1 kHz) direkt als Messwert zu
> erhalten
Wie genau brauchst du den Wert?
Reicht es auf 1/100 genau (z.B. 23%) oder musst du es auf drei 
Nachkommastellen genau wissen (z.B. 23,456%)?

Wie schnell brauchst du den Wert?
Kannst du da schon mal 100 PWM-Zyklen abwarten oder muss unbedingt in 
jedem einzelnen Zyklus ein Wert herauskommen?

Wenn du nämlich ein wenig Zeit hast, dann kannst du auch einfach die 
Mote-Carlo-Methode anwenden und in einem Timertick den Pegel des Signals 
einlesen. Nach ein paar Samples kannst du dann recht gut das Verhältnis 
zwischen high und low in den PWM-Wert umrechnen.

Das geht für ganz Mutige oft auch "hinreichend genau" ganz Monte-Carlo 
mäßig einfach nur per Sampling in der Mainloop. Probiers mal aus...  ;-)

von Johannes S. (Gast)


Lesenswert?

Bei Mbed läuft ein Systemtimer mit 1 MHz, also 1 μs. Die Zeitstempel 
kann man mit einem InterruptIn bei rise/fall erfassen und Pulsweite und 
Frequenz ausrechnen. Ein 1 ms Signal bekommt man damit in der 
gewünschten Genauigkeit aufgelöst.

von Olli Z. (z80freak)


Lesenswert?

Stefan ⛄ F. schrieb:
> Niemand zwingt dich dazu, überhaupt ein Framework zu verwenden. Es geht
Natürlich nicht :-) Beim Arduino fand ich es einfach, installieren, 
loslegen. Beim STM32 muss man sich erstmal zwischen zig Plattformen, 
IDE, Libraries, entscheiden... Nutzt man CMSIS oder HAL?
Ohne "Starthilfe" verliert man sich da ganz schnell.

In Assembler möchte ich das nicht unbedingt machen und ein klein wenig C 
kann ich.


> Wie kann man mit einem Timer die Pulsbreite messen? Zum Beispiel so:

Das aber ist doch im grunde exakt was ich oben in Theorie beschrieben 
hab :-) Es geht darum bei jedem Flankenwechsel die Zeit bis zum nächsten 
zu messen. Die Taktfrequenz des Timers entscheidet ja dann auch über die 
Auflösung des Messergebnisses als auch die Frage wie lang die zu 
messenden Pulsbreiten wohl sein werden. Ich denke das ich das auf eine 
Abstrakte weise schon verstehe, schön wäre halt wenn mir jemand bei der 
Realisierung helfen könnte damit ich die richtigen Schritte tue.

Dabei können wir gern auch mit STM32Cube IDE arbeiten, oder sonst was. 
Ich wollte halt auch sicher gehen das ich mir nicht eine Mordsarbeit 
mache und dann gibt es nachher sowas wie einen HAL_getPWM() oder so ;-)

von Gerd E. (robberknight)


Lesenswert?

Ich empfehle aus dem Reference Manual von Deinem STM32 mal das Kapitel 
zu den Advanced Timern (z.B. TIM1) durchzulesen. Den Teil zu PWM-Output 
kannst Du überfliegen, Dich interessiert hier das Input Capture, und 
zwar über 2 gekoppelte Kanäle um Rising und Falling-Events gleichzeitig 
zu bekommen. Soo kompliziert und viel zu lesen ist das dann auch nicht.

So, und jetzt könntest Du Dein restliches Arduino-Framework einfach 
lassen wie es bisher ist und nur in einer Funktion den Timer über die 
entsprechenden Register entsprechend initialisieren und abfragen.

Ich vermute mal stark daß Arduino die CMSIS-Header bereitstellt, so daß 
Du die einfach includen kannst und nicht alle Registeradressen von Hand 
berechnen und eintragen musst.

von Olli Z. (z80freak)


Lesenswert?

Herr Bert schrieb:
> Sauber ist es die Phasen der jeweils korrespondierenden Flanken zu
> messen und den sich bildenen Versatz über mehrere Phasen zu mitteln
Und das ist doch genau der von mir oben beschriebene Integral?! Eben um 
kleine Signal- oder Messungenauigkeiten zu unterdrücken.

von Olli Z. (z80freak)


Lesenswert?

Lothar M. schrieb:
> Das geht für ganz Mutige oft auch "hinreichend genau" ganz Monte-Carlo
> mäßig einfach nur per Sampling in der Mainloop. Probiers mal aus...  ;-)

Gern, allein ein Beispiel wäre gut...

von Olli Z. (z80freak)


Lesenswert?

Ich bin wirklich dankbar für Eure Hinweise und Beiträge, fürchte nur das 
zwischen Euren und meinem Know-How hier eine kleine Welt liegt. Ich 
glaube zwar weder faul noch doof zu sein, aber manchmal braucht man 
einfach etwas tatkräftigen Anschub... ich finde das der Einstieg in 
STM32 deutlich schwieriger ist als beim AVR, dennoch glaube ich das der 
STM die bessere Plattform ist, weshalb ich daran schon festhalten 
möchte.

Ich versuche mich jetzt erstmal mit Stefans Links und den dahinter 
liegenden Infos.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Olli Z. schrieb:
> Gern, allein ein Beispiel wäre gut...

Ach Olli. Wenn dir das Reference Manual zu viel ist, dann ist dir der 
Mikrocontroller zu viel. Einfacher sind nur die 8bit Controller, aber 
auch da musst du lesen.

Ich werde dir das Kapitel jedenfalls nicht übersetzen und vorkauen.

Den einfacheren weg über R/C Filter und analogem Eingang habe ich dir 
schon genannt. Du musst dein Hobby aber selbst durchführen.

Alternativ: Fernsehgucken. Da machen andere was für dich und du kannst 
zugucken.

von Olli Z. (z80freak)


Lesenswert?

Stefan ⛄ F. schrieb:
> Alternativ: Fernsehgucken. Da machen andere was für dich und du kannst
> zugucken.
Falsch, die machen nie was ICH will! ;-)
Stefan, mit solchen Sätzen schadest Du nur unnötig Deinem Image...
Und wenn Du glaubst das ich hier nur sitze und F5 drücke, dann täuschst 
Du dich gewaltig.

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Olli Z. schrieb:
> Falsch, die machen nie was ICH will!

Regisseur werden könnte ein Weg sein :-)

von Harald (Gast)


Lesenswert?

Man kann doch einen 100us Interrupt anlegen und den Input einfach 
pollen. Ist zwar eine grandiose Verschwendung/Verachtung von gegebenen 
Systemressourcen aber es passt evtl. zum eigenen Programmiervermögen.

von Veit D. (devil-elec)


Lesenswert?

Hallo,

@ Olli Z.
Die Microchip megaAVR0 und AVRxDB Serie haben auch Messtimer. Auf den 
Teilen kann ich dir das erklären, weil das ganz simpel ist. Ein Arduino 
Nano Every hat einen ATmega4809 (megaAVR0) drauf. Wäre ein 16Bit Timer. 
Wenn millis benötigt wird gibts nur Prescaler 1 oder 2. Wenn auf millis 
(bzw. das gesamte Arduino Framework) verzichtet werden kann gibts mehr 
Prescaler um den Messbereich anzupassen. Wobei man millis noch 
korrigieren könnte, ist auch möglich, wenn millis doch benötigt wird.
Ansonsten musste dich beim STM durchbeißen. Das Prinzip wird ähnlich 
sein, nur das die viel viel mehr Register haben.
Das wäre die hochgenaue Variante.

Wie schon gesagt wurde kannste das auch alles im Pin Interrupt 
erledigen, mit minimaler Messungenauigkeit. Musst dir nur immer merken 
welche Flanke du gerade gemessen hast und den Timerwert dafür 
hinterlegen.

PW:
1. steigende Flanke > Timercounter merken
2. fallende Flanke  > Timercounter merken
3. PW vorbei, PW berechnen.
4. auf nächste steigende Flanke warten und bei 1. beginnen.

PW und Frequenz:
1. steigende Flanke > Timercounter merken
2. fallende Flanke  > Timercounter merken
3. steigende Flanke > Timercounter merken
4. Periode vorbei, PW und Frequenz berechnen.
5. auf nächste steigende Flanke warten und bei 1. beginnen.
Hierbei kann nur jede 2. Periode vermessen werden.
Bauste dir ein schönes switch case zusammen. Das läuft.

Du kannst das auch ohne speziellen Timer machen. Nimmst dafür millis 
oder micros. Dann schauste wie genau das wird und ob es dir reicht.

: Bearbeitet durch User
von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

Ich habe mir nun ein kleines Beispiel zusammengeschrieben und einen 
PWM-Generator angeschlossen. Die Frequenz wird optimal angezeigt, 
allerdings ist der Duty-Cycle immer "0". Irgendwie finde ich den Fehler 
nicht...

Ich verwende den externen Quarz mit 8 MHz auf dem Bluepill Board und 
teile ihn so das am Timer 4 MHz ankommen. Damit erreiche ich eine 
ausreichend grobe Auflösung um so niedrige Frequenzen wie 250 Hz messen 
zu können.

: Bearbeitet durch User
von m.n. (Gast)


Lesenswert?

Ein Beispiel mit F407 für 0,05 Hz - 500 kHz:
http://www.mino-elektronik.de/FM_407/fmeter_407.htm#a5
Ein Programm für ATmega88, wobei nur eine Pulsweite ermittelt wird:
http://www.mino-elektronik.de/fmeter/fm_software.htm#bsp6

Sofern Dir 10% Genauigkeit reichen kann man das Signal auch über ein 
RC-Glied filtern, skalieren 3,3 V -> 1 V und mit einem Multimeter in 
Prozent anzeigen.
Bei einem Drehspulinstrument kann man sich das RC-Glied sparen :-)

von Olli Z. (z80freak)


Lesenswert?

Gibt es auch noch einen sinnvollen Kommentar? Evtl. ein Hinweis auf 
einen Programmierfehler in meinem Code?

von jo mei (Gast)


Lesenswert?

Olli Z. schrieb:
> Evtl. ein Hinweis auf einen Programmierfehler in meinem Code?
1
uint32_t Frequency = 0;
2
uint32_t Duty_Cycle = 0;

Diese Variablen werden im Interrupt-Kontext berechnet und im
normalen Programmlauf angezeigt. Deswegen empfiehlt es sich
die Variablen als volatile zu deklarieren. Sonst könnte der
Compiler auf die Idee kommen zu optimieren und dadurch fälsch-
licherweise jeweils zwei verschiedene Werte der Variablen
parallel zu halten. Das Ganze erübrigt sich natürlich wenn
die Compiler-Optimierungsstufe auf Null gesetzt ist.

von Olli Z. (z80freak)


Lesenswert?

jo mei schrieb:
> Diese Variablen werden im Interrupt-Kontext berechnet und im
> normalen Programmlauf angezeigt. Deswegen empfiehlt es sich
> die Variablen als volatile zu deklarieren. Sonst könnte der

Guter Tipp, hat aber leider nichts am Problem geändert. Die Frequenz 
wird einwandfrei angezeigt, der Dutycycle bleibt immer 0.

von Olli Z. (z80freak)


Lesenswert?

jo mei schrieb:
> Diese Variablen werden im Interrupt-Kontext berechnet und im
> normalen Programmlauf angezeigt. Deswegen empfiehlt es sich
> die Variablen als volatile zu deklarieren. Sonst könnte der

Guter Tipp, hat aber leider nichts am Problem geändert. Die Frequenz 
wird einwandfrei angezeigt, der Dutycycle bleibt immer 0. Er wird auch 
als 0 berechnet, sprich wenn ich den Startwert anders setzte, gibt es im 
Display trotzdem immer eine 0.
Mir scheint als würde IC_Val2 überhaupt keinen anderen Wert erhalten, 
außer 0. Wenn ich Duty_Cycle = IC_Val1; in der Sub mache, erhalte ich 
Werte, wenn ich Duty_Cycle = IC_Val2; setze nicht. D.h. die Falling-Edge 
wird nicht gemessen.

: Bearbeitet durch User
von m.n. (Gast)


Lesenswert?

Olli Z. schrieb:
> Mir scheint als würde IC_Val2 überhaupt keinen anderen Wert erhalten,
> außer 0.

Dann lass doch bitte diese ganze HAL-Gestrüpp in der Kiste und 
programmiere die Timerregister direkt. Da sieht man dann, was passieren 
soll und kann überprüfen, ob es so auch funktioniert.

von c.w. (Gast)


Lesenswert?

Du verwendet TIM_ICSELECTION_DIRECTTI für beide Channel. Damit muss das 
Signal auch an beiden Timer Eingängen (Channel 2 und Channel 3) 
anliegen.

Ist das so verdrahtet?

Es ist einfacher einen Kanal mit TIM_ICSELECTION_DIRECTTI und einen mit 
TIM_ICSELECTION_INDIRECTTI zu verwenden. (Pin reusen)

von Olli Z. (z80freak)


Angehängte Dateien:

Lesenswert?

c.w. schrieb:
> Ist das so verdrahtet?

AAAAAARGH! DAS wars, ich war einen Pin daneben gerutscht weil das 
Bluepill board ein wenig bescheiden beschriftet ist an den pins. GRRR, 
ok jetzt läuft es wie es soll.

Danke für den entscheidenden Hinweis, ich hatte die Verdrahtung nicht in 
Zweifel gezogen.

von E-Autobesitzer 🚗 (Gast)


Lesenswert?

Olli Z. schrieb:
> ...jetzt läuft es wie es soll.

Ohh, das ist aber interessant! Wenn du jetzt noch im Programm die 
Pulsweite mit 0,6 multiplizierst, hättest du ganz nebenbei eine 
Klartext-Ladestromanzeige für Ladesäulen entwickelt (die Frequenz ist 
bei den Ladesäulen konstant 1000 Hz).

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.