Forum: Mikrocontroller und Digitale Elektronik PWM für langsame periodische Signale


von Tilo (Gast)


Angehängte Dateien:

Lesenswert?

Hallo

Ich will einen langsamen Sinus erzeugen. Ich habe mir überlegt, das 
ganze mit PWM zu realisieren. Die Frequenz soll bei max. 100Hz liegen. 
Bei klassischem PWM, bei dem der Sinus mittels Filtern aus einem 
Rechteck erzeugt wird, wäre das Signal nicht schön und es werden große 
Filter benötigt.
Daher habe ich mir überlegt, den Sinus mit einem deutlich schnelleren 
PWM Signal zu erzeugen, bei dem laufend das on/off Verhältnis verstellt 
wird.
Die Ausgabe würde dann z.B. so aussehen:
high:     X    X  X X
          X    X  X X
          X    X  X X
 low:_____X____X__X_X[...]

In diesem Fall würde ein kleiner Tiefpass als Integrator ausreichen, um 
ein schönes Signal zu erzeugen:
high:              --
                ---
           -----
 low:------

Die PWM wollte ich mit einem kleinen AVR erzeugen, bin da allerdings 
noch nicht so ganz fit. Als Controller verwende ich einen ATTiny462. 
Gesteuert wird die PWM über Timer0 und seine beiden Compare Match 
Register. In CompareMatchA ist dabei die Periode gespeichert. Zu beginn 
einer Periode wird der Pin auf low gelegt. In CompareMatchB wird dann 
der Schaltzeitpunkt geladen, zu dem der Pin auf high gelegt werden soll. 
Nach Ablauf der Periode wird der Pin wieder zurück auf low gelegt.

Im AVR Studio Simulator funktioniert alles wie gewollt. Leider tut das 
nicht in der Realität. Verwende ich in der Tabelle durchgehend den 
selben Wert, z.B. 0x50, stimmt alles. Im nächsten Schritt wollte ich 
jeden 2. Wert der Tabelle mit 0x10 ersetzen. Ich hätte erwartet, einen 
Rechteck mit abwechselnd langer und kurzer off-Zeit zu erhalten. Statt 
dessen erhalte ich das ERgebnis, dass ich erwartet hätte, wenn in der 
Tabelle 16x 0x10 eingetragen wäre.

Ich bin jetzt ein wenig ratlos. Habe ich etwas nicht bedacht? Die 
Periode für die IRQs sollte bei einem Prescaler von 8 ausreichen oder?

Wäre das etwas für die Codesammlung wenn es läuft?

Viele Grüße, Tilo

von Karl H. (kbuchegg)


Lesenswert?

Deine Verwendung des Timers ist ....... unüblich.
Allerdings sollte es funktionieren ich seh aber im Code das Problem 
nicht, warum es nicht geht. Kannst du eine Fehlinterpretation deiner 
Messung bzw. überhaupt eine fehlerhafte Messung (flascher Trigger am 
Oszi. etc) ausschliessen?


Zum Code selber:
Dir ist klar, dass du dir eine Menge Mühe für nichts machst, weil sich 
90% deines Codes damit beschäftigen etwas nachzustellen, was die 
Hardware ganz von alleine machen kann?

(Nimm wenigstens das Umschalten der Enable Bits für die beiden Compare 
Matches raus. Solange der Wert den du ins B-Register schreibst kleiner 
als der im A-Register ist, treten die schon schön im Wechsel auf)


> Wäre das etwas für die Codesammlung wenn es läuft?

http://www.mikrocontroller.net/articles/Digitaler_Funktionsgenerator

Aber lass dich nicht aufhalten einen Artikel für die Codesammlung fertig 
zu machen.

von Tilo (Gast)


Lesenswert?

Hallo

:) Ich weiß. Ich denke gerne mit dem Finger von hinten ins Auge ;)

Ich habe mir überlegt, das ganze mit Hardware-PWM zu machen. Allerdings 
habe ich das Handbuch so verstanden, dass damit das Tastverhältnis immer 
fest ist.

DDS kenne ich, allerdings wird in dem Artikel ein DAC verwendet, den ich 
weglassen wollte.

Viele Grüße, Tilo

von Lehrmann M. (ubimbo)


Lesenswert?

Tilo schrieb:
> Ich habe mir überlegt, das ganze mit Hardware-PWM zu machen. Allerdings
> habe ich das Handbuch so verstanden, dass damit das Tastverhältnis immer
> fest ist.

