Forum: Mikrocontroller und Digitale Elektronik Arduino Uno / Atmega 328P Sinus PWM


von Peter (Gast)


Angehängte Dateien:

Lesenswert?

Hi,

zunächst einige Daten zu dem Projekt:
1)Verwendet wird ein Arduino Uno mit einem Atmega 328p
2)Ziel Sinus-PWM generieren mit anschließendem Tiefpass zum abschneiden 
der Harmonischen.
3)Ich habe hier 2 Look Up -Tables in Calc vorgeschrieben welche einmal 
einen Sinus mit 50 Hz und 200 Hz realisieren sollen bei denen ich später 
hin und her schalten möchte.(Ich weiß ich habe hier viel zu viele Werte 
eingespeichert das kann man sicherlich durch Spiegelung etc. eleganter 
lösen ich wollte hier trotzdem erst einmal das Grundprinzip verstehen 
das geht ja auch wenn ich die Werte von 0-2pi plotte).

Probleme:

1)Der Sinus der ausgegeben wird siehe Oszi (0.2ms/Div) hat nicht die 
gewünschten 50Hz sondern hat ca 1.67kHz warum?
2)Sobald ich den 2ten Sinus durchlaufen lasse ist dieser zwar schneller 
aber die Frequenz passt immer noch nicht mit den gewünschten 200Mhz 
überein
3)Wie erzeuge ich mir also eine richtige Lookup-Table für 0-2pi sodass 
hinten die richtige Frequenz raus kommt (ich weiß man bräuchte 
eigentlich nur 0-pi/2 aber um es zu vereinfachen langt mir erstmal von 
0-2pi).

Ich bin leider etwas neu im Programmieren mit Registern deshalb sorry, 
falls etwas nicht schlüssig sein sollte.

Mfg

Peter

Code:
//lookup1 -> sinus  50 HZ
//lookup2 -> sinus  200 HZ
int lookUp1[]= {128,  147,  167,  185,  202,  218,  231,  241,  249, 
253,  255,  253,  249,  241,  231,  218,  202,  185,  167,  147,  128, 
108,  88, 70, 53, 37, 24, 14, 6,  2,  0,  2,  6,  14, 24, 37, 53, 70, 
88, 108,  128,  147,  167,  185,  202,  218,  231,  241,  249,  253, 
255,  253,  249,  241,  231,  218,  202,  185,  167,  147,  128,  108, 
88, 70, 53, 37, 24, 14, 6,  2,  0,  2,  6,  14, 24, 37, 53, 70, 88, 108, 
128,  147,  167,  185,  202,  218,  231,  241,  249,  253,  255,  253, 
249,  241,  231,  218,  202,  185,  167,  147,  128,  108,  88, 70, 53, 
37, 24, 14, 6,  2,  0,  2,  6,  14, 24, 37, 53, 70, 88, 108,  128,  147, 
167,  185,  202,  218,  231,  241,  249,  253,  255,  253,  249,  241, 
231,  218,  202,  185,  167,  147,  128,  108,  88, 70, 53, 37, 24, 14, 
6,  2,  0,  2,  6,  14, 24, 37, 53, 70, 88, 108,  128,  147,  167,  185, 
202,  218,  231,  241,  249,  253,  255,  253,  249,  241,  231,  218, 
202,  185,  167,  147,  128,  108,  88, 70, 53, 37, 24, 14, 6,  2,  0, 
2,  6,  14, 24, 37, 53, 70, 88, 108,  128,  147,  167,  185,  202,  218, 
231,  241,  249,  253,  255,  253,  249,  241,  231,  218,  202,  185, 
167,  147,  128,  108,  88, 70, 53, 37, 24, 14, 6,  2,  0,  2,  6,  14, 
24, 37, 53, 70, 88, 108,  128,  147,  167,  185,  202,  218,  231,  241, 
249,  253,  255,  253,  249,  241,  231,  218,  202,  185,  167,  147, 
128,  108,  88, 70, 53, 37, 24, 14, 6,  2,  0,  2,  6,  14, 24, 37, 53, 
70, 88, 108,  128,  147,  167,  185,  202,  218,  231,  241,  249,  253, 
255,  253,  249,  241,  231,  218,  202,  185,  167,  147,  128,  108, 
88, 70, 53, 37, 24, 14, 6,  2,  0,  2,  6,  14, 24, 37, 53, 70, 88, 108, 
128,  147,  167,  185,  202,  218,  231,  241,  249,  253,  255,  253, 
249,  241,  231,  218,  202,  185,  167,  147,  127,  108,  88, 70, 53, 
37, 24, 14, 6,  2,  0,  2,  6,  14, 24, 37, 53, 70, 88, 108,  128,  147, 
167,  185,  202,  218,  231,  241,  249,  253,  255,  253,  249,  241, 
231,  218,  202,  185,  167,  147,  128,  108,  88, 70, 53, 37, 24, 14, 
6,  2,  0,  2,  6,  14, 24, 37, 53, 70, 88, 108,  128,  147,};
int lookUp2[]= {128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128, 
202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249, 
202,  128,  53, 6,  6,  53, 128,  202,  249,  249,  202,  128,  53, 6, 
6,  53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202, 
249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249,  202, 
128,  53, 6,  6,  53, 128,  202,  249,  249,  202,  128,  53, 6,  6, 
53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249, 
249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249,  202,  128, 
53, 6,  6,  53, 128,  202,  249,  249,  202,  127,  53, 6,  6,  53, 128, 
202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249, 
202,  127,  53, 6,  6,  53, 128,  202,  249,  249,  202,  127,  53, 6, 
6,  53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202, 
249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249,  202, 
128,  53, 6,  6,  53, 128,  202,  249,  249,  202,  128,  53, 6,  6, 
53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249, 
249,  202,  127,  53, 6,  6,  53, 128,  202,  249,  249,  202,  128, 
53, 6,  6,  53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128, 
202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249, 
202,  127,  53, 6,  6,  53, 128,  202,  249,  249,  202,  128,  53, 6, 
6,  53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202, 
249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249,  202, 
127,  53, 6,  6,  53, 128,  202,  249,  249,  202,  127,  53, 6,  6, 
53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249, 
249,  202,  128,  53, 6,  6,  53, 127,  202,  249,  249,  202,  127, 
53, 6,  6,  53, 128,  202,  249,  249,  202,  127,  53, 6,  6,  53, 128, 
202,  249,  249,  202,  127,  53, 6,  6,  53, 128,  202,  249,  249, 
202,  127,  53, 6,  6,  53, 127,  202,  249,  249,  202,  127,  53, 6, 
6,  53, 128,  202,  249,  249,  202,  128,  53, 6,  6,  53, 128,  202, 
249,  249,  202,  128,  53, 6,  6,  53, 128,  202,  249,  249,  202, 
128,  53, 6,  6,  53, 128,  202,};
const int ledPin = 3; //digitalerPin11
int i = 0;

void setup() {

DDRB |=(1<<ledPin); //setze LEDPin als Ausgang
TCCR2A =(1<<WGM21)|(1<<WGM20) //Lege Timer fest mit Vergleich am OCRA 
Register und Fast PWM
        |(1<<COM2A1);         //Lege VergleichsRegister an OC2A BOTTOM 
set
TCCR2B = (1<<CS20); //Prescaler hier ohne Prescaler
OCR2A = lookUp1[i]; //->AusgangsSpannung
TIMSK2 = (1<<OCIE2A); //Enable den Overflow ISR
sei();  //globale Interrupts alle on


}

//loop gehen wir nicht mehr
void loop() {
  }

//Timer Interrupt
ISR(TIMER2_COMPA_vect){

  if(i<sizeof(lookUp1)/sizeof(int)){
  i++;
  OCR2A = lookUp1[i]; //->AusgangsSpannung
  }else{
   i=0;
   }
}

von Peter (Gast)


Lesenswert?

> aber die Frequenz passt immer noch nicht mit den gewünschten 200Mhz
> überein

Sollte 200 Hz heißen

von S. Landolt (Gast)


Lesenswert?

Nun ja, der Uno läuft, glaube ich, mit 16 MHz, der Timer2 über seine 
vollen 8 bit: 16 MHz / 2^8 = 62500 Hz. Dies geteilt durch die Anzahl der 
Werte pro Wellenzug ergibt die resultierende Frequenz. Die Tabelle 
abzählen erspare ich mir jetzt.

von c-hater (Gast)


Lesenswert?

Peter schrieb:

> 1)Der Sinus der ausgegeben wird siehe Oszi (0.2ms/Div) hat nicht die
> gewünschten 50Hz sondern hat ca 1.67kHz warum?

Ja warum wohl? Offensichtlich gibst du die Werte aus der Tabelle zu 
schnell aus. Um mit deiner Methode auf die korrekte Frequenz zu kommen, 
bräuchtest du eine sehr viel größere Sinustabelle.

Deswegen benutzt man üblicherweise ein Methode, die sich DDS nennt, um 
den Fortschritt in der Sinustabelle so zu steuern, dass sich die 
gewünschte Ausgabefrequenz ergibt. Das hat außerdem noch den Vorteil, 
dass man nur eine einzige Sinustabelle benötigt, um unterschiedliche 
Frequenzen erzeugen zu können.

Als "Controllator" für den Tabellenfortschritt benutzt man 
typischerweise  Festkomma-Arithmetik, die so gestaltet ist, dass der 
ganzzahlige Teil den Tabellenindex darstellt. Bei jedem Interrupt 
addierst du dann einfach eine Konstante in diesem FP-Format zu einer 
Laufvariable im gleichen Format. Den ganzzahligen Teil der Laufvariable 
trennst du dann ab und benutzt ihn als Tabellenindex, um an den 
Ausgabewert zu kommen.
Aus Gründen der Laufzeiteffizient ist es übrigens sinnvoll, der 
Sinustabelle die Größe einer Zweierpotenz zu geben, also z.B. 256, 512 
oder 1024 Samples, dann kann man die FP-Arithmetik auch bezüglich des 
Überlaufproblems am Tabellenende sehr schön einfach halten.

> //Timer Interrupt
> ISR(TIMER2_COMPA_vect){
>
>   if(i<sizeof(lookUp1)/sizeof(int)){
>   i++;
>   OCR2A = lookUp1[i]; //->AusgangsSpannung
>   }else{
>    i=0;
>    }
> }

