Skip to content

Commit

Permalink
release lib v1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinStokroos committed Nov 2, 2020
1 parent b65c99c commit 52448b0
Show file tree
Hide file tree
Showing 9 changed files with 492 additions and 394 deletions.
54 changes: 29 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
# Native DDS Library for Arduino
This repository contains the *NativeDDS* library for Arduino. *NativeDDS* is a software implemented Direct Digital Synthesizer. *NativeDDS* generates sinusoidal waves for example for use in audio applications, motor drives, sine wave inverters or as a reference frequency source. The library is easy portable to other platforms.
# Native DDS Library for Arduino v1.2
This repository contains the *NativeDDS* library for Arduino. *NativeDDS* is a software implemented Direct Digital Synthesizer. *NativeDDS* generates sinusoidal waves for example for use in audio applications, motor drives, sine wave inverters or for a reference frequency source. The library is easy portable to other platforms.

## Function
The DDS frequency resolution is 32-bit. This means that the frequency can be tuned with steps of `f_update/(2^32)`, with `f_update = 1/timestep` (see below). The amplitude resolution can be set to eight bit or ten bit by defining `EIGHTBIT` or `TENBIT` in the `NativeDDS.h` file. The eight bit implementation is faster and will be precise enough for most applications.
The DDS frequency resolution is 32-bit. This means that the frequency can be tuned in steps of `f_update/(2^32)`, with `f_update = 1/timestep` (see below). The amplitude resolution can be eight bits or ten bits by choosing the class instances `DDS_8bit_xx` or `DDS_10bit_xx`. The eight bit implementation is faster and will be precise enough in most applications.

The following library classes are implemented:
The following library instance methods are implemented:

* `DDS_1Ch`
* `DDS_2Ch`
* `DDS_IQ`
* `DDS_3Ph`
* `DDS_8bit_1Ch or DDS_10bit_1Ch`
* `DDS_8bit_2Ch or DDS_10bit_2Ch`
* `DDS_8bit_IQ or DDS_10bit_IQ`
* `DDS_8bit_3Ph or DDS_10bit_3Ph`

`DDS_1Ch` is a single (output) channel DDS, `DDS_2Ch` is a dual channel DDS, `DDS_IQ` is a dual channel DDS with orthogonal outputs and `DDS_3Ph` is a three channel DDS.
The explanation below will be continued for the 8-bit case.

`DDS_8bit_1Ch` is a single (output) channel DDS, `DDS_8bit_2Ch` is a dual channel DDS, `DDS_8bit_IQ` is a dual channel DDS generating orthogonal outputs and `DDS_8bit_3Ph` is a three output channel DDS.

The following methods exists:

Expand All @@ -21,31 +23,31 @@ The following methods exists:

Initialize the class instance with the member function `begin()`. Also, `begin()` must be called each time when the frequency or phase has to be updated.

DDS_1Ch, use: `.begin(float frequency, float starting-phase, float timestep);`
DDS_8bit_1Ch, use: `.begin(float frequency, float starting-phase, float timestep);`

DDS_2Ch, use: `.begin(float frequency1, float frequency2, starting-phase1, float starting-phase2, timestep);`
DDS_8bit_2Ch, use: `.begin(float frequency1, float frequency2, starting-phase1, float starting-phase2, timestep);`

DDS_IQ, use: `.begin(float frequency, float starting-phase, float timestep);`
DDS_8bit_IQ, use: `.begin(float frequency, float starting-phase, float timestep);`

DDS_3Ph, use: `.begin(float frequency, float starting-phase, float timestep);`
DDS_8bit_3Ph, use: `.begin(float frequency, float starting-phase, float timestep);`

For the member function `.begin()`, the variable `frequency` is in Hz and `starting-phase` in radians. `timestep` is the loop iteration time period in seconds with which `.update()` is repeatedly invoked.
For the member function `.begin()`, the variable `frequency` is in Hz and `starting-phase` in radians. `timestep` is the loop repeating time in seconds at which `.update()` is invoked.

The following public variables are defined for the outputs:
The following public output variables are defined:

DDS_1Ch: `out1`
DDS_8bit_1Ch: `out1` and `uout1` for unsigned.

DDS_2Ch: `out1`, `out2`
DDS_8bit_2Ch: `out1`, `out2` and `uout1`, `uout2` for unsigned.

DDS_IQ: `outi`, `outq`
DDS_8bit_IQ: `outi`, `outq` and `uouti`, `uoutq` for unsigned.

DDS_3Ph: `outu`, `outv`, `outw`
DDS_8bit_3Ph: `outu`, `outv`, `outw` and `uoutu`, `uoutv`, `uoutw` for unsigned.

## Usage
* First create an instance of the library object, for example here we define *mySin*:

```
DDS_1Ch mySine;
DDS_8bit_1Ch mySine;
```

* Initialize the DDS on some place in your setup function:
Expand All @@ -60,24 +62,26 @@ void setup() {

* Call `mySine.update();` from the main loop or from an Interrupt Service Routine (ISR). Use `.update(void)` on the basis of a regular time interval to generate a jitter free output signal.

Right after the previous step, the instantaneous output value is obtained with: `int sample = mySine.out1;`
Right after the previous step, the instantaneous (unsigned) output value is obtained with: `int sample = mySine.uout1;`

```
void loop() {
...
mySine.update();
sample=mySine.out1
analogWrite(PWM_OUT, sample + 127); // add 127 to convert to unsigned.
sample=mySine.uout1
analogWrite(PWM_OUT, sample);
...
}
```

## Examples
`SineWave.ino` - Example of a single channel DDS. The time base is generated in the main loop. The maximum usable output frequency is limited to about 100Hz.
`SineWave_8bit.ino` - Example of a single channel 8-bits DDS. The time base is generated in the main loop. The maximum usable output frequency is limited to about 100Hz.

`SineWave_10bit.ino` - Example of a single channel 10-bits DDS. The time base is generated in the main loop. The maximum usable output frequency is limited to about 100Hz.

`WobblingServo.ino` - A slowly sine-wave modulated servo motor application.

`AudioGenerator.ino` - A generator for audible frequencies. A timer interrupt has been used to create a fixed time base.
`AudioGenerator.ino` - A generator for audible frequencies. A timer interrupt has been used to create the time base.

## Acknowledgement
A lot of time was saved in developing this library by using the alternative Arduino-IDE [Sloeber](https://eclipse.baeyens.it/). Sloeber is a wonderful Arduino plugin for Eclipse. Thanks to Jantje and his contributors!
13 changes: 6 additions & 7 deletions examples/AudioGenerator/AudioGenerator.ino
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
*
* File: AudioGenerator.ino
* Purpose: Native Direct Digital Synthesizer library example project
* Version: 1.0.2
* Version: 1.0.3
* Release date: 24-10-2018
* Last edit date: 10-08-2020
* Last edit date: 02-11-2020
*
* URL: https://github.com/MartinStokroos/NativeDDS
* License: MIT License
Expand All @@ -13,6 +13,7 @@
* v1.0.0, 24 Oct 2018 - initial release
* v1.0.1, 18 Nov 2018 - textual changes in comments
* v1.0.2, 10-08-2020 - select 8-bit/10bit DDS from main c file but it does not work (yet).
* v1.0.3, 02-11-2020 - library update for seperate instance methods for 8-bit and 10-bit DDS
*
* This sketch demonstrates the Native DDS library. Native DDS is a Direct Digital Synthesizer
* algorithm that runs in software on the Arduino. In this example, a 220Hz and a 440Hz sine wave are
Expand All @@ -31,7 +32,6 @@
*
*/

//#define DDS_8BIT //Why does defining it here won't work? Now it still should be done from NativeDDS.h
#include "NativeDDS.h"

//#define DEBUG
Expand All @@ -50,7 +50,7 @@
const float Ts = 3.1875E-5; // Timer2 period
float f = 220.0; // Wanted DDS output frequency

DDS_2Ch mySine; // Create instance of DDS_2Ch for a dual channel DDS.
DDS_8bit_2Ch mySine; // Create instance of DDS_8bit_2Ch for a dual channel DDS.



Expand Down Expand Up @@ -97,13 +97,12 @@ ISR(TIMER2_OVF_vect) {
#endif

mySine.update();
OCR2B = mySine.out1 + 127; // output PWM_OUT1 directly updated with the OCR2B register value (220Hz)
OCR2A = mySine.out2 + 127; // output PWM_OUT2 directly updated with the OCR2A register value (440Hz)
OCR2B = mySine.uout1; // PWM_OUT1 directly updated by writing the OCR2B register value (220Hz)
OCR2A = mySine.uout2; // PWM_OUT2 directly updated by writing the OCR2A register value (440Hz)

#if defined(DEBUG)
digitalWriteFast(DEBUG_PIN, false);
#endif
}

// end of AudioGenerator.ino

Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
/*
* File: SineWave.ino
* File: SineWave_10bit.ino
* Purpose: Native Direct Digital Synthesizer library example project
* Version: 1.1.1
* Version: 1.0.0
* First release date: 22-10-2018
* Last edit date: 10-08-2020
* Last edit date:
*
* URL: https://github.com/MartinStokroos/NativeDDS
* License: MIT License
*
* Version history:
* v1.0.0, 22 Oct 2018 - initial release
* v1.1.0, 18 Nov 2018 - looptime=200us (5kHz). Pin3 + pin11 PWM frequency=31250Hz and the output frequency=100Hz.
* v1.1.1, 10-08-2020 - select 8-bit/10bit DDS from main c file but it does not work (yet).
*
* v1.0.0, 02-11-2020 - separate instance methods for 8-bit / 10-bit DDS.
*
* This sketch demonstrates the Native DDS library. Native DDS is a Direct Digital Synthesizer
* algorithm that runs in software on the Arduino. In this example the output frequency is set to 100Hz.
Expand All @@ -32,28 +31,28 @@
*
*/

//#define DDS_8BIT //Why does defining it here won't work? Now it still should be done from NativeDDS.h
#include "NativeDDS.h"

#define LPERIOD 200 // loop period time in us. Update freq. is 5kHz

#define PWM_OUT 3 // define PWM output pin
#define DEBUG_PIN 13 // define debug PIN for timing observation (=LED pin)

double frequency = 100.0; // output frequency in Hz
unsigned long nextLoop;
int output;

DDS_1Ch mySine; // create instance of DDS_1Ch
DDS_10bit_1Ch mySine; // create instance of DDS_10bit_1Ch



void setup() {
// run once:
pinMode(PWM_OUT, OUTPUT);
pinMode(DEBUG_PIN, OUTPUT);
TCCR2B = (TCCR2B & 0xF8)|0x01; // set Pin3 + Pin11 PWM frequency to 31250Hz
TCCR2B = (TCCR2B & 0xF8)|0x01; // Set Pin3 + Pin11 PWM frequency to 31250Hz.

mySine.begin(frequency, 0, LPERIOD*1.0e-6); // initilize the DDS (frequency, startphase, update period time)
mySine.begin(frequency, 0, LPERIOD*1.0e-6); // Initilize the DDS (frequency, startphase, update period time).

nextLoop = micros() + LPERIOD; // Set the loop timer variable for the next loop interval.
}
Expand All @@ -63,27 +62,16 @@ void setup() {
void loop() {
// run repeatedly:

#ifdef DDS_8BIT
//digitalWrite(DEBUG_PIN, HIGH); // for checking loop period time and loop execution time (signal high time)

mySine.update(); //update the DDS, measured execution time <10us

//digitalWrite(DEBUG_PIN, LOW);
analogWrite(PWM_OUT, mySine.out1 + 127); // add 127 to convert to unsigned.

#elif defined (DDS_10BIT)
//digitalWrite(DEBUG_PIN, HIGH); // debugging: check loop period time and loop execution time (signal high time)

mySine.update(); // update the DDS, measured execution time <20us

//digitalWrite(DEBUG_PIN, LOW);
output = mySine.out1 + 511; // add 511 to convert to unsigned.
analogWrite(PWM_OUT, output>>2); // convert back to to 8-bit and write to analog out.
#endif
//digitalWrite(DEBUG_PIN, HIGH); // debugging: for checking loop cycling time and function execution time (signal high time)
mySine.update(); // update the DDS, measured execution time <20us
//digitalWrite(DEBUG_PIN, LOW);

//output = mySine.out1 + 511; // add 511 to convert to unsigned.
output = mySine.uout1; // use unsigned output.
analogWrite(PWM_OUT, output>>2); // convert back to 8-bit and write to analog out.

while(nextLoop > micros()); // wait until the end of the time interval
nextLoop += LPERIOD; // set next loop time at current time + LOOP_PERIOD
nextLoop += LPERIOD; // set next loop time at current time + LOOP_PERIOD
}

// end of SineWave.ino

// end of SineWave_10bit.ino
75 changes: 75 additions & 0 deletions examples/SineWave_8bit/SineWave_8bit.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* File: SineWave_8bit.ino
* Purpose: Native Direct Digital Synthesizer library example project
* Version: 1.0.0
* First release date: 02-11-2020
* Last edit date:
*
* URL: https://github.com/MartinStokroos/NativeDDS
* License: MIT License
* Version history:
*
* v1.0.0, 02-11-2020 - separate instance methods for 8-bit / 10-bit DDS.
*
* This sketch demonstrates the Native DDS library. Native DDS is a Direct Digital Synthesizer
* algorithm that runs in software on the Arduino. In this example the output frequency is set to 100Hz.
* The pulse width modulated sine wave is available on pin 3.
*
* In this example the DDS runs at 5kHz (the update frequency of the DDS). In theory the maximum output frequency
* at 5kHz update rate is 2500Hz. To get a smooth output wave, a low pass filter (RC or multistage LC), is required.
* For more information please read: A Technical Tutorial on Digital Signal Synthesis, Ken Gentile and Rick Cushing,
* Analog Devices, 1999.
*
* NOTE: The DDS output is sent to the PWM output pin with the analogWrite function. analogWrite sets the duty-cycle
* of the PWM signal corresponding to the wanted average output voltage level. The default PWM-frequency of pin 3
* is approximately 490Hz. Because of this low PWM frequency, the maximum usable DDS output frequency is even lower.
* For this reason the pwm frequency has been set to much a higher frequency than the DDS update frequency in this example.
*
* The loop timing can be observed with an oscilloscope on pin 13 (uncomment the lines with a digitalWrite).
* Please, also check the other example projects of the NativeDDS library.
*
*/

#include "NativeDDS.h"

#define LPERIOD 200 // loop period time in us. Update freq. is 5kHz

#define PWM_OUT 3 // define PWM output pin
#define DEBUG_PIN 13 // define debug PIN for timing observation (=LED pin)

double frequency = 100.0; // output frequency in Hz
unsigned long nextLoop;
int output;

DDS_8bit_1Ch mySine; // create instance of DDS_1Ch



void setup() {
// run once:
pinMode(PWM_OUT, OUTPUT);
pinMode(DEBUG_PIN, OUTPUT);
TCCR2B = (TCCR2B & 0xF8)|0x01; // Set Pin3 + Pin11 PWM frequency to 31250Hz.

mySine.begin(frequency, 0, LPERIOD*1.0e-6); // Initilize the DDS (frequency, startphase, update period time).

nextLoop = micros() + LPERIOD; // Set the loop timer variable for the next loop interval.
}



void loop() {
// run repeatedly:

//digitalWrite(DEBUG_PIN, HIGH); // for checking loop cycling time and function execution time (signal high time)
mySine.update(); // update DDS. Measured execution time <10us.
//digitalWrite(DEBUG_PIN, LOW);

//analogWrite(PWM_OUT, mySine.out1 + 127); // add 127 to convert to unsigned.
analogWrite(PWM_OUT, mySine.uout1); // use unsigned output.

while(nextLoop > micros()); // wait until the end of the time interval
nextLoop += LPERIOD; // set next loop time at current time + LOOP_PERIOD
}

// end of SineWave_8bit.ino
Loading

0 comments on commit 52448b0

Please sign in to comment.