Skip to content

Instantly share code, notes, and snippets.

@lorol
Last active March 8, 2021 03:59
Show Gist options
  • Save lorol/c0db4878ab84779868181d97e94a8921 to your computer and use it in GitHub Desktop.
Save lorol/c0db4878ab84779868181d97e94a8921 to your computer and use it in GitHub Desktop.
DDS AD9833 Generator with ArduinoMenu
#include <Arduino.h>
/********************
DDS AD9833 Generator
https://github.com/neu-rah/ArduinoMenu
Uses:
https://github.com/Billwilliams1952/AD9833-Library-Arduino
menu on LiquidCrystal 2 x 16 (or 4 x 20) lines
output: PCF8574 I2C + Serial
input: (Serial +) ClickEncoder (+ keyboard)
MCU AVR MEGA 2560
Custom floatField by ferchinas
https://github.com/neu-rah/ArduinoMenu/pull/238
32bit float decimal resolution
33554432 2
16777216 1
8388608.0 0.5
4194304.0 0.25
2097152.0 0.125
1048576.0 0.0625
*/
#define EECH 0x00
#define EEBEGIN 0x02
#define EEMARK 0x7B
#define NO_OUT 0xFFFF
#include <EEPROM.h>
#include <TimerOne.h> //AVR timer
#include <Wire.h>
#include <LiquidCrystal_PCF8574.h>
#include <menu.h>
#include <menuIO/PCF8574Out.h>
#include <menuIO/clickEncoderIn.h>
///#include <menuIO/keyIn.h>
#include <menuIO/chainStream.h>
#include <menuIO/serialOut.h>
#include <menuIO/serialIn.h>
#include <AD9833.h>
#include <Button.h>
using namespace Menu;
//-----Custom floatField----------------
/*
Usage:
altFIELD(decimalslField,ee.herz,"Fine"," Hz",0,999.9,10,0.1,setFreq,enterEvent,noStyle)
OR dFIELD(ee.herz,"Fine"," Hz",0,999.9,10,0.1,setFreq,enterEvent,noStyle)
((decimalslField<typeof(ee.herz)>*)&mainMenu[4])->setDecimals(2); // if needed - change dynamiclaly.
OR
fFdec(mainMenu_element,decimals) same for double: dFdec
*/
#define fFdec(m,d) ((decimalslField<float>*)&mainMenu[(m)])->setDecimals((d))
#define dFdec(m,d) ((decimalslField<double>*)&mainMenu[(m)])->setDecimals((d)) //double
#define dFIELD(...) altFIELD(decimalslField,__VA_ARGS__)
#define DECIMALSFLIED_DEFAULT 1
template<typename T>
class decimalslField :public menuField<T> { //https://github.com/neu-rah/ArduinoMenu/blob/master/examples/customField/customField/customField.ino
private:
idx_t decimals;
public:
decimalslField(constMEM menuFieldShadow<T>& shadow) :menuField<T>(shadow) { decimals = DECIMALSFLIED_DEFAULT; }
decimalslField(
T &value,
constText* text,
constText*units,
T low,
T high,
T step,
T tune,
action a = doNothing,
eventMask e = noEvent,
styles s = noStyle
) :decimalslField(*new menuFieldShadow<T>(value, text, units, low, high, step, tune, a, e, s)) {}
Used printTo(navRoot &root, bool sel, menuOut& out, idx_t idx, idx_t len, idx_t panelNr = 0) override {// https://github.com/neu-rah/ArduinoMenu/issues/94#issuecomment-290936646
//menuFieldShadow<T>& s=*(menuFieldShadow<T>*)shadow;
menuField<T>::reflex = menuField<T>::target();
idx_t l = prompt::printTo(root, sel, out, idx, len);
bool ed = this == root.navFocus;
//bool sel=nav.sel==i;
if (l < len) {
out.print((root.navFocus == this&&sel) ? (menuField<T>::tunning ? '>' : ':') : ' ');
l++;
if (l < len) {
l += out.print(menuField<T>::reflex, decimals);//NOTE: this can exceed the limits!
if (l < len) {
l += print_P(out, fieldBase::units(), len);
}
}
}
return l;
}
void setDecimals(idx_t d) { decimals = d; }
idx_t getDecimals(void) { return(decimals); }
};
//-----Custom floatField----------------END
// rotary encoder pins
#define encA 22
#define encB 23
#define encBtn 24
//--------------- Create an AD9833 object ----------------
#define FNC_PIN 25
// Note, SCK PB1 52 and MOSI PB2 51 must be connected to CLK and DAT pins on the AD9833 for SPI
// ----- AD9833 ( FNCpin, referenceFrequency = 25000000UL )
AD9833 gen(FNC_PIN); // Defaults to 25MHz internal reference frequency
LiquidCrystal_PCF8574 lcd(0x27); // 20 (SDA), 21 (SCL)
Button sens = Button(40, BUTTON_PULLUP_INTERNAL);
struct EE_bl {
byte memid;
// unsigned int duration;
unsigned int out;
unsigned int kilo;
float herz;
char buf1[14];
};
EE_bl ee = {0, SINE_WAVE, 12345, 678.9, "12345678.9 Hz"};
byte mem = 0;
byte memch = 0;
//unsigned int cnt = 1000; //counter in mS
char* constMEM dx1 MEMMODE="01";
char* constMEM dx9 MEMMODE="0123456789";
char* constMEM dNr[] MEMMODE={dx1,dx9,dx9,dx9,dx9,dx9,dx9,dx9,".",dx9," ","H","z"}; ////char buf1[]= "12345678.9 Hz"
float freq; // 12345678.9; //atof(ee.buf1);
char str_temp[11];
SELECT(ee.out, outMenu, "Out", setOut, exitEvent, wrapStyle
, VALUE("Sine", SINE_WAVE, doNothing, noEvent)
, VALUE("Triangle", TRIANGLE_WAVE, doNothing, noEvent)
, VALUE("Square", SQUARE_WAVE, doNothing, noEvent)
, VALUE("Half Square", HALF_SQUARE_WAVE, doNothing, noEvent)
, VALUE("Off", NO_OUT, doNothing, noEvent)
);
SELECT(mem, memMenu,"Mem", confMem, exitEvent, wrapStyle
,VALUE("OK",0, doNothing, noEvent)
,VALUE("Load",1, readEE, enterEvent)
,VALUE("Save",2, writeEE, enterEvent)
);
MENU(mainMenu, "DDS", doNothing, noEvent, noStyle
, FIELD(memch,"Select Mem"," Ch",0,3,1,0,doNothing, noEvent,wrapStyle)
, SUBMENU(memMenu)
, SUBMENU(outMenu)
, EDIT("F",ee.buf1,dNr,mapFreq,exitEvent,noStyle)
, FIELD(ee.kilo,"Tune"," KHz",0,12499,100,1,setFreq,enterEvent,noStyle)
, dFIELD(ee.herz,"Fine"," Hz",0,999.9,10,0.1,setFreq,enterEvent,noStyle)
, OP("Debug",doAlert,enterEvent)
);
#define MAX_DEPTH 2
result setOut() {
if (ee.out == NO_OUT) gen.EnableOutput(false);
else{
gen.SetWaveform(REG0,ee.out);
gen.SetOutputSource(REG0);
gen.EnableOutput(true);
}
return proceed;
}
result doFreq() {
if (freq < 8388608.0) fFdec(5,1); //((decimalslField<typeof(ee.herz)>*)&mainMenu[5])->setDecimals(1);
else {
fFdec(5,0);
ee.buf1[8] = ' ';
ee.buf1[9] = ' ';
}
gen.SetFrequency(REG0,freq);
return proceed;
}
result setFreq() {
char i = 0;
freq =(ee.kilo*1000.00) + ee.herz;
dtostrf(freq,10,1, str_temp);
sprintf(ee.buf1,"%s Hz", str_temp);
for (i=0;i<8;i++){
if (ee.buf1[i] == ' ') ee.buf1[i] = '0';
}
doFreq();
return proceed;
}
result mapFreq() {
freq = atof(ee.buf1);
ee.kilo = freq / 1000;
ee.herz = freq - (ee.kilo*1000.0);
doFreq();
return proceed;
}
result writeEE() {
ee.memid = EEMARK;
EEPROM.put(EEBEGIN + memch*sizeof(ee), ee);
EEPROM.put(EECH, memch);
return proceed;
}
result readEE() {
byte ChkEE;
EEPROM.get(EEBEGIN + memch*sizeof(ee), ChkEE);
if (ChkEE == EEMARK){
EEPROM.get(EEBEGIN + memch*sizeof(ee), ee);
setFreq();
setOut();
}
return proceed;
}
result confMem(){
mem = 0;
return proceed;
}
ClickEncoder clickEncoder(encA, encB, encBtn, 4);
ClickEncoderStream encStream(clickEncoder, 1);
//AVR timer
void timerIsr() {
clickEncoder.service();
// if (cnt > 0) cnt--;
}
//a keyboard with only one key as the encoder button //#define WITHOUT_BUTTON 1 // not yet implemented - see ClickEncoder
///keyMap encBtn_map[] = {{ -encBtn, defaultNavCodes[enterCmd].ch}}; //negative pin numbers use internal pull-up, this is on when low
///keyIn<1> encButton(encBtn_map);//1 is the number of keys
//menuIn* inputsList[]={&encButton,&Serial};
//chainStream<2> in(inputsList);//1 is the number of inputs MENU_INPUTS replaces these 2 lines
serialIn serial(Serial);
//-------------------------------------------------------
MENU_INPUTS(in, &encStream
, &serial
);///, &encButton);
MENU_OUTPUTS(out, MAX_DEPTH
, LCD_OUT(lcd,{0,0,16,2})
, SERIAL_OUT(Serial)
);
NAVROOT(nav, mainMenu, MAX_DEPTH, in, out);
//--------------------------------------------------------
result alert(menuOut& o,idleEvent e) {
if (e==idling) {
o.setCursor(0,0);
o.print("O:");
o.print(ee.out, HEX);
o.setCursor(0,1);
o.print("F:");
o.print(gen.GetActualProgrammedFrequency(REG0),1);
//o.print(freq,1);
}
return proceed;
}
result doAlert(eventMask e, prompt &item) {
nav.idleOn(alert);
return proceed;
}
void setup() {
Serial.begin(115200);
while (!Serial);
Serial.println("AD9833 DDS Signal Generator");
Serial.flush();
pinMode(LED_BUILTIN, OUTPUT); //if needed for debug (LED on pin 13)
digitalWrite(LED_BUILTIN, LOW);
gen.Begin(); // The loaded defaults are 1000 Hz SINE_WAVE using REG0
gen.EnableOutput(false); // Turn OFF the output
EEPROM.get(EECH, memch);
readEE();
setFreq();
setOut();
Timer1.initialize(1000);
Timer1.attachInterrupt(timerIsr);
clickEncoder.setAccelerationEnabled(true);
lcd.begin(16,2);
lcd.setBacklight(255);
lcd.setCursor(0, 0);
lcd.print("Signal Generator");
lcd.setCursor(0, 1);
lcd.print(" DDS AD9833 ");
delay(3000);
Serial.println("Setup done.");
Serial.flush();
nav.showTitle=false;
}
void loop() {
if(sens.isPressed()) digitalWrite(LED_BUILTIN, LOW);
else digitalWrite(LED_BUILTIN, HIGH);
//nav.poll();
nav.doInput();
if (nav.changed(0)) nav.doOutput();
delay(10); //10ms for other things
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment