Phase Shift PWM using Arduino Mega2560

1 post / 0 new
Runtime Micro's picture
Runtime Micro
Offline
Joined: 07/24/2016 - 9:18am
Phase Shift PWM using Arduino Mega2560

I was looking for ways to demonstrate RTM_TimerCalc and came across the idea to phase shift PWM. At first, I was surprised by the concept. But apparently phase-shifted PWM has useful applications.

I read about a Phase Shift PWM approach using 3 signals set 120 degrees apart. I decided to try making a 3 x 120 degree Project using an Arduino Mega2560.

The following animation shows 2 (of the 3) varying PWM project signals spread 120 degrees apart at 1000 Hz. Its possible we don't need to vary Duty-cycle but it helps to see the PWM is phase shifted.

Animation shows 2 PWM signals 120 degrees apart.

Phase Shift PWM Project Design

The only way I saw to get 120 degree Phase Shift PWM was use 4 separate Timers. One Timer would delay start several others. All 4 Timers would use a cycle frequency of 1000 Hz. I discovered using Fast-PWM for the delay-start Timer was better overall. The 3 waveform Timers use Phase-PWM mode.

Since this was a 3-channel project, each phase-shifted Timer delivered output from its channel-A. Channels B and C are available for Interrupt or general PWM use.

The next image shows resulting phase shift PWM captured on a logic analyzer.

Phase Shift PWM display shows three 120 shifted channels.

Phase Shift PWM Project Build

I sticky taped a Mega2560 to an 830 type breadboard. Permanent-Solderless style wire wrap made needed connections. Power was supplied via USB. And some modified double-sided header pins made several test-points. (See next Image)

My build to Phase Shift PWM using Arduino Mega 2560

I borrowed a push-button assembly from my previous Phase Shifted Square Waves project. It plugged into the breadboard and wiring was straight forward.

Phase Shift PWM Code Strategy

Timer-5 synchronizes two of three Timers in 120 degree steps. This requires starting Timer-5 and Timer-1 at nearly the same moment. EDIT: Now have synchronized-start using General Timer Register.

Image shows Phase-Shifted PWM Timing Strategy graphic

Then Timer-5 Interrupt ISR's enable Timer-3 and Timer-4 at 120 degree Intervals. I hooked up the O-Scope to the test-point pins and saw waveforms separated by 120 degrees. They were rock solid! Smile

Next I added code for push-buttons to vary the 3 channels of PWM duty cycle. I could change duty-cycle for any one channel or all three together. I left the code to change all three.

But there were concerns. At 100% and 0% duty, the scope traces were twitchy. I added OCR limits to avoid getting to zero or the ICR value. Then I could see tiny pulses at either duty-cycle extreme.

120 degree Phase-Shift Code (Updated)

RTM_TimerCalc generated most of the code for Timers 1, 3, 4 and 5. I pasted it into needed setup() blocks. I did remove some Timer enable statements. But now there are some revisions to discuss -- see next.

Pulse Center Markers

In my first Code version, all Timers used Fast-PWM mode. But after making a 6-phase project, I learned mode-10 is better to Phase Shift PWM Timers. The delay Timer still remains in Fast-PWM mode -- as it has twice the resolution of Phase-mode.

One reason to use Phase-mode PWM is its center-symmetric nature. The revised code exploits this by having waveform Timers generate Overflow Interrupts at channel-A pulse centers. You can use these Interrupts to sample signals at pulse middle points! In a motor control application, this could trigger a measurement of Voltage or Current.

The Code uses Overflow Interrupts to generate marker-pulses at Port-Pins. A logic-analyzer connected to those pins proves Interrupts occur center-pulse. This also reveals regardless of duty-cycle, channel-to-channel markers remain 120 degrees apart. Please see the next image.

Image reveals markers at center-pulse positions for 3-phase-PWM...

Phase Shift PWM Start-up

Initially I only considered how this project looked on a scope or analyzer while running. But in the follow-up 6 Phase Project, I discovered unwanted start-up behavior just after running setup(). I could spot setup's position in time by adding a Port-Pin marker-pulse. It triggered logic-analyzer capture. The revised Code includes that pulse marker.

I was surprised to see a clean 3-phase start-up. So, I didn't add any code to delay enable of PWM pins.

But if you build this project with intent to control some sort of hardware, consider how you'll shut it down. You might arrange a COMPB interrupt on Timer-4 to signal a good time to disable Timer output pins. Just a thought...

Changing PWM duty-Cycle

Varying PWM looks easy since Atmel designed the Timers to double-buffer changes to OCR registers. This means glitch-free updates to duty-cycle from Code.

