[DDS : AD9835 aansturing] nieuwe frequenties instellen

Informatie project
Ik heb een printje ontworpen voor een DDS met de AD9835 (link datasheet, 160 kb). Inmiddels heb ik bijna alle onderdelen erop gesoldeerd, op een paar onderdeeltjes na.
Ik heb het ontwerp hier vandaan :
http://www.malo-web.de/index.shtml?dds_mega8
Echter, ik heb er de nodige wijzigingen in aangebracht. Zo heb ik een numeriek toetsenbord ontworpen en nog wat kleine handigheidjes.

Programmeren
Inmiddels is de stap aangebroken om de Atmega8 (uC) te gaan programmeren. Ik heb het gros van de programma-code gereed, en ben nu aangekomen bij de aansturing van de AD9835.

In de broncode van MALO (Bascom bestand, 13 kb), zie ik dat bij het opstarten een testfrequentie van 1000 Hz wordt ingesteld. Vervolgens mag de gebruiker een nieuwe frequentie intoetsen.
De stappen die naar de AD9835 wordt uitgevoerd zijn bij het opstarten :

1) Sleep=1, Reset=1, Clear=1
2) 1000 Hz inklokken
3) Sync=1, Selsrc=1
3) Init Freq1
4) Sleep=0, Reset=0, Clear=0

Indien daarna een andere frequentie wordt ingesteld, dan worden deze 5 stappen niet meer gevolgd, maar alleen stap 2).

0 Hz
Ik heb echter een paar kleine wijzigingen voor ogen :
= Geen signaal naar buiten, wanneer apparaat wordt ingeschakeld.
= Stel dat de DDS een frequentie genereert. Zodra ik begin met het intoetsen van een nieuwe frequentie, mag er geen signaal naar buiten komen.

Het onderbreken van het signaal kan volgens mij op 2 manieren :
*1 Stel de uitgangsfrequentie in op 0 (a la stap 2)
*2 Voer stap 1) uit (Sleep=1, Reset=1, Clear=1)

Mijn vragen :
A) Wat doet de sleep-mode van de AD9835 eigenlijk ? Ik verwacht juist bij een sleepmode, dat de ingeklokte nieuwe frequentie helemaal niet ingelezen wordt. Of kan ie dat nog steeds wel, maar is de output (waar het opgewekte signaal uit komt) alleen in-actief ?

B) Wat kan ik het beste doen om de AD9835 te laten stoppen met het genereren van output-signaal (de stappen *1 en *2) ?

C) Kan het kwaad om bij iedere nieuwe ingestelde frequentie, stappen 1 /m 5 telkens te doorlopen ?

De AD9835 beschikt over twee registers, waarmee je de frequentie kan instellen. Telkens als ik een nieuwe frequentie opgeef, plaatst mijn AVR de bijpassende data in het register dat momenteel niet gebruikt wordt, waarna de DDS opdracht krijgt om van register te wisselen. Zo krijg je een mooie overgang van de ene frequentie naar de andere.

Voor mij is die vloeiende overgang niet zo'n belangrijk iets. Het mag van mij abrupt afgekapt worden. Maar ik maak op uit jouw beschrijving dat jij dus ook nog een sweep-achtige functie erin verwerkt hebt....

Ik had begrepen dat die 2 Freq-registers bedoeld waren voor elke een andere aansturing. Voor Freq 0 zou nog een extra pinnetje moeten worden aangestuurd. (Nou ja, zoiets maakte ik op uit de niet zo uitvoerig beschreven datasheet)

Maar, als ik het goed begrijp, mag ik dus de frequentie op 0 instellen en kan het telkens opnieuw uitvoeren van stappen 1 .. 5 geen kwaad ?

[Bericht gewijzigd door oxurane op (10%)]

Je kan ook van register wisselen door daarvoor het commando te geven.
stukje code:


#define FSEL0         0x6000
#define FSEL1         0x6800
#define DDS_STOP      0xF800
#define DDS_START     0xC000
#define SET_SYNC      0xB000
#define FREG0_L_LSB   0x3000
#define FREG0_L_MSB   0x2100
#define FREG0_H_LSB   0x3200
#define FREG0_H_MSB   0x2300
#define FREG1_MASK    0x0400
#define PHASE0_L_LSB   0x1800
#define PHASE0_L_MSB   0x0900
#define PHASE0_H_LSB   0x1A00
#define PHASE0_H_MSB   0x0B00


void DDS_WRITE(unsigned int data)
{
    char cnt;

    DDS_PORT |= _BV(DDS_CLK);					// CLK hoog
    DDS_PORT &= ~(_BV(DDS_EN));					// FSYNC laag
    delay(2);

    for (cnt = 0; cnt < 16; cnt++) {
	if ((data & 0x8000) == 0) {
	    DDS_PORT &= ~(_BV(DDS_D));
	} else {
	    DDS_PORT |= _BV(DDS_D);
	}
	delay(2);
	DDS_PORT &= ~(_BV(DDS_CLK));				// CLK laag
	data = data << 1;
	DDS_PORT |= _BV(DDS_CLK);				// CLK hoog
    }
    DDS_PORT |= _BV(DDS_EN);					// FSYNC hoog
    delay_us(2);
}


// Stel een nieuwe frequentie in.
// Om zonder haperen over te gaan van de ene naar de andere frequentie,
// plaatsen we de nieuwe gegevens in het frequentie-register dat momenteel
// niet in gebruik is, waarna we switchen
void FREQ_WORD_WRITE(unsigned long freq_word)
{
    unsigned int BYTE;

    if (FSEL == 0) {
	FSEL = FREG1_MASK;
    } else {
	FSEL = 0;
    }

    BYTE = (unsigned int) (freq_word & 0x000000FF);
    DDS_WRITE(FREG0_L_LSB | FSEL | BYTE);			// FREG-L-LSB in defer-register
    BYTE = (unsigned int) ((freq_word >> 8) & 0x000000FF);
    DDS_WRITE(FREG0_L_MSB | FSEL | BYTE);			// FREG-L-MSB + defer-register
    BYTE = (unsigned int) ((freq_word >> 16) & 0x000000FF);
    DDS_WRITE(FREG0_H_LSB | FSEL | BYTE);			// FREG-H-LSB in defer-register
    BYTE = (unsigned int) ((freq_word >> 24) & 0x000000FF);
    DDS_WRITE(FREG0_H_MSB | FSEL | BYTE);			// FREG-H-MSB + defer-register

    if (FSEL == FREG1_MASK) {
	DDS_WRITE(FSEL1);
    } else {
	DDS_WRITE(FSEL0);
    }
}

Verder kan je de DDS naar hartelust stoppen, een andere frequentie opgeven, en terug starten. Het enkel is de DAC-klok die stilgelegd wordt, niet de ganse DDS.

Wat is het freq. bereik van deze DDS ?

10MHz komt er nog heel netjes uit. Hoger kan ook, maar dan neemt de vervorming zienderogen toe.

Is deze geschikt voor 0...100.000 Hz in stappen van 1 Hz ?

Zeker. Zelfs in stappen van 0.01Hz en minder.

@MMSoft:
Dit IC kan tot 25.000.000 Hz. Tenminste, de snelste Xtal die erop aangesloten kan worden is 50 MHz. Ikzelf heb het schema van de site van MALO gebruikt en die gaat tot 20 MHz, bij gebruik van een 50 MHz Xtal.
De frequentie-stapjes (gewenste frequentie) kun je zelf bepalen, doordat je het 32-bit breed moet aansturen. Kortom, deel 50 MHz eens door 2^32 (zie het antwoord van Pros).

@Pros :
Aha, bedankt voor je opmerking over die DAC. Dat blijkt dus de energieslurper te zijn, die je tijdelijk kunt uitschakelen. Ik dacht dat juist die hele chip als het ware werd uitgeschakeld, en alleen door een interrupt of iets dergelijks weer "wakker geschud" kon worden.
Ik heb de werking / aansturing namelijk voor 85% kunnen achterhalen door de broncode van MALO na te vlooien. Zoals reeds eerder vermeld, de datasheet is karig met informatie.
Resteert mij nu alleen nog om jouw code na te lopen en kijken wat, waar en hoe iets gebeurd. (Mijn kennis van C is belabberd, dus ik zal er nog even zoet mee zijn. Maar voel je niet schuldig : het zal zeker leerzaam voor me zijn !)

[EDIT]
Ik zie al dat je standaard waarden gebruikt om bepaalde "commando's" naar de AD9835 te sturen. Maar waarom is die "Delay" en in welke tijdseenheid is deze uitgedrukt (sec, msec, usec)?
[/EDIT]


#define delay(x) _delay_loop_1(x)

Een heel korte delay, dus. Waarschijnlijk kan het ook zonder delay.

@Pros :
Hmm,. hoe werkt die "phase0" eigenlijk ? In de datasheet zie ik dat het waarschijnlijk te maken heeft met die overgang naar de nieuwe frequentie tussen FREG0 en FREG1. Maar het is me nog onduidelijk hoe dat aangestuurd zal worden. (Ja, waarschijnlijk iets van 32 bit breed)

Nou ja, ik ben in ieder geval blij met je toelichting op de delay. Ik zag elders dat men er soms 2 uS voor gebruikt.
Gelukkig heb ik een ISP-header op het printje ontworpen, dus kan straks eventueel verbeteringen in de uC proppen, mocht het niet meteen lukken (zoals die delay).

Ik heb zondag nog even de spanning aangesloten op het printje en vervolgens gekeken of de te verwachten spanningen allemaal kloppen op het boardje. 'T zag er goed uit, dus heb ik ook meteen wat andere componentjes erop gesoldeerd, met uitzondering van de ATMEGA8 en een potmetertje.
Dinsdag probeer ik het schrijven van de broncode af te ronden en in een ATMEGA8 te programmeren. Ik duim nu al voor een leuke dinsdag en dat de "wet van Murphy" die dag even niet voor mij zal gelden...

De DDS zorgt eigenhandig voor een nette overgang, als je een andere frequentie instelt.
Die PHASEx-registers heb je enkel nodig als je het uitgangssignaal wil moduleren.

Oef, de wereld staat even op z'n kop voor mij.
Begrijp ik het nu goed dat met de PHASEx-registers de vorm van het signaal bepaalt kan worden ?

Ik heb de datasheet nu maar eens uitgeprint, wat het lezen erg vergemakkelijkt. Ik zie nu ook meer dingen, die ik eerst niet had gezien.

Hmm, nu begin ik echt te twijfelen of ik alles goed heb begrepen, dus laat ik starten met wat ik nu denk te weten over de AD9835.

COS Look-up Table (LUT)
Heb ik het nu goed begrepen dat er in de AD9835 een soort tabel (LUT) is waarin een complete cosinus-golf is terugvertaald in 10-bits resolutie ? Oftewel de complete cosinus is opgedeeld in 4096 stapjes (Delta Phase), waarbij een bepaald spanningsnivo hoort ?

Terugvertaald :


+1   '.      .''.      .'
 0     |    |    |    |
-1      '..'      '..'

     +--------+
     0       4096
     (1 cyclus)

Stel ik geef het PHASEx-register de waarde 2048. Dan start de DAC de golf bij -1.

Grote stappen / aanpassing signaalvorm
Verder had ik vaag begrepen dat je die stapjes als het ware kan inkorten, of beter gezegd, dat je met "grote stappen" door die LUT zou kunnen lopen, waardoor je je uitgangssignaal van de DAC fors kan aanpassen.
Stel ik zou aangeven dat er per 4 grote stappen van 1024 door de LUT gewandeld moet worden. Dan produceert de DAC het volgende :


             
+1   .   .        
 0                
-1     .   . 
     +-----+
     0    4096

Wat vertaald kan worden naar :


+1   .   .        
 0    . . .  
-1     .   .
     +-----+
     0    4096

Klopt dit allemaal, of ben ik nu echt het spoor bijster ?

Je zit er een klein beetje naast. In werkelijkheid stap je niet door de LUT - bij slechts 4096 waarden zou het vrij nutteloos zijn om de frequentie in te stellen met een 32-bit woord.
Je stapt door een 32-bits fase-tabel, de NCO (een hoek-tabel, zeg maar). De 12 meest-significante bits daarvan worden gebruikt om de waarde uit de LUT te plukken die bij de hoek hoort.

Aha, dan had ik het gelukkig aardig begrepen. Maar klopt het dat je het aantal stappen dan kunt instellen, of de stapgrootte ?

Indien dat kan / klopt, hoe ziet de code eruit om die stappen / stapgrootte door te geven aan de AD9835 ? (Is het dat je de stappen aangeeft of de stapgrootte)
En,.. dan een logische vervolgvraag daarop : welke waarden kunnen het beste gebruikt worden voor het genereren van een vloeiende sinus, een driehoek, respectievelijk een blokgolf ? Bij een driehoek denk ik aan 4 stappen (stapgrootte 2048), maar denk / verwacht geen mooie overgang te zien op de scope. In mijn vorige bericht liet ik dat ook zien, middels een grafiekje. Laat ik hieronder dan ook meteen weergegeven wat ik theoretisch verwacht als output en wat ik daadwerkelijk verwacht te zien op de scope :


                 (theoretisch)            (daadwerkelijk)
+1   .   .          ^   ^                 _       _
 0                   \ / \                 '_   _' '_
-1     .   .          v   v                  '_'     '_
     +-----+        +-----+              +------------+
     0    4094      0     4094           0           4094

En moet er nog wat verder gesleuteld worden aan deze waarden, afhankelijk van de ingestelde frequentie ?
Uitleg : Bij een hoge frequentie zal het wel loslopen, en verwacht ik eerder die theoretische golf te zien. Bij een erg lage frequentie, bijv 1 Hz, zal het beeld eerder de meest rechter grafiek opleveren (daadwerkelijk)...

OPM : Ja ja, naast Sinterklaas, besloot ook Murphy vandaag -geheel onverwacht- toch nog even op bezoek te komen. Ik zie met lede ogen aan, dat de uC niet herkent wordt, wanneer ik het wil her-programmeren via de ISP op het printje. Ik voed het printje met een labvoeding, dus weet even niet waar het aan ligt. Ik hoor wel dat de uC nog een reset kan ontvangen, maar daar blijft het ook bij : uitlezen of opnieuw programmeren kan ik momenteel vergeten. GRMBL, mag ik de uC weer lossolderen (ja, da's het vervelende met SMD).

Beschouw de LUT als een ROM met 12 adressen.
De 12 hoogste bits v/d NCO worden gebruikt om die ROM te adresseren.
Nu heeft de NCO 32 bits, en ook het frequentie-register beschikt over 32 bits. Jij stelt de frequentie in, door een UL in dat frequentie-register te plaatsen.

Wat er verder gebeurt is dit:
Bij elke klokpuls op MCLK worden het frequentie-register en de NCO opgeteld; het resultaat komt in de NCO terecht.
De ROM (LUT) krijgt nu --als één van de 12 MSB's van de NCO wijzigde-- een ander adres toegespeeld en geeft een andere waarde door aan de DAC.
Als het frequentie-register een kleine waarde bevat, kan het verschillende klokpulsen vergen, voor één van de 12 MSB's van de NCO verandert. De uitgangsstroom blijft dan ongewijzigd.
Bij een grote waarde in het frequentie-register zal de sprong zo groot zijn, dat de ROM verschillende adressen overslaat.

Wat het uitgangssignaal betreft: dat gaat idd. in stapjes. Daarom is een goed uitgangsfilter onontbeerlijk om er een nette sinus van te maken.

Dank je voor deze uitleg. Het kwartje begint te vallen.

Het lijkt misschien dat ik doormauw / zeur, maar het volgende is bij mij nog wel onduidelijk. Ik stel me het volgende voor bij die LUT / ROM-tabel :


Stap      Waarde
1         1.000
2         0.999
..        .. 
2048     -1.000
2049     -0.999
..        ..
4095      0.999
4096      1.000

Stap is de "delta Phase" en "Waarde" is de waarde die naar de DAC gaat om het te vertalen naar een bepaalde spanning (bijvoorbeeld tussen 0 tot +1V)

Misschien doe ik moeilijk, maar ik kan me nu namelijk 3 mogelijk verschillende werkingen van die ingestelde stap voorstellen :
1) Faseverschuiving
2) Signaalvervorming / verkapping
3) Signaalsprongen

Stel, ik zou geen stap-aanpassing toepassen, dan ziet de output van de AD9835 er als volgt uit :


+1   '.      .''.      .'
 0     |    |    |    |
-1      '..'      '..'
     +--------+---------+
     0        0         0
           (=4096)   (=4096)

1) Faseverschuiving :
Ik stel de stapwaarde in op 2048. In de LUT / ROM staat op regel 2048 de waarde "-1". Vanaf daar wordt gestart en vervolgens wordt er gesprongen naar de volgende regel in de tabel (2049, 2050 etc). Dat zou resulteren in het volgende grafiekje :


+1      .''.      .''.
 0     |    |    |    |
-1   .'      '..'      '.
     +--------+---------+
     0        0         0

