Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,17 +168,17 @@ Manually set the amplitude scale factor of a specified channel. Channels are 0-3
- `set`:
Sets the value of instruction number `addr` for channel `channel` (zero indexed). `addr` starts at 0. It looks different depending on what mode the sweeper is in. If `Debug` is set to `on` it will respond with the actual values set for that instruction.
- Single Stepping (mode 0): `set <channel:int> <addr:int> <frequency:float> <amplitude:float> <phase:float> (<time:float>)`
- Sweep Mode (modes 1-3): `set <channel:int> <addr:int> <start_point:float> <end_point:float> <delta:float> (<time:float>)`
- Sweep Mode (modes 1-3): `set <channel:int> <addr:int> <start_point:float> <end_point:float> <sweep_rate:float> (<time:float>)`

`start_point` is the value the sweep should start from, and `end_point` is where it will stop. `delta` is the amount that the output should change by every cycle of the sweep clock. In the AD9959, the sweep clock runs at one quarter the system clock. The types of values expected for `start_point`, `end_point`, and `delta` different depending on the type of sweep
`start_point` is the value the sweep should start from, and `end_point` is where it will stop. `sweep_rate` is the amount that the output should change in each quantity's units per second (further info below). In the AD9959, the sweep clock runs at one quarter the system clock. The types of values expected for `start_point`, `end_point`, and `sweep_rate` are different depending on the type of sweep:
- Amplitude Sweeps (mode 1)
`start_point` and `end_point` should be decimals between 0 and 1 that represent the desired proportion of the maximum output amplitude. `delta` is the desired change in that proportion. For all three of those values there is a resolution of $\frac{1}{1024} \approx 0.09766\$
`start_point` and `end_point` should be decimals between 0 and 1 that represent the desired proportion of the maximum output amplitude. `sweep_rate` is then that proportion per second. The resolution of the start and end values is $\frac{1}{1024} \approx 0.09766$.
- Frequency Sweeps (mode 2)
`start_point`, `end_point`, and `delta` are frequencies in Hz. They can have decimal values, but they will be rounded to the nearest multiple of the frequency resolution.
`start_point` and `end_point` are frequencies in Hz, with `sweep_rate` in Hz per second. The start and end points can have decimal values, but they will be rounded to the nearest multiple of the frequency resolution ($\frac{f_{sysclk}}{2^{32}}$).
- Phase Sweeps (mode 3)
`start_point`, `end_point`, and `delta` are in degrees. They can have decimal values, but they will be rounded to the nearest multiple of the phase resolution (always $= 360^\circ / 2^{14} \approx 0.02197^\circ$).
`start_point`, `end_point` are in degrees, with `sweep_rate` in degrees per second. The start and end points can have decimal values, but they will be rounded to the nearest multiple of the phase resolution ($\frac{360^\circ}{2^{14}} \approx 0.02197^\circ$).

- Sweep and Single Stepping Mode (modes 4-6): `set <channel:int> <addr:int> <start_point:float> <end_point:float> <delta:float> <secondary1:double> <secondary2:double> (<time:float>)`
- Sweep and Single Stepping Mode (modes 4-6): `set <channel:int> <addr:int> <start_point:float> <end_point:float> <sweep_rate:float> <secondary1:double> <secondary2:double> (<time:float>)`

These modes perform a linear sweep on one of the parameters, while simultaneously single stepping on the other two parameters.
- Amplitude Sweeps (mode 4)
Expand Down
20 changes: 17 additions & 3 deletions SWEEP_DETAILS.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,36 @@
- Setting up a sweep:
![Sweep Setup Figure](img/sweep-setup.png)
Sweeps are defined by two parameters, sweep delta and ramp rate.
- Sweep Delta defines the change in output amplitude/frequency/phase on each sweep step
- Sweep Delta defines the change in output amplitude/frequency/phase on each sweep step.
- Ramp rate defines how often a sweep step is taken. It is based off of the AD9959's sync clock signal which will be one quarter of the AD9959's system clock. The ramp rate parameter specifies the number of sync clock cycles per sweep step. A ramp rate of 1 will cause the sweep delta to be applied every 1 sync clock cycle. For upward sweeps, the ramp rate parameter can have a value of 1-255. For downward sweeps the ramp rate can only be 1.

The time between sweep steps can be calculated with:
$ t = \frac{\textrm{Ramp Rate}}{\textrm{Sync Clock}} $
Using the Pico's 125 Mhz with a 4 times PLL Multiplier gives the AD9959 a system clock of 500 MHz and therefore a sync clock of 125 MHz.
For upward sweeps the time between sweeps can range from $\frac{1}{125 MHz} = 8 ns$ to $\frac{255}{125 MHz} = 2.04 \mu s$.
Downward sweeps will apply the sweep delta every $\frac{1}{125 MHz} = 8 ns$.
Given the frequency resolution of $\frac{f_{sys clk}}{2^{32}}$, the smallest sweep delta is $= \frac{1}{2^{32}} = 0.1164$ Hz. With the maximum ramp rates, the DDS-sweeper has a minimum sweeping rate of $\approx 47871$ Hz/sec when sweeping upwards or $\approx 12207031.25$ Hz/sec when sweeping downward.
Given the frequency resolution of $\frac{f_{sys clk}}{2^{32}}$, the smallest sweep delta is $\frac{1}{2^{32}} = 0.1164$ Hz. With the maximum ramp rates, the DDS-sweeper has a minimum sweeping rate of $\approx 47871$ Hz/sec when sweeping upwards or $\approx 12207031.25$ Hz/sec when sweeping downward.

## Finer Sweep Rate Resolution in `set`

The `set` command will internally calculate the sweep delta and ramp rate by given the user provided `sweep_rate` using the continued fraction algorithm. Given a conversion factor $B$ that allows us to convert a sweep rate $R$ to bits per sync clock cycle,

$$B_{freq} = \frac{2^{32}}{f_{sysclk}} [\frac{\text{bits}}{\text{Hz}}], B_{amp} = 2^{10} [\frac{\text{bits}}{\text{Arb.}}], B_{phase} = 2^{14}[\frac{\text{bits}}{\text{degree}}],$$

the ratio of sweep delta $d$ and ramp rate $r$ can be equated to the following:

$$
\frac{d}{r} = B\frac{4R}{f_{sysclk}}
$$.

The continued fraction algorithm then returns the integer ratio approximation of the desired `sweep_rate` float and therefore set an integer sweep delta and ramp rate. The algorithm will respect the limits of each sweep mode, so the frequency tuning word sweep deltas can be between $0$ and $2^{32}-1$, amplitude scale factor sweep deltas can be between $0$ and $2^{10}$, and phase offset word sweep deltas can be between $0$ and $2^{14}$. The ramp rates for each mode are constrained to be between $1$ and $255$ for upward sweeps, and held to $1$ for downward sweeps.

## Downward Sweeps

Downward sweeps are not well supported by the AD9959, but they can still be done.
The best method I have found for doing a downward sweep is to send the instructions via serial first, with the sweep autoclear bit set to active and the rising sweep tuning word set to the maximum.
Then issue the IO_UPDATE signal while keeping the profile pin for that channel high.
I belive this clears the sweep accumulator then quickly refills it with a max rate sweep before beginning the downward sweep.
I believe this clears the sweep accumulator then quickly refills it with a max rate sweep before beginning the downward sweep.
Other combinations of autoclear bit active or not and timing of the profile pins seem to cause even more issues with downward sweeps.
The biggest downside of this method is that you cannot slow down the downward sweep ramp rate as much as the upward sweep ramp rate.

Expand Down
114 changes: 114 additions & 0 deletions dds-sweeper/ad9959.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "ad9959.h"
#include "fast_serial.h"
#include <stdlib.h>

// =============================================================================
// calculate tuning words
Expand Down Expand Up @@ -167,3 +168,116 @@ void clear() {

spi_write_blocking(SPI, clear, sizeof clear);
}

// =============================================================================
// Sweep rate algorithms
// =============================================================================

double approx_double_32(double f, uint32_t * numerator, uint * denominator, uint32_t p_max, uint32_t q_max) {
// Approximates a double as a ratio of integers
// using a modified continued fractions algorithm

double eps = 0.000000001; // later

uint32_t a0 = floor(f);
double r_old = f - a0;
uint32_t q_double_old = 0;
uint32_t p_double_old = 1;
uint32_t p_old = a0;
uint32_t q_old = 1;

uint32_t a_new, p_new, q_new;
double r_new;

if (p_old == 0) {
// Returns slowest possible ramp rate
*numerator = 1;
*denominator = 255;
return 1.0/255.0;
}

uint num_iters = 100;
for (uint i = 1; i < num_iters; i++) {
if (fabs(f - ((double)p_old / (double)q_old)) < eps) {
// Ratio close to double within eps
break;
}

a_new = floor(1/r_old);
p_new = a_new * p_old + p_double_old;
q_new = a_new * q_old + q_double_old;

if (p_new > p_max || q_new > q_max){
// Either delta or rate out of bounds
break;
}

r_old = 1/r_old - a_new;

// update all vars
p_double_old = p_old;
p_old = p_new;
q_double_old = q_old;
q_old = q_new;

}

*numerator = p_old;
*denominator = q_old;
return (double)p_old / (double)q_old;
}

