// Program a DDS device, taking commands via USB serial, // and also implement a triggered phase shifting mode #include // DDS programing definitions byte pDATA0; byte pDATA1; byte pDATA2; byte pDATA3; byte pDATA4; byte pDATA5; byte pDATA6; byte pDATA7; byte pWCLK; //= OutPin[DPOS+7]; byte pFQUD; //= OutPin[DPOS+8]; byte pRESET;// = OutPin[DPOS+9]; volatile byte phasejumps[512]; // List of up to 512 phases which can be jumped through on a hardawre trigger. volatile int phasecounter = 0; int Nphasejumps = 0; boolean OUTHIGH = 1; boolean OUTLOW = 0; char *teststring = ">>>>>>>>>>>>>>>>>>>>>"; volatile int PhaseControlMode=0; byte W0 = 0b00000001; // This is the phase/powerdown/Serial/freq multiplier word. Please see the AD9851 datasheet, page 14 // This setting is for Phase=0deg, 6*REFCLK multiplier, Powered up mode void setup() { // Initialise serial comm. Serial.begin(9600); // Initiate DDS serial programming pins pDATA0 = 22; pDATA1 = 23; pDATA2 = 24; pDATA3 = 25; pDATA4 = 26; pDATA5 = 27; pDATA6 = 28; pDATA7 = 29; pWCLK = 43; pFQUD = 45; pRESET = 47; pinMode(pDATA0,OUTPUT); pinMode(pDATA1,OUTPUT); pinMode(pDATA2,OUTPUT); pinMode(pDATA3,OUTPUT); pinMode(pDATA4,OUTPUT); pinMode(pDATA5,OUTPUT); pinMode(pDATA6,OUTPUT); pinMode(pDATA7,OUTPUT); pinMode(pWCLK,OUTPUT); pinMode(pFQUD,OUTPUT); pinMode(pRESET,OUTPUT); // Set all these to zero output asap otherwise we might induce a "latch-up" state in the DDS chip! digitalWrite(pDATA0, 0); digitalWrite(pDATA1, 0); digitalWrite(pDATA2, 0); digitalWrite(pDATA3, 0); digitalWrite(pDATA4, 0); digitalWrite(pDATA5, 0); digitalWrite(pDATA6, 0); digitalWrite(pDATA7, 0); // Setup harware intterupt on pin2 attachInterrupt(0, phasejumpISR, FALLING); } void loop() { if (Serial.available()) { int opcode = Serial.read(); int b1 = Serial.read(); // Serial.read() returns an int, even though it only grabs 8 bits at a time! int b2 = Serial.read(); // Don't blame me! int b3 = Serial.read(); int b4 = Serial.read(); //////////////// /////////// PROGRAM frequency of DDS device /////// if (opcode==115){ // 115 = ascii "s"... // Program an Analog Devices 9851 Chip via serial using some default digital pins which are defined in setup() // Initially, I tried accepting a frequency in Hz from the user and // converting it to an AD9851 32bit frequency programming word. // However, conversion between the Frequency in Hz and the 32 bit word // strains the limits of floating point maths on the Mega - floating point // itself is only 32 bit - and I noticed the conversions were inaccurate // in the least significant bits. // Furthermore, it seems that the >> and << operators don't work properly past // 16 bits! This made shifting the 32bit frequency word into the AD9851 register // annoying. // By requiring the user to supply an appropriately scaled 32 bit frequency programming word // for the AD9851 instead of a frequency in Hz, we also get as an added bonus the word already split up // into 4 bytes ulb1...ulb4. This means we can just shift in a byte at a time, which may be // a little clunky, but sure is a lot easier on this fundamentally 8-bit processor! // To convert a frequency fHz given in Hz to a frequency programming word for the AD9851, use the formula: // fprog = round(fHz/180MHz * (2^32-1)) unsigned long ulb1 = b1; unsigned long ulb2 = b2; unsigned long ulb3 = b3; unsigned long ulb4 = b4; //unsigned long eventtime = b4<<24 + b3<<16 + b2<<8 + b1; // The reason that you can't use the method in the commented line above, // instead of the clunky method below, is that x<14! unsigned long ddsfreq = ulb4*16777216L + ulb3*65536L + ulb2*256L + ulb1*1L; //#if defined(TEXTDEBUG) Serial.print(">>>>>>>>>> Reconstructed freq: "); Serial.println(ddsfreq); Serial.println(ulb1); Serial.println(ulb2); Serial.println(ulb3); Serial.println(ulb4); Serial.println(">>>>>>>>>>>>>>>>>"); //#endif */ for(int k=0;k<2;k++){Serial.println(teststring);} // Now we have to convert the frequency into a 32 bit // binary number which can be used to program the DDS. // The AD9851 has a maximum frequency of 180MHz corresponding // to the maximum frequency word N=(2^32-1). To program a frequency // lower than 180MHz, we find the ratio between the desired frequency and // 180MHz and multiply by 2^32 //if (ddsfreq>DDSFLIM) {ddsfreq = DDSFLIM;} //double temp = (double)ddsfreq/(double)DDSMAXF*LMAX; //unsigned long fprog = round(temp); // This is NOT good practice in principle since the // result can overflow the unsigned long variable! // However, due to the limit applied to ddsfreq, // there should never actually be an overflow here... //Serial.print("fprog: "); //Serial.println(fprog,BIN); byte W0 = 0b00001001; // This is the phase/powerdown/Serial/freq multiplier word. Please see the AD9851 datasheet, page 14 // This setting is for Phase=11.25deg, 6*REFCLK multiplier, Powered up mode DDSParallelLoad(W0, b4, b3, b2, b1); } // End of serial s received if (opcode==114){ // 114 = ascii "r"... // Reset the phase array. To do this, we just // set the number of phase jumps back to zero Serial.println("Reset phase jumps"); Nphasejumps = 0; phasecounter=0; } if (opcode==106){ // 106 = ascii "j"... // Jump the phase of the DDS device leaving the frequency untouched Serial.print("Jumping to next phase: "); Serial.println((phasejumps[phasecounter]),BIN); Serial.print("Phase counter: "); Serial.println(phasecounter); PORTA = phasejumps[phasecounter]; // delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); //delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); // Pulse FQUD //delayMicroseconds(5); digitalWrite(pFQUD,OUTHIGH); //delayMicroseconds(5); digitalWrite(pFQUD,OUTLOW); if (phasecounter < (Nphasejumps-1)) phasecounter++; else phasecounter = 0; } if (opcode==97){ // 97 = ascii "a"... float testp; testp = (b1>>3)*11.25; Serial.print("Phase word: "); Serial.println(b1,BIN); Serial.print("Reconstructed phase: "); Serial.println(testp); // Range check if ((testp>=0)&&(testp<=360)) { // Add to the phase array phasejumps[Nphasejumps] = b1|W0; // Store the phase jump in a form ready to be written straight to the // portA register. Speed is of the essence Serial.print("Full phase word: "); Serial.println(phasejumps[Nphasejumps],BIN); Nphasejumps++; Serial.print("Phase added. Current number of phase jumps = "); Serial.println(Nphasejumps); } else { Serial.println("Phase out of range. No phase added"); } } // end of serial j received } // End of Serial check } void DDSSerialLoad(int W0,int W1, int W2,int W3,int W4){ // Program the device // STEP 1: RESET // The AD9851 is usually programmed in parallel mode but we only have access to it in serial mode. // This is a design choice made when building the circuit housing the AD9851, but it is a good choice, // since it reduces the number of outputs necessary by a factor of 4. // However, the default programming mode for the AD9851 is parallel and we have to write some bits in // parallel mode in order to enter serial mode. // Luckily, the necessary bits can be set at a hardware level (see Fig. 18, page 15 of AD9851 datasheet) // so that serial mode is always entered after a reset. We rely on this hard-wiring of the initial state // in order for the following code to work. digitalWrite(pRESET,OUTHIGH); delayMicroseconds(10); // We just want the minimum delay. The minimum time for the reset pulse is actually 50ns so even the // shortest delay we can achieve is overkill! Probably we can remove the delay function, since the // arduino clock period is ~60ns digitalWrite(pRESET,OUTLOW); // Assert WCLK high to load the default data digitalWrite(pWCLK,OUTHIGH); delayMicroseconds(10); // We just want the minimum delay. The minimum time for the reset pulse is actually 50ns so even the // shortest delay we can achieve is overkill! Probably we can remove the delay function, since the // arduino clock period is ~60ns digitalWrite(pWCLK,OUTLOW); delayMicroseconds(10); // After the reset we need to assert FQUD high digitalWrite(pFQUD,OUTHIGH); delayMicroseconds(10); // We just want the minimum delay. The minimum time for the reset pulse is actually 50ns so even the // shortest delay we can achieve is overkill! Probably we can remove the delay function, since the // arduino clock period is ~60ns digitalWrite(pFQUD,OUTLOW); delayMicroseconds(10); // STEP 2: LOAD DATA // First frequency block... // Least Significant Byte for(int i=0;i<8;i++){ digitalWrite(pDATA1,((~W1 & _BV(i))>>i)); // Write value to pins... // Serial.println((ulb1 & _BV(i))>>i,BIN); digitalWrite(pDATA2,((~W1 & _BV(i))>>i)); // Write value to pins... digitalWrite(pDATA3,((~W1 & _BV(i))>>i)); // Write value to pins... delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); } // Second byte for(int i=0;i<8;i++){ digitalWrite(pDATA1,((~W2 & _BV(i))>>i)); // Write value to pins... //Serial.println((ulb2 & _BV(i))>>i,BIN); digitalWrite(pDATA2,((~W2 & _BV(i))>>i)); // Write value to pins... digitalWrite(pDATA3,((~W2 & _BV(i))>>i)); // Write value to pins... delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); } // Third byte for(int i=0;i<8;i++){ digitalWrite(pDATA1,((~W3 & _BV(i))>>i)); // Write value to pins... //Serial.println((ulb3 & _BV(i))>>i,BIN); digitalWrite(pDATA2,((~W3 & _BV(i))>>i)); // Write value to pins... digitalWrite(pDATA3,((~W3 & _BV(i))>>i)); // Write value to pins... delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); } // Most Significant Byte for(int i=0;i<8;i++){ digitalWrite(pDATA1,((~W4 & _BV(i))>>i)); // Write value to pins... //Serial.println((ulb4 & _BV(i))>>i,BIN); digitalWrite(pDATA2,((~W4 & _BV(i))>>i)); // Write value to pins... digitalWrite(pDATA3,((~W4 & _BV(i))>>i)); // Write value to pins... delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); } // ... then phase/control block for(int i=0;i<8;i++){ digitalWrite(pDATA1,((~W0 & _BV(i))>>i)); // Write value to pins... digitalWrite(pDATA2,((~W0 & _BV(i))>>i)); // Write value to pins... digitalWrite(pDATA3,((~W0 & _BV(i))>>i)); // Write value to pins... delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); } // STEP 3: Pulse FQUD delayMicroseconds(5); digitalWrite(pFQUD,OUTHIGH); delayMicroseconds(5); digitalWrite(pFQUD,OUTLOW); // Now set data line levels back to zero for sake of cleanliness! digitalWrite(pDATA1,OUTLOW); // Write value to pins... digitalWrite(pDATA2,OUTLOW); // Write value to pins... digitalWrite(pDATA3,OUTLOW); // Write value to pins... } void DDSParallelLoad(byte W0, byte W1, byte W2, byte W3, byte W4){ // Program the device in parallel mode // STEP 1: RESET digitalWrite(pRESET,OUTHIGH); //delayMicroseconds(10); // We just want the minimum delay. The minimum time for the reset pulse is actually 50ns so even the // shortest delay we can achieve is overkill! Probably we can remove the delay function, since the // arduino clock period is ~60ns digitalWrite(pRESET,OUTLOW); // STEP 2: Assert FQUD low //delayMicroseconds(10); // After the reset we need to assert FQUD high // digitalWrite(pFQUD,OUTHIGH); //delayMicroseconds(10); // We just want the minimum delay. The minimum time for the reset pulse is actually 50ns so even the // shortest delay we can achieve is overkill! Probably we can remove the delay function, since the // arduino clock period is ~60ns digitalWrite(pFQUD,OUTLOW); // Check page 14 of the AD9851 datasheet for an explanation of the parallel frequency // setting procedure. Roughly the procedure is // Phase Word W0 // HSB frequency word W1 // frequency word W2 // frequency word W3 // LSB frequency word W4 // Load words from W0 -> W4 PORTA = W0; // delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); //delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); PORTA = W1; // delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); //delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); PORTA = W2; // delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); //delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); PORTA = W3; // delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); //delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); PORTA = W4; // delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); //delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); // STEP 3: Pulse FQUD //delayMicroseconds(5); digitalWrite(pFQUD,OUTHIGH); //delayMicroseconds(5); digitalWrite(pFQUD,OUTLOW); // Now set data line levels back to zero for sake of cleanliness! digitalWrite(pDATA0,OUTLOW); // Write value to pins... digitalWrite(pDATA1,OUTLOW); // Write value to pins... digitalWrite(pDATA2,OUTLOW); // Write value to pins... digitalWrite(pDATA3,OUTLOW); // Write value to pins... digitalWrite(pDATA4,OUTLOW); // Write value to pins... digitalWrite(pDATA5,OUTLOW); // Write value to pins... digitalWrite(pDATA6,OUTLOW); // Write value to pins... digitalWrite(pDATA7,OUTLOW); // Write value to pins... } void phasejumpISR(){ // Jump the phase when pin 2 goes low. PORTA = phasejumps[phasecounter]; // delayMicroseconds(5); digitalWrite(pWCLK,OUTHIGH); //delayMicroseconds(5); digitalWrite(pWCLK,OUTLOW); // Pulse FQUD //delayMicroseconds(5); digitalWrite(pFQUD,OUTHIGH); //delayMicroseconds(5); digitalWrite(pFQUD,OUTLOW); if (phasecounter < (Nphasejumps-1)) phasecounter++; else phasecounter = 0; Serial.println("intr"); }