2) Signaalvervorming / verkapping
Weer waarde 2048 ingesteld. Ook nu wordt gestart vanaf regel 2048, en wordt elke volgende tabelregel, een waarde gehaald. Maar er wordt zodra de laatste regel is uitgelezen (4096), de sprong naar regel 2048 gemaakt. En dit gaat maar zo door. Grafisch weergegeven :


+1      .'   .'   .'   .'
 0     | |  | |  | |  | |
-1   .'  |.'  |.'  |.'  |
     +--------+---------+

3) Signaalsprongen
Er wordt als het ware per X-stappen door de tabel gesprongen en de waarde uitgelezen. Grafische weergave met "stapsprong" 2048 :


+1   '''''    '''''     |
 0       |    |   |     |
-1       |....|   |.....|
     +--------+---------+
     0        0         0

Ik heb het gevoel dat de AD9835 volgens deze optie 3) werkt. Dat lijkt mij tenminste het meest zinnige.

Maar daar ontstaat ook meteen een extra probleem :
Een signaalfilter zorgt er voor dat er binnen een bepaalde frequentie-range een zekere signaal(golf)vorm ontstaat op de uitgang, toch ?

Dat zou betekenen dat je bijvoorbeeld in het gebied van 1 Hz - 20 MHz een blokgolf krijgt (filter A). Maar omdat die uitgangsfilter is ontworpen voor een blokgolf, kan er geen driehoek / zaagtand uitkomen. Daar dient dan een 2e uitgangsfilter (filter B) voor ontworpen te worden.
Met behulp van een switch of relais kan dan het uitgangssignaal die uit de AD9835 komt, in filter A (blokgolf) of in filter B (driehoek / zaagtand) "ge-injecteerd" worden.

Tsjonge jonge, ik had nooit gedacht dat het zo complex kon zijn. Ik dacht er aan het begin van dit project echt even te lichtzinnig over. Gewoon nabouwen en hupsakee, "even" wat code schrijven en gaan met die banaan.
Na het schrijven en teruglezen van deze posting, bekruipt me het gevoel dat ik juist weer te ver doorschiet, door het onnodig complex te maken, juist doordat ik zo veel mogelijke opties zie (3 !). Ik moet eerlijk bekennen, dat ik blij ben dat ik dit in de nacht schrijf, want daardoor heb ik al diverse wijzigingen in de text kunnen maken, om zo duidelijk mogelijk te beschrijven wat er door mijn koppie heen gaat.

Ik hoop in ieder geval dat dit topic leerzaam en zeker interessant is voor de CO'ers die hier heimelijk op de achtergrond meelezen.

Tot slot, en om nog terug te keren naar het onderwerp : Volgens welke optie werkt de AD9835 en klopt het wat betreft die 2 filters ?

[EDIT 25-11-2009 : 02:32]
Hmm,. als ik je verhaal nog eens aandachtig doorneem, merk ik dat ik waarschijnlijk op een ietwat andere "golflengte" zit. ;-)
Kwartje is dus nog niet gevallen.
Ik denk dat ik een hiaat bij mezelf heb ontdekt : wat is / doet de NCO ?
Dat van die frequentieregister denk ik wel te vatten, maar begrijp niet "Bij elke klokpuls op MCLK worden het frequentie-register en de NCO opgeteld; het resultaat komt in de NCO terecht."
Ik wil nog wel meer opschrijven, maar het lijkt me verstandiger dat ik eerst op een reactie van jou, of iemand anders wacht.
Pros, is het misschien mogelijk voor je om het geheel met plaatjes / tabelletjes toe te lichten (zoals ik het ook probeer) ?
Ja ja, plaatjes zeggen mij altijd meer dan ... nou ja, je weet wel.
[/EDIT]

Ik denk dat je het onnodig ingewikkeld maakt. Het basisprincipe kan je met enkele regels c-code schrijven:


void doe_als_DDS(unsigned int FREQ0_REG)
{
    unsigned char LUT[256] = { 0, 3, 6, 9, 11, enz... };
    unsigned int NCO = 0;

    DDRD = 0xFF;			// PORTD is verbonden met externe DAC
    while (1) {
	PORTD = LUT[NCO >> 8];		// Nieuwe waarde naar DAC
	NCO += FREQ0_REG;
    }
}

