AD9850 signal generator

matherp

Senior Member
Generate sine waves from 1Hz to 20MHz (also square waves 1-1MHz) using one of the AD9850 modules available on ebay.

Uses a 20X2 as the processor and a direct coupled LCD display. Input is via three switches or a rotary encoder (#define encoder). Switch 1 (or the rotary encoder push button) selects the field to be edited toggling through units, tens, hundreds and thousands and then a decade selection (x1, x10, x100, x1000, x10000). Numbers are incremented or decremented using the other two switches or the encoder rotation. The current field is indicated by the flashing cursor. Once the decade has been set the next field select button push updates the output frequency and the cursor disappears together with any leading zeroes on the display.

The file attached includes the circuit diagram (TinyCAD and jpg), a stripboard layout (VeeCAD and jpg) and the picaxe code
 

Attachments

John West

Senior Member
Thanks a lot for this, matherp. Customizing is a lot quicker, (and easier,) than working from scratch. I'll start with what you did, then hack it to pieces until it's unrecognizable and hope it still works. But when troubleshooting either hardware or software it's always most helpful to know that it once did, in fact, work. :)

John
*******
 

ferrymanr

Member
I have been trying to do the same for an AD9851 board that I have but getting along very slowly. Your project will make life much easier :)
Richard (Dick)
 

chan108

New Member
Thank you very much for posting this project. I have been searching the internet a long time for something like this. Please could you tell me if this circuit and code could be adapted for the 18M2?
 

radiogareth

Senior Member
Great job, mostly beyond me, although I could cope with wiring it up!
Just wondering what causes the 20MHz limit. The Picaxe is clearly working hard at 64MHz, so I'd guess its the maths overhead to produce a reasonable reaction time to inputs, but would be interested why, as once instructed, the DDS should run as instructed.
Guess it would not run on a 20M2 (I have those in the toolbox).

Running one of these boards with a native PIC is detailed here...

http://www.g4jnt.com/AD9850-Controller.pdf
 

matherp

Senior Member
The 20MHz limit is arbitrary and just represents the limit of what the AD9850 will produce as a sensible waveform. It could be anything.
 
Last edited:

chan108

New Member
AD9850 Signal Generator

Generate sine waves from 1Hz to 20MHz (also square waves 1-1MHz) using one of the AD9850 modules available on ebay.

Uses a 20X2 as the processor and a direct coupled LCD display. Input is via three switches or a rotary encoder (#define encoder). Switch 1 (or the rotary encoder push button) selects the field to be edited toggling through units, tens, hundreds and thousands and then a decade selection (x1, x10, x100, x1000, x10000). Numbers are incremented or decremented using the other two switches or the encoder rotation. The current field is indicated by the flashing cursor. Once the decade has been set the next field select button push updates the output frequency and the cursor disappears together with any leading zeroes on the display.

The file attached includes the circuit diagram (TinyCAD and jpg), a stripboard layout (VeeCAD and jpg) and the picaxe code
A similar circuit using 18 pin native PIC 16F628 is found here: http://www.g4jnt.com/AD9850-Controller.pdf
Do you not think this code could run on a PICAXE 18M2?
Please could you tell me what kind of LCD display is required in your circuit, whether it is 16X2 lines or 16X4 lines or something else.
Also I do not understand how the numbering system of the rotary encoder is figured out in your circuit. The most common ones found on ebay are digital incremental 5 pin types with switch (eg EC-11). Where pins A,C,B are the encoder pins and D,E are the switch contacts. Am I to understand that pin 1 corresponds to pin A, pin 2 corresponds to pin B and so on in your circuit?
I would really appreciate your help here.
 

matherp

Senior Member
The code will only run on X2 parts without significant rework as it uses variables b0-b52. Any LCD will work, but the code is designed for a 1 row x 8 characters. For a rotary encoder, connect the switch pins to J4 pins 4 and 5 (any way round). Connect the middle pin of the encoder to pin 1 of J4. Connect the two outer encoder pins to the other pins on J4 (2 and 3). Switch 2 and 3 if required to get the encoder to increment when turned clockwise. Remember to uncomment the "#define encoder" statement if using a rotary encoder.
 

ferrymanr

Member
Have I missed something? I can set a frequency, say 14000000 but there is then no way to alter the last four digits. In my case I want to increment/decrement by 1Hz steps such as 14101003Hz. This can be done up to 9999Hz but then the decade selection kicks in.and anything after the four most significant digits can only be zeros.
Otherwise the code works very well.
Dick
Input is via three switches or a rotary encoder (#define encoder). Switch 1 (or the rotary encoder push button) selects the field to be edited toggling through units, tens, hundreds and thousands and then a decade selection (x1, x10, x100, x1000, x10000). Numbers are incremented or decremented using the other two switches or the encoder rotation. The current field is indicated by the flashing cursor. Once the decade has been set the next field select button push updates the output frequency and the cursor disappears together with any leading zeroes on the display.

The file attached includes the circuit diagram (TinyCAD and jpg), a stripboard layout (VeeCAD and jpg) and the picaxe code
 

John West

Senior Member
I don't believe the code in this project can provide that degree of resolution, ferrymanr. That's the only reason I have yet to implement it, as I too want more precise frequency control. Well, that and the fact that I also want to be able to sweep frequencies and enter the info via keypad.

But that's something I figure I can add myself with a bit of work. It's the math implemented here that was the sticking point, and matherp has done a good job with that.

Here's the original thread regarding this project, with the observation on the resolution associated with it:
http://www.picaxeforum.co.uk/showthread.php?22477-Driving-the-AD9850-signal-generator-with-a-picaxe&highlight=ad9850

Note the line:
The code allows for frequencies from 1 to 99,990,000 Hz accurate to 1 in 10,000.
 
Last edited:

ferrymanr

Member
Oh! I missed the bit about resolution John. What a pity. I'm not up to the heavy maths these days, old age is taking it's toll, so don't think I will be able to progress much further with either the AD9850 or AD9851 boards I have. I may have to see if I can find any code that has been developed on other platforms than the Picaxe system. Either way I need 1Hz resolution although the crystal oscillator is not really accurate. Actually the AD9851 is better for me as I can replace the crystal with 30MHz drive derived from my very accurate GPS disciplined 10MHz standard. Not so easy with the 125MHz crystal on the AD9850
Dick
 

John West

Senior Member
I too run a Rubidium std that I plan on using with the sig gen chip. So it makes sense to be able to more precisely control its output frequency. Then phase noise will be the limiting factor for frequency accuracy.

There are other online sources for operating pic code, but if one doesn't write C themselves, they're stuck with whatever the author decided to have the sig gen do. I much prefer being able to make things do what I want them to do, and that means a PICAXE.
 

hippy

Technical Support
Staff member
The problem is rather simple; determine the 32-bit value 'N' to send for a desired frequency ...

N = ( freqoutput * 232 ) / freqcrystal

The non-trivial part is the maths to achieve that because the PICAXE doesn't natively handle 64-bit numbers. None of that however is too difficult to do with a PICAXE. It just needs two primitives; a means to specify/enter/create a 32-bit number and a 64-bit divided by 32-bit routine.

It's not a five minute job, does require some knowledge of binary maths to achieve it and a strategy to implement it but it's not too onerous. More a question of finding time for someone who could do it.
 

John West

Senior Member
Thanks for the comment, hippy. It points out just how slim my programming skills are, as I have no idea what a "primitive" is, other than perhaps that I'm one, and as you say, it needs two of them. :)
 

hippy

Technical Support
Staff member
It points out just how slim my programming skills are, as I have no idea what a "primitive" is, other than perhaps that I'm one ... :)
A "primitive" is a fundamental operation from which other things can be built. For example, to do multiplication you can do that by repeated addition; you'd need something to (1) clear the result, (2) add the number to the result, (3) something to decrement the number of times left to add, (4) a means to determine that the number to add has reached zero - those would be the primitives of doing that. Then you can easily code that as -

Code:
Multiply:
  Gosub ClearResult
  Do
    Gosub AddNumberToResult
    Gosub DecrementMultiplier
    Gosub CheckIfMultiplierIsZero
  Loop Until flgZero = 1
  Return
Decrement multiplier might itself be a primitive or be composed of primitives; subtract one from multiplier, which in turn could be get one, invert it, add 1 to it, add to multiplier.

Once you've got "multiply" you can simply use it elsewhere to built far more complicated calculations from.
 

John West

Senior Member
Got it. Thank you for the explanation, hippy. It's rather like the old joke: If you can't count to ten, just count to one ten times.
 

John West

Senior Member
I was going to get to work today on my AD9850 PICAXE interface, but the AD9850 bd is so small that I've lost it somewhere around my workbench. Sigh. As my old eyes start to fail me everything electronic gets smaller and smaller. A bad combination.
 

John West

Senior Member
Thanks, matherp. I've seen some functions I want to have, using other code and processors available elsewhere on the web, but as I mentioned, I want a PICAXE based device so I can make the function gen chip do what I want, not be stuck with whatever someone else thought it should do. The ability to make my gear do what I want done is ultimately more important to me than immediate convenience.

It'll take some time and effort, but I'll get there.
 
Last edited:

matherp

Senior Member
If it is any help I have attached some Swordfish code that takes the required frequency as a string terminated by "N" on a serial port and outputs the hex string back on the serial port as 8 ascii characters. The difficult bit of this was provided by Jerry Messina one of the main contributers to the Swordfish user forum - so thanks are due to him.
I have tested the code against the calculator on the AD web site and it appears to work perfectly based on a random sample of 30 test points.

The code as-is can work as a co-processor to a picaxe based system. If anyone wants to use it I can provide a hex dump which could be programmed direct into a blank 14K22.

Code:
* 
/* 
   SF port of C code adapted from int64.c 

   original code: 
   Copyright (c) Stuff, February 2013 

   Use of this program, for any purpose, is granted by the author, 
   Harry W (1and0), as long as this copyright notice is included in 
   the source code or any source code derived from this program. 
   The user assumes all responsibility for using this code. 
*/ 
*) 
Device = 18F14K22
Clock = 64
Include "SUART.bas"
Include "convert.bas"
include "system.bas"
public type 
    uint8_t = byte, 
    uint16_t = word, 
    uint32_t = longword, 
    int16_t  = integer, 
    int32_t  = longint 

public structure uint64_t 
    b(8) as byte 

    L as b(0).asword    // uint16_t 
    H as b(2).asword    // uint16_t 
    U as b(4).asword    // uint16_t 
    M as b(6).asword    // uint16_t 

    LW as b(0).aslongword   // uint32_t 
    HW as b(4).aslongword   // uint32_t 
    MW as b(2).aslongword   // uint32_t 
end structure 

(* 
//-------------------------------------------------------------- 
  Integer 64/32-bit Unsigned Division With Remainder 

    quotient  = dividend / divisor 
    remainder = dividend % divisor 

    where 
        dividend = quotient * divisor + remainder 

  Arguments: 
    dividend  = 64-bit unsigned integer to be divided 
    divisor   = 32-bit unsigned integer being divided by 
    remainder = pointer to 32-bit unsigned integer holding the remainder 

  Return Values: 
    The function returns a 64-bit unsigned integer of the quotient and 
    a pointer to a 32-bit unsigned integer of the remainder. 

  Method: 
  * This division algorithm is known as long division, the same one we learned 
    in elementary school.  It shifts gradually from the left to the right end 
    of the dividend, subtracting the largest possible multiple of the divisor 
    at each stage; the multiples become the digits of the quotient, and the 
    final difference is the remainder.  The binary implementation is to 
    repeatedly double the quotient.  When the divisor is less than or equal to 
    the dividend, subtract the divisor from the dividend and set the least 
    significant bit of the quotient to '1'.  Then halve the divisor and repeat 
    the shift-and-subtract until 64 bits are computed.  The remaining dividend 
    is the remainder. 
//-------------------------------------------------------------- 
*) 
public function div6432u(byval dividend as uint64_t, byval divisor as uint32_t, byref remainder as uint32_t) as uint64_t 
    dim 
        num_bits as uint8_t, 
        divisorL as uint32_t, 
        quotient as uint64_t 

    quotient.HW = 0 
    quotient.LW = 0 
    remainder = 0 

    if (divisor = 0) then 
        result = quotient 
        exit 
    endif 

    num_bits = sizeof(uint32_t)*8 + 1 
    while (divisor.bits(31) = 0)      // divisor & (1UL<<31) 
        divisor = divisor << 1 
        num_bits = num_bits+1 
    end while 
    divisorL = 0 

    repeat 
        quotient.HW = quotient.HW << 1 
        if (quotient.LW.bits(31) = 1) then        // quotient.LW & (1UL<<31) 
            quotient.HW.bits(0) = 1 
        endif 
        quotient.LW = quotient.LW << 1 

        if ((divisor < dividend.HW) or 
            ((divisor = dividend.HW) and (divisorL <= dividend.LW))) then 
            dividend.HW = dividend.HW - divisor 
            if (dividend.LW < divisorL) then 
                dividend.HW = dividend.HW-1 
            endif 
            dividend.LW = dividend.LW - divisorL 
            quotient.LW.bits(0) = 1 
        endif 

        divisorL = divisorL >> 1 
        if (divisor.bits(0) = 1) then 
            divisorL.bits(31) = 1 
        endif 
        divisor = divisor >> 1 

        num_bits = num_bits - 1 
    until (num_bits = 0) 

    remainder = dividend.LW 
    result = quotient 
end function 

// 
// 
const 
   ArraySize = 16,
   HexArray(ArraySize) as byte = ("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F")
dim B as uint32_t 
dim N as uint64_t
dim istring as string 

dim remainder as uint32_t 
dim b64 as uint64_t 
Dim hValue,lValue As byte // default is 23 chars SetAllDigital

// 
// compute N = (B * 2^32)/125000000 
// 
main: 
uart.SetTX(PORTA.0)
uart.SetRX(PORTA.5)
uart.SetBaudrate(sbr4800)
uart.SetMode(umiNVERTED)
UART.readterminator="N"
while true
	  uart.read(istring)
      B = strtodec(istring)
      b64.HW = B          // b64 = (B * 2^32) 
      b64.LW = 0 
      N = div6432u(b64, 125000000, remainder)
	  if remainder>=62500000 then //round the result
		   N.LW=N.LW+1
 	  endif
      hvalue=N.b(3)/16
      lValue=N.b(3) AND 15
      write(HexArray(hValue),Hexarray(lvalue))
      hvalue=N.b(2)/16
      lValue=N.b(2) AND 15
      write(HexArray(hValue),Hexarray(lvalue))
      hvalue=N.b(1)/16
      lValue=N.b(1) AND 15
      write(HexArray(hValue),Hexarray(lvalue))
      hvalue=N.b(0)/16
      lValue=N.b(0) AND 15
      write(HexArray(hValue),Hexarray(lvalue))
    wend
 

John West

Senior Member
Thanks, matherp. That's a very interesting idea, a two chip solution. I'll look further into it and then if I think I can manage it I'll ask for the hex code. I already have a PICKIT3 programmer so I'm in good shape for something like this, but I'll need to see about the 14K22.

I also have a variety of other surplus pic's in fairly large quantities that I've scavenged. It would be great to use one of them as a sub for the 14K22. But I know very little about the capabilities of each type and which might be suitable to implement in this project as a substitute, so I'll need to read up on the 14K22 and see if one of the others might be suitable.
 

matherp

Senior Member
John

Any 18F chip would do with Swordfish I just use the 14K22 as a default as they are cheap and powerful and of course fully compatible with the picaxe. There is a free version of Swordfish you might want to play with which may be good enough to compile this program. Then you could build and program yourself with the Pickit on any 18 series chip you have handy. The only tricky bit as always is setting the fuses on the chip
 
Last edited:

John West

Senior Member
I don't know anything yet about setting fuses on the chips, but I'll dig into my surplus chip stock, reread your comments, read up on the chips themselves, and take it from there this weekend. It should be a good step in my further pic education.

Once I have a pic decoding the freq info as a dedicated math co processor the rest will all be in the hardware and PICAXE realm and should be a lot of fun. As always, matherp, thanks for the info and suggestions.
 
Last edited:

radiogareth

Senior Member
Although its NOT a picaxe (just a plain old 16F628A) this website has several versions, the last two of which work (at least) from DC - 40MHz.

http://www.vk5tm.com/homebrew/dds/dds.php

Its nice to see how it was done on a Picaxe, but buy a PIC programmer (£7 on china ebay) and try a native pic too. The code is well commented and its easy to assemble.

Its a seriously wide range signal generator for pocket money prices :) I'm thinking of sorting a commercial pcb for my radio club to run it as a project. Anyone interested?