Das war übrigens selbst für deinen Ansatz falsch. Du mußt immer einen 
Wert ausgeben, auch bei Tabellenende. Die Ausgabe gehört also hinter das 
if-else-Konstrukt, nicht in einen der beiden Zweige desselben.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Ich habe jetzt auch nicht die Tabelle abgezählt, um herauszufinden, 
wieviel Steps ein voller Wellenzug beinhaltet.

Sinnvollerweise betreibst du den Timer im CTC Modus, um die 
Abtastgeschwindigkeit der Tabelle einzustellen.

von S. Landolt (Gast)


Lesenswert?

an Peter:

>  Anzahl der Werte pro Wellenzug
'Wellenzug', wohlgemerkt, davon stehen offenbar in der Tabelle mehrere.

von Peter (Gast)


Angehängte Dateien:

Lesenswert?

> Sinnvollerweise betreibst du den Timer im CTC Modus, um die
> Abtastgeschwindigkeit der Tabelle einzustellen.

Also wie gesagt in der Uni haben wir das nie gelernt wie man sowas 
wirklich in die Praxis umsetzt haha, aber ich hab gedacht die 
Schaltfrequenz von meinem
Sägezahn-Signal muss immer konstant sein und das ist es ja grade nicht 
beim CTC Modus oder?

von Peter (Gast)


Lesenswert?

S. Landolt schrieb:
> an Peter:
>
>>  Anzahl der Werte pro Wellenzug
> 'Wellenzug', wohlgemerkt, davon stehen offenbar in der Tabelle mehrere.

Ja ich weiß hab halt mal ein paar mehr gemacht

von S. Landolt (Gast)


Lesenswert?

Mal den Mode 5 näher anschauen: da lässt sich mit OCRA der TOP-Wert und 
damit die Frequenz vorgeben, mit OCRB die Amplitude (d.h. die 
Tabellenwerte kommen bei diesem Modus in OCRB).
  Allerdings wird damit die Frequenz erstmal nur höher; für eine 
niedrigere Frequenz benötigt man mehr Werte pro Wellenzug oder man muss 
die paar vorhandenen mehrfach hintereinander zuweisen.

von S. Landolt (Gast)


Lesenswert?

"Uni"? - Dann sollte doch der Wikipedia-Artikel ausreichen:
https://de.wikipedia.org/wiki/Direct_Digital_Synthesis

von S. Landolt (Gast)


Lesenswert?

PS:
Vielleicht findet sich auch etwas hier in der Artikelsammlung des 
Forums, wäre dann sicher praxisnäher.

von Peter (Gast)


Lesenswert?

S. Landolt schrieb:
> "Uni"? - Dann sollte doch der Wikipedia-Artikel ausreichen:
> https://de.wikipedia.org/wiki/Direct_Digital_Synthesis


Erstmal danke für die ganzen Antworten ja da muss ich mich jetzt erstmal 
rein arbeiten :).
Ja Uni deswegen kommt man aber lange nicht weiter nur weil man einen 
Wiki Artikel gelesen hat der gibt mir max. einen groben Überblick haha. 
Außerdem studiere ich Elektrotechnik und keine Info. also gehört so ein 
Projekt grade nicht zu meinem Tagesgeschäft mache das alles neben dem 
ganzen Lern-Stress in meiner Freizeit also alles mit der Ruhe haha.

von S. Landolt (Gast)


Lesenswert?

Okay - hier gibt es viele, die gerne weiterhelfen, auch bei den 
Grundlagen.
  Dann also erstmal einen Abend in Ruhe.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Peter schrieb:
> Sägezahn-Signal muss immer konstant sein und das ist es ja grade nicht
> beim CTC Modus oder?

Der CTC Modus ist das gleiche wie ein OVF, nur das der OVF immer beim 
Überlauf auftaucht und im CTC Modus beim Vergleichswert, sozusagen ein 
einstellbarer Überlauf. Während der Timer im Normalmodus also immer beim 
Überlauf von 0xFF nach 0 die ISR auslöst, tut er das im CTC Modus also 
früher.
Damit kann man die Abtastfrequenz erhöhen. Um die Abtastfrequenz zu 
verlangsamen, kann man in der ISR eine Variable X hochzählen und das PWM 
Register erst laden, wenn diese einen bestimmten Wert erreicht hat.

Wenn die Tabelle fix ist, kann man auch eine Kombination beider Methoden 
verwenden, um möglichst viele verschiedene Frequenzen zu erzeugen. Dabei 
wird eine Verkleinerung des CTC Vergleichswertes die Frequenz erhöhen 
und eine Vergrösserung von X die Frequenz senken.

von Peter (Gast)


Lesenswert?

Matthias S. schrieb:
> Damit kann man die Abtastfrequenz erhöhen. Um die Abtastfrequenz zu
> verlangsamen, kann man in der ISR eine Variable X hochzählen und das PWM
> Register erst laden, wenn diese einen bestimmten Wert erreicht hat.
>
> Wenn die Tabelle fix ist, kann man auch eine Kombination beider Methoden
> verwenden, um möglichst viele verschiedene Frequenzen zu erzeugen. Dabei
> wird eine Verkleinerung des CTC Vergleichswertes die Frequenz erhöhen
> und eine Vergrösserung von X die Frequenz senken.

