/*
  Waveform Generator with Arduino and R-2R Ladder DAC

  This Arduino sketch generates four types of waveforms (sine, triangle, square, sawtooth) using pulse width modulation (PWM) and outputs them through an R-2R ladder digital-to-analog converter (DAC) connected to pins 2 to 9. The user can select the waveform type ('s' for sine, 't' for triangle, 'q' for square, 'h' for sawtooth) and set the frequency in Hz through the serial monitor.

  Circuit:
  - Connect pins 2 to 9 of Arduino to the inputs of the R-2R ladder DAC
  - Connect the output of the R-2R ladder DAC to an analog output device (e.g., speaker or oscilloscope)

  Author: Ioannis G. Intzes
  Date: 28/05/2024
*/

#include <TimerOne.h>
#include <math.h>

// Define the default interrupt frequency
unsigned long interruptFrequency = 1000; // Default to 1 kHz (1 ms interval)
#define POINTS 256 // number of points in a waveform cycle

volatile uint8_t waveIndex = 0;
volatile char waveformType = 's'; // Default to sine wave
int amplitudeLevel = 127; // Default amplitude level (max)
int dcOffsetLevel = 127; // Default DC offset level

// Sine wave lookup table
const uint8_t sineWave[POINTS] = {128, 131, 134, 137, 140, 144, 147, 150, 153, 
  156, 159, 162, 165, 168, 171, 174, 177, 179, 182, 185, 188, 191, 193, 196, 
  199, 201, 204, 206, 209, 211, 213, 216, 218, 220, 222, 224, 226, 228, 230, 
  232, 234, 235, 237, 239, 240, 241, 243, 244, 245, 246, 248, 249, 250, 250, 
  251, 252, 253, 253, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 254, 
  254, 254, 253, 253, 252, 251, 250, 250, 249, 248, 246, 245, 244, 243, 241, 
  240, 239, 237, 235, 234, 232, 230, 228, 226, 224, 222, 220, 218, 216, 213, 
  211, 209, 206, 204, 201, 199, 196, 193, 191, 188, 185, 182, 179, 177, 174, 
  171, 168, 165, 162, 159, 156, 153, 150, 147, 144, 140, 137, 134, 131, 128, 
  125, 122, 119, 116, 112, 109, 106, 103, 100, 97, 94, 91, 88, 85, 82, 79, 77, 
  74, 71, 68, 65, 63, 60, 57, 55, 52, 50, 47, 45, 43, 40, 38, 36, 34, 32, 30, 
  28, 26, 24, 22, 21, 19, 17, 16, 15, 13, 12, 11, 10, 8, 7, 6, 6, 5, 4, 3, 3, 
  2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 6, 6, 7, 8, 10, 11, 12, 
  13, 15, 16, 17, 19, 21, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 43, 45, 47, 
  50, 52, 55, 57, 60, 63, 65, 68, 71, 74, 77, 79, 82, 85, 88, 91, 94, 97, 100, 
  103, 106, 109, 112, 116, 119, 122, 125
};

const uint8_t ECG[POINTS] =
{
  73, 74, 75, 75, 74, 73, 73, 73, 73, 72, 71, 69, 68, 67, 67, 67, 68, 68, 67,
  65, 62, 61, 59, 57, 56, 55, 55, 54, 54, 54, 55, 55, 55, 55, 55, 55, 54, 53,
  51, 50, 49, 49, 52, 61, 77, 101, 132, 169, 207, 238, 255, 254, 234, 198,
  154, 109, 68, 37, 17, 5, 0, 1, 6, 13, 20, 28, 36, 45, 52, 57, 61, 64, 65,
  66, 67, 68, 68, 69, 70, 71, 71, 71, 71, 71, 71, 71, 71, 72, 72, 72, 73, 73,
  74, 75, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 86, 88, 91, 93, 96, 98, 100,
  102, 104, 107, 109, 112, 115, 118, 121, 123, 125, 126, 127, 127, 127, 127,
  127, 126, 125, 124, 121, 119, 116, 113, 109, 105, 102, 98, 95, 92, 89, 87,
  84, 81, 79, 77, 76, 75, 74, 73, 72, 70, 69, 68, 67, 67, 67, 68, 68, 68, 69,
  69, 69, 69, 69, 69, 69, 70, 71, 72, 73, 73, 74, 74, 75, 75, 75, 75, 75, 75,
  74, 74, 73, 73, 73, 73, 72, 72, 72, 71, 71, 71, 71, 71, 71, 71, 70, 70, 70,
  69, 69, 69, 69, 69, 70, 70, 70, 69, 68, 68, 67, 67, 67, 67, 66, 66, 66, 65,
  65, 65, 65, 65, 65, 65, 65, 64, 64, 63, 63, 64, 64, 65, 65, 65, 65, 65, 65,
  65, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 66, 67, 68, 69, 71, 72, 73
};

void setup() {
  Serial.begin(921000); // Start the serial communication at 9600 baud rate
  Serial.println("Enter new waveform type (s, t, q, h, n, e) followed by frequency in Hz:");

  // Initialize Timer1
  Timer1.initialize(1000000 / interruptFrequency); // Set timer interval
  Timer1.attachInterrupt(timerISR); // Attach the interrupt service routine (ISR)
  
  // Set pins 2 to 9 as outputs for the R-2R ladder
  for (int pin = 2; pin <= 9; pin++) {
    pinMode(pin, OUTPUT);
  }
}

