Forum: Mikrocontroller und Digitale Elektronik Signalgenerator, Grenzen eines Atmega8


von A. F. (elagil)


Angehängte Dateien:

Lesenswert?

Hallo,

ich baue mir einen Signalgenerator, ich bin eigentlich schon fertig 
damit.

Ein Atmega8 (16Mhz) hängt mit 8 Pins an einem 8Bit DAC. Es sind 
verschiedene Signalformen wählbar (Sinus, Rechteck, Dreieck, Sägezahn).

Bei jeder Signalform werden in einer vollen Periode 256 Werte 
abgetastet, egal bei welcher Frequenz. Damit muss die Interruptfrequenz 
256 mal höher liegen, als die gewählte Ausgangsfrequenz.

Problem nun: ab 96Hz gewählter Frequenz ist der Atmega an der Grenze 
(24576Hz Interruptfrequenz) und es lassen sich z.B. keine Tastendrücke 
mehr einlesen.

Ist mein Programm so ineffizient oder muss ich mit geringerer Auflösung 
leben? Was kann man da machen? 96Hz sind mir zumindest ein bisschen zu 
wenig.

Natürlich kann ich beim Rechtecksignal direkt den Port schalten, bei den 
anderen Signalformen geht das aber nicht.

Anbei der Code.. Er ist im Moment sicher nicht sehr hübsch, ich habe ihn 
noch in keiner Weise optimiert..

Die "freqErrorPercent" Funktion errechnet übrigens die Abweichung der 
ausgegebenen von der eingestellten Frequenz durch die Nichtstetigkeit 
des OCR1A Zählregisters.

Danke im Voraus!

von Ulrich (Gast)


Lesenswert?

Wenn man den Generator unbedingt in Software mit einem µC machen will, 
dann schon eher als DDS Generator - also mit konstanter Abtastrate für 
den DAC und dann variablen Daten. Spezielle Chips (z.B. AD9851) sind da 
aber einiges Leistungsfähiger.

Vermutlich kann dem den Code noch um einiges beschleunigen, aber es 
bleibt eine suboptimale Lösung. Schon der Code um den DAC zu setzen ist 
ggf. noch schön universell, aber auch extrem ineffizient.

von Spess53 (Gast)


Lesenswert?

Hi

Das geht schon schneller. Nur nicht, so wie du dir das gedacht hast:

http://www.elektronik-labor.de/AVR/DDSGenerator.htm

MfG Spess

von Viktor N. (Gast)


Lesenswert?

>double posTimesTen;

in der Tat, etwas Optimierung waere faellig. Aber double ist etwas 
abgehoben.. Was soll double ueberhaupt ? Ich sah's an verschiedenen 
Orten.

von Peter R. (pnu)


Lesenswert?

So etwas macht man mit dem DDS-Prinzip, nicht über viele int's.
google deshalb nach: jesper, poor man's dds.

Eine solche Aufgabe muss sehr zeit-sparsam ausgeführt werden, deshalb 
ist sie ein Musterfall für Ausführung in Assembler.

Auch hier im forum gibt es mehrere threads zu diesem Thema.

Bei 16 MHz Taktfrequenz lässt sich in Assembler mit einem atmega8 selbst 
20 kHz als recht sauberer Sinus erzeugen. Wegen der einfachen Schleife 
kann man auch gut übertakten, mit externem Oszillator hab ich den atmega 
schon mit 30MHz gequält.

Sinnvoller wäre natürlich der Einsatz eines DDS-IC. Diese sind 
inzwischen nicht so teuer wie zu der Zeit als Jesper seine Lösung 
vorstellte.

von A. F. (elagil)


Lesenswert?

> Schon der Code um den DAC zu setzen ist
> ggf. noch schön universell, aber auch extrem ineffizient.

Ja allerdings! dummerweise habe ich ihn nicht an einen einzigen port 
angeschlossen, vermutlich ändere ich das noch.

> Was soll double ueberhaupt ?

Die Frequenz wird als double gespeichert.. das mag übertrieben sein, der 
Wert wird aber beim eigentlichen Generieren der Ausgangsspannung nicht 
mehr benutzt. All die großen Funktionen rechnen ja nur aus der gewählten 
Taktrate die nötigen Interrupt-Einstellungen aus.
Das von dir zitierte Beispiel ist aber was anderes, das ist wirklich 
unnötig ;)

> So etwas macht man mit dem DDS-Prinzip, nicht über viele int's.

Ich benutze ja "fast" nur einen interrupt (der für die taster arbeitet 
nur bei ca. 60 Hz) ;)

Das DDS Prinzip war mir neu! Man opfert also also mit steigender 
Ausgabefrequenz zeitliche Auflösung, bis man mit der Samplingfrequenz 
gerade noch mehr als die doppelte auszugebende Frequenz erreicht hat?

Zu meinem Verständnis:
Es gibt einen Zähler, der mit maximaler Rate hochgezählt wird, bis er 
überläuft und zurückgesetzt wird. Je nach gewählter Frequenz wird alle 
"x" Zählschritte ein Wert aus der Wertetabelle abgetastet (also der 
Nächstgelegene) und an den DAC übertragen, je höher die Zahl der 
Zählschritte, desto kleiner die Frequenz.

Ist ein Interrupt dafür denn schlecht geeignet? Immerhin kann ich damit 
sicher sein, wie schnell der Zähler läuft. Ich brauche keine hohen 
Frequenzen, ich bin auch mit 10kHz (bei mir momentan dann 2,56Mhz 
Interrupt Frequenz) zufrieden. Takte zählen kann man ja tatsächlich nur 
im (mir ziemlich unangenehmen) Assembler..

Ein Sinus mit knapp über zwei Abtastwerten pro Periode sieht doch nun 
wirklich nicht mehr hübsch aus..

von spess53 (Gast)


Lesenswert?

Hi

>ich bin auch mit 10kHz (bei mir momentan dann 2,56Mhz
>Interrupt Frequenz) zufrieden.

