PDA

View Full Version : Calculate tuning value for the AD9850 DDS.



HenrikOlsson
- 5th January 2013, 12:09
Hi,
I've written an article in the Wiki (http://www.picbasic.co.uk/forum/content.php?r=497-How-to-calculate-the-32bit-tuning-value-for-the-AD9850-DDS-chip.) which shows two possible ways to calculate the 32bit tuning value for the AD9850 DDS chip from Analog Devices.

This thread is intended for discussions and questions regarding the article.

/Henrik.

tasmod
- 6th January 2013, 16:23
Very interesting Henrik.

I have only skimped through the wiki just now and will read properly offline.

I'm currently using one of the cheap ebay modules on a board i designed and a 16f628 to produce a tunable signal generator dds, this is other peoples code in asm though. One other code is a sweep generator/signal generator with decade digit selection by rotary encoder.

I had been trying to do the maths (Nowhere near my capability) to use a rotary encoder in pbp. I did achieve an incrementing counter in pbp with it but got no further to convert to the tuning word.
I was also able to get it to any frequency by inserting the byte sequence for the tuning word into the code. I had all the amateur bands set up as subs and using a pushbutton could step through them.

I wanted to do the dds in pbp so I could add further features such as keypad freq entry, vfo a and vfo b, along with band switching outputs for a ham radio transceiver I'm building.

Below is some sample code. I did move on much further but then got into a jam with ideas.

Regards,
Rob




'************************************************* ***************
'* Name : DDS Frequency Generator *
'* Author : * *
'* Date : 10/29/2012 *
'* Version : 1.0 *
'* Notes : Uses ebay DDS board and rotary encoder *
'* : *
'************************************************* ***************

'
; 16f628a
; __config _CP_OFF&_LVP_OFF&_BODEN_OFF&_PWRTE_OFF&_WDT_OFF&_INTRC_OSC_NOCLKOUT
;
'
'
'
' LCD Display
' -----------
'
'
Define LCD_DREG PORTB ' Port for LCD Data
Define LCD_DBIT 0 ' Use lower 4 bits of Port
Define LCD_RSREG PORTB ' Port for RegisterSelect (RS) bit
Define LCD_RSBIT 6 ' Port Pin for RS bit
Define LCD_RWBIT 5 ' Port Pin for RS bit
Define LCD_EREG PORTB ' Port for Enable (E) bit
Define LCD_EBIT 4 ' Port Pin for E bit
Define LCB_BITS 4 ' Using 4-bit bus
Define LCD_LINES 2 ' Using 2 line Display
Define LCD_COMMANDUS 1200 ' Command Delay (uS)
DEFINE LCD_DATAUS 50 ' Data Delay (uS)
'
' Control Buttons/Lines
' ---------------------
PB_1 var PortA.4 ' Take this pin low momentarily to change step
PB_2 var PortA.3 ' Take this pin low momentarily to change band
PB_3 var PortA.2 ' Take this pin low momentarily to RESET
EncoderRight var PortA.1 ' rotary encoder pin
EncoderLeft var PortA.0 ' rotary encoder pin


ddsload var PortB.7 ' dds control word pin
ddsdata var PortB.3 ' dds data input
ddsclock var PortB.2 ' dds clock input



fStep var byte
fcount var byte
Counter var word


CMCON=7 'sets 16f628 comparator pins to digital
TRISA=%00011111 'Button & encoder inputs

Counter = 0
fcount = 0
fstep = 1 ;start at 1 digit count

LOW DDSLOAD

'CONFIGURE DISPLAY
pause 1000
LCDOUT $FE,1 ' Clear screen
pause 10
lcdout $fe,$c0,dec5 counter ; reset to zero on start


ddstart:
shiftout ddsdata,ddsclock,0,[ $07,$2B,$02,$0C ] ;start value
toggle ddsload
pause 100

;Rotary Encoder Code############################################## ##

mainloop1:

if EncoderRight=0 then 'here is switch 2 of the rotary encoder
counter=counter+1
gosub up
gosub dds
gosub lcd
endif

'IF PB_1=0 THEN inc_step

if EncoderLeft=0 then 'here is switch 1 of the rotary encoder
counter = counter-1
gosub down
gosub dds
gosub lcd
endif

