The goal of this project is to generate three square wave signals with frequencies adjustable from 5 Hz to 1000 Hz, controlled by a potentiometer, on an Arduino Zero (SAMD21). The three signals will have phase shifts of 0°, 120°, and 240°, and their duty cycles will be modulated using a D flip-flop.
#include <Arduino.h>
// Pins for square wave outputs
#define WAVE_PIN1 9 // First wave
#define WAVE_PIN2 10 // Second wave
#define WAVE_PIN3 11 // Third wave
#define POT_PIN A0 // Potentiometer input
// Timer and frequency definitions
#define MIN_FREQ 5 // Minimum frequency in Hz
#define MAX_FREQ 1000 // Maximum frequency in Hz
#define TIMER_CLOCK 48000000UL // SAMD21 clock frequency
// Filtering settings
#define FILTER_DEPTH 10
int filterBuffer[FILTER_DEPTH];
int filterIndex = 0;
int filterSum = 0;
volatile uint32_t wavePhase[3] = {0, 0, 0}; // Phase values for each wave
volatile uint32_t periodTicks = 0; // Timer period in ticks
void setup() {
// Configure wave output pins
pinMode(WAVE_PIN1, OUTPUT);
pinMode(WAVE_PIN2, OUTPUT);
pinMode(WAVE_PIN3, OUTPUT);
// Configure analog input for potentiometer
analogReadResolution(12); // 12-bit resolution
analogReference(AR_DEFAULT);
// Set up TC4/TC5 for 32-bit timer
GCLK->GENDIV.reg = GCLK_GENDIV_ID(4) | GCLK_GENDIV_DIV(1); // Divide by 1
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(4) | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_GENEN;
while (GCLK->STATUS.bit.SYNCBUSY) {}
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_TC4_TC5 | GCLK_CLKCTRL_GEN_GCLK4 | GCLK_CLKCTRL_CLKEN;
while (GCLK->STATUS.bit.SYNCBUSY) {}
TC4->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32 | TC_CTRLA_PRESCALER_DIV1;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY) {}
TC4->COUNT32.CTRLBSET.reg = TC_CTRLBSET_ONESHOT; // One-shot mode for manual triggering
while (TC4->COUNT32.STATUS.bit.SYNCBUSY) {}
TC4->COUNT32.INTENSET.reg = TC_INTENSET_MC0; // Enable match interrupt
NVIC_EnableIRQ(TC4_IRQn);
TC4->COUNT32.CTRLA.reg |= TC_CTRLA_ENABLE; // Enable the timer
while (TC4->COUNT32.STATUS.bit.SYNCBUSY) {}
}
void loop() {
// Read and filter potentiometer value
int potValue = analogRead(POT_PIN);
filterSum -= filterBuffer[filterIndex];
filterBuffer[filterIndex] = potValue;
filterSum += potValue;
filterIndex = (filterIndex + 1) % FILTER_DEPTH;
int filteredValue = filterSum / FILTER_DEPTH;
// Map potentiometer value to frequency range
int frequency = map(filteredValue, 0, 4095, MIN_FREQ, MAX_FREQ);
// Calculate the timer period in ticks
noInterrupts();
periodTicks = TIMER_CLOCK / frequency;
interrupts();
// Update phases based on frequency
wavePhase[0] = 0; // First wave (0° phase shift)
wavePhase[1] = periodTicks / 3; // Second wave (120° phase shift)
wavePhase[2] = 2 * periodTicks / 3; // Third wave (240° phase shift)
delay(10); // Small delay for stabilization
}
void TC4_Handler() {
static uint32_t ticks = 0;
// Clear interrupt flag
TC4->COUNT32.INTFLAG.reg = TC_INTFLAG_MC0;
// Check phases and toggle pins
if (ticks == wavePhase[0]) {
digitalWrite(WAVE_PIN1, !digitalRead(WAVE_PIN1));
}
if (ticks == wavePhase[1]) {
digitalWrite(WAVE_PIN2, !digitalRead(WAVE_PIN2));
}
if (ticks == wavePhase[2]) {
digitalWrite(WAVE_PIN3, !digitalRead(WAVE_PIN3));
}
// Increment ticks and reset if necessary
ticks++;
if (ticks >= periodTicks) {
ticks = 0;
TC4->COUNT32.COUNT.reg = 0; // Reset the counter
}
}
I would like to know if the code I provided can actually run on a board, as I am currently unable to run it myself. Additionally, if anyone could help me with this project, it would be greatly appreciated.
I have a SAMD21 based Arduino, although I haven't used it for a while. I may be able to test it tomorrow.
But it doesn't sound that taxing of a challenge for any Arduino, just by using micros() and digitalWrite(). The signal edges would be 166us apart at 50% duty cycle.
I think I did that what you want a little while ago for a Attiny13. Now that has only one 8-bit timer and 1Kbyte of memory, but it worked quite well.
What helped me most is realizing that 3 phase shifted square waves can be seen as a 6-case statement driven by a timer interrupt.
Could you let me know if the code works on your Arduino Zero tomorrow? I plan to use a D flip-flop to generate three signals with a 50% duty cycle. However, I’m struggling to achieve a precise 50% duty cycle. I suspect that using an Arduino with digitalWrite and micros() might introduce delays, which could prevent accurate phase shifts of 0°, 120°, and 240°.
...and now I remember why... frustrating experience, constantly having to put it into bootloader mode, ports appearing & disappearing... ugh!
setup() doesn't complete:
11:46:38.705 -> Starting setup()
11:46:38.705 -> Set up TC4/TC5 for 32-bit timer
11:46:38.705 -> while #1
11:46:38.705 -> while #2
11:46:38.705 -> while #3
11:46:38.705 -> while #4
11:46:38.705 -> Enable match interrupt
Updated code for above:
#include <Arduino.h>
// Pins for square wave outputs
#define WAVE_PIN1 9 // First wave
#define WAVE_PIN2 10 // Second wave
#define WAVE_PIN3 11 // Third wave
#define POT_PIN A0 // Potentiometer input
// Timer and frequency definitions
#define MIN_FREQ 5 // Minimum frequency in Hz
#define MAX_FREQ 1000 // Maximum frequency in Hz
#define TIMER_CLOCK 48000000UL // SAMD21 clock frequency
// Filtering settings
#define FILTER_DEPTH 10
int filterBuffer[FILTER_DEPTH];
int filterIndex = 0;
int filterSum = 0;
volatile uint32_t wavePhase[3] = {0, 0, 0}; // Phase values for each wave
volatile uint32_t periodTicks = 0; // Timer period in ticks
void setup() {
SerialUSB.begin(115200);
while (!SerialUSB);
SerialUSB.println("Starting setup()");
// Configure wave output pins
pinMode(WAVE_PIN1, OUTPUT);
pinMode(WAVE_PIN2, OUTPUT);
pinMode(WAVE_PIN3, OUTPUT);
// Configure analog input for potentiometer
analogReadResolution(12); // 12-bit resolution
analogReference(AR_DEFAULT);
SerialUSB.println("Set up TC4/TC5 for 32-bit timer");
// Set up TC4/TC5 for 32-bit timer
GCLK->GENDIV.reg = GCLK_GENDIV_ID(4) | GCLK_GENDIV_DIV(1); // Divide by 1
GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(4) | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_GENEN;
SerialUSB.println("while #1");
while (GCLK->STATUS.bit.SYNCBUSY) {}
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_TC4_TC5 | GCLK_CLKCTRL_GEN_GCLK4 | GCLK_CLKCTRL_CLKEN;
SerialUSB.println("while #2");
while (GCLK->STATUS.bit.SYNCBUSY) {}
TC4->COUNT32.CTRLA.reg = TC_CTRLA_MODE_COUNT32 | TC_CTRLA_PRESCALER_DIV1;
SerialUSB.println("while #3");
while (TC4->COUNT32.STATUS.bit.SYNCBUSY) {}
TC4->COUNT32.CTRLBSET.reg = TC_CTRLBSET_ONESHOT; // One-shot mode for manual triggering
SerialUSB.println("while #4");;
while (TC4->COUNT32.STATUS.bit.SYNCBUSY) {}
SerialUSB.println("Enable match interrupt");;
TC4->COUNT32.INTENSET.reg = TC_INTENSET_MC0; // Enable match interrupt
NVIC_EnableIRQ(TC4_IRQn);
TC4->COUNT32.CTRLA.reg |= TC_CTRLA_ENABLE; // Enable the timer
SerialUSB.println("while #5");
while (TC4->COUNT32.STATUS.bit.SYNCBUSY) {}
SerialUSB.println("setup() complete");
}
void loop() {
// Read and filter potentiometer value
int potValue = analogRead(POT_PIN);
SerialUSB.println(potValue);
filterSum -= filterBuffer[filterIndex];
filterBuffer[filterIndex] = potValue;
filterSum += potValue;
filterIndex = (filterIndex + 1) % FILTER_DEPTH;
int filteredValue = filterSum / FILTER_DEPTH;
// Map potentiometer value to frequency range
int frequency = map(filteredValue, 0, 4095, MIN_FREQ, MAX_FREQ);
// Calculate the timer period in ticks
noInterrupts();
periodTicks = TIMER_CLOCK / frequency;
interrupts();
// Update phases based on frequency
wavePhase[0] = 0; // First wave (0° phase shift)
wavePhase[1] = periodTicks / 3; // Second wave (120° phase shift)
wavePhase[2] = 2 * periodTicks / 3; // Third wave (240° phase shift)
delay(10); // Small delay for stabilization
}
void TC4_Handler() {
static uint32_t ticks = 0;
// Clear interrupt flag
TC4->COUNT32.INTFLAG.reg = TC_INTFLAG_MC0;
// Check phases and toggle pins
if (ticks == wavePhase[0]) {
digitalWrite(WAVE_PIN1, !digitalRead(WAVE_PIN1));
}
if (ticks == wavePhase[1]) {
digitalWrite(WAVE_PIN2, !digitalRead(WAVE_PIN2));
}
if (ticks == wavePhase[2]) {
digitalWrite(WAVE_PIN3, !digitalRead(WAVE_PIN3));
}
// Increment ticks and reset if necessary
ticks++;
if (ticks >= periodTicks) {
ticks = 0;
TC4->COUNT32.COUNT.reg = 0; // Reset the counter
}
}
I would like to clarify that the flip-flop is used purely to ensure a 50% duty cycle for the generated signals. The Arduino board generates the three square wave signals with the desired frequencies and phase shifts. These signals are then passed through hardware flip-flops to achieve a 50% duty cycle.
Currently, I cannot test the code on hardware, but I will be able to start testing it from next Monday.
Thank you for the updated code. I would like to know if the code correctly generates the three signals with the correct phase shift and if the frequency variation works properly. Could you let me know?
But isn't the flip-flop simply halving the frequency? That could easily be done in the code, removing the need for the flip-flop(s), unless I'm missing something. A schematic of the proposed circuit would help.
The project is to simulate a motor with three phases, each phase being 120° and 240° out of phase. The frequency should be adjustable using a potentiometer while maintaining the phase shift of each PWM signal. The frequencies range from 5Hz to a maximum of 1kHz, which corresponds to the motor's rotation in the simulation. The D flip-flop is not necessarily required for this project if my code works correctly with a 50% phase shift.
The three signals must have the same frequency and be synchronized according to the requested phase shift.
Could you try this code, but I’m afraid that using digitalWrite might cause delays in the phase shift. Thanks in advance for everything and everyone.
#include "Arduino.h"
const int pinPhase1 = 5; // Pin for phase 1
const int pinPhase2 = 6; // Pin for phase 2
const int pinPhase3 = 7; // Pin for phase 3
const int pinFreqControl = A0; // Pin to control the frequency
volatile uint32_t period = 1000; // Period in microseconds (initially 1 ms for 1 kHz)
volatile uint32_t phaseShift120 = 333; // Phase shift for 120° (in microseconds)
volatile uint32_t phaseShift240 = 666; // Phase shift for 240° (in microseconds)
void setup() {
// Initialize the pins as output
pinMode(pinPhase1, OUTPUT);
pinMode(pinPhase2, OUTPUT);
pinMode(pinPhase3, OUTPUT);
// Set initial states to LOW
digitalWrite(pinPhase1, LOW);
digitalWrite(pinPhase2, LOW);
digitalWrite(pinPhase3, LOW);
// Configure the TC3 timer to manage the signal generation
configureTimer();
}
void loop() {
// Read the potentiometer value from A0 to adjust the frequency
int potValue = analogRead(pinFreqControl);
float freq = map(potValue, 0, 1023, 5, 1000); // Map the value to a frequency range (5 Hz to 1 kHz)
// Calculate the new period based on the frequency
noInterrupts(); // Disable interrupts while updating the values
period = 1000000 / freq; // Period in microseconds
phaseShift120 = period / 3; // Calculate 120° phase shift
phaseShift240 = (2 * period) / 3; // Calculate 240° phase shift
interrupts(); // Re-enable interrupts
}
// Function to configure the TC3 timer
void configureTimer() {
// Enable TC3 clock
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3;
while (GCLK->STATUS.bit.SYNCBUSY);
// Disable the counter while configuring
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Configure the 16-bit mode and the prescaler
TC3->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 | TC_CTRLA_PRESCALER_DIV16;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Set the period for 1 kHz (initial value, will be updated in real-time)
TC3->COUNT16.CC[0].reg = (48000000 / 16) / 1000; // F_CPU / prescaler / frequency
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Enable the overflow interrupt
TC3->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_EnableIRQ(TC3_IRQn);
// Enable the timer
TC3->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
}
// Interrupt handler for TC3
void TC3_Handler() {
static uint32_t t = 0;
// Clear the interrupt flag
TC3->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
// Generate the three-phase signals
if (t == 0) {
digitalWrite(pinPhase1, !digitalRead(pinPhase1)); // Toggle phase 1
} else if (t == phaseShift120) {
digitalWrite(pinPhase2, !digitalRead(pinPhase2)); // Toggle phase 2 at 120° shift
} else if (t == phaseShift240) {
digitalWrite(pinPhase3, !digitalRead(pinPhase3)); // Toggle phase 3 at 240° shift
}
// Increment or reset the counter
t += period / 2;
if (t >= period) {
t = 0;
}
// Adjust the timer period for the next interrupt
TC3->COUNT16.CC[0].reg = (48000000 / 16) / (1000000 / period);
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
}
Or maybe these can work with a hardware timer.
#include "Arduino.h"
// Pin assignments for three phases
const int pinPhase1 = 5;
const int pinPhase2 = 6;
const int pinPhase3 = 7;
// Pin for controlling frequency via analog input
const int pinFreqControl = A0;
// Variables for controlling the signal frequency and phase shift
volatile uint32_t period = 1000; // Period in microseconds (initially set to 1 ms for 1 kHz)
volatile uint32_t phaseShift120 = 333; // Phase shift for 120° (in microseconds)
volatile uint32_t phaseShift240 = 666; // Phase shift for 240° (in microseconds)
void setup() {
// Initialize pins as output
pinMode(pinPhase1, OUTPUT);
pinMode(pinPhase2, OUTPUT);
pinMode(pinPhase3, OUTPUT);
// Set initial states to LOW
digitalWrite(pinPhase1, LOW);
digitalWrite(pinPhase2, LOW);
digitalWrite(pinPhase3, LOW);
// Configure the timer for signal generation
configureTimer();
}
void loop() {
// Read potentiometer value from A0 to adjust frequency
int potValue = analogRead(pinFreqControl);
float freq = map(potValue, 0, 1023, 5, 1000); // Map value to frequency range (5 Hz to 1 kHz)
// Update period based on frequency
noInterrupts(); // Disable interrupts while updating values
period = 1000000 / freq; // Period in microseconds
phaseShift120 = period / 3; // Calculate 120° phase shift
phaseShift240 = (2 * period) / 3; // Calculate 240° phase shift
interrupts(); // Re-enable interrupts
}
// Function to configure the TC3 timer
void configureTimer() {
// Enable TC3 clock
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3;
while (GCLK->STATUS.bit.SYNCBUSY);
// Disable the counter during configuration
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Configure 16-bit mode and prescaler
TC3->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 | TC_CTRLA_PRESCALER_DIV16;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Set initial period for 1 kHz frequency (adjusted later in loop)
TC3->COUNT16.CC[0].reg = (48000000 / 16) / 1000; // F_CPU / prescaler / frequency
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Enable overflow interrupt for TC3
TC3->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_EnableIRQ(TC3_IRQn);
// Enable the timer
TC3->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
}
// Interrupt handler for TC3
void TC3_Handler() {
static uint32_t t = 0;
// Clear the overflow interrupt flag
TC3->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
// Directly toggle the phases based on timing
if (t == 0) {
// Set phase 1 HIGH (direct manipulation of output)
PORT->Group[g_APinDescription[pinPhase1].ulPort].OUTSET.reg = (1 << g_APinDescription[pinPhase1].ulPin);
} else if (t == phaseShift120) {
// Set phase 2 HIGH (direct manipulation of output)
PORT->Group[g_APinDescription[pinPhase2].ulPort].OUTSET.reg = (1 << g_APinDescription[pinPhase2].ulPin);
} else if (t == phaseShift240) {
// Set phase 3 HIGH (direct manipulation of output)
PORT->Group[g_APinDescription[pinPhase3].ulPort].OUTSET.reg = (1 << g_APinDescription[pinPhase3].ulPin);
}
// Increment or reset the counter
t += period / 2;
if (t >= period) {
t = 0;
}
// Reset phases to LOW at the correct times
if (t == (period / 2)) {
// Set all phases LOW at the halfway point
PORT->Group[g_APinDescription[pinPhase1].ulPort].OUTCLR.reg = (1 << g_APinDescription[pinPhase1].ulPin);
PORT->Group[g_APinDescription[pinPhase2].ulPort].OUTCLR.reg = (1 << g_APinDescription[pinPhase2].ulPin);
PORT->Group[g_APinDescription[pinPhase3].ulPort].OUTCLR.reg = (1 << g_APinDescription[pinPhase3].ulPin);
}
// Update the timer period based on the new frequency
TC3->COUNT16.CC[0].reg = (48000000 / 16) / (1000000 / period);
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
}
#include "Arduino.h"
const int pinPhase1 = 5; // Pin for phase 1
const int pinPhase2 = 6; // Pin for phase 2
const int pinPhase3 = 7; // Pin for phase 3
const int pinFreqControl = A0; // Pin to control the frequency
volatile uint32_t period = 1000; // Period in microseconds (initially 1 ms for 1 kHz)
volatile uint32_t phaseShift120 = 333; // Phase shift for 120° (in microseconds)
volatile uint32_t phaseShift240 = 666; // Phase shift for 240° (in microseconds)
void setup() {
SerialUSB.begin(115200);
while (!SerialUSB);
SerialUSB.println("Starting setup()");
// Initialize the pins as output
pinMode(pinPhase1, OUTPUT);
pinMode(pinPhase2, OUTPUT);
pinMode(pinPhase3, OUTPUT);
// Set initial states to LOW
digitalWrite(pinPhase1, LOW);
digitalWrite(pinPhase2, LOW);
digitalWrite(pinPhase3, LOW);
// Configure the TC3 timer to manage the signal generation
configureTimer();
SerialUSB.println("setup() complete");
}
void loop() {
// Read the potentiometer value from A0 to adjust the frequency
int potValue = analogRead(pinFreqControl);
float freq = map(potValue, 0, 1023, 5, 1000); // Map the value to a frequency range (5 Hz to 1 kHz)
//SerialUSB.println(freq);
// Calculate the new period based on the frequency
noInterrupts(); // Disable interrupts while updating the values
period = 1000000 / freq; // Period in microseconds
phaseShift120 = period / 3; // Calculate 120° phase shift
phaseShift240 = (2 * period) / 3; // Calculate 240° phase shift
interrupts(); // Re-enable interrupts
}
// Function to configure the TC3 timer
void configureTimer() {
// Enable TC3 clock
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_TCC2_TC3;
while (GCLK->STATUS.bit.SYNCBUSY);
// Disable the counter while configuring
TC3->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Configure the 16-bit mode and the prescaler
TC3->COUNT16.CTRLA.reg = TC_CTRLA_MODE_COUNT16 | TC_CTRLA_PRESCALER_DIV16;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Set the period for 1 kHz (initial value, will be updated in real-time)
TC3->COUNT16.CC[0].reg = (48000000 / 16) / 1000; // F_CPU / prescaler / frequency
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
// Enable the overflow interrupt
TC3->COUNT16.INTENSET.reg = TC_INTENSET_OVF;
NVIC_EnableIRQ(TC3_IRQn);
// Enable the timer
TC3->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE;
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
}
// Interrupt handler for TC3
void TC3_Handler() {
static uint32_t t = 0;
// Clear the interrupt flag
TC3->COUNT16.INTFLAG.reg = TC_INTFLAG_OVF;
// Generate the three-phase signals
if (t == 0) {
digitalWrite(pinPhase1, !digitalRead(pinPhase1)); // Toggle phase 1
} else if (t == phaseShift120) {
digitalWrite(pinPhase2, !digitalRead(pinPhase2)); // Toggle phase 2 at 120° shift
} else if (t == phaseShift240) {
digitalWrite(pinPhase3, !digitalRead(pinPhase3)); // Toggle phase 3 at 240° shift
}
// Increment or reset the counter
t += period / 2;
if (t >= period) {
t = 0;
}
// Adjust the timer period for the next interrupt
TC3->COUNT16.CC[0].reg = (48000000 / 16) / (1000000 / period);
while (TC3->COUNT16.STATUS.bit.SYNCBUSY);
}
using four ESP32 timers using interrupts
one timer to generate the three signals of required period and three others to generate the pulses of duty cycle width
// ESP32 three phase square wave variable duty cycle using timers
#define phase1 16 // signal output pins
#define phase2 18
#define phase3 17
#define PERIOD 10000 // period 1KHz is 10000
#define DUTY_CYCLE 50 // percentage
#define PULSE_WIDTH PERIOD*DUTY_CYCLE/100 // resultant pulse width
hw_timer_t *timerPeriod = NULL; // hardware timer
hw_timer_t *timerPhase1 = NULL; // hardware timer
hw_timer_t *timerPhase2 = NULL; // hardware timer
hw_timer_t *timerPhase3 = NULL; // hardware timer
// interrupt service routine to generate HIGH levels
volatile int counter = 0; // interrupt counter
void ARDUINO_ISR_ATTR onTimer() {
static byte state = 0; // determines which phase to generate
switch (state) {
case 0:
digitalWrite(phase1, HIGH); // set phase output HIGH
timerWrite(timerPhase1, 0); // clear timer
timerAlarm(timerPhase1, PULSE_WIDTH, false, 0); // generate one shot
break;
case 1:
digitalWrite(phase2, HIGH);
timerWrite(timerPhase2, 0);
timerAlarm(timerPhase2, PULSE_WIDTH, false, 0); // generate one shot
break;
case 2:
digitalWrite(phase3, HIGH);
timerWrite(timerPhase3, 0);
timerAlarm(timerPhase3, PULSE_WIDTH, false, 0); // generate one shot
break;
}
if (++state >= 3) state = 0; // reset state ?
counter++;
}
// interrupt service routines to generate LOW levels
void ARDUINO_ISR_ATTR onTimerPhase1() {
digitalWrite(phase1, LOW); // set phase output LOW
}
void ARDUINO_ISR_ATTR onTimerPhase2() {
digitalWrite(phase2, LOW);
}
void ARDUINO_ISR_ATTR onTimerPhase3() {
digitalWrite(phase3, LOW);
}
void setup() {
Serial.begin(115200);
delay(2000);
Serial.println("\n\nESP32 timer three phase square wave with duty Cycle");
pinMode(phase1, OUTPUT); // enable phase outputs
pinMode(phase2, OUTPUT);
pinMode(phase3, OUTPUT);
digitalWrite(phase3, 1);
// setup timer interrupts for 1KHz three phase
if ((timerPeriod = timerBegin(10000000)) == NULL) // Set timer frequency to 10Mhz
Serial.println("ERROR! timer period initialisation failed");
else Serial.println("Timer period initialization OK");
if ((timerPhase1 = timerBegin(10000000)) == NULL) // Set timer frequency to 10Mhz
Serial.println("ERROR! timer Phase 1 initialisation failed");
else Serial.println("Timer Phase 1 initialization OK");
if ((timerPhase2 = timerBegin(10000000)) == NULL) // Set timer frequency to 10Mhz
Serial.println("ERROR! timer Phase 2 initialisation failed");
else Serial.println("Timer Phase 2 initialization OK");
if ((timerPhase3 = timerBegin(10000000)) == NULL) // Set timer frequency to 10Mhz
Serial.println("ERROR! timer Phase 3 initialisation failed");
else Serial.println("Timer Phase 3 initialization OK");
timerAttachInterrupt(timerPeriod, &onTimer); // Attach timer ISR for period timing
timerAttachInterrupt(timerPhase1, &onTimerPhase1); // attch timers ISR for pulse timing
timerAttachInterrupt(timerPhase2, &onTimerPhase2);
timerAttachInterrupt(timerPhase3, &onTimerPhase3);
// Set alarm to call onTimer function every second (value in 10 microseconds).
// Repeat the alarm (third parameter) with unlimited count = 0 (fourth parameter).
timerAlarm(timerPeriod, PERIOD / 3, true, 0);
}
// display interrupt coun ter every seconds
void loop() {
static unsigned long timert = millis();
if (millis() - timert >= 1000) {
Serial.println(counter);
counter = 0;
timert = millis();
}
}
I want to be able to do this with an Arduino Zero: generate three square waves with phase shifts of 0°, 120°, and 240°, like a real motor, using a 50% duty cycle and variable frequency. Currently, I can achieve this using the code with micros(). However, when I run it on my board, the signals are not synchronized, and the frequency cannot reach 1 kHz.
const int potPin = A0; // Analog pin where the potentiometer is connected
float frequency = 1; // Calculated frequency in Hz (initial minimum value)
const int pwmPin1 = 3; // PWM pin 1
const int pwmPin2 = 5; // PWM pin 2
const int pwmPin3 = 6; // PWM pin 3
const int clearPin = 8; // Reset pin
const int clearPin2 = 9; // Reset pin
const int clearPin3 = 10; // Reset pin
bool clearPinHandled = false; // Flag to execute handleClearPin() only once
unsigned long period; // Period in microseconds
unsigned long delayPhase; // Phase shift in microseconds
unsigned long lastUpdate1; // Update time for PWM 1
unsigned long lastUpdate2; // Update time for PWM 2
unsigned long lastUpdate3; // Update time for PWM 3
// Variables for filtering
const int smoothingFactor = 10; // Smoothing factor (number of samples)
float smoothedValue = 0; // Smoothed potentiometer value
const int tolerance = 5; // Tolerance to ignore small variations
void setup() {
pinMode(pwmPin1, OUTPUT);
pinMode(pwmPin2, OUTPUT);
pinMode(pwmPin3, OUTPUT);
pinMode(clearPin, OUTPUT);
pinMode(clearPin2, OUTPUT);
pinMode(clearPin3, OUTPUT);
// Initialize serial communication for debugging
Serial.begin(9600);
}
void loop() {
// Read the raw value from the potentiometer (0 to 1023)
int potValue = analogRead(potPin);
// Apply a moving average filter
smoothedValue += (potValue - smoothedValue) / smoothingFactor;
// Map the smoothed value to obtain a frequency between 5 Hz and 1000 Hz
int mappedFrequency = map(smoothedValue, 0, 1023, 5, 1000);
// Update the frequency if the variation exceeds the tolerance
if (abs(mappedFrequency - frequency) > tolerance) {
frequency = mappedFrequency;
}
// Calculate the period and phase shift
period = (1.0 / frequency) * 1000000; // Period in microseconds
delayPhase = period / 3; // 120° phase shift in microseconds
static unsigned long lastPrintTime = 0;
if (millis() - lastPrintTime > 1000) { // Display every 1 second
lastPrintTime = millis();
Serial.print("Frequency: ");
Serial.print(frequency);
Serial.print(" Hz | ");
Serial.print("Period: ");
Serial.print(period);
Serial.print(" us | Phase shift: ");
Serial.print(delayPhase);
Serial.println(" us");
}
// Get the current time
unsigned long currentTime = micros();
// PWM 1: Based on the period
if (currentTime - lastUpdate1 >= period) {
lastUpdate1 += period; // Prepare for the next pulse
digitalWrite(pwmPin1, HIGH);
delayMicroseconds(period / 2); // Pulse width 50%
digitalWrite(pwmPin1, LOW);
}
// PWM 2: Shifted by 120° (1/3 of the period)
if (currentTime - lastUpdate2 >= period) {
lastUpdate2 += period; // Prepare for the next pulse
unsigned long phaseDelay = delayPhase;
digitalWrite(pwmPin2, HIGH);
delayMicroseconds(period / 2); // Pulse width 50%
digitalWrite(pwmPin2, LOW);
digitalWrite(clearPin2, HIGH);
}
// PWM 3: Shifted by 240° (2/3 of the period)
if (currentTime - lastUpdate3 >= period) {
lastUpdate3 += period; // Prepare for the next pulse
unsigned long phaseDelay = delayPhase * 2;
digitalWrite(pwmPin3, HIGH);
delayMicroseconds(period / 2); // Pulse width 50%
digitalWrite(pwmPin3, LOW);
}
}
If someone could help me write code to achieve this using hardware timers, I would greatly appreciate it.