Een signaal generator is altijd makkelijk om er bij te hebben. Dertig jaar geleden (… ik wordt oud) had ik er een gemaakt met een XR2206. Dezelfde ontwerpen vind je trouwens nu nog als bouwpakketje of als kant een klaar printje op diverse plaatsen. Zoek maar eens op Ebay.
Van dat zelfde Ebay had ik enige tijd geleden een AD9851 module gekocht en er ook een signaal generator mee gemaakt.
Daar komt een mooie 1 Volt tt sinus uit van 1Hz tot 10MHz, ja hij kan hoger maar dat heb ik toch nooit nodig. Het ding heeft een klokfrequentie van 180MHz en als er per 2 klokcycli een sample van de sinus gemaakt wordt zijn er bij 10 MHz nog krap 9 over. Dat zal al geen mooie sinus meer zijn. Het apparaat is nog niet af, geen uitgangsbuffer, geen volume regeling en geen offset. De output is nu een sinus van 1 Volt tt met een vaste offset van 0.5 Volt. Niet altijd even practisch.
Maar het is leuker om zelf een signaal generator te maken, uiteraard ook met DDS. Die kan dan mooi die oude XR2206 vervangen. Het maken van een DDS blijkt verrassend eenvoudig, de slimmigheid zit ‘m in de phase-accumulator. Ik heb ‘m gemaakt met een ATMEGA328 op 16MHz. Met een timer laat ik 100.000 interrupts per seconde genereren die dus minder dan 160 klokcycli lang mogen duren. En dan blijft er niets over voor de rest van het programma, die de rotary encoder, een drukknopje en het LCD scherm nog moet aansturen.
Als eerste een R2R ladder netwerkje gemaakt met 1k en 2k weerstanden, vrij lage waarden maar het blijkt goed te werken. Omdat de phase-accumulator routine heel regelmatig moet functioneren laat ik de rotary encoder een eenmalige interrupt genereren die vervolgens een timer inschakeld voor het debouncen van de encoder (die zijn echt heel slecht). Na ongeveer een kwart seconde gaan alle verstorende interrupts weer uit en blijft alleen die van de phase-accumulator over. Je kan zien dat er kleine onregelmatigheden in het signaal zitten als je aan de knoppen zit, maar zolang je er van af blijft is het signaal mooi schoon. Al zitten er nog wel problemen aan het maken van signalen met een dds, waar ik later op terug zal komen. (TODO: jitter)
Zie hier de schema’s
Als microcontroler gebruik ik een Arduino Pro Mini die gewist is en direct vanuit Atmel Studio geprogrammeerd wordt het is dus geen Arduino meer. De software van Arduino maakt het ding veel te traag en ik mis de controle over de interrupts.
Hier de opbouw op een breadboard. (links de generator, rechts het filter)
Ik had flink problemen met het kiezen van de juiste poorten op de ATMEGA328. Het mooiste is natuurlijk als je de juiste waarde voor het volgende sample van de golfvorm in één keer op de uitgang kan zetten, maar helaas het mocht niet zo zijn. PORTB kon ik niet gebruiken omdat daar het kristal al op is aangesloten, en ik wilde natuurlijk wel kristal stabiliteit en een “hoge” klok frequentie. De interne klok is 8MHz en niet nauwkeurig en ook temperatuur afhankelijk. PORTC viel ook af omdat die maar zes bits breed is, PC6 is de RESET en PC7 werkt om een of andere reden ook niet. En helaas kon ik ook niet alle 8 bits van PORTD gebruiken omdat ik gebruik wil maken van de INT0 (externe) interrupt.
Er zat dus niets anders op dan de data in twee nibbles via PORTC en PORTD naar buiten te sturen. Achteraf blijkt dat geen probleem te zijn, de glitches zijn klein en zeer kort zodat het filter ze volledig verwijderd. Hieronder de hele interrupt voor het genereren van de golfvormen:
//interrupt voor het genereren van de golfvorm, 100k / seconde
//omdat er erg weinig tijd tussen de interrupts zit geen andere dingen uitvoeren
//als er wel andere interrupts worden uitgevoerd is de golfvorm niet meer zuiver.
ISR(TIMER0_COMPA_vect)
{
static uint32_t counter = 0;
static uint8_t output = 0;
//step defines the frequency (42950 --> 1 Hz, 429500 --> 10Hz, etc.)
counter += stepsize;
switch (waveform)
{
case 1:
output = sine[counter >> 24];
break;
case 2:
output = triangle[counter >> 24];
break;
case 3:
output = sawtooth[counter >> 24];
break;
case 4:
output = squarewave[counter >> 24];
break;
case 5:
output = sinxdivx[counter >> 24];
break;
default:
break;
}
//door een tekort aan ports de output verdelen over PORTC en PORTD
//lower nibble van PORTD is ingangen, pull-up weerstanden ingeschakeld laten
PORTC = output & 0x0F;
PORTD = (output & 0xF0) | (0x0F);
}
Het gebruik van “switch – case” bleek sneller te werken dan een lijst van “if – else”. Weten we dat ook weer :-) En zoals je ziet, wordt er gebruik gemaakt van vijf lookup tabellen voor de verschillende golfvormen.
Om ervoor te zorgen dat bovenstaande routine niet gestoord wordt staat er maar één andere interrupt aan die een neergaande flank van de rotary encoder detecteert, de INT0 interrupt doet niets anders dan een andere timer-interrupt inschakelen die naar de stapjes van de encoder luistert, zichzelf na een kwart seconde uit zet en de externe interrupt (INT0) weer inschakelt.
Hier de output van de generator op 1 kHz, mooi he!
Helaas bleek het niet allemaal zo mooi te zijn. Ik heb er de tabellen voor een sinus, driehoek, zaagtand, blokgolf en iets dat sin(x)/x heet in geladen. De sinus, driehoek en sin(x)/x zijn goed tot redelijk, ook op de hoogste frequentie (10kHz). Maar de zaagtand en blokgolf hebben vreselijk last van jitter ook wel dds-phase-noise genoemd geloof ik. Na een poos er over zitten na denken kan het ook niet anders dan een rommelig signaal geven. Zeker bij de hogere frequenties wordt het aantal samples uit de lookuptabel erg klein, bij 10kHz nog maar 10 en het is natuurlijk heel goed mogelijk dat de ene keer een andere waarde uit de tabel wordt gekozen dna de volgende keer. Bij een blokgolf en een zaagtand is dat desastreus! Daar is er namelijk een moment dat de waarde van 0 naar 255 (en andersom) gaat. Als je die de ene keer 10uS eerder of 10 uS later treft heb je een flinke jitter te pakken. Bij een sinus gaat het allemaal veel geleidelijker zodat de jitter er in principe wel is maar nauwelijks zichtbaar is (en door het filter nog minder wordt?)
Dus maak ik nu een TTL blokgolf door de sinus door een schmitt trigger te halen. De blokgolf uit de DDS laat ik er wel in zitten, net als de zaagtand, ik heb toch geen idee wat ik er mee moet doen :-).
Nog een paar plaatjes:
Deze zijn allemaal gemaakt op 1 kHz, op de maximale frequentie van 9,999.99 Hz ziet het er allemaal wat minder mooi uit. En wat ik met die sin(x)/x moet weet ik ook nog steeds niet.
Wat heel goed gaat met een DDS is heel lage frequenties maken. Ik ben niet verder gegaan dan 10mHz (milli, niet Mega). Je moet er even geduld voor hebben maar dan heb je ook een mooie sinus op 0.01 Hz. Ook daarvan weet ik echt niet waar het ooit goed voor is.
En daarna moet het allemaal nog in een kastje, de oude XR2206 er uitgesloopt, de voor en achterkant verwisseld en een stukje pertinax (jawel dat heb ik ook nog) op de achterkant gelijmd. En dan volgt het voor mij altijd erg moeilijke deel, een indeling maken voor het frontpaneel en er een beetje netjes gaten in maken.
Inmiddels zit er nog een gaatje extra in het front waar ik de TTL blokgalf naar buiten laat komen. Klaar.
Nu eens gaan kijken naar die HF signaal generator met de AD9851, dat is voor mij extreem hoog frequent (10MHz) dus een gewone opamp gaat niet meer werken. Ik zit te kijken naar een OPA603, die is nog redelijk in prijs en, heel belangrijk, verkrijgbaar bij mijn lokale hardware boer.