Generating PWM signals with varying frequency with POT using Arduino Mega 2560

Hi folks,
Iam using Arduino Mega 2560 & trying to generate 3 PWM signals with 50% duty cycle and 120 degree phase shifts between all 3 signals using Timer1(pin11), Timer3(pin5) and Timer4(pin6). So far Iam able to achieve this all with manually varying frequency from 10Hz to 2KHz in loop, but Iam not able to achieve exact phaseshift of 120degree when Iam varying my frequency using POT and reading it using ADC. Help me find out the issue and modification to be done in the code to achieve this. Thank you.
Below is the code

int i = 1;
#define ICRVALUE(i) (125000 / (1 * i))

ISR(TIMER5_COMPA_vect) {
  TCCR3B |= 3;  // DELAYED START Timer 3, 120 degrees
}

ISR(TIMER5_COMPB_vect) {
  TCCR4B |= 3;  // DELAYED START Timer 4, 240 degrees
}
ISR(TIMER1_OVF_vect) {
}
ISR(TIMER3_OVF_vect) {
}
ISR(TIMER4_OVF_vect) {
}

// Use multiple timers (Timer1, Timer3, Timer4, Timer5) on the ATmega2560 to generate PWM signals.
// Set each timer's PWM frequency based on the ADC value and adjust each output pin compare match to introduce phase shifts.
// Set up the ADC to continuously read the voltage from a potentiometer, which dictates the PWM frequency.
uint8_t PRESCALER = 1;
void setupADC() {
  ADMUX = (1 << REFS0);                                               // AVcc as reference
  ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);  // Enable ADC, prescaler 128
}
// uint16_t readADC()
// {
//     ADCSRA |= (1<<ADSC);  // Start conversion
//     while (ADCSRA & (1<<ADSC));  // Wait for conversion to finish
//     return ADC;
// }


void setup() {
  Serial.begin(9600);
  setupADC();

  // Timer-5 16-bit, Mode-14 FAST, Top=ICR
  // 1000 Hz Frequency, Clock is 16 MHz
  // Only this Timer is using Fast-PWM

  TCCR5B = 0x18;  // 0001 1000, Disable Timer
  TCCR5A = 0x50;  // 0101 0000

  ICR5 = 16000 - 1;
  OCR5A = (int)(ICR5 * 0.33333);  // 120 degrees at Freq
  OCR5B = (int)(ICR5 * 0.66666);  // 240 degrees at Freq
  TCNT5 = 0x0;

  TIMSK5 = 0x06;  // Enable CompA and CompB Interrupts

  //////////////////////////////////////////////

  // Timer-1 16-bit, Mode-10 Phase-PWM, Top=ICR
  // 1,000 Hz Frequency, Clock is 16 MHz

  TCCR1B = 0x10;  // 0001 1000, Disable Timer
  TCCR1A = 0x82;  // 1000 0010

  ICR1 = 8000 - 1;
  OCR1A = (int)(ICR1 * 0.5);
  TCNT1 = 0x0;
  TIMSK1 = 0x01;  // OVF Channel-A Center Pulse Interrupt

  pinMode(11, OUTPUT);  // OC1a, 0 degrees phase shift

  //////////////////////////////////////////////////

  // Timer-3 16-bit, Mode-10 Phase, Top=ICR
  // 1,000 Hz Frequency, Clock is 16 MHz

  TCCR3B = 0x10;  // 0001 1000, Disable Timer
  TCCR3A = 0x82;  // 1010 0010

  ICR3 = 8000 - 1;
  OCR3A = (int)(ICR3 * 0.5);
  TCNT3 = 0x0;
  TIMSK3 = 0x01;  // OVF Center Pulse Interrupt

  pinMode(5, OUTPUT);  // OC3a, 120 degrees from Timer-1

  ///////////////////////////////////////////////////

  // Timer-4 16-bit, Mode-10 Phase, Top=ICR
  // 1,000 Hz Frequency, Clock is 16 MHz

  TCCR4B = 0x10;  // 0001 1000, Disable Timer
  TCCR4A = 0x82;  // 1010 0010

  ICR4 = 8000 - 1;
  OCR4A = (int)(ICR4 * 0.5);
  TCNT4 = 0x0;
  TIMSK4 = 0x01;  // OVF Center Pulse Interrupt

  pinMode(6, OUTPUT);  // OC4a, 240 degrees from Timer-1

  // Center Pulse Marker Pins
  pinMode(14, OUTPUT);  // Center-Pulse Marker T1-Channel-A
  pinMode(15, OUTPUT);  // Center-Pulse Marker T3-Channel-A
  pinMode(16, OUTPUT);  // Center-Pulse Marker T4-Channel-A

  pinMode(18, OUTPUT);  // Setup Marker Pulse

  //  UPDATE
  //  Synchronize Start of Timers 1 and 5
  GTCCR = 0x81;  // Timer Sync Control -- STOP ALL TIMERS, Clear preScalers

  TCCR5B |= 3;  // Prescale=1, ENABLE Timer 5
  TCCR1B |= 3;  // Prescale=1, ENABLE Timer 1

  GTCCR = 0x0;  // START ALL TIMERS

  // Place Marker Pulse to watch Timer(s) startup
  digitalWrite(18, HIGH);
  digitalWrite(18, LOW);
}  // end setup