goto mainloop1

lcd:
lcdout $fe,$c0,dec5 counter
while (EncoderLeft=0 or EncoderRight=0):pause 10:wend
return


inc_step:
fcount=fcount+1
if fcount=2 then fstep=100
if fcount=1 then fstep=10
fcount=1
return

dds:
shiftout ddsdata,ddsclock,0,[counter]
toggle ddsload
return

up:

counter[0]=counter[0]+1
if counter[0]>99 then
counter[0]=0
counter[1]=counter[1]+1
if counter[1]>99 then
counter[1]=0
counter[2]=counter[2]+1
if counter[2]>99 then
counter[2]=0
counter[3]=counter[3]+1
if counter[3]>99 then
counter[3]=0 ;reset to zero
endif
endif
endif
endif
return

down:

counter[0]=counter[0]-1
if counter[0]<1 then
counter[0]=0
counter[1]=counter[1]-1
if counter[1]<1 then
counter[1]=99
counter[2]=counter[2]-1
if counter[2]<1 then
counter[2]=99
counter[3]=counter[3]-1
if counter[3]<1 then
counter[3]=99
endif
endif
endif
endif
return

end

HenrikOlsson
- 6th January 2013, 18:18
Hi Rob,
Yes, I remember the problem with your up/down counter not working properly....
You're still having that Counter variable declared as a WORD but treating it as a 4 byte array in your UP/DOWN subrouitines. That's a crash waiting to happen since the up/down routines writing to the array will write to memory "outside" of the declared variable corrupting what ever is there. I thought we covered that...

When setting the frequency you need to shift 40 bits (32bits tuning value + 8bits control) into the AD9850 and then pulse the Update Frequency pin. Looking at your code I see two main issues (appart from the Counter variable discussed above):
A) In your DDS subroutine that shifts out the tuning value you're only shifting out 8 bits. Even if Counter is declared as a WORD (which it shouldn't be anyway) it will only shift 8 bits when written like that.
B) You're not pulsing the Update Frequency pin, you're toggling it. The AD9850 updates the frequency on the rising edge of that signal so by toggling it like you're doing every second update would fail.

/Henrik.

tasmod
- 6th January 2013, 18:56
Hmm yes. I posted the first version i worked as i was on the wrong pc.
I did move on quite a bit.
The toggle was changed later.
The 40 bit control word has the 32 bit then next 8 bit as zero. So this can be set as a constant to be addaed at end of the word.

Sorry if this is messy its from my mobile.

iw2fvo
- 1st December 2016, 09:56
Hi to all in this forum.
I am using the magic number to control my AD9850 DDS and it worked very well for years.
Now I am going to use the AD9912 DDS and I will like to calculate or to have the magic number to control it : the ref clock is 100 MHz internally multiplied by 10 so the real ref should be 1 GHz. I am using PBP 2.50 and longs.
I understand that the 9912 has a 48 bit tuning word (FTW): what will be the limitations in this case ?
Thanks in advance for any assistance .
regards,
Ambrogio
iw2fvo

HenrikOlsson
- 1st December 2016, 19:33
Hi,
The AD9912 datasheet says: FTW = 2^48 * (fdds / 1GHz) where fdds is the desired frequency and 1GHz is the reference clock.
As per the Wiki example for the AD9850, but with these numbers instead, you calculate 2^48/1GHz=281475 which is what I believe is the "magic number" you're referring to.

So, FTW = fdds * 281475 which we can verify against the example in the datasheet:
19,440,000 * 281475 = 5471874000000 which in reality would result in a frequency 1.6085Hz higher than ideal due to 2^48/1GHz not being exactly 281475. Does it matter? That's up to you.

Now, LONGS are "only" 32 bits so what's the highest frequency possible? Well, 2^32/281475=15258.788Hz.

Since the ** operator, when used with LONGs, results in an 48bit wide intermediate result I would guess we could use some trickery and retrieve all 48 bits directly but I don't have time to play around with that right now.

/Henrik.

iw2fvo
- 2nd December 2016, 11:10
Thanks Henrik for the kind reply on the matter.
On the AD9850 the FTW is computed by the following : FTW = Frequency ** 2251800.
I call magic number the " 2251800" constant.
What will be the constant for the ad9912 DDS?
How could I determine the frequency limitation due to the longs .. etc ?
Thanks again for the assistance.
Regards,
Ambrgio
iw2fvo