Deze heeft maar een 8-bits LUT en een 16-bits NCO, maar het principe blijft gelijk.
Stel dat je de routine aanroept met 0x0100 als argument, dan blijven de 8 minst-significante bits van NCO bij elke doorgang ongewijzigd. De 8 hoogste bits worden telkens met 1 verhoogd en de waarden in de LUT komen één voor één op PORTD te staan.
Geef je 0x0001 op, dan dient de while-lus telkens 256 maal doorlopen te worden, voor een volgende waarde uit de LUT naar PORTD gestuurd wordt. Dat resulteert in een lage frequentie.
Geef je 0x1000 op, dan wordt er met grote passen door de LUT gestapt. De frequentie is nu heel hoog, maar het uitgangssignaal ziet er minder fraai uit.

Als je de moeite doet, het LUT-array volledig in te vullen, zou het nog werken ook... :-)

Afgelopen weekend had ik alle onderdelen op het printje gesoldeerd en heb nog een paar dagen lopen stoeien om het ding aan de praat te krijgen. In Bascom had ik nog wat foutjes zitten, maar die zijn grotendeels opgelost. Inmiddels heb ik het met een scope getest en ik ben dik tevreden. Ik heb er zelfs rechtstreeks een piezo-element op aangesloten om het ook hoorbaar te maken. Wel heb ik nog een klein ontwerpfoutje maar is voor het prototype niet hinderlijk.

Nou zat ik nog steeds met die blokgolf in mijn maag en om het zo eenvoudig mogelijk op te lossen. Ik speel met de gedachte om het schema verder aan te passen, om het toch voor elkaar te krijgen : Sinus en blokgolf. Nu bedacht ik me dat ik het wellicht met een ultrasnelle Voltage-comparator zou kunnen oplossen, zoals de MAX9010 of de LT1719.

Van de AD9835 komt een output, dat over het filter gaat. Stel, ik laat het blokgolf-gedeelte weg (BF256 etc), maar plaats na het filter meteen een relais, waarbij ik enerzijds rechtstreeks, anderzijds via een MAX9010 of LT1719 het signaal over de potmeter naar de AD811 kan laten gaan. Dus als volgt :

                            
                              o----------o
AD9835 - Filter - Relais 1 -/              \--- Relais 2 --- AD811
                              o--MAX9010-o

OPM : potmeter staat niet in het "schema".

Door het signaal rechtstreeks van filter naar de AD811 te laten gaan, krijg ik dan nog steeds de sinus. Laat ik het signaal over de LT1719 gaan dan komt er juist een blokgolf uit, richting de AD811.

Maar, klopt het wat ik hierboven bedenk ?

Die MAX9010 en het relais lijken me OK. Maar die AD811 lijkt me wat te traag om de steile flanken, die de comparator aflevert, netjes weer te geven. Ik zou toch op zoek gaan naar een sneller type - de LM7171 misschien?

Ik wil kijken of ik de LT1719 kan samplen, mede omdat deze direct te bestellen is bij hen en ook nog eens voor een leuk bedrag. Verder zag ik op de site van LT nog een mogelijke vervanger voor de AD811 en de door jou voorgestelde LM7171 :
LTC6404
Een nadeel : ik zie veel aansluitingen en schrik er daarom een beetje voor terug.
Misschien toch maar eens kijken naar die LM7171 bij Digikey.

[EDIT]
Verhip, nu realiseer ik het me : het gaat bij de OpAmp om de SlewRate en de snelheid van het "samplen". Ik ben eens gaan rekenen en nu snap ik waarom je uitgaat van de LM7171 met een SR van 4100V / uS.
De SlewRate is dus het belangrijkste en die moet zo hoog mogelijk zijn, maar waarom koos je voor de LM7171 ? 140MHz versus 220MHz is niet zo drastisch, eerder een kleine verbetering.

Om te kijken of ik het daadwerkelijk heb "gesnopen" : zou de THS3061 dan een nog betere oplossing zijn ? (SR 7000V / us, 300MHz bandwidth)

En de comparator, is dat niet zelf te ontwerpen via een alternatief ? Het grote nadeel van de DDS is dat het omslagpunt bij 0.2V ligt , wat het wel erg moeilijk maakt om (zener)diodes of iets dergelijks te gebruiken.
[/EDIT]

Inmiddels ben ik al weer een stukje verder gekomen, en ben nog bezig met experimenteren voor wat betreft de juiste uitgangsversterker.