Bei deinen 16MHz Takt bleiben dann 6,25 Takte für einen Interrupt. Ein 
Interrupt braucht schon allein ein paar Takte um zum Interrupt zu 
springen und für das abschließende RETI. Und du rufst vom Interrupt noch 
so eine 'Warteschleife'

>void setDacValue(uint8_t value) {
>  for(int i=0; i<=7; i++) {
>    if((value>>i)&1 == 1) {*(dacPorts[i]) |= (1<<dacPins[i]);}
>    else {*(dacPorts[i]) &= ~(1<<dacPins[i]);}
>  }
>}

auf. Deine angestrebten 10 kHz sind absolut utopisch.

MfG Spess

von Pink S. (pinkshell)


Lesenswert?

Im Hauptprogramm, in Assembler vielleicht so in der Art:

neu:
    lade aus der Tabelle
    ab auf den Port
    goto neu

Tastendrücke dann per Interrupt

von Michael A. (Gast)


Lesenswert?

Adrian Figueroa schrieb:
> Ist ein Interrupt dafür denn schlecht geeignet?

Ja.
Wenn es Ziel ist, die Daten aus der Tabelle mit maximaler Frequenz 
auszugeben, ist es von der Programmausführung am schnellsten, das 
Programm genau darauf zu beschränken. Wenn dort mehr als ein Pointer mit 
festgelegter Schrittweite kreist und das gelesene Byte auf einen Port 
ausgegeben wird, kostet das zusätzlich Zeit, was unweigerlich die 
maximale Frequenz verringert. Die DDS-Kernschleife muss natürlich so 
aufgebaut sein, dass sich eine konstante Laufzeit ergibt. 
Tastaturbedienung über Interrupts kostet natürlich Zeit, die zu 
Phasensprüngen oder Aussetzern im Signal führt. Ein korrigierender 
Eingriff in den Phasenzähler bringt allenfalls bei niedrigen Frequenzen 
etwas.

von Karl H. (kbuchegg)


Lesenswert?

> Das DDS Prinzip war mir neu! Man opfert also also mit
> steigender Ausgabefrequenz zeitliche Auflösung, bis man mit
> der Samplingfrequenz gerade noch mehr als die doppelte
> auszugebende Frequenz erreicht hat?

Ja. Irgendeinen Tod musst du sterben. Man kann nicht alles haben, wenn 
die verfügbaren Resourcen nicht ausreichen.

von A. F. (elagil)


Lesenswert?

Ich habe mal vier Wertetabellen in meinem Programm abgelegt. Das sind 
1024Byte und die passen (wenn die anderen Variablen noch dazukommen) 
nicht mehr in den 1kb Datenspeicher..

Wie hat der Mensch das mit dem Attiny2313 angestellt? Was habe ich da 
übersehen? Es scheint mir unvorstellbar, auch in Assembler, das Programm 
UND die Daten auf 2kB zu bekommen.

von Karl H. (kbuchegg)


Lesenswert?

Adrian Figueroa schrieb:
> Ich habe mal vier Wertetabellen in meinem Programm abgelegt. Das sind
> 1024Byte und die passen (wenn die anderen Variablen noch dazukommen)
> nicht mehr in den 1kb Datenspeicher..


> Bei jeder Signalform werden in einer vollen Periode 256 Werte

Für einen kompletten Sinus musst du zb nicht die komplette Signalform 
ablegen. Einen kompletten Sinus kann man aus einer 1/4 Kurve und 
entsprechende Spiegelungen erzeugen.

Weiters: Niemand sagt, dass alle Kurventabellen im SRAM liegen müssen. 
Eine reicht. Und wenn der Benutzer die Kurvenform wechselt, wird diese 
Tabelle aus dem Flash nachgeladen. Flash hat man mehr als SRAM


>  Es scheint mir unvorstellbar, auch in Assembler, das
> Programm UND die Daten auf 2kB zu bekommen.

1Kb Flashspeicher für das Programm ist schon mächtig viel Holz. Da geht 
so einiges an Funktionalität rein.

von Michael (Gast)


Lesenswert?

Adrian Figueroa schrieb:
> Es scheint mir unvorstellbar, auch in Assembler, das Programm
> UND die Daten auf 2kB zu bekommen.

Guck dir einfach den Code an. Im Source Code steht es schwarz auf weiss. 
Die Tabellen sind z.B. nur 256 Byte lang, d.h. bei vier Kurvenformen ist 
die Hälfte des Speichers belegt.
http://www.myplace.nu/avr/minidds/minidds.asm

von spess53 (Gast)


Lesenswert?

Hi

>Es scheint mir unvorstellbar, auch in Assembler, das Programm
>UND die Daten auf 2kB zu bekommen.

Wieso nicht? In dem restlichen 1k Programmspeicher lassen sich ca. 
350...400 Assemblerbefehle unterbringen. Die eigentliche Ausgabe der 
Tabellenwerte in der richtigen Frequenz beansprucht gerade mal 8 
Assemblerbefehle. Der ganze Rest ist für die Bedienung übrig. Und da 
packt man eben rein, was rein passt.

MfG Spess

von A. F. (elagil)


Lesenswert?

Das Problem war eher, dass die Daten im Datenspeicher standen und nicht 
im Programmspeicher.. Ich habe jetzt "PROGMEM" entdeckt, wohl das 
Äquivalent zur Auswahl des Speichersegments in Assembler.

Mein Code, speziell für das Display, ist sehr allgemein und riesig groß, 
ich habe aber immerhin 8kB Platz.

von A. F. (elagil)


Lesenswert?

So soll es dann erstmal aussehen..
1
  for(;;) {
2
    static uint8_t xpos;
3
    static uint32_t counter;
4
    
5
    if(++counter == limit) {
6
      counter=0;
7
      PORTB = buffer[xpos++];  // Overflow bei 256
8
    }
9
  }

von W.S. (Gast)


Lesenswert?

Ach Leute, warum sowas immer wieder und wieder...

Warum bloß habt ihr es nicht drauf, wenigstens einen AD9833 zu kaufen 
und diesen IC die Arbeit machen zu lassen?

