Last active
March 8, 2021 03:59
-
-
Save lorol/c0db4878ab84779868181d97e94a8921 to your computer and use it in GitHub Desktop.
DDS AD9833 Generator with ArduinoMenu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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