0.4.2 AD9833

This commit is contained in:
Rob Tillaart 2024-07-04 13:04:09 +02:00
parent 66e9ef06bb
commit 1fd76995da
10 changed files with 246 additions and 43 deletions

View File

@ -2,12 +2,14 @@
// FILE: AD9833.cpp
// AUTHOR: Rob Tillaart
// PURPOSE: Arduino library for AD9833 function generator
// VERSION: 0.4.1
// DATE: 2023-08-25
// VERSION: 0.4.2
// URL: https://github.com/RobTillaart/AD9833
//
#include "AD9833.h"
// FREQUENCY REGISTER BITS
#define AD9833_FREG1 0x8000
#define AD9833_FREG0 0x4000
@ -25,7 +27,6 @@
#define AD9833_MODE (1 << 1)
// HARDWARE SPI
AD9833::AD9833(uint8_t slaveSelect, __SPI_CLASS__ * mySPI)
{
@ -34,6 +35,7 @@ AD9833::AD9833(uint8_t slaveSelect, __SPI_CLASS__ * mySPI)
_mySPI = mySPI;
}
// SOFTWARE SPI
AD9833::AD9833(uint8_t slaveSelect, uint8_t spiData, uint8_t spiClock)
{
@ -67,6 +69,15 @@ void AD9833::begin()
pinMode(_clockPin, OUTPUT);
digitalWrite(_dataPin, LOW);
digitalWrite(_clockPin, HIGH);
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
uint8_t _port = digitalPinToPort(_dataPin);
_dataOutRegister = portOutputRegister(_port);
_dataOutBit = digitalPinToBitMask(_dataPin);
_port = digitalPinToPort(_clockPin);
_clockRegister = portOutputRegister(_port);
_clockBit = digitalPinToBitMask(_clockPin);
#endif
}
reset();
}
@ -94,16 +105,23 @@ void AD9833::hardwareReset()
bool AD9833::setPowerMode(uint8_t mode)
{
if (mode > 3) return false;
_control &= 0xFF3F; // clear previous power flags
_control |= (mode << 6); // set the new power flags
// clear previous power bits
_control &= ~(AD9833_SLEEP1 | AD9833_SLEEP12);
_control |= (mode << 6); // set the new power bits
writeControlRegister(_control);
return true;
}
uint8_t AD9833::getPowerMode()
{
return (_control & (AD9833_SLEEP1 | AD9833_SLEEP12)) >> 6;
}
void AD9833::setWave(uint8_t waveform)
{
if (waveform > 4) return;
if (waveform > AD9833_TRIANGLE) return;
// store waveform
_waveform = waveform;
@ -143,24 +161,29 @@ uint8_t AD9833::getWave()
float AD9833::setFrequency(float frequency, uint8_t channel)
{
if (channel > 1) return -1;
_freq[channel] = frequency;
if (_freq[channel] > AD9833_MAX_FREQ) _freq[channel] = AD9833_MAX_FREQ;
if (_freq[channel] < 0) _freq[channel] = 0;
// if (_freq[channel] == frequency) return frequency;
// local variable is faster.
float newFrequency = frequency;
if (newFrequency < 0) newFrequency = 0;
else if (newFrequency > AD9833_MAX_FREQ) newFrequency = AD9833_MAX_FREQ;
// convert to bit pattern
// fr = round(frequency * pow(2, 28) / 25 MHz)); // 25 MHz == crystal frequency.
// _crystalFreqFactor == (pow(2, 28) / crystal frequency);
// rounding
uint32_t freq = round(_freq[channel] * _crystalFreqFactor);
// round() to minimize error / use the whole range
uint32_t freq = round(newFrequency * _crystalFreqFactor);
writeFrequencyRegister(channel, freq);
return _freq[channel];
// cache the newFrequency;
_freq[channel] = newFrequency;
return newFrequency;
}
float AD9833::getFrequency(uint8_t channel)
{
// return round(_freq[channel] * _crystalFreqFactor) / _crystalFreqFactor;
return _freq[channel];
}
@ -184,23 +207,43 @@ void AD9833::setFrequencyChannel(uint8_t channel)
float AD9833::setPhase(float phase, uint8_t channel)
{
if (channel > 1) return -1;
_phase[channel] = phase;
while (_phase[channel] >= AD9833_MAX_PHASE) _phase[channel] -= AD9833_MAX_PHASE;
while (_phase[channel] < 0) _phase[channel] += AD9833_MAX_PHASE;
// local variable is faster.
float newPhase = phase;
while (newPhase >= AD9833_MAX_PHASE) newPhase -= AD9833_MAX_PHASE;
while (newPhase < 0) newPhase += AD9833_MAX_PHASE;
uint16_t ph = _phase[channel] * (4095.0 / 360.0);
// round() to minimize error / use the whole range 0..4095
uint16_t ph = round(newPhase * (4095.0 / 360.0));
writePhaseRegister(channel, ph);
return _phase[channel];
// cache the newPhase
_phase[channel] = newPhase;
return newPhase;
}
float AD9833::getPhase(uint8_t channel)
{
// more precise => more math;
// return round(_phase[channel] * (4095.0 / 360.0)) / (4095.0 / 360.0);
return _phase[channel];
}
// returns phase set (radians) - not optimized.
// [0 .. 2 PI>
float AD9833::setPhaseRadians(float phase, uint8_t channel)
{
return setPhase(phase * RAD_TO_DEG, channel) * DEG_TO_RAD;
}
float AD9833::getPhaseRadians(uint8_t channel)
{
return getPhase(channel) * DEG_TO_RAD;
}
float AD9833::getMaxPhase()
{
return AD9833_MAX_PHASE;
@ -289,12 +332,14 @@ void AD9833::writePhaseRegister(uint8_t channel, uint16_t value)
void AD9833::setCrystalFrequency(float crystalFrequency)
{
// calculate the often used factor
// 268435456.0 == POW2TO28
_crystalFreqFactor = 268435456.0 / crystalFrequency;
}
float AD9833::getCrystalFrequency()
{
// 268435456.0 == POW2TO28
return 268435456.0 / _crystalFreqFactor;
}
@ -335,7 +380,7 @@ void AD9833::writeFrequencyRegisterMSB(uint8_t channel, uint16_t MSB)
_control |= AD9833_HLB;
writeControlRegister(_control);
// send the least significant 14 bits
// send the most significant 14 bits
writeData(MSB);
}
@ -356,17 +401,40 @@ void AD9833::writeData(uint16_t data)
}
else
{
// SPI MODE2
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
uint8_t cbmask1 = _clockBit;
uint8_t cbmask2 = ~_clockBit;
uint8_t outmask1 = _dataOutBit;
uint8_t outmask2 = ~_dataOutBit;
// MSBFIRST
for (uint16_t mask = 0x8000; mask; mask >>= 1)
{
uint8_t oldSREG = SREG;
noInterrupts();
if (data & mask) *_dataOutRegister |= outmask1;
else *_dataOutRegister &= outmask2;
*_clockRegister &= cbmask2;
*_clockRegister |= cbmask1;
SREG = oldSREG;
}
#else // REFERENCE IMPLEMENTATION
// local variables is fast.
uint8_t clk = _clockPin;
uint8_t dao = _dataPin;
// MSBFIRST
for (uint16_t mask = 0x8000; mask; mask >>= 1)
{
digitalWrite(dao, (data & mask) !=0 ? HIGH : LOW);
digitalWrite(dao, (data & mask) != 0 ? HIGH : LOW);
digitalWrite(clk, LOW);
digitalWrite(clk, HIGH);
}
digitalWrite(dao, LOW);
#endif
}
if (_useSelect) digitalWrite(_selectPin, HIGH);
}
@ -384,25 +452,57 @@ void AD9833::writeData28(uint16_t LSB, uint16_t MSB)
}
else
{
// SPI MODE2
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
uint8_t cbmask1 = _clockBit;
uint8_t cbmask2 = ~_clockBit;
uint8_t outmask1 = _dataOutBit;
uint8_t outmask2 = ~_dataOutBit;
for (uint16_t mask = 0x8000; mask; mask >>= 1)
{
uint8_t oldSREG = SREG;
noInterrupts();
if (LSB & mask) *_dataOutRegister |= outmask1;
else *_dataOutRegister &= outmask2;
*_clockRegister &= cbmask2;
*_clockRegister |= cbmask1;
SREG = oldSREG;
}
for (uint16_t mask = 0x8000; mask; mask >>= 1)
{
uint8_t oldSREG = SREG;
noInterrupts();
if (MSB & mask) *_dataOutRegister |= outmask1;
else *_dataOutRegister &= outmask2;
*_clockRegister &= cbmask2;
*_clockRegister |= cbmask1;
SREG = oldSREG;
}
#else // REFERENCE IMPLEMENTATION
// local variables is fast.
uint8_t clk = _clockPin;
uint8_t dao = _dataPin;
// MSBFIRST
for (uint16_t mask = 0x8000; mask; mask >>= 1)
{
digitalWrite(dao, (LSB & mask) !=0 ? HIGH : LOW);
digitalWrite(dao, (LSB & mask) != 0 ? HIGH : LOW);
digitalWrite(clk, LOW);
digitalWrite(clk, HIGH);
}
for (uint16_t mask = 0x8000; mask; mask >>= 1)
{
digitalWrite(dao, (MSB & mask) !=0 ? HIGH : LOW);
digitalWrite(dao, (MSB & mask) != 0 ? HIGH : LOW);
digitalWrite(clk, LOW);
digitalWrite(clk, HIGH);
}
digitalWrite(dao, LOW);
#endif
}
if (_useSelect) digitalWrite(_selectPin, HIGH);
}

View File

@ -3,7 +3,8 @@
// FILE: AD9833.h
// AUTHOR: Rob Tillaart
// PURPOSE: Arduino library for AD9833 function generator.
// VERSION: 0.4.1
// DATE: 2023-08-25
// VERSION: 0.4.2
// URL: https://github.com/RobTillaart/AD9833
@ -11,7 +12,7 @@
#include "SPI.h"
#define AD9833_LIB_VERSION (F("0.4.1"))
#define AD9833_LIB_VERSION (F("0.4.2"))
#ifndef __SPI_CLASS__
@ -53,7 +54,7 @@ public:
void hardwareReset();
// mode = 0..3 (datasheet)
bool setPowerMode(uint8_t mode = 0);
uint8_t getPowerMode();
void setWave(uint8_t waveform = AD9833_OFF);
uint8_t getWave();
@ -72,6 +73,10 @@ public:
float getPhase(uint8_t channel = 0);
float getMaxPhase();
void setPhaseChannel(uint8_t channel);
// returns phase set (radians)
// [0 .. 2 PI>
float setPhaseRadians(float phase, uint8_t channel = 0);
float getPhaseRadians(uint8_t channel = 0);
// Hardware SPI settings
@ -107,8 +112,14 @@ private:
SPISettings _spi_settings;
// PINS
uint8_t _dataPin = 0;
uint8_t _clockPin = 0;
#if defined(ARDUINO_ARCH_AVR) || defined(ARDUINO_ARCH_MEGAAVR)
volatile uint8_t *_dataOutRegister;
uint8_t _dataOutBit;
volatile uint8_t *_clockRegister;
uint8_t _clockBit;
#endif
uint8_t _dataPin;
uint8_t _clockPin;
uint8_t _selectPin = 0;
bool _useSelect = false;
@ -119,6 +130,7 @@ private:
float _freq[2] = { 0, 0 }; // Hz
float _phase[2] = { 0, 0 }; // angle 0..360
// POW2TO28 / 25 MHz
float _crystalFreqFactor = 268435456.0 / 25000000.0;
};

View File

@ -6,6 +6,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).
## [0.4.2] - 2024-07-03
- optimize AVR SW SPI (about factor 3.4 faster)
- add rounding in **setPhase()** to minimize error.
- add **float setPhaseRadians(float phase, uint8_t channel = 0)**
- add **float getPhaseRadians(uint8_t channel = 0)**
- add **getPowerMode()** for completeness
- extend performance measurements (examples)
- minor edits
## [0.4.1] - 2024-05-24
- add support for ARDUINO_ARCH_MBED
- add sweep example

View File

@ -139,6 +139,7 @@ and sets the control register to B28 for the **setFrequency()**
Default is 0, wake up. So use ```setPowerMode(0)``` to wake up the device.
Returns false if mode is out of range.
Details see datasheet.
- **uint8_t getPowerMode()** returns current powerMode bits.
| powerMode | meaning |
|:-----------:|:------------------------------|
@ -190,12 +191,18 @@ Returns the phase set in degrees.
- **float getMaxPhase()** returns the maximum phase to set (convenience).
- **void setPhaseChannel(uint8_t channel)** select the active phase channel (0 or 1).
The library does not support get and set the phase in radians (yet).
Since 0.4.2 the library supports get and set the phase in radians.
- **float setPhaseRadians(float phase, uint8_t channel = 0)**
setPhase sets the phase and is limited to 0 - 2PI.
Returns the phase set in radians.
- **float getPhaseRadians(uint8_t channel = 0)** returns the phase set in radians.
#### Hardware SPI
To be used only if one needs a specific speed.
Has no effect when using SW SPI.
- **void setSPIspeed(uint32_t speed)** set SPI transfer rate.
- **uint32_t getSPIspeed()** returns SPI transfer rate.
@ -292,24 +299,31 @@ As this implementation is experimental, the interface might change in the future
#### Should
- evaluate the **setCrytalFrequency()** implementation / performance.
- runtime adjust-ability is preferred however...
- investigate external clock
- investigate timing (response time)
- change freq
- change channels etc.
- test on ESP32 (3V3)
#### Could
- **setFrequency()** if cache value equals new frequency?
- ```if (_freq[channel] == frequency) return frequency;```
- **setPhase()** if cache value equals new phase?
- ```if (_phase[channel] == phase) return phase;```
- make phase array 16 bit "register value"?
- **getFrequency()** calculate freq back from set register value?
- slower
- more accurate.
- separate function **getFrequencyFromRegister()** !
- **getPhase()** calculate phase back from set register value?
- idem.
- extend unit tests
- add examples
- for ESP32 HWSPI interface
- solve MAGIC numbers (defaults)
- extend performance measurements
- add defines AD9833_POWERMODE_xxxx ?
- investigate compatibility AD9834 a.o.
- add **setPhaseRadians(float radians, uint8_t channel)** wrapper.
- add **getPhaseRadians(uint8_t channel)** wrapper.
- need time / HW for this.
#### Wont

