I'd written something not to far (without the Sine Wave stuff) that will work with an I2C LCD, a rotary encoder and switch and the PWM Frequency library.
rotating the know increases or decreases the frequency and clicking on the knob will switch the steps between 1,10,100, 1k, 10k, 100k, display the value you set on the 1st line of the LCD and the actual frequency the UNO achieves on the second line.
I've just tested it again after recompiling with the current IDE and checked on my oscilloscope and it seems to work fine all the way to 2MHz.
here is the code:
// -----------------------------------------
// written for Arduino UNO, should work on a Mega is you switch the frenquencyPin to 11
// Leveraging multiple libraries (see links below)
// Author: J-M-L for Arduino Forum (https://forum.arduino.cc/index.php?action=profile;u=438300)
// originally posted at https://forum.arduino.cc/index.php?topic=117425.msg4309683#msg4309683
//
// Pin 0 & 1 : left unused for Serial
// Pin 2 to rotary encoder DT (will use interrupts)
// Pin 3 to rotary encoder CLK (will use interrupts)
// Pin 4 to rotary encoder SW
// Pin 5
// Pin 6
// Pin 7
// Pin 8
// Pin 9 The Pin exhibing the frequency
// Pin 10
// Pin 11
// Pin 12
// Pin 13
// Pin A0
// Pin A1
// Pin A2
// Pin A3
// Pin A4 to I2C SDA (LCD)
// Pin A5 to I2C SCL (LCD)
// -----------------------------------------
#include <PWM.h> // https://github.com/terryjmyers/PWM (https://forum.arduino.cc/index.php?topic=117425.msg883455#msg883455)
// As frequency increases, resolution will decrease and you won't get the exact match
const long MinFrequency = 0L; // 0 Hz
const long MaxFrequency = 2000000L; // 2 MHz
const byte frenquencyPin = 9;
#include <LiquidCrystal_I2C.h> // https://github.com/fdebrabander/Arduino-LiquidCrystal-I2C-library
LiquidCrystal_I2C lcd(0x27, 16, 2); // 16 cols, 2 lignes. I2C address is 0x27. connect LDC's SDA on A4, SCL on A5
#include <Encoder.h> // https://www.pjrc.com/teensy/td_libs_Encoder.html
Encoder rotaryEncoder(2, 3); // connect pin D2 to Encoder's DT & D3 to Encoder's CLK)
#include <simpleBouton.h> // http://forum.arduino.cc/index.php?topic=375232.0 @bricoleau efficient button's library.
simpleBouton encoderSwitch(4);// connect pin D4 to Encoder's SW
long currentFrequency;
long currentRotaryValue;
enum : long {x1 = 1L, x10 = 10L, x100 = 100L, x1000 = 1000L, x10000 = 10000L, x100000 = 100000L} stepMultiplier = x1;
void displayFrequencyTarget(long f)
{
char freqMessage[20];
dtostrf(f, 7, 0, freqMessage); // right aligned, white space padding 7 positions to accomodate from 0 to 2000000
strcat(freqMessage, "Hz");
Serial.println(freqMessage);
lcd.setCursor(0, 0);
lcd.print(freqMessage);
}
void displayActualFrequency()
{
char freqMessage[20];
dtostrf(Timer1_GetFrequency(), 7, 0, freqMessage); // right aligned, white space padding 7 positions to accomodate from 0Hz to 2000000Hz
strcat(freqMessage, "Hz");
Serial.println(freqMessage);
lcd.setCursor(0, 1);
lcd.print(freqMessage);
}
void displayStepMultiplier()
{
char multMessage[20];
switch (stepMultiplier) {
case x1: strcpy(multMessage, " 1"); break;
case x10: strcpy(multMessage, " 10"); break;
case x100: strcpy(multMessage, " 100"); break;
case x1000: strcpy(multMessage, " 1k"); break;
case x10000: strcpy(multMessage, " 10k"); break;
case x100000: strcpy(multMessage, "100k"); break;
}
Serial.println(multMessage);
lcd.setCursor(12, 0);
lcd.print(multMessage);
}
void setup()
{
Serial.begin(115200); // initialize Serial connection
lcd.begin(); // initialize the LCD
lcd.backlight();
currentRotaryValue = 0;// initialize the rotary encoder
rotaryEncoder.write(currentRotaryValue);
InitTimersSafe(); //initialize all timers except for 0, to save time keeping functions
currentFrequency = 0L;
displayFrequencyTarget(currentFrequency);
displayStepMultiplier();
pwmWriteHR(frenquencyPin, 32768u);
SetPinFrequency(frenquencyPin, currentFrequency); //setting the frequency
}
void loop()
{
// modify step factor
if (encoderSwitch) {
switch (stepMultiplier) {
case x1: stepMultiplier = x10; break;
case x10: stepMultiplier = x100; break;
case x100: stepMultiplier = x1000; break;
case x1000: stepMultiplier = x10000; break;
case x10000: stepMultiplier = x100000; break;
case x100000: stepMultiplier = x1; break;
}
displayStepMultiplier();
}
long newRotaryValue = rotaryEncoder.read() >> 1; // my rotary delivers 2 ticks per step, others delivers 4 in which case do >>2
if (newRotaryValue != currentRotaryValue) { // we turned the knob
long newFrequency = currentFrequency + (newRotaryValue - currentRotaryValue) * stepMultiplier;
currentRotaryValue = newRotaryValue;
newFrequency = constrain(newFrequency, MinFrequency, MaxFrequency);
if (newFrequency != currentFrequency) {
currentFrequency = newFrequency;
displayFrequencyTarget(currentFrequency);
pwmWriteHR(frenquencyPin, 32768u);
if (SetPinFrequencySafe(frenquencyPin, currentFrequency)) displayActualFrequency();
}
}
}
Note that the library I use for the rotary encoder connected to those pins will switch to interrupt based tick capture, so you can't use those pins for the sine wave of the example. The library works though with only 1 pin on interrupt (or none) so you could free up pin 2 to generate the the Sine wave.
of course issuing ISR at 2MHz would put quite a toll on your UNO and so you should not go that far then.