Ah okay das hört sich nach einer Idee an danke :

also das heißt den CTC Wert verkleinern erhöht mir die Abtastfrequenz je 
schneller die Frequenz meines Sinus desto schneller muss ich natürlich 
abtasten nach Nyquist ja min. 2*fmax = fabtast. Und mit der 
Zähl-Variable steuer ich mir quasi welche Frequenz hinten raus kommen 
soll.

Jetzt dazu einige Fragen:
1)
Die Abtast Frequenz ist ja dementsprechend nicht mehr constant wir 
hatten gelernt diese Immer constant zu lassen und deshalb eine sehr 
schnelle Frequenz zu wählen das wir immer genug Samples haben, deshalb 
habe ich auch in meinem Code keinen Prescaler verwendet. Nun habe ich 
gedacht das das Sägezahnssignal unabhängig von meinem auf moduliertem 
Signal nämlich dem Sinus ist solang der Sägezahn schneller ist als 
dieser (nur dann sind ja genügend Samples da). Deshalb verstehe ich 
nicht warum sich jetzt durch eine höhere Abtast-Frequenz irgendwas an 
der Frequenz meines Sinus ändern soll es ist doch immer noch der gleiche 
Sinus ?

2)
Was ich auch immer noch nicht verstanden habe bei der ISR musste ich nie 
das Bit für den Ausgang an PORTB setzen sondern das wurde schon immer 
automatisch anscheinend Hardware Intern gemacht. Wie kann ich jetzt mit 
einer Laufvariable verhindern das meine ISR die ganze Zeit ausgeführt 
wird? nach Compare versteht sich. Soll ich einfach solange ich nicht 
will das geschaltet wird einfach OCR2A auf einem konstanten Wert lassen 
? Oder wie soll ich das mit der Zählvariable machen?


Code:


//Timer Interrupt
ISR(TIMER2_COMPA_vect){

  if(i<sizeof(lookUp1)/sizeof(int)){
  i++;
  }else{
   i=0;
   }
   OCR2A = lookUp1[i]; //->AusgangsSpannung
}

von M. K. (sylaina)


Lesenswert?

Peter schrieb:
> desto schneller muss ich natürlich
> abtasten nach Nyquist ja min. 2*fmax = fabtast

Ich denke genau das ist dein Problem.

1. Da gehört kein = hin sondern ein < nach Nyquist ;)
2. Du weißt ja schon, dass du einen Sinus haben wirst und auch die 
Frequenz ist bekannt (entweder 50 Hz oder 200 Hz). Muss dann immer noch 
das Nyquist-Kriterium fabtast > 2*fmax erfüllt sein? Denk mal drüber 
nach...und denke auch dran, du willst ja den Sinus generieren, nicht 
messen ;)


Ich hab mal für eine Resolver-Auswertung ein Sinus generieren lassen mit 
144 (alternativ 57 und 28) Stützstellen im Sinus, die Frequenz des 
Referenzsignals lies sich zwischen 200 Hz und 2 kHz dabei einstellen und 
ich nutzte dabei den OVF-Interrupt um in das PWM-Register den nächsten, 
passenden Wert zu schreiben. Die Frequenz des Sinus ergab sich aus der 
Anzahl der Stützstellen (weniger Stellen, Array ist schneller 
durchlaufen, Frequenz des Sinus ist höher). Der Vorteil dieses 
Verfahrens war/ist dass ich immer einen vollständigen PWM-Zyklus hatte, 
der Nachteil ist halt, dass man nicht beliebige Frequenzen einstellen 
konnte (war bei mir auch nicht nötig, es wurden nur 400, 1kHz und 2kHz 
gefordert).

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Peter schrieb:
> Was ich auch immer noch nicht verstanden habe bei der ISR musste ich nie
> das Bit für den Ausgang an PORTB setzen sondern das wurde schon immer
> automatisch anscheinend Hardware Intern gemacht.

Das ist wahr. Aber du hast so viele Timer im Mega328, das du einen als 
PWM Erzeuger und einen als Abtastfrequenzgenerator (Samplerate) benutzen 
könntest. Wenn dir 256 Stufen in der PWM reichen, nimmst du Timer 0 oder 
2 als PWM Generator und Timer 1 als Samplerate, denn das ist ein 16-Bit 
Timer.

Läuft Timer 1 über (entweder als CTC oder freilaufend), lädst du in 
seiner ISR das PWM Register von Timer 0 oder 2 nach.

: Bearbeitet durch User
von Peter (Gast)


Lesenswert?

M. K. schrieb:
> 1. Da gehört kein = hin sondern ein < nach Nyquist ;)

> 2. Du weißt ja schon, dass du einen Sinus haben wirst und auch die
> Frequenz ist bekannt (entweder 50 Hz oder 200 Hz). Muss dann immer noch
> das Nyquist-Kriterium fabtast > 2*fmax erfüllt sein? Denk mal drüber
> nach...und denke auch dran, du willst ja den Sinus generieren, nicht
> messen ;)

zu 1.) Ja deswegen habe ich ja auch min.=mindestens geschrieben :)