Gareth
 

Axel87

Senior Member
How has this thread blown up yet?
I just stumbled across this chip giving up troubleshooting my ancient generator and thought how awesome would it be if this worked with our Picaxe!

Upon opening the code though, I died a little inside. lol How does one write code like this?
Thank you for providing this information, but I have to ask, would anyone whos completed this build recommend this build for noob amateurs? easier build suggestions?
Would be lost in that code if I ran into any snags!
Will wait to hear back before I order up some parts, Thanks guys!
 

matherp

Senior Member
Axel

I don't think any one knows quite what you are asking. If you want to build the project I posted and follow the circuit precisely then the code will work as is. I eventually used this display:
http://www.ebay.com.au/itm/200659768960?ssPageName=STRK:MEWAX:IT&_trksid=p3984.m1438.l2649,
but if you do the same then be careful of the VDD/VCC wiring as it is not the normal way round and pins 1 and 2 need to be swapped in the circuit diagram. The diagram is correct for all other 2x8 pinout LCDs I have used.
It is on my todo list to modify the code to allow 8 digits of precision in setting the frequency. At the moment it only allows 4 i.e. NNNN, NNNN0, NNNN00, NNNN000 to NNNN0000

Rgds

Peter
 

Paix

Senior Member
Upon opening the code though, I died a little inside. lol How does one write code like this?
Given the code, one character at a time . . .

