/*
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.");
}
}
}