double approx_double_16(double f, uint16_t * numerator, uint * denominator, uint16_t p_max, uint16_t q_max) {
// Approximates a double as a ratio of integers
// using a modified continued fractions algorithm

double eps = 0.000000001; // later

uint16_t a0 = floor(f);
double r_old = f - a0;
uint16_t q_double_old = 0;
uint16_t p_double_old = 1;
uint16_t p_old = a0;
uint16_t q_old = 1;

uint16_t a_new, p_new, q_new;
double r_new;

if (p_old == 0) {
// Returns slowest possible ramp rate
*numerator = 1;
*denominator = 255;
return 1.0/255.0;
}

uint num_iters = 100;
for (uint i = 1; i < num_iters; i++) {
if (fabs(f - ((double)p_old / (double)q_old)) < eps) {
// Ratio close to double within eps
break;
}

a_new = floor(1/r_old);
p_new = a_new * p_old + p_double_old;
q_new = a_new * q_old + q_double_old;

if (p_new > p_max || q_new > q_max){
// Either delta or rate out of bounds
break;
}

r_old = 1/r_old - a_new;

// update all vars
p_double_old = p_old;
p_old = p_new;
q_double_old = q_old;
q_old = q_new;

}

*numerator = p_old;
*denominator = q_old;
return (double)p_old / (double)q_old;

}
4 changes: 4 additions & 0 deletions dds-sweeper/ad9959.h
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,8 @@ void set_ref_clk(ad9959_config* c, uint64_t freq);
void single_step_mode();
void clear();

// Sweep rate algorithm
double approx_double_32(double f, uint32_t * numerator, uint * denominator, uint32_t p_max, uint32_t q_max);
double approx_double_16(double f, uint16_t * numerator, uint * denominator, uint16_t p_max, uint16_t q_max);

#endif
87 changes: 60 additions & 27 deletions dds-sweeper/dds-sweeper.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
#include "pico/multicore.h"
#include "pico/stdlib.h"
#include "trigger_timer.pio.h"

#define VERSION "0.2.1"

// Mutex for status
Expand Down Expand Up @@ -70,6 +69,7 @@ int clk_mode = INTERNAL;
// PIO VALUES IT IS LOOKING FOR
#define UPDATE 0


