How much can 328p handle?

I'm considering a GPS disciplined modulated? sweep frequency source.

The Arduino would control:
20x4 LCD showing start/stop frequency (or CW), GPS coordinates, time (maybe selectable fields showing speed and bearing, etc.), 1 sec refresh would be fine unless adjusting a parameter.
AD9850 DDS with sweept frequency points as fast as possible (what would be a reasonable expectation?); step resolution and speed (and maybe phase modulation) would be programmable parameters.
Analog 0 to 5 V ramp generator (I think I would need a separate DAC for this or maybe something more crude from PWM)

Arduino would receive input from NMEA GPS serial data, lock indicators (both for phase and GPS lock), quadrature encoder data to program the DDS.

My programming experience is very limited and my approaches are probably very inefficient using loops to get things done, so I was just wondering if the chip has the capability to handle that much I/O quick enough.

Could I send a 32-bit tuning word every 1 us when in step sweep mode and keep a 1 second time counter/coordinates going from NMEA serial input processed to the LCD while monitoring lock indicators?

Arduino could receive 1 byte every 1uS using SPI at maximum 8 MHz speed. Would not be able to much else tho.

The AD9850 can be loaded either in parallel or serially. If I've read the datasheet correctly the minimum clock speed of that device is 7ns, so if you can load it in parallel it will take a minimum of 35ns. Otherwise you would have to bitbang it serially which would be around 280ns. The clock cycle (maximum instruction speed) of 16MHz Arduinos is 62.5ns and it will take several clock cycles to send each bit (serially) or byte (parallel). I would estimate on the order of 25us for serial and 3us for parallel and while that is going on you won't be doing anything else otherwise the sweep frequency will jitter.
I suspect that this would be a job for a Due rather than a Nano or similar Arduino.
However, you say your "programming experience is very limited". I think that, as a whole, this project may be beyond your capabilities but if you're not in a rush it'll be a good learning experience.

Pete

Wow, those figures are much more optmistic than I was thinking, but I knew there are smarter ways to do things...

I was figuring it this way:

To change the DDS frequency (SoftwareSerial):
32-bit word @ 115200 bps = 2.8 us
DDS freq. change (spec.): 18 clocks @ 125 MHz = 15 ns, plus I think there is a 7 ns delay added.
So total is less than 3 ns for DDS frequency change.

I'm not sure how to figure out how long Arduino takes to calculate the tuning word. I was considering an IF loop incrementing the frequency variable up to the resolution count variable. The frequency variable gets sent to a function which handles the conversion, which I imagine all that takes a while (and I've read that digitalWrite is slow too).

void SetFrequency(unsigned long frequency)
{
  unsigned long tuning_word = (frequency * pow(2, 32)) / DDS_CLOCK;
  digitalWrite (LOAD, LOW); 

  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 8);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 16);
  shiftOut(DATA, CLOCK, LSBFIRST, tuning_word >> 24);
  shiftOut(DATA, CLOCK, LSBFIRST, 0x0);
  
  digitalWrite (LOAD, HIGH);

There are a couple different libraries for these DDS as well, so there are options...

NMEA 37 byte @ 4800 bps = 62 ms. Arduino HW serial has 64 byte buffer, I guess that data can be handled when needed, but hopefully each second to keep the time update perceivably accurate.

I could also offload all of the GPS NMEA and display to another arduino chip and LCD as they are not relevant to controlling the DDS.

Thanks for the insights...

32-bit word @ 115200 bps = 2.8 us

My calculator says 278us but I think you have to write 40 bits to the DDS so it'll be 347us.

DDS freq. change (spec.): 18 clocks @ 125 MHz = 15 ns

My calculator says 144ns but where do you get 18 and 125MHz from?

So total is less than 3 ns for DDS frequency change

I think you meant (from the numbers you derived) 3uS but I stand by my original estimates. 35ns for parallel and 280ns for serial and a 16MHz Arduino going flat out will require on the order of 25us for serial and 2us for parallel.

Offloading the GPS and LCD to another Arduino would make the project more feasible but that would then mean that the processor handling the DDS would also need to be reading and writing to/from that other Arduino while it is also updating the DDS.

  unsigned long tuning_word = (frequency * pow(2, 32)) / DDS_CLOCK;

This will cause you problems. First of all pow() is a floating point function which will slow down the computation more than it needs to be. Secondly it doesn't always return exact answers which are then mangled further by using the result as an integer. You need to do this calculation in 64-bit integer arithmetic (which makes using the Due look better!)

