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!
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.
>double posTimesTen;
in der Tat, etwas Optimierung waere faellig. Aber double ist etwas
abgehoben.. Was soll double ueberhaupt ? Ich sah's an verschiedenen
Orten.
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.
> 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..
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
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.
> 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.
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.
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.
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
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
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.
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.
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.
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 ;-)
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.
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
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
@ 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 ;-)
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.
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
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.
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.
> 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.
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.
> 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...
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.
> 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.
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.
> 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
staticuint32_tcounter=0;
3
counter+=steps;
4
PORTD=buffer[counter>>24];
5
}
wie schon oben vorgeschlagen, schreibe ich dazu vielleicht ein bisschen
inline asm code.
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.
> 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 :)
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.
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.
@ 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.
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.
@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.
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.
> 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?
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.
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.
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).
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
staticuint8_tlaststate;
3
staticuint8_tstate;
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).
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.
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.
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.
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.
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?
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.
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.
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.