#define MAX_SIZE 248832 // 243 * 1024
#define TIMERS 5000
#define TIMING_OFFSET (MAX_SIZE - TIMERS * 4)
Expand Down Expand Up @@ -487,6 +487,9 @@ void parse_amp_sweep_ins(uint addr, uint channel,
uint32_t ftw;
uint rate = 1;

uint32_t delta_max = 1023;
uint32_t rate_max = 255;

if (ad9959.sweep_type == AMP2_MODE) {
// Convert others into integer values
get_ftw(&ad9959, freq, &ftw);
Expand All @@ -497,28 +500,26 @@ void parse_amp_sweep_ins(uint addr, uint channel,
start = get_asf(start, &asf_start);
end = get_asf(end, &asf_end);

delta = round(sweep_rate * 1024);
double t_sync = 4 / (ad9959.ref_clk * ad9959.pll_mult);
double bits_per_arb = 1024;
double arb_per_sync = sweep_rate * t_sync;
double bits_per_sync_clk_cycle = bits_per_arb * arb_per_sync;
double approx_bits_per_sync;

if (start < end) {
approx_bits_per_sync = approx_double_16(bits_per_sync_clk_cycle, &delta, &rate, delta_max, rate_max);
} else {
approx_bits_per_sync = approx_double_16(bits_per_sync_clk_cycle, &delta, &rate, delta_max, 1);

if (delta < 1) {
if (asf_end > asf_start) {
// If rising, we can use rate to divide down
rate = 255;
delta = round(sweep_rate * 1024 / 255);
if (delta < 1) {
delta = 1;
}
} else {
delta = 1;
}
} else if (delta > 1023) {
delta = 1023;
}

// back to human readable units
sweep_rate = approx_bits_per_sync / bits_per_arb / t_sync;

if (DEBUG) {
fast_serial_printf(
"Set ins #%d for channel %d from %3lf%% to %3lf%% with delta %3lf%% "
"and rate of %d\n",
addr, channel, start / 10.23, end / 10.23, delta / 10.23, rate);
"Set ins #%d for channel %d from %3lf%% to %3lf%% with sweep rate %3lf%% arb/s\n",
addr, channel, start, end, sweep_rate);
}

set_amp_sweep_ins(addr, channel, asf_start, asf_end, delta, rate, ftw, pow);
Expand Down Expand Up @@ -619,6 +620,8 @@ void parse_freq_sweep_ins(uint addr, uint channel,
uint32_t ftw_start, ftw_end, delta;
uint rate = 1;

uint32_t delta_max = 4294967296 - 1;
uint32_t rate_max = 255;
if (ad9959.sweep_type == FREQ2_MODE) {
// Convert others into integer values
get_asf(amp, &asf);
Expand All @@ -628,13 +631,27 @@ void parse_freq_sweep_ins(uint addr, uint channel,
// Convert percentages to integers, check values in range
start = get_ftw(&ad9959, start, &ftw_start);
end = get_ftw(&ad9959, end, &ftw_end);
sweep_rate = get_ftw(&ad9959, sweep_rate, &delta);

double sys_clk = ad9959.ref_clk * ad9959.pll_mult;
double t_sync = 4 / sys_clk;
double bits_per_hz = 4294967296 / sys_clk;
double hz_per_sync = sweep_rate * t_sync;
double bits_per_sync_clk_cycle = bits_per_hz * hz_per_sync;
double approx_bits_per_sync;

if (start < end) {
approx_bits_per_sync = approx_double_32(bits_per_sync_clk_cycle, &delta, &rate, delta_max, rate_max);
} else {
approx_bits_per_sync = approx_double_32(bits_per_sync_clk_cycle, &delta, &rate, delta_max, 1);
}

// back to human readable units
sweep_rate = approx_bits_per_sync / bits_per_hz / t_sync;

if (DEBUG) {
fast_serial_printf(
"Set ins #%d for channel %d from %4lf Hz to %4lf Hz with delta %4lf "
"Hz and rate of %d\n",
addr, channel, start, end, sweep_rate, rate);
"Set ins #%d for channel %d from %4lf Hz to %4lf Hz with sweep rate %lf Hz/s\n",
addr, channel, start, end, sweep_rate);
}

set_freq_sweep_ins(addr, channel, ftw_start, ftw_end, delta, rate, asf, pow);
Expand Down Expand Up @@ -740,6 +757,9 @@ void parse_phase_sweep_ins(uint addr, uint channel,
uint32_t ftw;
uint rate = 1;

uint16_t delta_max = 16384 - 1;
uint16_t rate_max = 255;

if (ad9959.sweep_type == PHASE2_MODE) {
// Convert others into integer values
get_ftw(&ad9959, freq, &ftw);
Expand All @@ -749,14 +769,26 @@ void parse_phase_sweep_ins(uint addr, uint channel,
// Convert from degrees to tuning words
start = get_pow(start, &pow_start);
end = get_pow(end, &pow_end);
sweep_rate = get_pow(sweep_rate, &delta);

double bits_per_deg = 16384;
double t_sync = 4 / (ad9959.ref_clk * ad9959.pll_mult);
double deg_per_sync = sweep_rate * t_sync;
double bits_per_sync_clk_cycle = bits_per_deg * deg_per_sync;
double approx_bits_per_sync;

if (start < end) {
approx_bits_per_sync = approx_double_16(bits_per_sync_clk_cycle, &delta, &rate, delta_max, rate_max);
} else {
approx_bits_per_sync = approx_double_16(bits_per_sync_clk_cycle, &delta, &rate, delta_max, 1);
}

// back to human readable units
sweep_rate = approx_bits_per_sync / bits_per_deg / t_sync;

if (DEBUG) {
fast_serial_printf(
"Set ins #%d for channel %d from %4lf deg to %4lf deg with delta "
"%4lf deg and rate of %d\n",
addr, channel, pow_start / 16384.0 * 360, pow_end / 16384.0 * 360, delta / 16384.0 * 360,
rate);
"Set ins #%d for channel %d from %4lf deg to %4lf deg with sweep rate %4lf deg/s\n",
addr, channel, start, end, sweep_rate);
}

set_phase_sweep_ins(addr, channel, pow_start, pow_end, delta, rate, ftw, asf);
Expand Down Expand Up @@ -974,6 +1006,7 @@ void loop() {
OK();
} else if (strncmp(readstring, "getmode", 7) == 0) {
get_memory_layout(ad9959.sweep_type);

} else if (strncmp(readstring, "setchannels", 11) == 0) {
uint channels;

Expand Down
Loading