25 MHz DDS-Takt mit 10 Bit analoger Auflösung schafft ihr mit keinem 
Atmel und der AD9833 ist auch ein recht billiger Chip, der 
Sinus+Rechteck+Sägezahn liefern kann.

Warum also bloß immer wieder diese Verrenkungen, ein DDS per Software 
auf eher ungeeignetem uC zu bauen? Ist das ein spezieller 
Atmel-Fetischismus?

W.S.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

W.S. schrieb:
> Sägezahn

Nö kann er nicht, der kann Dreieck und den Rest.

Zudem ist ein AD9833 jetzt nicht nen IC den man am liebsten einlötet.
MSOP10, 3x3mm groß und 10 Pins.

von Michael (Gast)


Lesenswert?

Adrian Figueroa schrieb:
> So soll es dann erstmal aussehen..

Da wirst du noch nicht glücklich mit werden, weil die Frequenz nicht 
einstellbar ist. Es fehlt der Phasenzähler für die Bruchteile und durch 
den if-Block hast du eine variable Schleifenlaufzeit.
Den if-Block spart man sich, indem man den Variablentyp vom Phasenzähler 
passend zur Länge der Wellenformdaten wählt.

Außerdem sollten xpos und counter besser gekoppelt sein ;-)

von Wolfgang (Gast)


Lesenswert?

Martin Wende schrieb:
> Zudem ist ein AD9833 jetzt nicht nen IC den man am liebsten einlötet.
> MSOP10, 3x3mm groß und 10 Pins.

Mit ein bisschen Flussmittel sind die 0,5mm doch wirklich kein Problem
http://www.youtube.com/watch?v=erb6-i54tbo

von eProfi (Gast)


Lesenswert?

Dann kauf Dir ein AD9850-Board in China mit 125 MHz-Oszillator für 4,30 
Euro inkl. Versand.

Jespers DDS hat noch Optimierungspotential:
Beitrag "DDS normal ?"


Adrian, Du musst noch ein bisschen über das DDS-Prinzip meditieren.
Und wenn Du den Chip ausreizen willst, kommst Du um Asm nicht herum.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Angehängte Dateien:

Lesenswert?

Sag ja nich, dasses unmöglich is, aber macht einfach kein Spaß ;)

von Frank K. (fchk)


Lesenswert?

Martin Wende schrieb:
> Sag ja nich, dasses unmöglich is, aber macht einfach kein Spaß ;)

Na ja, mit Deiner Leiterplattenqualität und ohne Lötstopplack hast Du ja 
auch erschwerte Bedingungen. Mit vernünftigen Boards ist das kein 
Problem.

fchk

von Frank K. (fchk)


Lesenswert?

W.S. schrieb:

> Warum also bloß immer wieder diese Verrenkungen, ein DDS per Software
> auf eher ungeeignetem uC zu bauen? Ist das ein spezieller
> Atmel-Fetischismus?

Ich frage mich das auch immer. Ihm wäre ja auch schon mit einem 
dsPIC33F256MC802 geholfen. Gleiches Gehäuse wie ein Mega8, aber 
mindestens dreifache Leistung und zwei eingebaute 14 Bit DACs, die bis 
100 kHz gehen und per DMA gefüttert werden können.

Die Idioten sterben halt nicht aus.

fchk

von Falk B. (falk)


Lesenswert?

@ W.S. (Gast)

>Warum also bloß immer wieder diese Verrenkungen, ein DDS per Software
>auf eher ungeeignetem uC zu bauen?

Keine Verrenkung sondern sportliche Herausforderung!

> Ist das ein spezieller Atmel-Fetischismus?

AVRs können's halt. Und für NF bis vielleicht 100kHz voll OK. Clever und 
leistungsstark. Sogar ein Videosignal incl. Terminal kann man mit so 
einem kleinen Käfer machen. Klar, man kann auch spezielle Hardware 
nutzen, aber das ist ja keine Kunst ;-)

von A. F. (elagil)


Lesenswert?

Hier ist ja einiges passiert :D

Danke für die (meisten) Antworten. Ich hatte bisher keine Ahnung von der 
Existens eigens für diesen Zweck gemachter DDS Chips, allerdings brauche 
ich diesen Signalgenerator auch kaum für seine eigentliche Funktion, er 
ist ein reines Lernobjekt. Es handelt sich bei ihm um mein erstes 
AVR-Projekt, das über einfache blinkende Lampen und kleine Uhren 
hinausgeht und ich wollte das Problem erstmal ohne fertige Lösungen 
angehen. Bis 96Hz bin ich gekommen, jetzt befasse ich mich wohl näher 
mit DDS.

von Spess53 (Gast)


Lesenswert?

Hi

>Bis 96Hz bin ich gekommen, jetzt befasse ich mich wohl näher mit DDS.

Dann fang auch gleich noch mit Assembler an. Mit C kommst du da auch 
nicht viel weiter.

MfG Spess

von Peter R. (pnu)


Lesenswert?

Dann bau doch Dein Projekt in zwei Teile auf:

Ein atmega8 als DDS-IC. Die DDS-Schleife als Assembler-inline-Teil, das 
sind
nur etwa 10 Befehle.
Zusätzlich, als int-Teil, die Verstellung per I2C, in C geschrieben.
Dann verhält sich dieser Atmega wie ein I2C-Baustein.

Ein  zweiter atmega8 als Bedienzentrale. Gibt Sollwerte an I2c ab, 
bedient Tastatur,Scrollrad,Display,Speicher, RS232 zum PC usw. Der kann 
voll in C programmiert werden.

Letztendlich sinnvoller wäre: den analogen Teil als DDS-IC und die 
Steuerung als Kontroller aufzuteilen.

von Helmut L. (helmi1)


Lesenswert?

Adrian Figueroa schrieb:
> Ein Sinus mit knapp über zwei Abtastwerten pro Periode sieht doch nun
> wirklich nicht mehr hübsch aus..

Da fehlt ja auch noch ein Tiefpass am Ausgang. Und das macht dann wieder 
einen schoenen Sinus draus.

von A. F. (elagil)


Lesenswert?

> den analogen Teil als DDS-IC

Das werde ich wohl anstreben!

> Da fehlt ja auch noch ein Tiefpass am Ausgang. Und das macht dann wieder
> einen schoenen Sinus draus.

Ich habe einen 50kHz Tiefpass eingeplant.

von Helmut L. (helmi1)


Lesenswert?

Adrian Figueroa schrieb:
> Ich habe einen 50kHz Tiefpass eingeplant.

Dein Tiefpass muss eine Grenzfrequenz < fs/2 haben.
fs = Samplefrequenz fuer deine Abtastungen.

In deinem Beispiel von 50kHz muss die Sample (Ausgabefrequenz) bei mehr 
als 100 kHz liegen. Damit das Filter nicht unendlich steil sein muss 
eher bei 150kHz. Die Daempfung des Filters muss groesser sein als die 
Aufloesung deines DACs. Mit einem einfachen RC Glied funktioniert das 
nur recht selten.

von A. F. (elagil)


Lesenswert?

> Dein Tiefpass muss eine Grenzfrequenz < fs/2 haben.

warum ist das so? Mir ist klar, dass der Tiefpass bei seiner 
Grenzfrequenz kaum filtert, aber wie kommt man auf fs/2?

Mein Generator gibt ja eigentlich immer Rechtecksignale aus, ich habe 
mal vereinfacht angenommen, dass nur die 5. Oberwelle noch relevant ist.

Die Samplingfrequenz liegt ja bei 10 Assemblerbefehlen bei (angenommen) 
einem Takt pro Befehl bei 1,6Mhz. Bei einer 10kHz-Sinusschwingung werden 
dann 160 Werte abgetastet, zurückgerechnet komme ich natürlich wieder 
auf 1,6Mhz Samplingfrequenz.. Es entsteht also ein "eckiges" 1,6Mhz 
Signal, die relevanten Oberwellen gehen nach obiger Vereinfachung bis 
8Mhz.

Muss der Tiefpass dann nicht auch in der Region arbeiten? Ich tu mich 
schwer mit der Dimensionierung...

von Helmut L. (helmi1)


Lesenswert?

Wir sprechen hier vom DDS Prinzip. Da wird auf einen Summenakku immer in 
abhaengigkeit der eingestellten Frequenz ein bestimmter Phasenwinkel 
addiert.
Dieses addieren erfolgt in der festen Samplingfrequenz fs.
Nun ist es ja so das dieses abtasten eine Multiplikation ist. Da durch 
erscheint am Ausgang des DAC im Spektrum jetzt neben deiner gewuenschten 
Ausgangsfrequenz auch die Mischung der Samplingfrequenz mit dem 
Ausgangssignal und noch weitere Komponenten. Diese unerwunschten Signale 
gilt es nun wegzubekommen. z.B.

Ausgangsfrequenz fa: 10kHz
Samplingfrequenz fs: 30kHz

Dann hast du im Signal Spektralanteile von:

fa = 10kHz, fs-fa=20kHz,fs=30kHz,fs+fa=40kHz usw.

Das heist also alles was oberhalb von 10kHz ist muss weg.

Das geht in diesem Fall problemlos da zwischen 10kHz (die nicht 
abgeschwaecht werden sollen) und dem kleinsten Stoersignal von 20kHz 
viel Platz ist fuer das Filter abzufallen.

Wenn man jetzt die Ausgangsfrequenz weiter erhoehen wuerde, wuerde sich 
folgendes Bild einstellen.


Ausgangsfrequenz fa: 15kHz
Samplingfrequenz fs: 30kHz

Dann hast du im Signal Spektralanteile von:

fa = 15kHz, fs-fa=15kHz,fs=30kHz,fs+fa=45kHz usw.

Du siehst dein erstes Stoersignal ist identisch mit der 
Ausgangsfrequenz.
Das Filter muesste in dem Fall unendlich Steil sein. Was aber nicht 
geht.

Macht man die Ausgangsfrequenz jetzt noch groesser kann man ueberhaupt 
nicht mehr trennen.

Mit DDS kann man zwar theoretisch Frequenzen bis zu fs/2 erzeugen 
praktisch scheitert das aber am Filter. Deshalb nimmt man meistens fs/3 
als hoechste Ausgangsfrequenz.

Zur Filterdaempfung:

Du siehst im ersten Fall das wir einen Bereich von 10kHz .. 20Khz zum 
Abfall des Tiefpasses haben. Bei 20kHz muss der Tiefpass dann 
theoretisch eine Daempfung groesser als die Aufloesung des DACs haben. 
Im Fall eines 8Bit DAC dann 48dB. Da 10kHz .. 20kHz eine 
Oktave->verdoppelung ist und ein einfacher Tiefpass 6db/ Oktave macht 
muesste man hier einen Tiefpass 8. Ordnung einsetzen. Praktisch kaemme 
man auch mit 6. Ordnung aus wenn man an der Signalqualitaet etwas 
Abstriche macht.

von A. F. (elagil)


Lesenswert?

> Deshalb nimmt man meistens fs/3 als hoechste Ausgangsfrequenz.

Alles klar! Bei 1,6Mhz Abtastfrequenz bin ich ja gerade mal bei fs/160 
für 10kHz Ausgangsfrequenz.. Damit muss mein Filter doch nur ab 10kHz 
bis 1,6Mhz-10kHz (~ 1,6Mhz) abfallen..

> theoretisch eine Daempfung groesser als die Aufloesung des DACs haben.

Bei 8Bit 48dB? Heißt das, der niedrigste ausgebbare Wert bei einem 8bit 
DAC liegt 48dB unter dem höchsten?

Kommt man darauf so?:
8Bit DAC -> niedrigster Wert ist um Faktor 2^8-1 kleiner als der 
höchste, damit um 20*log(255/1) = ca. 48dB..

