#include <Arduino.h>
#include <math.h>
#include "driver/dac.h"
#define DAC1_PIN 25
#define DAC2_PIN 26
#define SAMPLE_RATE 40000
#define TABLE_SIZE 256
#define TIMER_DIVIDER 80
#define TIMER_INTERVAL_US (1000000 / SAMPLE_RATE)
// Signal channel structure
typedef struct {
uint8_t dacPin;
volatile uint32_t phaseAcc; // 32-bit phase accumulator
volatile uint32_t phaseStep; // Frequency control
volatile bool enabled; // Channel enable/disable
uint8_t waveTableA[TABLE_SIZE]; // Primary waveform table
uint8_t waveTableB[TABLE_SIZE]; // Secondary table (double buffering)
volatile uint8_t* volatile activeWaveTable; // Current active table
hw_timer_t* timer; // Dedicated hardware timer
portMUX_TYPE mux; // Critical section mutex
} SignalChannel;
// Initialize two independent channels
SignalChannel ch1 = { DAC1_PIN, 0, 0, false, {}, {}, nullptr, nullptr, portMUX_INITIALIZER_UNLOCKED };
SignalChannel ch2 = { DAC2_PIN, 0, 0, false, {}, {}, nullptr, nullptr, portMUX_INITIALIZER_UNLOCKED };
// Generate waveform into lookup table
void generateWaveToTable(uint8_t* table, const char* type, float amplitude, float offset) {
amplitude = constrain(amplitude, 0, 255);
offset = constrain(offset, 0, 255);
for (int i = 0; i < TABLE_SIZE; i++) {
float val = 0;
float angle = (2.0 * PI * i) / TABLE_SIZE;
if (strcmp(type, "SINE") == 0) val = (sin(angle) + 1) * 0.5;
else if (strcmp(type, "SQUARE") == 0) val = (i < TABLE_SIZE/2) ? 1.0 : 0.0;
else if (strcmp(type, "TRIANGLE") == 0) val = (i < TABLE_SIZE/2) ? (2.0*i/TABLE_SIZE) : (2.0 - 2.0*i/TABLE_SIZE);
else if (strcmp(type, "SAW") == 0) val = (float)i / TABLE_SIZE;
else if (strcmp(type, "DC") == 0) val = 0;
table[i] = (uint8_t)constrain((val * amplitude) + offset, 0, 255);
}
}
// Update frequency by setting phase step
void updateFrequency(SignalChannel* ch, float freq) {
portENTER_CRITICAL(&ch->mux);
ch->phaseStep = (uint32_t)((freq * TABLE_SIZE * 65536.0) / SAMPLE_RATE);
portEXIT_CRITICAL(&ch->mux);
}
// Timer ISR for Channel 1
void IRAM_ATTR onTimerCh1() {
if (!ch1.enabled) return;
ch1.phaseAcc += ch1.phaseStep;
uint8_t idx = ch1.phaseAcc >> 16; // Extract upper 8 bits for table index
dac_output_voltage(DAC_CHANNEL_1, ch1.activeWaveTable[idx]);
}
// Timer ISR for Channel 2
void IRAM_ATTR onTimerCh2() {
if (!ch2.enabled) return;
ch2.phaseAcc += ch2.phaseStep;
uint8_t idx = ch2.phaseAcc >> 16;
dac_output_voltage(DAC_CHANNEL_2, ch2.activeWaveTable[idx]);
}
// Initialize signal channel
void initChannel(SignalChannel* ch, int timerID, void (*isr)()) {
generateWaveToTable(ch->waveTableA, "SINE", 255, 0);
ch->activeWaveTable = ch->waveTableA;
ch->timer = timerBegin(timerID, TIMER_DIVIDER, true);
timerAttachInterrupt(ch->timer, isr, true);
timerAlarmWrite(ch->timer, TIMER_INTERVAL_US, true);
timerAlarmEnable(ch->timer);
}
// Process command from serial port
// Format: CH1 SINE 1000 1 200 50
// | | | | | |
// | | | | | +-- DC offset (0-255)
// | | | | +------ Amplitude (0-255)
// | | | +-------- Enable (0=off, 1=on)
// | | +------------- Frequency (Hz)
// | +------------------ Waveform type
// +---------------------- Channel number
void processCommand(String cmd) {
int channel;
char wave[10];
float freq;
int enable;
float amplitude;
float offset;
if (sscanf(cmd.c_str(), "CH%d %s %f %d %f %f", &channel, wave, &freq, &enable, &litude, &offset) != 6)
return;
SignalChannel* ch = (channel == 1) ? &ch1 : &ch2;
uint8_t* newTable = (ch->activeWaveTable == ch->waveTableA) ? ch->waveTableB : ch->waveTableA;
generateWaveToTable(newTable, wave, amplitude, offset);
portENTER_CRITICAL(&ch->mux);
ch->enabled = false;
ch->phaseAcc = 0;
ch->phaseStep = (uint32_t)((freq * TABLE_SIZE * 65536.0) / SAMPLE_RATE);
ch->activeWaveTable = newTable;
ch->enabled = (bool)enable;
portEXIT_CRITICAL(&ch->mux);
}
void setup() {
Serial.begin(115200);
delay(1000);
updateFrequency(&ch1, 1000);
updateFrequency(&ch2, 1000);
initChannel(&ch1, 0, onTimerCh1);
initChannel(&ch2, 2, onTimerCh2);
dac_output_enable(DAC_CHANNEL_1);
dac_output_enable(DAC_CHANNEL_2);
Serial.println("Generador listo - CH1 y CH2 totalmente independientes");
}
void loop() {
if (Serial.available()) {
String cmd = Serial.readStringUntil('\n');
cmd.trim();
if (cmd.length() == 0) return;
processCommand(cmd);
Serial.println("OK");
}
}