I was considering an IF loop

IF isn't a loop.

I'm not sure how to figure out how long Arduino takes to calculate the tuning word.

Just call it a few thousand times in a FOR loop and time how long it takes.

Pete

DDS freq. change (spec.): 18 clocks @ 125 MHz = 15 ns, plus I think there is a 7 ns delay added.
So total is less than 3 ns for DDS frequency change.

The '328P clock is only 16 MHz tho - the best this could Possibly be is 1.125uS.

OK, el_supremo, you must be using a scientific calculator. Yes, I'm out to lunch on my numbers - don't know what I was thinking...

I don't understand how Arduino can send 40 bits that fast. I'm assuming I'll be sending data via serial at Arduino's maximum rate of 115200 bps, but I'm likely going to use the DDS library and don't know how the data is being sent anyway.

From FQ_UD, the datasheet says 18 clocks for a frequency change (page 3). I could be misinterpreting what that means though...

The DDS does not need to know anything about the GPS, except I want to use a 15 kHz clock output from the GPS module that I have in order to achieve GPS lock of the DDS. I figured it would be nice to use the NMEA data that is there. I might just split that entire idea and move the GPS to a 10 MHz reference box instead and let the DDS synth be stand-alone and take a 10 MHz reference input (BNC). I really have not figured out how I'd get the 125 MHz clock to lock anyway.

CrossRoads - I was trying to compute the time the DDS chip takes to process the frequency word after it was received. The DDS clock is 125 MHz, and el_supremo had properly calculated that time.

I'll play with it once the DDS board comes in and see if any of the programming I've found works. Should be any day now...

Thanks

You need to do this calculation in 64-bit integer arithmetic (which makes using the Due look better!)

This works on the Uno:

unsigned long long DDSword, freq, DDSclock;
DDSclock = 125000000LL;
freq = 50000000LL;  // required freq
DDSword = (0x100000000LL) * freq ; 
DDSword = DDSword / DDSclock;

I just noticed you have 115,200 (software serial).
I don't think software serial goes that fast.

http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf
Serial loading:
"The first byte controls phase modulation, power-down enable, and loading format; Bytes 2 to
5 comprise the 32-bit frequency tuning word. Serial loading is accomplished via a 40-bit serial data stream on a single pin. "

That sounds like SPI to me, with a low pulse on the Frequency Update to update the frequency registers.
5 SPI.transfers can blast data out very quickly. You can SPI rate of 8 MHz even.
Those 5 bytes will take about 5us to clock in.

I'm assuming I'll be sending data via serial at Arduino's maximum rate of 115200 bps

No. The code you showed which uses shiftOut is not the same as the SoftwareSerial library. SoftwareSerial implements the RS-232 protocol which isn't any use to you because the AD9850 doesn't use that protocol. The shiftOut function bitbangs the data and is not limited to 115,200 bps.

And it isn't SPI either.

Pete

..but you have to update the frequency "points" while sweeping - that is the bottleneck, I think (you have to provide 64bit calcs - see my post above).
BTW the AD makes much better DDS chips, which include automatic sweeping, afaik. Also the 9850 is very old, noisy, power hungry (90mA!, I built several with 9851/180MHz and the chips were hot, I even glued a small cooler on top of them), 10bit DAC only (poor spectral purity even with low-pass filter).
For example: 9951 or 9912 (both w/ 14bit DAC) are much better, though.

I'm considering a GPS disciplined modulated? sweep frequency source.

The 9850 does not include x6 multiplier (9851 does) so you have to discipline the 125MHz clock - that is not easy as the source has to be a "quality" source, moreover disciplined. Disciplining a 125MHz clock source with help of a 1sec "jittery" tick from a GPS module is not a trivial task - a job for very experienced makers only, I would say :)..

CrossRoads:
Arduino could receive 1 byte every 1uS using SPI at maximum 8 MHz speed. Would not be able to much else tho.

And its not quite that fast, there is a delay of a few cycles to setup the next SPI transfer. The DDS control
word is 32 bits I believe, so perhaps upto 200kHz update rate absolute max.

What are you trying achieve?

Nick Gammon helped me work this code out:

SPDR = (testArray[fakestartPoint + 0]);nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;nop; nop; nop;nop; nop;
SPDR = (testArray[fakestartPoint + 1]);nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;nop; nop; nop;nop; nop;
SPDR = (testArray[fakestartPoint + 2]);nop; nop; nop; nop; nop; nop; nop; nop; nop; nop;nop; nop; nop;nop; nop;