HenrikOlsson
- 2nd December 2016, 17:57
Did you READ my previous reply?

iw2fvo
- 3rd December 2016, 08:12
sorry Henrik:
I do not want you to hurry at all.
Just in case you will have time ... it will be very interesting result for all members of the radio hams club here.
thanks for all again
regards,
iw2fvo

Mike, K8LH
- 14th July 2018, 02:22
I enjoyed your article, Henrik, but I'm shocked at the processing overhead. I wrote and tested a simple calc_ftw() function in XC8, really not much more than a basic 32x32 multiplication routine, and I was wondering if something similar would be worth implementing in PBP?

Cheerful regards, Mike



unsigned char ftw[8]; // ftw calculation array


/************************************************** **********************
* Calculate AD9850 'FTW' (Frequency Tuning Word) *
* *
* The 32-bit 'f' input is frequency*100 (0.01-Hz resolution) which *
* allows scaling the DDS constant up to 32-bits. Multiply the two *
* 32-bit terms and divide the 64-bit result by 2^32 (use the upper *
* four bytes) for the 32-bit 'FTW'. *
* *
* FTW * 2^32 = freq*100 * 2^32/RefClk*2^32/100 *
* FTW * 2^32 = freq*100 * 1475739526 *
* *
* Fitting the frequency*100 value into a 32-bit variable makes our *
* upper frequency limit ~42,949,672.95-Hz. *
* *
* --- target -- ---- ftw ---- --- actual -- *
* 42,949,672.00 1,475,739,493 42,949,672.00 764 cycles *
* 37,000,000.00 1,271,310,320 37,000,000.01 764 " *
* 25,000,000.00 858,993,459 24,999,999.99 764 " *
* 10,000,000.00 343,597,384 10,000,000.01 764 " *
* 1,000,000.00 34,359,738 999,999.99 764 " *
* 125,000.00 4,294,967 124,999.99 764 " *
* 10,000.00 343,597 9,999.99 764 " *
* *
* XC8 example (50 words, <1000 cycles) *
************************************************** **********************/

void calcFTW(unsigned long f) // calculate AD9850 32-bit "FTW"
{ long c = 1475739526; // the "constant" term
asm("movlb _ftw/128 "); // bank 0 |00
asm("clrf _ftw+0 "); // clear FTW array |00
asm("clrf _ftw+1 "); // " |00
asm("clrf _ftw+2 "); // " |00
asm("clrf _ftw+3 "); // " |00
asm("clrf _ftw+4 "); // " |00
asm("clrf _ftw+5 "); // " |00
asm("clrf _ftw+6 "); // " |00
asm("clrf _ftw+7 "); // " |00
asm("bsf _ftw+3,7 "); // loop count (32 iterations) |00
/* *
* multiply 32-bit freq*100 value by our 32-bit "constant" and use *
* the upper 32-bits of the 64-bit result ftw[4..7] as the "FTW". *
* */
asm("mult32x32: "); //
// asm("rrf calcFTW@c+0,W "); // preserve 'constant" variable |00
asm("rrf calcFTW@c+3,F "); // constant uh |00
asm("rrf calcFTW@c+2,F "); // constant ul |00
asm("rrf calcFTW@c+1,F "); // constant hi |00
asm("rrf calcFTW@c+0,F "); // constant lo |00
asm("skipc "); // |00
asm("bra nextbit "); // |00
asm("movf calcFTW@f+0,W "); // frequency lo |00
asm("addwf _ftw+4,F "); // |00
asm("movf calcFTW@f+1,W "); // frequency hi |00
asm("addwfc _ftw+5,F "); // |00
asm("movf calcFTW@f+2,W "); // frequency ul |00
asm("addwfc _ftw+6,F "); // |00
asm("movf calcFTW@f+3,W "); // frequency uh |00
asm("addwfc _ftw+7,F "); // |00
asm("nextbit: "); // |00
asm("rrf _ftw+7,F "); // |00
asm("rrf _ftw+6,F "); // |00
asm("rrf _ftw+5,F "); // |00
asm("rrf _ftw+4,F "); // |00
asm("rrf _ftw+3,F "); // |00
asm("rrf _ftw+2,F "); // |00
asm("rrf _ftw+1,F "); // |00
asm("rrf _ftw+0,F "); // |00
asm("skipc "); // done? yes, skip, else |00
asm("bra mult32x32 "); // |00
asm("movlw 0x80 "); // rounding... |00
asm("addwf _ftw+3,W "); // " |00
asm("movlw 0 "); // " |00
asm("addwfc _ftw+4,F "); // " |00
asm("addwfc _ftw+5,F "); // " |00
asm("addwfc _ftw+6,F "); // " |00
asm("addwfc _ftw+7,F "); // " |00
}

Mike, K8LH
- 17th July 2018, 07:33
Here's a slightly optimized version of the AD9850 calcFTW() function (XC8) from the previous post. This version is smaller and nearly 100 cycles faster weighing in at 42 words and 665 cycles.

I hope someone can take advantage of it and turn it into a PBP function.



unsigned long ftw; // 32-bit Frequency Tuning Word

/************************************************** **********************
* Calculate AD9850 'FTW' (Frequency Tuning Word) Mike McLaren *
* *
* The 32-bit 'f' input is frequency*100 (0.01-Hz resolution) which *
* allows scaling the DDS constant up to 32-bits. Multiply the two *
* 32-bit terms and divide the 64-bit product by 2^32. The 32-bit *
* result is the frequency tuning word in the 'ftw' variable. *
* *
* FTW * 2^32 = freq*100 * 2^32/RefClk*2^32/100 *
* FTW * 2^32 = freq*100 * 1475739526 *
* *
* Fitting the frequency*100 value into a 32-bit variable makes our *
* upper frequency limit ~42,949,672.95-Hz. *
* *
* --- target -- ---- ftw ---- --- actual -- *
* 42,949,672.00 1,475,739,493 42,949,672.00 *
* 37,000,000.00 1,271,310,320 37,000,000.01 *
* 25,000,000.00 858,993,459 24,999,999.99 *
* 10,000,000.00 343,597,384 10,000,000.01 *
* 1,000,000.00 34,359,738 999,999.99 *
* 125,000.00 4,294,967 124,999.99 *
* 10,000.00 343,597 9,999.99 *
* *
* XC8 example (42 words, 665 cycles) *
************************************************** **********************/

void calcFTW(unsigned long f) // calculate AD9850 32-bit "FTW"
{ long c = 1475739526; // the "constant" term
unsigned char n = 32; //
/* *
* multiply 32-bit freq*100 value by our 32-bit "constant" and use *
* the upper 32-bits ('ftw') of the 64-bit result *
* */
asm("mult32x32: "); //
asm("clrc "); // |00
asm("btfss calcFTW@c+0,0 "); // |00
asm("bra nextbit "); // |00
asm("movf calcFTW@f+0,W "); // frequency lo |00
asm("addwf _ftw+0,F "); // |00
asm("movf calcFTW@f+1,W "); // frequency hi |00
asm("addwfc _ftw+1,F "); // |00
asm("movf calcFTW@f+2,W "); // frequency ul |00
asm("addwfc _ftw+2,F "); // |00
asm("movf calcFTW@f+3,W "); // frequency uh |00
asm("addwfc _ftw+3,F "); // |00
asm("nextbit: "); // |00
asm("rrf _ftw+3,F "); // |00
asm("rrf _ftw+2,F "); // |00
asm("rrf _ftw+1,F "); // |00
asm("rrf _ftw+0,F "); // |00
asm("rrf calcFTW@c+3,F "); // |00
asm("rrf calcFTW@c+2,F "); // |00
asm("rrf calcFTW@c+1,F "); // |00
asm("rrf calcFTW@c+0,F "); // |00
asm("decfsz calcFTW@n,F "); // done? yes, skip, else |00
asm("bra mult32x32 "); // |00
asm("movlw 0x80 "); // rounding... |00
asm("addwf calcFTW@c+3,W "); // " |00
asm("movlw 0 "); // " |00
asm("addwfc _ftw+0,F "); // " |00
asm("addwfc _ftw+1,F "); // " |00
asm("addwfc _ftw+2,F "); // " |00
asm("addwfc _ftw+3,F "); // " |00
}