In de tussentijd heb ik informatie proberen te vinden over de Low-pass-filter die na de AD9835 zit. Ik was erg nieuwsgierig geworden naar hoe de dat ding berekend was. Op enkele sites werd ik al meteen doodgegooid met complexe wiskundige formules, waar ik niet uit kwam.
Vandaar dat ik dacht : laat ik dan een site zoeken waar je een dergelijk filter zou kunnen berekenen. Er kwamen 2 sites voor mij in zicht :

1) http://www.wa4dsy.net/filter/hp_lp_filter.html

2) http://www.calculatoredge.com/electronics/ch%20pi%20low%20pass.htm

http://www.calculatoredge.com/images/bw%20pi%20low.jpg

Uit het ontwerp van de DDS begreep ik dat het waarschijnlijk om een 7-pole Low pass Chebyshev-filter gaat.

  • 7 Pole
    Omdat er 7 componenten worden gebruikt voor het filter
  • Low pass
    Omdat er tot een bepaalde frequentie niet gefilterd wordt
  • Chebyshev
    Omdat ik bij het meten aan de uitgang zie dat de frequentie vrijwel vlak achter de 20 MHz compleet inkakt. 23 MHz krijg ik er niet uitgeperst.

"Nou, dat wordt makkelijk" : dacht ik. Gewoon wat gegevens op die sites inkloppen en kijken wat voor componentwaarden er worden voorgesteld.
*slik*
Niets van de berekende componentwaarden bleken ook maar enigzins in de buurt te komen van de gebruikte componenten van deze DDS. Kortom, theorie en praktijk lagen ver uit elkaar.
Kijk, zou het nu op 1 van de pagina's gebeuren, dan had ik doodleuk kunnen zeggen : "Site van een prutser die, of niet overweg kan met formules, of met een programmeerscript."
Maar nu voel ik mijzelf een prutser, omdat de componentwaarden totaal niet overeenkomen en dat op notabene beide sites. Hmm,. het moet dan wel aan mij liggen. Of ik voer onjuiste gegevens in, of staar me blind op het verkeerde filter.

Tsja, met mijn nieuwsgierigheid heb ik me weer eens in de nesten weten te werken. Velen zullen zeggen : ach maak je niet druk, je DDS werkt toch ?
Klopt, maar ik heb mijn oog al laten vallen op een nog snellere DDS en wil daar straks mee aan de slag gaan. En ik vermoed dat ik dan zelf het filter moet gaan berekenen (voor 50 MHz). En dan zal het erg handig zijn om te weten met welke waarden ik moet gaan sleutelen.

Kortom, welke waarden dien ik in op de "reken"-sites in te vullen ? Op de eerste site had ik de volgende gegevens ingevuld :
Type : Lowpass
Poles : 7
21 MHz 3db cutoff frequency
Voor I/O impedance in Ohms heb ik verschillende waarden geprobeerd. Van hele lage (20 Ohm) tot hoge (10 K).

Op 21 november 2009 16:43:00 schreef pros:
Je kan ook van register wisselen door ...

void DDS_WRITE(unsigned int data)
{}
void FREQ_WORD_WRITE(unsigned long freq_word)
{
}
[/code]

pros,

heb jij voor mij ook het stukje waar je je frequentie omzet naar je FREG? Die berekening wil mij nog niet echt goed lukken en ik blijf maar 0.12% naast mn ingestelde frequentie zitten.

Mijn code (in JAL):


-- step per hz = (2^28 / MCLK) * 2^16
const dword _dds_steps_per_hz = 0x_10_00_00_00 / (dds_mclk >> 16) -- 703786.442 
--                32b         =      28b       - (  25b    -  16bits ) = 28b-9b = 19 bits
PROCEDURE dds_set_frequency(dword in hz) IS

-- Multiply causes overflow:
--	data = hz * _dds_steps_per_hz -- 19 bits * 24 bits = 43 bits -> overflow!
--  data = data >> 16

	var dword data = 0;
	var dword part;
	var byte n = 0
	while n < 4 loop
		part = hz & 0x00_00_00_FF
		part = part * _dds_steps_per_hz
		if n == 0 then
			part = part >> 12 -- >> 12 << 0
		end if
		if n == 1 then
			part = part >> 4  -- >> 12 << 8
		end if
		if n == 2 then
			part = part << 4  -- >> 12 << 16
		end if
		if n == 3 then
			part = part << 12  -- >> 12 << 24
		end if
		data = data + part
		hz = hz >> 8
		n = n + 1
	end loop
	data = data >> 4
        .. schrijf data naar dds.