zu 2.) Hmm ja ich denke schon oder? Wenn ich im Zeibereich meinen 
Sinus-abtaste(das ist ja grade die LookUp) dann dürfen sich die 
Frequenz-Spektren im Bild-Bereich ja nicht überlappen sonst bekomm ich 
bei der Rück-Transformation in den Zeitbereich Schrott raus (so viel zum 
messen).Wenn ich nun zu wenige Werte in der LookUp habe würde ich eben 
eine lineare Spline Interpolation dieser Punkte am Oszi sehen oder? Klär 
mich auf ich verstehs nicht haha

von M. K. (sylaina)


Lesenswert?

Peter schrieb:
> zu 1.) Ja deswegen habe ich ja auch min.=mindestens geschrieben :)

Das ist aber falsch, nicht "gleich" sondern "größer als", das ist ein 
Unterschied. Ansonsten einfach mal ein Sinus vergegenwärtigen: Wäre 
"gleich" OK und man hat "Glück" (Murphy und so ;)) dann erwischt man 
immer genau die Null-Durchgänge und meint, da ist ja nix ;)

Peter schrieb:
> zu 2.) Hmm ja ich denke schon oder?

Nein, da du weißt, dass es ein Sinus ist und da dir auch die Frequenz 
bekannt ist brauchst du ausschließlich 2 Punkte deren Abstand kein 
ganzzahliges Vielfaches der halbe Periode entspricht. Damit lässt sich 
das Signal exakt rekonstruieren. Probiers einfach mal aus. ;)

von Peter (Gast)


Angehängte Dateien:

Lesenswert?

M. K. schrieb:
> ausschließlich 2 Punkte deren Abstand kein
> ganzzahliges Vielfaches der halbe Periode entspricht

Das verstehe ich nicht siehe Bild hätte ich nur 2 Punkte meines Sinus 
(die deinen Bedingungen entsprechen) so würde nach der 
Tiefpass-Filterung eben genau das grüne Signal wieder rauskommen oder?

von M. K. (sylaina)


Lesenswert?

Dein TP weiß ja auch nicht, dass er einen 50Hz Sinus filtern soll ;)

von Peter (Gast)


Lesenswert?

M. K. schrieb:
> Dein TP weiß ja auch nicht, dass er einen 50Hz Sinus filtern soll
> ;)

Du sprichst in Rätseln.
Keine Ahnung ich mach da meine Fourier Traffo und knall mit meinem 
Tiefpass alle Harmonischen weg damit ich meine Grundschwingung raus 
bekomme ich weiß nicht was du mir sagen willst und ich weiß auch immer 
noch nicht wie ich mir eine LookUp-Table generiere sodass bspw. mein 
Sinus mit 50Hz hinten raus kommt ich hab gedacht ich kann das mit dem 
Fast PWM Mode machen-> überlagere die Zägezahn Schwingung meinem Sinus 
und fertig ist der Lack ist aber anscheinend nicht so einfach ich 
versteh einfach nicht warum ich da die Geschwindigkeit regulieren muss 
mit der die Werte in das VergleichsRegister geladen werden ich möchte 
das doch gerade schnell machen damit ich dort auch einen Sinus mit hohen 
Frequenzen einlesen kann. Denn mein Zägezahnssignal muss ja im Endeffekt 
schneller sein als mein Sinus ansonsten könnte ich ja bspw 2 
Sinus-Perioden in einer Zägezahn-Schwingung haben und somit wäre mein 
Signal verfälscht.

von M. K. (sylaina)


Lesenswert?

OK, ich versuchs mal einfach zu halten, am WE kann ich auch mal nen 
konkreten Code senden.

Ich will aber erstmal das Ganze abstecken: Du hast einen Atmega328P und 
willst hier ein Sinussignal generierern, dass Wahlweise mit 50 Hz oder 
mit 200 Hz schwingt, ja? Einen Schaltplan hast du schon vorliegen oder 
brauchst du den auch noch?

von Peter (Gast)


Lesenswert?

M. K. schrieb:
> OK, ich versuchs mal einfach zu halten, am WE kann ich auch mal nen
> konkreten Code senden.

Oh wow okay danke haha

Ja das mit den 50 Hz bzw. 200 Hz hatte ich gemacht um es erstmal einfach 
zu halten, ich wollte aber eigentlich zum Schluss einen Sinus von 
(0-max)Hz generieren ich weiß nicht wo da die Obergrenze beim Atmega328P 
liegt wie gesagt ich kenne mich mit IC Programmierung noch nicht so 
wirklich aus taste mich da gerade erst ran. Danke gell für die 
Unterstützung :)

von Peter (Gast)


Lesenswert?

M. K. schrieb:
> Einen Schaltplan hast du schon vorliegen oder
> brauchst du den auch noch?

Nein Schaltplan brauch ich nicht das bekomme ich schon selber hin mir 
geht es nur um die Programmierung :)

von M. K. (sylaina)


Angehängte Dateien:

Lesenswert?

Ich hab ein Beispiel geschrieben, schau es dir mal an. Ich gehe davon 
aus, dass du C kannst.
Ich benutze als PWM-Ausgang hierbei PB1 und Timer 1 als PWM-Generator. 
Zudem nutze ich hierbei dem Overflow-Interrupt des Timer 1 um nach jedem 
PWM-Zyklus den nächsten Wert einzuladen.
Die Lookup-Table ist so aufgebaut, dass sich ein 50 Hz Sinus ergibt wenn 
der Atmega auf 16 MHz läuft und man jeden Wert der Tabelle nutzt.
Die Frequenz der PWM ergibt sich dadurch zu 62.5 kHz, wenn du deinen 
Tiefpass also auf z.B. 1 kHz Grenzfrequenz auslegst solltest du da einen 
recht brauchbaren Sinus für 50 Hz und 200 Hz bekommen.

Ich hoffe, dass dir das hilft.

von Peter (Gast)


Angehängte Dateien:

Lesenswert?

Hi erstmal vielen vielen Dank für den Code ich hab deinen Code jetzt mal 
nachvollzogen also man geht quasi so vor:

1) Erzeuge eine Look-Up-Table mit möglichst vielen Werten des Sinus (200 
Stützstellen sollten ausreichen)

2) Durchlaufe die Tabelle des Sinus einmal schneller einmal langsamer um 
die Frequenz dadurch zu skalieren das heißt aber im klar Text ich brauch 
viele Samples für eine hohe Frequenz wenn ich mit dieser Methode hohe 
Frequenzen ausgeben will berechne ich mir  eben eine neue Look-Up Table 
für verschiedene Frequenz Bereiche oder? :)

3) Des Weiteren verstehe ich auch noch nicht warum man den TIMER1_OVF 
benutzt das heißt ja das wir immer nach einem Überlauf in dem Fall dem 
(Timer_1 8 bit Fast PWM) immer nach 255 Werten überlaufen jetzt 
vergleichen wir ja die ganze zeit mit dem OCR1A Register und lösen die 
Interrupt Methode immer bei einem Vergleich aus und nicht nach einem 
Überlauf deshalb verstehe ich nicht warum man da nicht den TIMER1 COMPA 
Fall nehmen muss?

4) Ich hab deinen Code mit LookUp 1:1 bei mir rein kopiert und bekomme 
zum schluss einen -|sin(w*t)| raus also eventuell ist dir da ein Fehler 
passiert beim berechnen der Werte oder ich hab einen Fehler gemacht?

von M. K. (sylaina)


Angehängte Dateien:

Lesenswert?

Ich hab grad nochmal drüber geschaut und meine zwei Fehler entdeckt zu 
haben.

1. Meine Zählvariable i hab ich als 8-Bit-Wert definiert, soll aber bis 
1250 laufen...finde den Fehler ;)

2. Ich hatte für 50Hz bei 16MHz Taktfrequenz 1250 Werte 
ausgerechnet...jetzt wo ich noch mal so drüber nachdenke...müssten 
eigentlich 625 Werte sein, die mein Beispiel für 50Hz braucht.

Nun zu deinen Anmerkungen

zu 2)

Ja, das ist der Nachteil meiner Methode. Ich lasse den Timer immer 
konstant laufen und hole mir die Frequenz dadurch, dass ich eine recht 
große Wertetabelle in unterschiedlichen Schritten durchlaufen.
Man könnte die Wertetabelle auch konstant lassen und alternativ den 
Timer schneller laufen lassen.

zu 3)

Vergleiche mein angehangenes Bild (hier wurden 32 Stützstellen für den 
Sinus verwendet damit man auch die PWM-Zyklen gut erkennen kann). Wir 
starten beim ersten Puls der PWM. Hier in diesem Beispiel ist das ein 0% 
Puls. Der nächste Puls muss etwas größer sein und nun die Frage: Wann 
muss der Wert für den nächsten Puls in das zugehörige PWM-Register 
geladen werden? Genau, wenn der aktuelle PWM-Zyklus durchgelaufen ist. 
Und das ist der Fall wenn der Timer sein Maximum erreicht hat und zurück 
zu 0 springt. Netter Nebeneffekt dabei: Das erzeugt auch eine 
Overflow-Event des Timers und genau den nutze ich um genau dann als 
erstes den neuen Wert ins PWM-Register zu schreiben ;)

zu 4)

Ich habs grad auch bei mir getestet (deshalb fielen mir die oben 
genannten beiden Fehler auf ;)) und da funktioniert der Code 
erwartungsgemäß. Ändere mal in Zeile 13 das

"volatile uint8_t i=0;"

in

"volatile uint16_t i=0;"

Dann solltest du einen 25 Hz Sinus (genauer bekommst du dann den 
Verlauf: Vcc/2*sin(t*pi/20ms)+(Vcc/2)) nach dem Tiefpass erhalten.

: Bearbeitet durch User
von Peter (Gast)


Angehängte Dateien:

Lesenswert?

M. K. schrieb:
> Vergleiche mein angehangenes Bild

Haha welches Bild?

und ja optimal jetzt läufts bekomme allerdings einen sauberen 50 Hz 
Sinus raus für i=1 :) Daaankeee ! Top top

okay zu 3) Habe ich jetzt verstanden danke!