richard
- 17th July 2018, 14:38
mike you may find this interesting
www.picbasic.co.uk/forum/showthread.php?t=12433

pedja089
- 19th July 2018, 09:12
Mike,
It can be almost copy/paste in PBP. I can try to sort details to work in PBP. But I don't understand what is calcFTW@c,@f,@n... Is it just variables declared in function?
Can you explain?

Mike, K8LH
- 19th July 2018, 21:10
Yes, in XC8 that's how you would address the "local" variables (c, f, and n) using assembly language.

pedja089
- 19th July 2018, 23:33
Here it is. It wasn't tested...

ftw var long ; unsigned long ftw; // 32-bit Frequency Tuning Word
f var long

;/************************************************** **********************
; * Calculate AD9850 'FTW' (Frequency Tuning Word) Mike McLaren *
; * *
; * The 32-bit 'f' input is frequency*100 (0.01-Hz resolution) which *
; * allows scaling the DDS constant up to 32-bits. Multiply the two *
; * 32-bit terms and divide the 64-bit product by 2^32. The 32-bit *
; * result is the frequency tuning word in the 'ftw' variable. *
; * *
; * FTW * 2^32 = freq*100 * 2^32/RefClk*2^32/100 *
; * FTW * 2^32 = freq*100 * 1475739526 *
; * *
; * Fitting the frequency*100 value into a 32-bit variable makes our *
; * upper frequency limit ~42,949,672.95-Hz. *
; * *
; * --- target -- ---- ftw ---- --- actual -- *
; * 42,949,672.00 1,475,739,493 42,949,672.00 *
; * 37,000,000.00 1,271,310,320 37,000,000.01 *
; * 25,000,000.00 858,993,459 24,999,999.99 *
; * 10,000,000.00 343,597,384 10,000,000.01 *
; * 1,000,000.00 34,359,738 999,999.99 *
; * 125,000.00 4,294,967 124,999.99 *
; * 10,000.00 343,597 9,999.99 *
; * *
; * XC8 example (42 words, 665 cycles) *
; ************************************************** **********************/

calcFTW: ; void calcFTW(unsigned long f) // calculate AD9850 32-bit "FTW"
; { long c = 1475739526; // the "constant" term
; unsigned char n = 32; //
c VAR LONG : C=1475739526
n VAR BYTE : n=32
; /* *
; * multiply 32-bit freq*100 value by our 32-bit "constant" and use *
; * the upper 32-bits ('ftw') of the 64-bit result *
; * */
ASM
mult32x32: ; //
BCF STATUS,C ; clrc ; // |00
btfss _c+0,0 ; // |00
bra nextbit ; // |00
movf _f+0,W ; // frequency lo |00
addwf _ftw+0,F ; // |00
movf _f+1,W ; // frequency hi |00
addwfc _ftw+1,F ; // |00
movf _f+2,W ; // frequency ul |00
addwfc _ftw+2,F ; // |00
movf _f+3,W ; // frequency uh |00
addwfc _ftw+3,F ; // |00
nextbit: ; // |00
RRCF _ftw+3,F ; // |00
RRCF _ftw+2,F ; // |00
RRCF _ftw+1,F ; // |00
RRCF _ftw+0,F ; // |00
RRCF _c+3,F ; // |00
RRCF _c+2,F ; // |00
RRCF _c+1,F ; // |00
RRCF _c+0,F ; // |00
decfsz _n,F ; // done? yes, skip, else |00
bra mult32x32 ; // |00
movlw 0x80 ; // rounding... |00
addwf _c+3,W ; // " |00
movlw 0 ; // " |00
addwfc _ftw+0,F ; // " |00
addwfc _ftw+1,F ; // " |00
addwfc _ftw+2,F ; // " |00
addwfc _ftw+3,F ; // " |00
ENDASM; }
Return

clrc and rrf are pseudo instruction from XC8. So I replaced it with real instruction.

Usage:

F=123 : Call calcFTW
result is in ftw

Mike, K8LH
- 20th July 2018, 16:49
Very interesting. Thank you. I'm not a PBP user so I had no idea inserting an assembly code function into a PBP program was that easy.

