/*--------------------------------------------------------------------- * AD9833 Function Generator * By John Bradnam * based on: * DIY Function/Waveform Generator by GreatScottLab * (https://www.instructables.com/DIY-FunctionWaveform-Generator/) * * 230114 - Rewrote code */ #include #include #include "AD9833.h" #include "Rotary.h" //Pins #define ROTARY_A 2 #define ROTARY_B 3 #define SWITCH A3 #define led 12 #define AD9833_FSYNC 10 #define AD9833_SCLK 13 #define AD9833_SDATA 11 //Modules AD9833 sigGen(AD9833_FSYNC, 24000000);// Initialise our AD9833 with FSYNC and a master clock frequency of 24MHz LiquidCrystal_I2C lcd(0x27, 16, 2); Rotary encoder(ROTARY_A, ROTARY_B); // Initialise the encoder on pins 2 and 3 (interrupt pins) enum MenuEnum { X1, X10, X100, X1K, X10K, X100K, X1M, WAVEFORM }; enum WaveEnum { SINE, TRANGLE, SQUARE }; #define MENU_TEXT_LEN 10 const String menuText[] = {"x 1 Hz", "x 10 Hz", "x 100 Hz","x 1 kHz", "x 10 kHz", "x 100 kHz", "x 1 MHz", "Waveform"}; const String waveText[] = {" Sinus", "Triang", "Square"}; //EEPROM handling //Uncomment next line to clear out EEPROM and reset //#define RESET_EEPROM #define EEPROM_ADDRESS 0 #define EEPROM_MAGIC 0x0BAD0DAD typedef struct { uint32_t magic; MenuEnum menuState; WaveEnum waveState; long frequency; } EEPROM_DATA; EEPROM_DATA EepromData; //Current EEPROM settings #define EEPROM_UPDATE_TIME 60000 //Check for eprom update every minute unsigned long eepromTimeout = 0; bool eepromUpdate = false; #define FREQ_MAX 5000000 volatile bool updateDisplay = false; //-------------------------------------------------------------------- //Setup hardware void setup() { lcd.init(); lcd.backlight(); digitalWrite(led,LOW); //Serial.begin(57600); //Serial.println("Starting..."); //Get last settings readEepromData(); // Initialise the LCD, start the backlight and print a "bootup" message for two seconds lcd.begin(16, 2); lcd.print(" === AD9833 === "); lcd.setCursor(0, 1); lcd.print("Signal Generator"); delay(2000); // Display initial set values lcd.clear(); displayFrequency(); displayMenu(); displayWaveform(); // Initialise the AD9833 with 1KHz sine output, no phase shift for both // registers and remain on the FREQ0 register // sigGen.lcdDebugInit(&lcd); sigGen.reset(1); sigGen.setFreq(EepromData.frequency); sigGen.setPhase(0); sigGen.setFPRegister(1); sigGen.setFreq(EepromData.frequency); sigGen.setPhase(0); sigGen.setFPRegister(0); sigGen.mode((int)EepromData.waveState); sigGen.reset(0); // Set pins A and B from encoder as interrupts attachInterrupt(digitalPinToInterrupt(ROTARY_A), encChange, CHANGE); attachInterrupt(digitalPinToInterrupt(ROTARY_B), encChange, CHANGE); // Initialise pin as input with pull-up enabled and debounce variable for // encoder button pinMode(SWITCH, INPUT_PULLUP); // Set Cursor to initial possition lcd.setCursor(0, 1); eepromTimeout = millis() + EEPROM_UPDATE_TIME; } //-------------------------------------------------------------------- //Main loop void loop() { // If button is pressed, change the menu if (testButton(true)) { EepromData.menuState = (EepromData.menuState == WAVEFORM) ? X1 : (MenuEnum)((int)EepromData.menuState + 1); eepromUpdate = true; updateDisplay = true; } // Update display if needed if (updateDisplay) { displayFrequency(); displayMenu(); displayWaveform(); updateDisplay = false; } //Update EEPROM if settings changed if (millis() > eepromTimeout) { if (eepromUpdate) { writeEepromData(); eepromUpdate = false; } eepromTimeout = millis() + EEPROM_UPDATE_TIME; } } //-------------------------------------------------------------------- // Test if button has been pressed // waitForRelease - True to wait until button gos up // Returns true if button is pressed bool testButton(bool waitForRelease) { bool pressed = false; if (digitalRead(SWITCH) == LOW) { pressed = true; while (waitForRelease && digitalRead(SWITCH) == LOW) { yield(); } } return pressed; } //-------------------------------------------------------------------- // Encoder has been rotated // Change frequency or waveform based on menu selection void encChange() { unsigned char state = encoder.process(); if (state != DIR_NONE) { switch (EepromData.menuState) { case X1: updateFrequency(state,1); break; case X10: updateFrequency(state,10); break; case X100: updateFrequency(state,100); break; case X1K: updateFrequency(state,1000); break; case X10K: updateFrequency(state,10000); break; case X100K: updateFrequency(state,100000); break; case X1M: updateFrequency(state,1000000); break; case WAVEFORM: updateWaveform(state); break; } } } //-------------------------------------------------------------------- //Change the current frequency based on menu and stepValue // state - Either DIR_CW or DIR_CCW // stepValue - Current amount to change frequency by void updateFrequency(unsigned char state, long stepValue) { bool update = false; long old = EepromData.frequency; if (state == DIR_CW) { EepromData.frequency = min(EepromData.frequency + stepValue,FREQ_MAX); } else { EepromData.frequency = max(EepromData.frequency - stepValue,0); } if (old != EepromData.frequency) { sigGen.setFreq(EepromData.frequency); eepromUpdate = true; updateDisplay = true; } } //-------------------------------------------------------------------- //Change the current waveform // state - Either DIR_CW or DIR_CCW void updateWaveform(unsigned char state) { if (state == DIR_CW) { EepromData.waveState = (EepromData.waveState == SQUARE) ? SINE : (WaveEnum)((int)EepromData.waveState + 1); } else { EepromData.waveState = (EepromData.waveState == SINE) ? SQUARE : (WaveEnum)((int)EepromData.waveState - 1); } sigGen.mode((int)EepromData.waveState); eepromUpdate = true; updateDisplay = true; } //-------------------------------------------------------------------- //Display current frequency void displayFrequency() { lcd.setCursor(0, 0); lcd.print(formatNumber(EepromData.frequency,"F: "," Hz",16)); } //-------------------------------------------------------------------- //Display active menu state void displayMenu() { lcd.setCursor(0, 1); lcd.print(padString(menuText[(int)EepromData.menuState],MENU_TEXT_LEN)); } //-------------------------------------------------------------------- //Display active waveform void displayWaveform() { lcd.setCursor(10, 1); lcd.print(waveText[(int)EepromData.waveState]); digitalWrite(led,HIGH); } //----------------------------------------------------------------------------------- //Display a number with comma seperators // number - number to format // prefix - String to prefix number with // postfix - String to append to number // pad - Add spaces to the right pad string // returns String with formatted number String formatNumber(long number, String prefix, String postfix, int pad) { String s = ""; bool space = true; for (uint8_t i = 0; i < 8; i++) { if ((i==3 || i==6) && !space && number > 0) { s = String(',') + s; } if (number > 0 || i == 0) { s = String((char)((number % 10) + 48)) + s; space = false; } else { space = true; } number = number / 10; } s = prefix + s + postfix; return padString(s, pad); } //----------------------------------------------------------------------------------- //Pad string with spaces // s - string to pad // prefix - String to prefix number with // returns padded String String padString(String s, int pad) { String sOut = String(s); int len = pad - s.length(); for (uint8_t i = 0; i < len; i++) { sOut += ' '; } return sOut; } //-------------------------------------------------------------------- //Write the EepromData structure to EEPROM void writeEepromData(void) { //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change. EEPROM.put(EEPROM_ADDRESS,EepromData); } //-------------------------------------------------------------------- //Read the EepromData structure from EEPROM, initialise if necessary void readEepromData(void) { #ifndef RESET_EEPROM EEPROM.get(EEPROM_ADDRESS,EepromData); if (EepromData.magic != EEPROM_MAGIC) { #endif EepromData.magic = EEPROM_MAGIC; EepromData.menuState = WAVEFORM; EepromData.waveState = SINE; EepromData.frequency = 1000; writeEepromData(); #ifndef RESET_EEPROM } #endif }