This sends out SPI data as fast as it will go at 8MHz setting: SPI.setClockDivider(SPI_CLOCK_DIV2 );

The # of NOPs were confirmed via logic analyzer - they can't be reduced with affecting the data.
So I suppose you could kick off an SPI transfer, then do what you could in 15 clock cycles before updating the array pointer for the next byte ...

I'm sure now I'll separate the GPS and build it into a 10 MHz reference such as: http://gpsdo.i2phd.com/

If I can get the DDS to sweep with less than 1 ms switching time, I'll likely be happy with that. I'm sure I'll have a hard enough time just doing the frequency step calcs (based on F1 - F2 and step size, plus sweep time) and monitoring the other things I need.

It is going to be a big learning project for me and I really appreciate the help so far.

So I ran this sample code to measure frequency switching time set by Arduino:

/*
 * A simple single freq AD9850 Arduino test script
 * Original AD9851 DDS sketch by Andrew Smallbone at www.rocketnumbernine.com
 * Modified for testing the inexpensive AD9850 ebay DDS modules
 * Pictures and pinouts at nr8o.dhlpilotcentral.com
 * 9850 datasheet at http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf
 * Use freely
 */
 
 #define W_CLK 8       // Pin 8 - connect to AD9850 module word load clock pin (CLK)
 #define FQ_UD 9       // Pin 9 - connect to freq update pin (FQ)
 #define DATA 10       // Pin 10 - connect to serial data load pin (DATA)
 #define RESET 11      // Pin 11 - connect to reset pin (RST).
 
 #define pulseHigh(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); }
 
 // transfers a byte, a bit at a time, LSB first to the 9850 via serial DATA line
void tfr_byte(byte data)
{
  for (int i=0; i<8; i++, data>>=1) {
    digitalWrite(DATA, data & 0x01);
    pulseHigh(W_CLK);   //after each bit sent, CLK is pulsed high
  }
}
 
 // frequency calc from datasheet page 8 = <sys clock> * <frequency tuning word>/2^32
void sendFrequency(double frequency) {
  int32_t freq = frequency * 4294967295/125000000;  // note 125 MHz clock on 9850
  for (int b=0; b<4; b++, freq>>=8) {
    tfr_byte(freq & 0xFF);
  }
  tfr_byte(0x000);   // Final control byte, all 0 for 9850 chip
  pulseHigh(FQ_UD);  // Done!  Should see output
}
 