Das ist aber ein komisches Handbuch !
Der Sinn und Zweck von PWM ist, dass man das Tastverhältniss verändern 
kann. Die Frequenz ist meist nur sekundär und wird nicht verändert 
sondern je nach Einsatzgebiert einmal ausgesucht (damit wird das PWM 
Modul initialisiert!)
Wenn man z.B. LEDs dimmen will initialisiert man beispielsweise mit 5kHz 
und dimmen tut man dann mittels Tastverhältnis (meist 8Bit = 2^8 = 255 
Schritte).

von Lehrmann M. (ubimbo)


Lesenswert?

Als Beispiel vom PIC mit Microe Compiler:

[alles mögliche Initialisieren]

int i=0;

PWM1_Init(5000); // Initialisiere PWM mit 5kHz
PWM1_Start;

for(i=0;i<256;i++){
 PWM1_Set_Duty(i);
 Delay_ms(80);
}

for(i=255;i>0;i--){
 PWM1_Set_Duty(i);
 Delay_ms(80);
}


// Ende

Das Programm dimmt die LED von 0 bis maximum und wieder zurück. 
(Nonlinearität der LED-Dimmung mal ausgenommen).

Siehst du was ich meine? Man arbeitet mit dem Duty-Cycle und nicht mit 
der Frequenz!

von Tilo (Gast)


Angehängte Dateien:

Lesenswert?

Danke.

Ich hab das Handbuch nochmal genau gelesen. Ich war wohl beim ersten mal 
nicht so ganz auf der Höhe. Diesmal habe ich einige Dinge die keinen 
Sinn ergeben einfach ignoriert und etwas brauchbares hinbekommen.

von Karl H. (kbuchegg)


Lesenswert?

OK.
Natürlich wirst du noch das Weiterschalten von einem Datenbyte zum 
nächsten vom PWM Timer entkoppeln indem du zb dafür einen anderen Timer 
benutzt. Denn erstens willst du ja deinen Sinus mit einer bestimmten 
Frequenz erzeugen, die erst mal nichts mit der PWM Frequenz zu tun hat 
und zum anderen möchtest du ja die PWM im Interesse einer möglichst 
guten Ausgangsspannung nach der Filterung mit maximaler Frequenz (sprich 
einem Vorteiler von 1) laufen lassen.

Noch ein Tip:
Wenn du dir an das Ende deines Datenfeldes noch ein label machst, dann 
brauchst du in der Reloadroutine die Anzahl der Datenbytes nicht wissen, 
weil du dann einfach dieses Label für die Erkennung der letzten gültigen 
Adresse hernehmen kannst.
Das spart einen möglichen Fehler, wenn du in das Datenfeld welches deine 
Schwingung beschreibt noch Bytes einfügst oder rausnimmst.
1
Timer1_Reload:
2
  LPM R16, Z+            ; Load new value from PWM table
3
  OUT OCR1D, R16
4
  LDI R17, HIGH(2*sine_end)  ; Copy end address of table
5
  LDI R16, LOW(2*sine_end)   ; Copy end address of table
6
7
  ....
8
9
; this table contais off values. period is 0xA3. the difference between 0xA3 and value is the on value
10
sine:
11
    .db 0x52, 0x4F, 0x4E, 0x4C, 0x4A, 0x48, 0x46, 0x44, 0x42, 0x40, 0x3E, 0x3C, 0x3A, 0x38, 0x36, 0x34
12
13
....
14
  .db 0x71, 0x6F, 0x6D, 0x6B, 0x69, 0x67, 0x65, 0x63, 0x61, 0x5F, 0x5D, 0x5B, 0x59, 0x57, 0x55, 0x54
15
16
sine_end:

von Tilo (Gast)


Lesenswert?

Guten morgen

Die Entkopplung ist eine gute Idee. Ich habe nur leider keinen freien 
Timer mehr,  Timer0 brauche ich für etwas anderes. Da fällt mir aber 
vieleicht noch etwas ein, eventuell eine Zählvariable und gut. Der Timer 
kann mit einem höhrem asynchronen Takt betrieben werden. Damit könnte 
ich auch noch etwas raus holen, muss mir aber noch genau anschauen, wie 
das tut.

Das Label am Ende der Tabelle ist eine gute Idee. Der Schwachpunkt ist 
mir auch aufgefallen. Ich habe zu erst an sizeof() gedacht. Allerdings 
kann der Assembler nicht wissen, wie groß die Tablle ist. Auf die Idee 
ein Label zu verwenden bin ich nicht gekommen. Klingt aber logisch, 
Labels sind auch nicht mehr dynamische Aliase für eine Adresse im 
Programmspeicher.

Viele Grüße, Tilo

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.