Nun habe ich von 10kHz bis 1,6Mhz ganze 160 Oktaven "Platz", es reicht 
eine Steilheit von 48/160 dB/Oct = 0,3dB/Oct. Ich habe einen aktiven 
Filter zweiter Ordnung mit einem OPAmp gebaut (12dB/oct), damit bin ich 
bei 800kHz(?) bei -48dB.

von Helmut L. (helmi1)


Lesenswert?

Adrian Figueroa schrieb:
> Kommt man darauf so?:
> 8Bit DAC -> niedrigster Wert ist um Faktor 2^8-1 kleiner als der
> höchste, damit um 20*log(255/1) = ca. 48dB..

Richtig.

Adrian Figueroa schrieb:
> Alles klar! Bei 1,6Mhz Abtastfrequenz bin ich ja gerade mal bei fs/160
> für 10kHz Ausgangsfrequenz.. Damit muss mein Filter doch nur ab 10kHz
> bis 1,6Mhz-10kHz (~ 1,6Mhz) abfallen..

Warum tastes du mit 1.6Mhz ab? Dann macht dein uC nix anderes mehr falls 
er die ueberhaupt schafft. Es reichen fuer 10KHz locker 30Khz 
Samplefrequenz.

von A. F. (elagil)


Lesenswert?

> Dann macht dein uC nix anderes mehr falls er die ueberhaupt schafft.

Muss er das denn? Außer wenn eine Eingabe erfolgt tastet er ja nur ab. 
Ob er die Rate schafft, weiß ich nicht. Weiter oben hat aber jemand von 
10 Takten gesprochen, die für die Abtastung in Assembler nötig wären.

Eine andere Frage:
Den Phasenzähler (32bit) muss ich ja umrechnen auf einen Wert zwischen 0 
und 255 für meine Tabelle. Ich schiebe ihn also 24 Stellen nach rechts?
1
while (1) {
2
  static uint32_t counter = 0;
3
  counter+=steps;
4
  PORTD = buffer[counter>>24];
5
}

wie schon oben vorgeschlagen, schreibe ich dazu vielleicht ein bisschen 
inline asm code.

von Helmut L. (helmi1)


Lesenswert?

Man nimmt die hoechsten Bits des Counter zur addressierung der 
Sinustabelle.

buffer[x] ist deine Sinustabelle?

Adrian Figueroa schrieb:
> Muss er das denn?

Das weist nur du ob der noch was anders machen soll und ob dir 10kHz 
max. Ausgabefrequenz genuegt.

Bei 1.6MHz koenntes du bis rund 500kHz kommen. Hinter dem DAC ein 
Cauerfilter 7. Ordnung (3 Spulen,7 Kondensatoren) und das wars. Ops 
wuerde ich da nicht mehr nehmen. Die Ops muessen in dem Bereich bis 
1.6Mhz und drueber noch vernueftig Verstaerkung haben damit das Aktiv 
Filter funktionieren kann.

von A. F. (elagil)


Lesenswert?

> Man nimmt die hoechsten Bits des Counter
das kommt ja dem schieben gleich..?

Ja, der buffer enthält die Signalform.

DDRD = buffer[counter&FF000000]:
Einfaches maskieren reicht doch nicht, die Werte sind ja viel zu groß

> Cauerfilter
Mal sehen was das wieder ist :)

von Helmut L. (helmi1)


Lesenswert?

Adrian Figueroa schrieb:
> Einfaches maskieren reicht doch nicht, die Werte sind ja viel zu groß

So isses.

Adrian Figueroa schrieb:
>> Cauerfilter
> Mal sehen was das wieder ist :)

Sit eine Filteruebertragunsgfunktion wie 
Bessel,Butterworth,Tschibyscheff nur steiler. von Wilhelm Cauer in den 
30 u. 40 Jahren erfunden.

http://de.wikipedia.org/wiki/Wilhelm_Cauer

Schaltungstechnisch sehen die alle gleich aus:


DAC ---+---L1 ----+-----L2----+----L3-----+-----Ausgang
       |          |           |           |
       +---C01----+----C02----+----C03----+
       |          |           |           |
       C1         C2          C3          C4
       |          |           |           |
      GND        GND         GND         GND

Wird meistens als Ausgangsfilter in DDS Schaltungen verwendet.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Die Steilheit erkauft man sich allerdings durch ordentliche 
Überschwinger und die Gruppenlaufzeit is auch eher Banane ;)

von Helmut L. (helmi1)


Lesenswert?

Martin Wende schrieb:
> Die Steilheit erkauft man sich allerdings durch ordentliche
> Überschwinger und die Gruppenlaufzeit is auch eher Banane ;)

Macht aber in dem Fall nix.

von Falk B. (falk)


Lesenswert?

@ Martin Wende (Firma: fritzler-avr.de) (fritzler)

>Die Steilheit erkauft man sich allerdings durch ordentliche
>Überschwinger und die Gruppenlaufzeit is auch eher Banane ;)

Was bei einem SINUS wohl kaum eine Rolle spielt. Anders sieht es bei 
Rechteck, Dreieck und Sägezahn aus, hier sollte man besser einen 
Besselfilter nehmen, auch wenn der weniger steil ist.

von Egal (Gast)


Lesenswert?