void loop() {
  static uint16_t previous_freq = 0xFFFF;
  while (1)
  //for(int i=1;i<=200;i++)
  {

    uint16_t adcValue = analogRead(A1);
    Serial.print("ADC value = \n");
    Serial.println(adcValue);
    float voltage = adcValue * (5.0 / 1023.0);
    uint16_t newFrequency = (uint16_t)adcValue * 2;  //(i*0.004*1000)/2;; //map(voltage, 0.004, 5.0, 2, 2048);

    Serial.print("Mapped value = \n");
    Serial.println(newFrequency);
    ////////////////////////////////////////////////////////////////////////////
    // Timer-5 16-bit, Mode-14 FAST, Top=ICR
    // 1000 Hz Frequency, Clock is 16 MHz
    // Only this Timer is using Fast-PWM

    TCCR5B = 0x18;  // 0001 1000, Disable Timer
    TCCR5A = 0x50;  // 0101 0000


    ICR5 = (ICRVALUE(newFrequency) * 2) - 1;  //16000 - 1;
    OCR5A = (int)(ICR5 * 0.33333);            // 120 degrees at Freq
    OCR5B = (int)(ICR5 * 0.66666);            // 240 degrees at Freq
    TCNT5 = 0x0;

    TIMSK5 = 0x06;  // Enable CompA and CompB Interrupts

    ///////////////////////////////////////////////////////////////////////////////////

    // Timer-1 16-bit, Mode-10 Phase-PWM, Top=ICR
    // 1,000 Hz Frequency, Clock is 16 MHz

    TCCR1B = 0x10;  // 0001 1000, Disable Timer
    TCCR1A = 0x82;  // 1000 0010

    ICR1 = ICRVALUE(newFrequency) - 1;  //8000 - 1;
    OCR1A = (int)(ICR1 * 0.5);
    TCNT1 = 0x0;
    TIMSK1 = 0x01;  // OVF Channel-A Center Pulse Interrupt

    pinMode(11, OUTPUT);  // OC1a, 0 degrees phase shift

    ///////////////////////////////////////////////////////////////////////////////////

    // Timer-3 16-bit, Mode-10 Phase, Top=ICR
    // 1,000 Hz Frequency, Clock is 16 MHz

    TCCR3B = 0x10;  // 0001 1000, Disable Timer
    TCCR3A = 0x82;  // 1010 0010

    ICR3 = ICRVALUE(newFrequency) - 1;  //8000 - 1;
    OCR3A = (int)(ICR3 * 0.5);
    TCNT3 = 0x0;
    TIMSK3 = 0x01;  // OVF Center Pulse Interrupt

    pinMode(5, OUTPUT);  // OC3a, 120 degrees from Timer-1

    ///////////////////////////////////////////////////////////////////////////////////

    // Timer-4 16-bit, Mode-10 Phase, Top=ICR
    // 1,000 Hz Frequency, Clock is 16 MHz

    TCCR4B = 0x10;  // 0001 1000, Disable Timer
    TCCR4A = 0x82;  // 1010 0010

    ICR4 = ICRVALUE(newFrequency) - 1;  //8000 - 1;
    OCR4A = (int)(ICR4 * 0.5);
    TCNT4 = 0x0;
    TIMSK4 = 0x01;  // OVF Center Pulse Interrupt

    pinMode(6, OUTPUT);  // OC4a, 240 degrees from Timer-1

    // Center Pulse Marker Pins
    pinMode(14, OUTPUT);  // Center-Pulse Marker T1-Channel-A
    pinMode(15, OUTPUT);  // Center-Pulse Marker T3-Channel-A
    pinMode(16, OUTPUT);  // Center-Pulse Marker T4-Channel-A

    pinMode(18, OUTPUT);  // Setup Marker Pulse

    ///////////////////////////////////////////////////////////////////////////////////

    //  UPDATE
    //  Synchronize Start of Timers 1 and 5
    GTCCR = 0x81;  // Timer Sync Control -- STOP ALL TIMERS, Clear preScalers

    TCCR5B |= 3;  // Prescale=1, ENABLE Timer 5
    TCCR1B |= 3;  // Prescale=1, ENABLE Timer 1

    GTCCR = 0x0;  // START ALL TIMERS
    sei();
    delay(1000);
    cli();

  }  //end of while loop

}  // end loop()