View File

@ -23,14 +23,14 @@ uint32_t start, stop;
void setup()
{
Serial.begin(115200);
while(!Serial);
while (!Serial);
Serial.println(__FILE__);
Serial.print("AD9833_LIB_VERSION: ");
Serial.println(AD9833_LIB_VERSION);
Serial.println();
delay(10);
SPI.begin();
// SPI.begin();
AD.begin();
@ -72,6 +72,20 @@ void setup()
Serial.print("writeFrequencyRegisterMSB:\t");
Serial.println(stop - start);
delay(10);
start = micros();
AD.setFrequencyChannel(0);
stop = micros();
Serial.print("setFrequencyChannel(0):\t");
Serial.println(stop - start);
delay(10);
start = micros();
AD.setPhaseChannel(0);
stop = micros();
Serial.print("setPhaseChannel(0):\t");
Serial.println(stop - start);
delay(10);
}

View File

@ -0,0 +1,23 @@
Board: UNO (16 Mhz)
IDE: 1.8.19
AD9833_performance.ino
AD9833_LIB_VERSION: 0.4.1
hardware: 1 (hardware SPI)
setFrequency: 72 (increased after 0.3.1 setCrystalFrequency() )
setPhase: 20
setWave: 20
writeFrequencyRegisterLSB: 40
writeFrequencyRegisterMSB: 40
AD9833_LIB_VERSION: 0.4.1
hardware: 0 (software SPI)
setFrequency: 660 (increased after 0.3.1 setCrystalFrequency() )
setPhase: 216
setWave: 216
writeFrequencyRegisterLSB: 436
writeFrequencyRegisterMSB: 428

View File

@ -0,0 +1,27 @@
Board: UNO (16 Mhz)
IDE: 1.8.19
AD9833_performance.ino
AD9833_LIB_VERSION: 0.4.2
hardware: 1 (hardware SPI)
setFrequency: 72 (increased after 0.3.1 setCrystalFrequency() )
setPhase: 20
setWave: 20
writeFrequencyRegisterLSB: 40
writeFrequencyRegisterMSB: 40
setFrequencyChannel(0): 20
setPhaseChannel(0): 20
AD9833_LIB_VERSION: 0.4.2 - AVR SWSPI REGISTER OPTIMIZED
hardware: 0 (software SPI)
setFrequency: 192
setPhase: 60
setWave: 60
writeFrequencyRegisterLSB: 116
writeFrequencyRegisterMSB: 116
setFrequencyChannel(0): 60
setPhaseChannel(0): 56

View File

@ -7,7 +7,10 @@
#include "AD9833.h"
AD9833 AD(10); // HW SPI, select pin 10
// AD9833 AD(10); // HW SPI, select pin 10
// comment SPI.begin if HW SPI pins are used for SW SPI
// AD9833 AD(10, 11, 13); // SW SPI, select, data , clock
AD9833 AD(5,6,7); // SW SPI, select, data , clock
int freq = 100;
bool up = true;
@ -22,6 +25,7 @@ void setup()
Serial.println(AD9833_LIB_VERSION);
Serial.println();
// comment next line if HWSPI pins are used for SW SPI
SPI.begin();
AD.begin();
AD.setWave(AD9833_SINE);
@ -37,7 +41,7 @@ void loop()
if (freq <= 100) up = true;
AD.setFrequency(freq);
delay(100); // to simulate other tasks
delay(10); // to simulate other tasks
}

View File

@ -15,7 +15,7 @@
"type": "git",
"url": "https://github.com/RobTillaart/AD9833.git"
},
"version": "0.4.1",
"version": "0.4.2",
"license": "MIT",
"frameworks": "*",
"platforms": "*",

View File

@ -1,5 +1,5 @@
name=AD9833
version=0.4.1
version=0.4.2
author=Rob Tillaart <rob.tillaart@gmail.com>
maintainer=Rob Tillaart <rob.tillaart@gmail.com>
sentence=Arduino library for AD9833 function generator. Supports hardware SPI and software SPI.