Eventuell kannst Du ja hiermit was anfangen:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <avr/pgmspace.h>
4
#include <inttypes.h>
5
6
#define F_CPU 8000000UL  // Quarz mit 8.000.000 Mhz
7
#define TIMERRELOAD 256  // Nachladewert des Timers 255?
8
#define AUFLOESUNG 65536 // 16-Bit Counter 65535?
9
10
const uint8_t sinustabelle[256] PROGMEM = {127,130,133,136,139,143,146,149,152,155,158,161,164,167,170,173,176,178,181,184,187,190,192,195,198,200,203,205,208,210,212,215,217,219,221,223,225,227,229,231,233,234,236,238,239,240,242,243,244,245,247,248,249,249,250,251,252,252,253,253,253,254,254,254,254,254,254,254,253,253,253,252,252,251,250,249,249,248,247,245,244,243,242,240,239,238,236,234,233,231,229,227,225,223,221,219,217,215,212,210,208,205,203,200,198,195,192,190,187,184,181,178,176,173,170,167,164,161,158,155,152,149,146,143,139,136,133,130,127,124,121,118,115,111,108,105,102,99,96,93,90,87,84,81,78,76,73,70,67,64,62,59,56,54,51,49,46,44,42,39,37,35,33,31,29,27,25,23,21,20,18,16,15,14,12,11,10,9,7,6,5,5,4,3,2,2,1,1,1,0,0,0,0,0,0,0,1,1,1,2,2,3,4,5,5,6,7,9,10,11,12,14,15,16,18,20,21,23,25,27,29,31,33,35,37,39,42,44,46,49,51,54,56,59,62,64,67,70,73,76,78,81,84,87,90,93,96,99,102,105,108,111,115,118,121,124};
11
12
volatile union {uint16_t u16; uint8_t u8[2];} counter; //0 ist Low, 1 ist High Byte
13
volatile uint16_t offset;
14
15
void frequenz_setzen(uint8_t Ausgabefrequenz){ // 614 Takte mit -Os
16
 offset = ((F_CPU/2 + Ausgabefrequenz * TIMERRELOAD * AUFLOESUNG) / F_CPU); // F_CPU/2 ist fürs Runden
17
}
18
19
int main(void){
20
 OSCCAL = 0xAE; //Kalibrieren des Internen RC OSC
21
 TCCR1A = (1<<COM1A1) | (1<<WGM10); //Clear OC0A on Compare Match & Fast PWM Mode 5
22
 TCCR1B = (1<<WGM12) | (1<<CS10); //CPU-Takt & Fast PWM Mode 5
23
 TIMSK = (1<<TOIE1); //Timer 1 Overflow interrupt an
24
 DDRB = (1<<PB1); //PB1 (OC1A) als Ausgang 
25
 frequenz_setzen(50);
26
 sei();
27
28
 while(1);
29
30
 return 0;
31
}
32
33
ISR(TIMER1_OVF_vect){
34
 OCR1A = pgm_read_byte(&sinustabelle[counter.u8[1]]);
35
 counter.u16 += offset;
36
 return;
37
}

von Helmut L. (helmi1)


Lesenswert?

Das Cauerfilter bei Sinusausgabe hat aber noch einen Vorteil.
Im Sperrbereich hat es Stellen wo die Daemfung gegen unendlich geht. Man 
kann durch geschickte Dimensonierung die 1. Stelle jetzt so legen das 
sie genau auf die Samplefrequenz faellt. Schon ist diese maximal 
unterdrueckt.

von Mw E. (Firma: fritzler-avr.de) (fritzler)


Lesenswert?

Im Eingangspost steht nix von einer Signalform, also kann ja auch was 
anderes als nen Sinus erwünscht sein.

von eProfi (Gast)


Lesenswert?

@Autor:  Egal (Gast)
Dein Code ist schon mal ein guter Ansatz fürs Verständnis.
Aber: der counter sollte 24 Bit haben, damit die Frequenz feiner 
eingestellt werden kann. 24 Bit geht nur in ASM.

Zur Berechnung des offset:
Wenn die Frequenz häufig geändert wird, kann die Berechnung des offset 
auf eine Multiplikation mit der Konstanten TIMERRELOAD * AUFLOESUNG / 
F_CPU reduziert werden. Die Rundung erfolgt -falls überhaupt nötig- über 
die Addition des nächstniedrigeren Bits (bei 24Bit FTW und 8 Bit 
Tabelle: Bit15)

Die eigentlich DDS-Schleife soll nicht in einer ISR, sondern in der Main 
(mit voller Geschwindigkeit) laufen.

von eProfi (Gast)


Lesenswert?

Und wenn die Tabelle statt im Flash  im RAM steht, spart man einen 
Cycle.

So wie ich das sehe, willst Du nur kleine Frequenzen ausgeben, da reicht 
Deine PWM-Ausgabe. Sonst sollte es schon ein 8 - 10-Bit-DAC sein, zur 
Not R2R an einem Port.

von A. F. (elagil)


Lesenswert?

> volatile union {uint16_t u16; uint8_t u8[2];} counter;

Ah das ist ja geschickt :D da spart man sich das ganze schieben..

Für 32bit Zähler dann "union {uint32_t u32; uint8_t u8[4];} counter;"?
Greife ich dann mit counter.u8[0] auf die 8 höchsten Stellen zu? Ich 
weiß nicht so ganz, wie C die Werte im Speicher ablegt.

Martin Wende schrieb:
> Im Eingangspost steht nix von einer Signalform, also kann ja auch was
> anderes als nen Sinus erwünscht sein.

Doch, da steht doch was von Dreieck, Rechteck, Sägezahn und Sinus? ;)

eProfi schrieb:
> So wie ich das sehe, willst Du nur kleine Frequenzen ausgeben, da reicht
> Deine PWM-Ausgabe. Sonst sollte es schon ein 8 - 10-Bit-DAC sein, zur
> Not R2R an einem Port.

Ja, ich möchte einen 8bit Dac benutzen. Die Schaltung habe ich schon 
gebaut, das Programm ist bekannterweise noch zu optimieren.

Ein 8bit dac hängt an einem Port, an ihm ein paar OPAmps zur Wandlung 
von Strom zu Spannung, Offsetkorrektur, Verstärkung...

eProfi schrieb:
> Und wenn die Tabelle statt im Flash  im RAM steht, spart man einen
> Cycle.

Wie adressiert man den Ram-Bereich? ich habe da nichts gefunden.. Oder 
muss man mit assembler manuell die Speicheradresse angeben?

von Helmut L. (helmi1)


Lesenswert?

Adrian Figueroa schrieb:
> Ah das ist ja geschickt :D da spart man sich das ganze schieben..

Wenn der Compiler gescheit ist sollte er das auch so machen.

Adrian Figueroa schrieb:
> Greife ich dann mit counter.u8[0] auf die 8 höchsten Stellen zu? Ich
> weiß nicht so ganz, wie C die Werte im Speicher ablegt.