void loop() {
  // Check if there is serial input available
  
}

// Function to update the amplitude level
void updateAmplitudeLevel(int level) {
  amplitudeLevel = level;
}

void updateDcOffsetLevel(int level) {
  dcOffsetLevel = level;
}

// Function to generate sine waveform with adjustable amplitude and DC offset
uint8_t generateSineWave() {
  return map(sineWave[waveIndex], 0, 255, dcOffsetLevel, amplitudeLevel + dcOffsetLevel);
}

// Function to generate sine waveform with adjustable amplitude and DC offset
uint8_t generateECG() {
  return map(ECG[waveIndex], 0, 255, dcOffsetLevel, amplitudeLevel + dcOffsetLevel);
}

// Function to generate triangle waveform with adjustable amplitude and DC offset
uint8_t generateTriangleWave() {
  int value = map(waveIndex, 0, POINTS / 2, dcOffsetLevel, amplitudeLevel + dcOffsetLevel); // Increasing ramp
  if (waveIndex >= POINTS / 2) {
    value = map(waveIndex, POINTS / 2, POINTS, amplitudeLevel + dcOffsetLevel, dcOffsetLevel); // Decreasing ramp
  }
  return value;
}

// Function to generate sawtooth waveform with adjustable amplitude and DC offset
uint8_t generateSawtoothWave() {
  return map(waveIndex, 0, POINTS, dcOffsetLevel, amplitudeLevel + dcOffsetLevel); // Increasing ramp
}

// Function to generate square waveform with adjustable amplitude and DC offset
uint8_t generateSquareWave() {
  if (waveIndex < POINTS / 2) {
    return dcOffsetLevel;
  } else {
    return amplitudeLevel + dcOffsetLevel;
  }
}

// Function to generate noise waveform with adjustable amplitude and DC offset
uint8_t generateNoiseWave() {
  return map(random(0, 256), 0, 255, dcOffsetLevel, amplitudeLevel + dcOffsetLevel);
}

// Interrupt Service Routine (ISR)
void timerISR() {
  // Output waveform value based on waveformType
  switch (waveformType) {
    case 's':
      for (int i = 0; i < 8; i++) {
        digitalWrite(9 - i, bitRead(generateSineWave(), i)); // Sine wave
        //Serial.println(generateSineWave());
      }
      break;
    case 't':
      for (int i = 0; i < 8; i++) {
        digitalWrite(9 - i, bitRead(generateTriangleWave(), i)); // Triangle wave
        //Serial.println(generateTriangleWave());
      }
      break;
    case 'q':
      for (int i = 0; i < 8; i++) {
        digitalWrite(9 - i, bitRead(generateSquareWave(), i)); // Square wave
        //Serial.println(generateSquareWave());
      }
      break;
    case 'h':
      for (int i = 0; i < 8; i++) {
        digitalWrite(9 - i, bitRead(generateSawtoothWave(), i)); // Sawtooth wave
        //Serial.println(generateSawtoothWave());
      }
      break;
    case 'n':
      for (int i = 0; i < 8; i++) {
        digitalWrite(9 - i, bitRead(generateNoiseWave(), i)); // Noise wave
        //Serial.println(generateNoiseWave());
      }
      break;
    case 'e':
      for (int i = 0; i < 8; i++) {
        digitalWrite(9 - i, bitRead(generateECG(), i)); // Sine wave
        //Serial.println(generateECG());
      }
      break;
    default:
      break;
  }
  
  // Move to the next value in the waveform array
  waveIndex = (waveIndex + 1) % POINTS;
}

// Serial event handler
void serialEvent() {
  while (Serial.available()) {
    // Read the command
    String command = Serial.readStringUntil('\n');
    // Parse the command
    if (command.startsWith("a")) {
      // Extract amplitude level
      int newAmplitude = command.substring(1).toInt();
      if (newAmplitude >= 0 && newAmplitude <= 255) {
        updateAmplitudeLevel(newAmplitude);
        Serial.print("Updated amplitude level to: ");
        Serial.println(newAmplitude);
      } else {
        Serial.println("Invalid amplitude level. Please enter a value between 0 and 255.");
      }
    } else if (command.startsWith("d")) {
      // Extract DC offset level
      int newDcOffset = command.substring(1).toInt();
      updateDcOffsetLevel(newDcOffset);
      Serial.print("Updated DC offset level to: ");
      Serial.println(newDcOffset);
    } else if (command.charAt(0) == 'f') {
      // Start frequency sweep
      // Implement frequency sweep functionality here
    } else if (command.length() >= 2) {
      waveformType = command.charAt(0);
      interruptFrequency = command.substring(1).toInt();
      // Update Timer1 interval
      Timer1.setPeriod(1000000 / (POINTS * interruptFrequency));
      Serial.print("Updated waveform type to: ");
      Serial.println(waveformType);
      Serial.print("Updated frequency to: ");
      Serial.print(interruptFrequency);
      Serial.println(" Hz");
    } else {
      Serial.println("Invalid command.");
    }
  }
}
D0D1D2D3D4D5D6D7GNDLOGIC