Yes, please. Please format your code for the forum.

Is 2K the highest frequency you need? Maybe a plain old state machine and digitalWrite would be good enough.

I like the idea of updating the settings only when the pot changes, not each time through the loop.


// Quadrature Generator
// https://wokwi.com/projects/403218793081155585

// Define the output pins
const int pinA = 2;
const int pinB = 3;

// Define the state machine states
enum State {STATE0, STATE1, STATE2, STATE3};
State currentState = STATE0;

// Timing variables
unsigned long previousMicros = 0;
unsigned long interval = 357; // 357 microseconds for each state
bool on = false;
void setup() {
  // Initialize the output pins
  pinMode(pinA, OUTPUT);
  pinMode(pinB, OUTPUT);

  // Initialize the pins to a known state
  digitalWrite(pinA, LOW);
  digitalWrite(pinB, LOW);
  Serial.begin(115200);
}

void readPot(void) {
  static int lastADC = -1;
  int pot = analogRead(A0);
  if (pot != lastADC) { // change detection on potentiometer
    lastADC = pot;
    long speed = map(pot, 0, 1023, 0, 200000);
    if (speed == 0) {
      on = false;
    } else {
      on = true;
      interval = 10000000.0 / speed / 4;
      previousMicros = micros();
    }
    Serial.print(pot);
    Serial.print(", ");
    Serial.print(speed/10.0);
    Serial.print("Hz us:");
    Serial.println(interval);
  }
}



void loop() {
  // Get the current time in microseconds
  unsigned long currentMicros = micros();

  static unsigned long lastScan;
  if (millis() - lastScan > 128) {
    lastScan += 128;
    readPot();
  }

  // Check if the interval has passed
  if (on && currentMicros - previousMicros >= interval) {
    // Save the current time for the next interval
    //    previousMicros = currentMicros;
    previousMicros += interval;

    // Advance the state machine
    switch (currentState) {
      case STATE0:
        digitalWrite(pinA, HIGH);
        // digitalWrite(pinB, LOW);
        currentState = STATE1;
        break;

      case STATE1:
        //digitalWrite(pinA, HIGH);
        digitalWrite(pinB, HIGH);
        currentState = STATE2;
        break;

      case STATE2:
        digitalWrite(pinA, LOW);
        //digitalWrite(pinB, HIGH);
        currentState = STATE3;
        break;

      case STATE3:
        //digitalWrite(pinA, LOW);
        digitalWrite(pinB, LOW);
        currentState = STATE0;
        break;
    }
  }
}
1 Like

Thanks. I will try with this.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.