You will have to examine the little bits to figure what they are doing and use those building bricks to understand the larger constructs and eventually have a fair idea of how it all hangs together.

Finally with about 90%+ under your belt, you will have to use your intuition to guide you and take a leap of faith.

If you don't understand something, then it is perhaps wise to add bits of code to display results on the monitor or elsewhere so that you can get a handle on them. Typically as a programmer I probably wrote four times as much code as I ever used. Most of what was eventually deleted or commented out was to prove the correctness of assumptions and constructs that I had been building to do the required job.

Have fun :) Good project, thanks Matherp.
 

matherp

Senior Member
Update that uses 56bit arithmetic to give full 1hz resolution from 1hz to 40Mhz.

I've used an AXE401 (28X2) with LCD/Keypad shield as a convenient development platform with inglewoodpete's simple LCD driving routines as the shield does not keep the LCD data pins on a contiguous set of picaxe pins.

To use press "Select" to enter edit mode. Move the cursor using left/right to select the digit and up/down to edit it. Press select again to update the generator and exit edit mode (cursor and leading zeroes disappear)- simples:)
 

Attachments

Last edited:

John West

Senior Member
Just a note to matherp in case he doesn't pay attention to this sort of thing.

There are over 3,600 views of this thread, so I'd say you efforts have not gone unnoticed.
 
Top