I suspect you're coding for an 18F which has RRCF and RRNCF instructions where my example was for an "enhanced mid-range" 16F1823 device which has slightly different RRF, ASRF, and LSRF instructions. Hopefully, those 16F instructions are supported in PBP. Also, when using assembly language, careful attention to banking is required. I found XC8 assigned the 'ftw' variable in Bank 0 RAM and the other variables were assigned in the "common" 0x70..0x7F area.

One might conclude that implementing an assembly language method in a program could save hundreds of words of program memory and thousands of instruction cycles processing time but it also requires careful attention to detail and environment.

Hopefully, someone may be able to take advantage of a similar method in their PBP AD9850/AD9851 or Si5351 Digital VFO project.

Cheerful regards, Mike, K8LH

pedja089
- 21st July 2018, 00:49
I use 18F for 99% of my project. So by default i go to 18F ASM. Also pbp for 16F doesn't support long variables(because of large overhead, but in my opinion it should).

You are right, but I put all variables used in ASM into BANKA. So you dont need banking, at all. I forgot to do that here.
Here is corrected version.


ftw var long ; unsigned long ftw; // 32-bit Frequency Tuning Word
f var long BANKA

;/************************************************** **********************
; * Calculate AD9850 'FTW' (Frequency Tuning Word) Mike McLaren *
; * *
; * The 32-bit 'f' input is frequency*100 (0.01-Hz resolution) which *
; * allows scaling the DDS constant up to 32-bits. Multiply the two *
; * 32-bit terms and divide the 64-bit product by 2^32. The 32-bit *
; * result is the frequency tuning word in the 'ftw' variable. *
; * *
; * FTW * 2^32 = freq*100 * 2^32/RefClk*2^32/100 *
; * FTW * 2^32 = freq*100 * 1475739526 *
; * *
; * Fitting the frequency*100 value into a 32-bit variable makes our *
; * upper frequency limit ~42,949,672.95-Hz. *
; * *
; * --- target -- ---- ftw ---- --- actual -- *
; * 42,949,672.00 1,475,739,493 42,949,672.00 *
; * 37,000,000.00 1,271,310,320 37,000,000.01 *
; * 25,000,000.00 858,993,459 24,999,999.99 *
; * 10,000,000.00 343,597,384 10,000,000.01 *
; * 1,000,000.00 34,359,738 999,999.99 *
; * 125,000.00 4,294,967 124,999.99 *
; * 10,000.00 343,597 9,999.99 *
; * *
; * XC8 example (42 words, 665 cycles) *
; ************************************************** **********************/

calcFTW: ; void calcFTW(unsigned long f) // calculate AD9850 32-bit "FTW"
; { long c = 1475739526; // the "constant" term
; unsigned char n = 32; //
c VAR LONG BANKA : C=1475739526
n VAR BYTE BANKA : n=32
; /* *
; * multiply 32-bit freq*100 value by our 32-bit "constant" and use *
; * the upper 32-bits ('ftw') of the 64-bit result *
; * */
ASM
mult32x32: ; //
BCF STATUS,C ; clrc ; // |00
btfss _c+0,0 ; // |00
bra nextbit ; // |00
movf _f+0,W ; // frequency lo |00
addwf _ftw+0,F ; // |00
movf _f+1,W ; // frequency hi |00
addwfc _ftw+1,F ; // |00
movf _f+2,W ; // frequency ul |00
addwfc _ftw+2,F ; // |00
movf _f+3,W ; // frequency uh |00
addwfc _ftw+3,F ; // |00
nextbit: ; // |00
RRCF _ftw+3,F ; // |00
RRCF _ftw+2,F ; // |00
RRCF _ftw+1,F ; // |00
RRCF _ftw+0,F ; // |00
RRCF _c+3,F ; // |00
RRCF _c+2,F ; // |00
RRCF _c+1,F ; // |00
RRCF _c+0,F ; // |00
decfsz _n,F ; // done? yes, skip, else |00
bra mult32x32 ; // |00
movlw 0x80 ; // rounding... |00
addwf _c+3,W ; // " |00
movlw 0 ; // " |00
addwfc _ftw+0,F ; // " |00
addwfc _ftw+1,F ; // " |00
addwfc _ftw+2,F ; // " |00
addwfc _ftw+3,F ; // " |00
ENDASM; }
Return