Yepp, das ist hier die Frage. Das weiss nur der Compiler und haengt ab 
von der Endiness der Maschine.

Adrian Figueroa schrieb:
> Wie adressiert man den Ram-Bereich? ich habe da nichts gefunden.. Oder
> muss man mit assembler manuell die Speicheradresse angeben?

Die meisten Speicherzugriffe beim AVR gehen auf das RAM. Nur bei 
Konstanten im Flash bedarf es eines gesonderten Befehles.

Also ein Array anlegen in C un los gehts.

von A. F. (elagil)


Lesenswert?

Ok, dann probiere ich das einfach aus, von wo der Compiler zu zählen 
anfängt.


buffer enthält die aktuelle Signalform.
"intervallSetzen" setzt die Schrittweite.
schleifenFreq muss ich noch herausfinden, indem ich die Takte der 
Hauptschleife zähle, freq ist die Vorgabefrequenz.

256/schleifenFreq kann ich ja vorher schon ausrechnen.
1
        uint8_t buffer[256];
2
3
        union {uint32_t total; uint8_t part[4]} counter;
4
5
        // ...
6
7
  for(;;) {
8
    counter.total+=steps;
9
    PORTD = buffer[counter.part[0]];
10
  }
11
12
        // ...
13
14
        void intervallSetzen() {
15
          steps = abs(256*freq/schleifenFreq + 0.5);
16
        }

von A. F. (elagil)


Lesenswert?

Die Schleife wird laut Atmel Studio zu 36 Clock Cycles (2,25 us; ca. 
444kHz max. Samplingrate). Nicht ganz die 10 Takte, die man mit 
Assembler schafft :D

Ohne Union mit dem Shift um 24 Stellen braucht man 40 Takte (2,5 us; ca 
400kHz).

von A. F. (elagil)


Lesenswert?

Ich habe noch ein Problem. Der Atmega 8 kann keine Pinchange interrupts 
auf beliebigen Pins, nur auf Port D (int0 und int1) den ich für den dac 
komplett brauche.

Es bleibt nur ein niederfrequenter timer interrupt (vielleicht 40 Hz..), 
der die Pins abfragt, z.B.:
1
ISR(TIMER0_OVF_vect) {
2
  static uint8_t laststate;
3
  static uint8_t state;
4
  state = PINC;
5
  if (state==laststate) {return;}
6
  else {
7
            // ...
8
        }
9
}

Das Teil braucht inklusive hin- und Rücksprung 10 Takte, wenn die Tasten 
nicht gedrückt werden. Gibt es einen anderen Weg? Ich wollte erst über 
den Interrupt an einem Knopf die Eingabe starten (und damit den Timer), 
das geht aber nicht, weil ich den Port D brauche (kein anderer bietet 
mir 8 Pins).

von Route_66 H. (route_66)


Lesenswert?

Hallo!
Ich hatte damals die Bedienerschnittstelle mit einem zweiten µC gelöst. 
Der macht Tasten, Drehencoder, LCD, Umrechnung Frequenz->Phasenakku, 
Umschaltung Filter und Ansteuerung des dB-Teilers am Ausgang. Mit dem 
DDS-AVR wird wie bei Jesper seriell kommuniziert, per RX-Interrupt. War 
aber in Assembler auf 'nem Atmel 89c2051.

von Helmut L. (helmi1)


Lesenswert?

Adrian Figueroa schrieb:
> Ich habe noch ein Problem. Der Atmega 8 kann keine Pinchange interrupts
> auf beliebigen Pins, nur auf Port D (int0 und int1) den ich für den dac
> komplett brauche.

Wenn du noch einen externen Interrupt haben willst kannst du einen der 
ICP Eingaenge nehmen. ICP1/PB0.

Du brauchst den Caputrewert ja nicht fuer was zu verwenden. Aber du hast 
deinen Interrupt von aussen.

von W.S. (Gast)


Lesenswert?

Falk Brunner schrieb:
> AVRs können's halt. Und für NF bis vielleicht 100kHz voll OK. Clever und
> leistungsstark.

Sportliche Herausforderung.. Wer fordert dich denn da heraus?

AVRs könnens halt.. Was können die denn? DDS-Takt bis vielleicht 400 
kHz?

Bis 100 kHz..  jahaha.. bis 100 kHz DDS-Takt sicherlich, aber ein 
wirklich brauchbares Ausgangssignal für NF-Zwecke wohl eher nicht.

Clever.. ähem, bitte?

Also, das Ganze ist offensichtlich eine zweckfreie Betätigung, beí der 
nicht beabsichtigt ist, zum Schluß irgend etwas tatsächlich benutzbares 
zu erzeugen. Nun, wenn man das genau so in aller Klarheit am Beginn 
sieht und trotzdem Lust drauf hat, dann nur zu. Ich hab ja auch den 
halben Bastelkeller voll mit Bauteilen, die ich mal mit 'da könnte man 
ja mal...' angeschafft hatte und die seitdem ihrer Verschrottung 
entgegensehen.

Aber wer tatsächlich was BRAUCHBARES erbasteln will, sollte den AVR oder 
sonstigen uC Favoriten mit nem LCD und ein paar Tasten nebst Drehgeber 
versehen und zum eigentlichen Signalerzeugen den o.g. AD9833 benutzen. 
So herum kommt Vernunft in die Sache und die betreffenden Chips kriegen 
die ihnen angemessene Aufgabe.

W.S.

von Fallobst (Gast)


Lesenswert?

W.S. schrieb:
> Aber wer tatsächlich was BRAUCHBARES erbasteln will, sollte den AVR oder
> sonstigen uC Favoriten mit nem LCD und ein paar Tasten nebst Drehgeber
> versehen und zum eigentlichen Signalerzeugen den o.g. AD9833 benutzen.
> So herum kommt Vernunft in die Sache und die betreffenden Chips kriegen
> die ihnen angemessene Aufgabe.