Dann noch einige Fragen bezüglich der maximal möglichen Frequenz ich 
kann ja mit meinem Arduino max. f=16Mhz einspeißen das heißt also bei 
einem 8 bit Timer und der obigen OVF-Methode bekomme ich eine maximale 
Sägezahn-Freq. von ca. 63Khz hin Formel für die 63kHz-> 
1/((1/(16*10^6Hz))*255)

Jetzt kann ich ja maximal einen Sinus ausgeben der langsamer als diese 
63kHz sind oder?

Ist auch irgendwie möglich in den MHz oder gar GHz Bereich zu kommen? 
Oder geht das nur mit einem externen Oszillator oder wie wird das 
üb­li­cher­wei­se gemacht weißt du das zufällig?

von Peter (Gast)


Lesenswert?

Okay das Bild hatte wohl erst später geladen :)

von M. K. (sylaina)


Lesenswert?

Peter schrieb:
> Ist auch irgendwie möglich in den MHz oder gar GHz Bereich zu kommen?

Das geht natürlich nicht. Der Atmega läuft maximal mit 16 MHz und selbst 
der kürzeste Befehl braucht 1 Zyklus, für ein periodisches Signal müsste 
man einen Befehl aber zwei mal aufrufen (einmal High und einmal Low 
setzen), da wären also schon mal nur noch 8 MHz drin. Und ich habs grad 
nicht auswendig im Kopf ob das Setzen der Portpins, bis das Signal am 
Pin auch wirklich steht, auch innerhalb dieses einen Zyklus anlegbar 
ist, ich fürchte aber dass das auch 1-2 Zyklen braucht.

Peter schrieb:
> Dann noch einige Fragen bezüglich der maximal möglichen Frequenz ich
> kann ja mit meinem Arduino max. f=16Mhz einspeißen das heißt also bei
> einem 8 bit Timer und der obigen OVF-Methode bekomme ich eine maximale
> Sägezahn-Freq. von ca. 63Khz hin Formel für die 63kHz->
> 1/((1/(16*10^6Hz))*255)

Ne, das bedeutet nur dass du maximal alle 1/63k Sekunden einen neuen 
Wert für das Sägezahn-Signal ins entsprechende Register laden kannst, 
damit hast du am Ausgang noch lange kein Sägezahnsignal. In jedem 
OVF-Interrupt wird lediglich der nächste Wert zur Signalerzeugung ins 
PWM-Register geladen. Damit hat man aber noch lange nicht das Signal 
erzeugt. ;)

Peter schrieb:
> und ja optimal jetzt läufts bekomme allerdings einen sauberen 50 Hz
> Sinus raus für i=1 :) Daaankeee ! Top top

Sehr schön ;)

von oldeurope O. (Gast)


Lesenswert?

M. K. schrieb:
> Ich hab ein Beispiel geschrieben, schau es dir mal an. Ich gehe davon
> aus, dass du C kannst.

Ich kann kein C, würde es aber trotzdem gerne sehen.
Welche Datei muss ich da womit wie öffnen?
Oder kannst Du den Code hier bitte so einstellen,
dass man ihn mit der Foren-Codeansicht lesen kann?

Vielen Dank,

LG
old.

von Carl D. (jcw2)


Lesenswert?

Ruhig war's.

von M. K. (sylaina)


Lesenswert?

Aus der W. schrieb:
> Ich kann kein C, würde es aber trotzdem gerne sehen.
> Welche Datei muss ich da womit wie öffnen?

Einfach das Zip entpacken und die Dateien mit einem Texteditor 
betrachten, am besten einer, der auch Syntaxt Highlighting für C-Files 
kann, dann ist der Code recht gut lesbar ;)
Interessant sind die Files mit .c und .h, der Rest ist für 
Compiler/Linker und Co ;)

: Bearbeitet durch User
von oldeurope O. (Gast)


Lesenswert?

Vielen Dank sylaina. Mit WordPad konnte ich mal reinschnuppern.

Da ich kein C kann, ist auch nicht mehr als reinschnuppern.
Geschweige denn das in meinen Code zu fummeln.

Hier die Codeansicht:
https://www.mikrocontroller.net/attachment/highlight/450237
Beitrag "Re: Arduino Kondensatormotor Drehzahlsteuerung Schaltplan und Sketch 200327"

Wo läge denn da der Vorteil gegenüber der Methode
Multivibrator, Zähler und if-Liste allgemein und
speziell bezogen auf meine FU Anwendung?

LG
old.

von M. K. (sylaina)


Lesenswert?

Aus der W. schrieb:
> Da ich kein C kann, ist auch nicht mehr als reinschnuppern.
> Geschweige denn das in meinen Code zu fummeln.

Ich hab mal reingeschaut in deinen Code. Also ich empfehle dir erstmal 
was einfaches in C zu machen, so Back to the Roots like.

Schon die ganzen If-Statements sind da ziemlich unnötig. Kleines 
Beipsiel hierzu mal:
1
...
2
int myVariable;
3
...
4
myVariable = 1;
5
if (myVariable == 1) doSomething_1();
6
if (myVariable == 2) doSomething_2();
7
if (myVariable == 3) doSomething_3();
8
...

Hierzu jetzt die Frage an dich: Meinst du es ist sinnvoll alle 
If-Statements abzuarbeiten? Oder liese sich das nicht geschickt anders 
formulieren?