But keep in mind the time-delay nature of this project means each phase updates at 120 degree time-spacing! It'll take at least 1 full Timer period for all three channels to assume a new pulse-duty value. : /

Phase Shift PWM Example Code

Here's the (revised 9-19-2022) project Code listing;

// REVISED 3-Channel, 120 Degree Phase Shift PWM code from Runtime Micro
// Uses 4 Mega2560 16-bit Timers, T5 @ mode-14-FAST, T1, T3 and T4 @ Mode-10-PHASE
// Timer setup code generated by RTM_TimerCalc v1.4

int btn_DutyIncrease_Pin = A13;  // Using ADC pins for push-buttons
int btn_DutyDecrease_Pin = A15;
int OCR_Result;             // a calculation variable
int const Duty_Limit = 20;  // Numeric LIMIT for 0% or 100% duty

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

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

ISR(TIMER5_COMPB_vect)
{
  TCCR4B |= 1;   // DELAYED START Timer 4, 240 degrees
  TIMSK5 = 0x0;  // STOP further Interrupts from Timer5
}

////////////////////////////////////////////////////////////////////////
// Center Pulse Marker Routines for 120 degree channels
// Used by Logic Analyzer, adapt for S&H measurements

ISR(TIMER1_OVF_vect)
{
digitalWrite(14, HIGH); // Center Pulse Marker T1-A
digitalWrite(14, LOW);
}

ISR(TIMER3_OVF_vect)
{
digitalWrite(15, HIGH); // Center Pulse Marker T3-A
digitalWrite(15, LOW);
}

ISR(TIMER4_OVF_vect)
{
digitalWrite(16, HIGH); // Center Pulse Marker T4-A
digitalWrite(16, LOW); 
}

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

void setup()
{
  // PUSH BUTTON PINS need Pulled-Up Inputs
  pinMode(btn_DutyIncrease_Pin, INPUT_PULLUP);  // Push-button on ADC pin
  pinMode(btn_DutyDecrease_Pin, INPUT_PULLUP);  // Push-button on ADC pin

  // 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.33333);
  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.33333);
  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.33333);
  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 |= 1;  // Prescale=1, ENABLE Timer 5
  TCCR1B |= 1;  // 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 PWM_adjust(int up_down)  // Adjust PWM on Timers T1,  T3 and T4
{
  // calculate
  OCR_Result = OCR1A;
  OCR_Result += up_down;

  // constrain old-school style
  if (OCR_Result < Duty_Limit) OCR_Result = Duty_Limit;                // OCR cannot be less than 1
  if (OCR_Result > ICR1 - Duty_Limit) OCR_Result = ICR1 - Duty_Limit;  // OCR cannot be at or more than ICR

  // Assert new Duty Cycle to Timers double-buffered OCRs
  OCR1A = (int)OCR_Result;
  OCR3A = (int)OCR_Result;
  OCR4A = (int)OCR_Result;
}  // end PWM_adjust

void loop()
{
  int state_DutyIncrease;
  int state_DutyDecrease;

  // read state of pushbutton(s):
  state_DutyIncrease = digitalRead(btn_DutyIncrease_Pin);
  state_DutyDecrease = digitalRead(btn_DutyDecrease_Pin);

  delay(10);  // PACE loop speed a bit

  // check if pushbutton pressed.
  if (state_DutyIncrease == LOW)
  {
    PWM_adjust(+20);
  }

  // check if pushbutton pressed.
  if (state_DutyDecrease == LOW)
  {
    PWM_adjust(-20);
  }
}  // end loop()

Phase Shift PWM Diagram

The diagram does not show marker pulse pins. They are D14, D15, D16 and (D18 for setup pulse). You can safely remove marker Interrupts and/or pinMode code as you see fit.

Images shows Phase Shift PWM project diagram

Other Phase Shift PWM Angles

Timer-5 OCR thresholds of 1/3 and 2/3 (of ICR) made possible delayed starting for 120 degree phase shifts. Changing OCR values for 1/4 can buy you 90 degree shift. Many different phase-shift arrangements are possible. These fractions are independent of Timer cycle frequency. The thing to avoid is low N-Divide values. Lower frequencies require larger N-Divides. The opposite is true as you go up in frequency.

With RTM_TimerCalc you can visualize waveforms, inspect N-Divide calculated values and create needed code. Here's a short Video covering RTM_TimerCalc v1.4 -- revealing its latest capabilities and features.

Have fun Phase-Shifting PWM

Lee Smile

Created: Aug 29, 2022   Updated: Jan 11, 2024