void setup() 
{
  Serial.begin(115200);
  Serial.println("DDS Sweep");
  
  // configure arduino data pins for output
  pinMode(FQ_UD, OUTPUT);
  pinMode(W_CLK, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(RESET, OUTPUT);
 
  pulseHigh(RESET);
  pulseHigh(W_CLK);
  pulseHigh(FQ_UD);  // this pulse enables serial mode - Datasheet page 12 figure 10
}
 
void loop() 
{
  long F1 = 1;
  long F2 = 100000;
  long long freq = 1;
    
  Serial.print("Start Frequency: ");
  Serial.print(long (freq));
  Serial.println(" Hz");
  
  long timeStart = micros();

  while (freq <= F2) {
    sendFrequency(freq);  // freq
    freq++;
  }

  long timeStop = micros();

  Serial.print("Start Time: ");
  Serial.print(timeStart);
  Serial.println(" us");
  Serial.print("Stop Frequency: ");
  Serial.print(long (freq));
  Serial.println(" Hz");
  Serial.print("Stop Time: ");
  Serial.print(timeStop);
  Serial.println(" us");
  Serial.print("Sweep Time: ");
  Serial.print(timeStop - timeStart);
  Serial.println(" us");
  Serial.print("Switching Time: ");
  Serial.print((timeStop - timeStart) / long(freq));
  Serial.println(" us/pt");
  Serial.println("");

}

And the serial monitor:

DDS Sweep
Start Frequency: 1 Hz
Start Time: 544 us
Stop Frequency: 100001 Hz
Stop Time: 60864612 us
Sweep Time: 60864068 us
Switching Time: 608 us/pt

Start Frequency: 1 Hz
Start Time: 60871704 us
Stop Frequency: 100001 Hz
Stop Time: 121735956 us
Sweep Time: 60864252 us
Switching Time: 608 us/pt

You guys are suggesting this could be 10x faster?

..instead of the freq calcs just add the step (when you do a linear sweep)..

Oh yeah - way faster
add this
#include <SPI.h>

Get rid of all these
 #define W_CLK 8       // Pin 8 - connect to AD9850 module word load clock pin (CLK)
 #define FQ_UD 9       // Pin 9 - connect to freq update pin (FQ)
 #define DATA 10       // Pin 10 - connect to serial data load pin (DATA)
 #define RESET 11      // Pin 11 - connect to reset pin (RST).

use this:
// pin 13 to clock
// pin 12 to devices data out pin if you will read anything back
// pin 11 to devices data in pin that you are sending data to
 #define FQ_UD 10       // Pin 10 - connect to freq update pin (FQ)
 #define RESET 9      // Pin 9 - connect to reset pin (RST).

Put your 5 bytes in an array, and add this in place of the bit banging you have now"

PORTB = PORTB & B11111011; // clear pin 10
SPI.transfer(dataArray[0]);
SPI.transfer(dataArray[1]);
SPI.transfer(dataArray[2]);
SPI.transfer(dataArray[3]);
SPI.transfer(dataArray[4]);
PORTB = PORTB | B00000100; // set pin 10

Well, it compiles, but it seems to hang somewhere ?

I was also wondering if the array is needed, instead of dataArray = (freq & 0xFF);, just sending the data byte in the for loop?
```
**#include <SPI.h>
#define W_CLK 13    // Pin 13 - connect to AD9850 module word load clock pin (CLK)
#define DATA 11      // Pin 11 - connect to serial data load pin (DATA)
#define FQ_UD 10    // Pin 10 - connect to freq update pin (FQ)
#define RESET 9      // Pin 9 - connect to reset pin (RST)
#define pulseHigh(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); }

// frequency calc from datasheet page 8 = * /2^32
void sendFrequency(double frequency) {
  byte dataArray[5];
  int32_t freq = frequency * 4294967295/125000000;  // note 125 MHz clock on 9850
  for (int b=0; b<4; b++, freq>>=8) {
    dataArray[b] = (freq & 0xFF);
  }
  dataArray[4] = 0x000;  // Final control byte, all 0 for 9850 chip
  PORTB = PORTB & B11111011; // clear pin 10
  SPI.transfer(dataArray[0]);
  SPI.transfer(dataArray[1]);
  SPI.transfer(dataArray[2]);
  SPI.transfer(dataArray[3]);
  SPI.transfer(dataArray[4]);
  PORTB = PORTB | B00000100; // set pin 10
  pulseHigh(FQ_UD);  // Done!  Should see output
}

void setup() {
  Serial.begin(115200);
  Serial.println("DDS Sweep");
  // configure arduino data pins for output
  pinMode(FQ_UD, OUTPUT);
  pinMode(W_CLK, OUTPUT);
  pinMode(DATA, OUTPUT);
  pinMode(RESET, OUTPUT);
  pulseHigh(RESET);
  pulseHigh(W_CLK);
  pulseHigh(FQ_UD);  // this pulse enables serial mode - Datasheet page 12 figure 10
}

void loop() {
  long F1 = 1;
  long F2 = 10;
  long long freq = 1;
  Serial.print("Start Frequency: ");
  Serial.print(long (freq));
  Serial.println(" Hz");
  long timeStart = micros();
  while (freq <= F2) {
    sendFrequency(freq);  // freq
    freq++;
  }
  long timeStop = micros();
  Serial.print("Start Time: ");
  Serial.print(timeStart);
  Serial.println(" us");
  Serial.print("Stop Frequency: ");
  Serial.print(long (freq));
  Serial.println(" Hz");
  Serial.print("Stop Time: ");
  Serial.print(timeStop);
  Serial.println(" us");
  Serial.print("Sweep Time: ");
  Serial.print(timeStop - timeStart);
  Serial.println(" us");
  Serial.print("Switching Time: ");
  Serial.print((timeStop - timeStart) / long(freq));
  Serial.println(" us/pt");
  Serial.println("");
}**
```

..
int32_t freq_start = (frequency1 * 4294967296LL)/125000000;
int32_t freq_stop = (frequency2 * 4294967296LL)/125000000;
int32_t freq_step = (frequency3 * 4294967296LL)/125000000;
..

and then something like this:

for (f=freq_start; f<=freq_stop; f=f+freq_step) {
sendDDSword(f);
}

PS: shall it be 4294967295 or 4294967296 ?? :slight_smile:

int32_t freq = frequency * 4294967295/125000000;

I doubt that that will work. It would have a better chance if you force the size of the integers and get 2^32 right:

int32_t freq = frequency * 4294967296LL/125000000L;

Pete