von oldeurope O. (Gast)


Lesenswert?

M. K. schrieb:
> Hierzu jetzt die Frage an dich: Meinst du es ist sinnvoll alle
> If-Statements abzuarbeiten?

Es ist schlicht egal. Die Zeit zum Überspringen addiert sich zur
Periodendauer und senkt die Frequenz unmerklich.

Wo ich drauf aufpassen muss ist, nicht irgendwelche Dinge
sporadisch zu tun. Das gibt einen Jitter in der
Periodendauer.

> Oder liese sich das nicht geschickt anders
> formulieren?

Keine Ahnung. Mir erscheint das nicht ungeschickt weil
ich das nicht anders kenne.

Was verstehst Du unter if-Statements?
In Deinem Beispiel sind die if Bedingungen doch auch.
myvariable = zahl
doSomething_1() = (enup = LOW; enun = HIGH; enzp = LOW; enzn = LOW; usin 
= 100; zsin = 0;)
Ich erkenne da jetzt keine Abkürzung.

LG
old.

PS: Falls das hier stört, können wir gerne in meinen Thread
dafür wechseln.

von M. K. (sylaina)


Lesenswert?

Aus der W. schrieb:
> Es ist schlicht egal. Die Zeit zum Überspringen addiert sich zur
> Periodendauer und senkt die Frequenz unmerklich.

Wenn du beim ersten If-Statement einen Treffer bei dir hast und da noch 
19 andere folgen...das macht keinen Sinn die 19 anderen Statements auch 
noch abzuarbeiten.

Aus der W. schrieb:
> Was verstehst Du unter if-Statements?
> In Deinem Beispiel sind die if Bedingungen doch auch.
> myvariable = zahl
> doSomething_1() = (enup = LOW; enun = HIGH; enzp = LOW; enzn = LOW; usin
> = 100; zsin = 0;)
> Ich erkenne da jetzt keine Abkürzung.

Ist ja auch keine Abkürzung aber man könnte stattdessen schreiben:
1
...
2
int myVariable;
3
...
4
myVariable = 1;
5
switch (myVariable){
6
  case 1:
7
    doSomething_1();
8
    break;
9
  case 2:
10
    doSomething_2();
11
    break;
12
  case 3:
13
    doSomething_3();
14
    break;
15
  default:
16
    doNothing();
17
    break;
18
}

Das wäre 1. lesbarer als die ganzen Sachen mit if-Statements zu machen 
und 2. auch noch schneller weil nicht so viel geprüft werden muss (es 
wird immer nur ein Case abgearbeitet bzw. ohne Treffer das default).
Warum du hier kein if...else... benutzt hast verstehe ich auch gar 
nicht, schon das hätte wenigstens etwas Rechenaufwand reduziert 
(wenngleich auch nicht alle if-Prüfungen damit eleminiert werden, wenn 
z.B. erst das letzte if ein Treffer ist werden ja immer noch alle 
vorhergehenden abgearbeitet)

Und jetzt gehe ich noch ein wenig weiter rein bei dir:
1
...
2
    if(zahl == 5)
3
     {enup = LOW; enun = LOW; enzp = HIGH; enzn = LOW; usin = 0; zsin = 100;}
4
    if(zahl == 6)
5
     {enup = LOW; enun = LOW; enzp = HIGH; enzn = LOW; usin = 0; zsin = 100;}
6
    if(zahl == 7)
7
     {enup = HIGH; enun = LOW; enzp = HIGH; enzn = LOW; usin = 45; zsin = 89;}
8
    if(zahl == 8)
9
     {enup = HIGH; enun = LOW; enzp = HIGH; enzn = LOW; usin = 71; zsin = 71;} 
10
    if(zahl == 9)
11
     {enup = HIGH; enun = LOW; enzp = HIGH; enzn = LOW; usin = 89; zsin = 45;}

Die Statements unterscheiden sich doch nur in usin und zsin, enup und Co 
sind hier immer gleich. Das liese sich doch vereinfachen ;)
Aber, und das sieht man das nächste Manko, die ersten beiden 
if-Statments unterscheiden sich gar nicht, es ist schlicht egal ob zahl 
5 oder 6 ist.

Generell sieht es mir so aus als seien hier alle Variablen von Zahl 
abhängig. Wie hast du das berechnet? Wäre es hier nicht eine Idee 
generell die Variablen direkt aus der Variable zahl zu berechnen? Die 
ganzen ifs zeigen nämlich eins: Die Variablen enup, enun, enzp, enzn, 
usin und zsin sind anscheinend nur von zahl abhängig, es sind also 
f(zahl)-Funktionen. Statt also hunderte von ifs abzuarbeiten könntest du 
auch einfach 6 Werte ausrechnen lassen. Das dürfte erheblich schneller 
sein als deine if-Orgien abzuabreiten.

von oldeurope O. (Gast)


Lesenswert?

Hallo sylaina,
ich werde versuchen die switch-case-Struktur umzusetzen.
Vielen Dank.

Weil das jetzt speziell um meinen FU geht, habe ich Dir da

Beitrag "Re: Arduino Kondensatormotor Drehzahlsteuerung"

geantwortet.

LG
old.

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.