Waar ik dus mn _steps_per_hz van te voren bereken en voor de precisie met 2^16 vermenigvuldig. Daarna per 8 bits vermenigvuldigen, delen door 2^12. En dan aan het einde nogmaals delen door 2^4 om in de laatste 4 bits nog overflows door te tellen. Dat laatste haalt overigens niets uit (in 1x door 2^16 delen per stap werkt net zo goed). Maar ik krijg het niet mooi.

Vraag me af of de waarde wel fout is of dat mn scope er gewoon 0.12% naast zit.. Zal de FREG er eens serieel uit printen om te controleren ook.


Fset            Fout            Fout/Fset
399.508         400.000         0.12%
799.016         800.000         0.12%
 79.902          80.0003        0.12%
    999           1.000         0.1%

Grt Tijs

E: opeens ging het licht branden. Bij de >>16 op de MCLK gooi ik 16 bits weg van 25*10^6, dat is 30784: 30784/25000000 = 0.12% !

Ik doe het met een floating-point berekening:


// De frequentie wordt opgegeven in (Hz *100)
// freq_word = ((freq / MCLK) * PH_MAX + 0.5);
// Dit is de vereenvoudigde versie voor een 50MHz-oscillator.
unsigned long FREQ_CALC(unsigned long frequentie)
{
    double freq = (double) frequentie;

    freq *= 1.073741824;
    freq += 0.5;
    return ((unsigned long) freq);
}

Als ik bv. 1234.56 als frequentie opgeef, maakt de software daar eerst 123456 van, waarna bovenstaande berekening aangeroepen wordt. Het resusltaat gaat naar de DDS.

Ah! Ja dat doet jal niet (floating point, automatisch). Jij gebruikt C, dus ook een 18F PIC? Ik zit met een 16F628.

Maar goed nieuws: het licht hierboven was inderdaad het juiste lampje. Ik heb het nu anders opgelost:
* het integere deel gewoon uitgerekend
* het decimale deel (<1) vermenigvuldigd met 2^24. Dan kan ik die vermenigvuldigen met elke byte van mn frequency dword.
* alles optellen.

Nu werkt het goed.

Voor de gene die er (misschien) ook niet uit komt:


-- f = (MCLK / 2^28) * Freg => Freg = (2^28 / MCLK) * f = steps_per_hz * f
-- steps_per_hz = 10.7274182 @ 25Mhz
const dword param_int = 10
const dword param_dec = 12_371_824 -- = 0.7274182 * 2^24 = (steps_per_hz - param_int) * 2^24
PROCEDURE dds_set_frequency(dword in hz) IS
	-- integer part
	var dword data = hz * param_int

        -- decimal part
	-- data = data + (hz * param_dec) >> 24, causes overflow
	var dword part;
	var byte n = 0
	while n < 4 loop
		part = hz & 0x00_00_00_FF
		part = part * param_dec
		if n == 0 then
			part = part >> 24 -- >> 24 << 0
		end if
		if n == 1 then
			part = part >> 16 -- >> 24 << 8
		end if
		if n == 2 then
			part = part >> 8  -- >> 24 << 16
		end if
		-- n = 3 -> no shift
		data = data + part
		hz = hz >> 8
		n = n + 1
	end loop
	
	-- better for actuall precision (average 'error')
	data = data + 2

	-- send via 2 freq0 words	
	var word freqmsg -- only 2+14 bits at a time	
	for 2 loop
		freqmsg = word(data & 0b_00000000_00000000_00111111_11111111) -- 14 lsb
		freqmsg = freqmsg | 0b_0100_0000_0000_0000 -- register = FREQ0
		_dds_send_word(freqmsg)
		data = data >> 14 -- next 14 bits
	end loop
END PROCEDURE

Ik heb hem nu vanaf 1 -> 12.500.000 hz op de hz nauwkeurig en voor zover zichtbaar op de Rigol ook helemaal gelijk met zijn counter.
Het is wel veel code, en het kan waarschijnlijk ook nog wel beter. Moet ik nog naar kijken. Maar aan de andere kant ben ik ook wel benieuwd hoeveel code er uit eindelijk uit die floating point code komt. Dit is 52 words.

Deze library voor de AD9833 zit binnenkort in jallib overigens.

Bedankt voor je (snelle) antwoord pros!

Grt Tijs