Nach meiner Schätzung reichen 9 Takte für DDS. Einen weiteren Takt nutzt 
man für die Ein- und Ausgabe, indem man einen einfachen Softcore 
programmiert. Taktfrequenz wahrscheinlich Faktor ~50 langsamer, aber für 
das Interface reicht das locker. Am Ende hat man 2 MHz DDS-Takt @20 Mhz 
Taktfrequenz. Und das weniger als einen Euro. Dein IC kostet das 
10fache.

von A. F. (elagil)


Lesenswert?

Helmut Lenzen schrieb:
> Aber du hast
> deinen Interrupt von aussen.

Danke, sehr praktisch!

W.S. schrieb:
> Aber wer tatsächlich was BRAUCHBARES erbasteln will, sollte den AVR oder
> sonstigen uC Favoriten mit nem LCD und ein paar Tasten nebst Drehgeber
> versehen und zum eigentlichen Signalerzeugen den o.g. AD9833 benutzen.
> So herum kommt Vernunft in die Sache und die betreffenden Chips kriegen
> die ihnen angemessene Aufgabe.

Das kann ich ja später noch erweitern, wenn ich weiß, wozu ich das Teil 
überhaupt benutzen will.

Fallobst schrieb:
> Nach meiner Schätzung reichen 9 Takte für DDS.

Wenn ich das so lese, bin ich mit 36 Takten nicht mehr so ganz zufrieden 
;)

Fallobst schrieb:
> indem man einen einfachen Softcore
> programmiert.

Was ist denn das?

von L. K. (ladde)


Angehängte Dateien:

Lesenswert?

Interessantes Thema (DDS) finde ich.
Habe gerade mal überlegt, wie das ganze wohl minimal in Assembler 
funktionieren könnte und komme auf 7 Takte für den main loop (siehe 
Anhang).

Kann das so funktionieren? Habe länger nichts mehr mit uCs gemacht.
Der Trick ist, die Wellenformdaten auf ganze 256-Byte-Blöcke im Ram zu 
legen und das höchste Byte des Counters als unteres X-Pointer-Byte zu 
verwenden.

von Ulrich (Gast)


Lesenswert?

Mit den Daten im RAM gewinnt man noch 1 Zyklus. Allerdings macht es für 
mehr Qualität schon Sinn die Tabelle auf 1024 Einträge zu verlängern, 
auch wenn das 2 oder 3 Zyklen extra kostet, das kommt der Qualität zu 
Gute, vor allem bei sehr niedrigen Frequenzen. Dazu gab es hier auch 
schon mal einen Thread.

Trotzdem bringt die Lösung mit dem AVR und DA Wandler eine eher geringe 
Qualität. Für den NF Bereich reicht es aber immerhin, etwa auf dem 
Niveau des XR2206 oder ICL8038, wenn auch mit anderen Schwächen. Als 
eine Programmierübung (in ASM) bzw. zum lernen ist das auch kein so 
schlechtes Projekt.

So ein IC wie AD9833 oder AD9832 oder auch das oben schon erwähnte 
fertige Modul mit dem AD9850 (auch wenn die Qualität nicht optimal ist) 
ist auch nicht mehr so teurer, dafür aber deutlich besser (2 Bit mehr 
Auflösung beim DA und eine etwa 12-fache maximale Abtastrate (beim 
AD983x).

Die Idee mit dem Softcore klingt interessant, ist aber doch schon recht 
aufwendig zu Programmieren. Die DDS Schleife wäre entrollt, würde also 
je Durchgang mehr als 1 Sample ausgeben, also etwa 4 DDS Samples und 
dann 2-4 Befehle für die 2. parallel auszuführende Aufgabe. Im Prinzip 
eine Arte Multitasking, nur direkt durch sorgfältiges mischen des Codes 
der beiden Aufgaben. Ein Softcore ist halt die Simulation einer 
einfachen CPU, die dann das eigentliche nicht zeitkritische Programm 
ausführen kann. Wenn man einmal den Softcore fertig hat, wäre man beim 
Rest flexibel. Die Frage wäre ob der Softcore einfacher ist als die 
direkte Bedienung von LCD und Tasten usw., möglich wäre das schon. 
Zumindest den Code für ein Kommunikation mit einem 2. µC könnte man aber 
auch direkt mit dem DDS Code mischen.

von Fallobst (Gast)


Lesenswert?

Einen richtigen Softcore zu schreiben, wäre in der Tat aufwändig. Ich 
dachte dabei eher an eine Mischung aus Softcore und kooperativen 
Multitasking: Man nutzt einen Takt für den "Softcore" pro DDS-Takt. In 
diesem wird schon angefangen, den nächsten DDS-Wert zu berechnen. Das 
macht man solange bis man genügend Takte frei hat. An dieser Stelle 
könnte man in externen Programmcode springen, der natürlich rechtzeitig 
und vor allem pünktlich zurückspringen muss. Bsp.:
1
movw r0:r1, Z  //Z sichern
2
movw Z, spcL:spcH  //Soft-PC
3
out DDS_PORT, dds_value1  //vorberechneten Wert ausgeben
4
ijmp           //2 Takte ijmp, 2 Takte rjmp zurück
5
DDS_next:
6
out DDS_PORT, dds_value2  //2. vorberechneten Wert ausgeben
7
movw Z, r0:r1  //alter Z-Pointer (um Daten aus der Sinus-Tabelle zu laden)
8
...
9
//ext. Code
10
block1:
11
lds r20, daten
12
inc r20
13
nop
14
ldi spcL, LOW(block2)  //Soft-PC auf nächsten Block setzen
15
rjmp DDS_next
16
block2:
17
...

Dieser "Softcore" würde 12 Takte pro Softzyklus benötigen. Die 
DDS-Schleife müsste man also 12+2 (rjmp für die Schleife) = 14 fach 
aufrollen. Bei einem DDS-Zyklus von 10 Takten und einem Softcore, der 
immer 4 Takte in einem Block ausführt, läge dessen Taktfrequenz bei 20 
Mhz  10  14 * 4 = 429 kHz.

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.