Puzzling PWM Problems

mborgerson

Well-known member
I'm working on a document describing a satellite ELINT system from the 1970's (That's old enough that I can actually find declassified technical information.) I want to simulate the display the ground station operators would see if they were receiving two channels of data from each of two satellites. The timing of the intercepted radar pulses could be used with Differential Time Of Arrival (DTOA) techniques to locate the emitting radars. When I set up T4.1 with 4 PWM outputs from pins 14, 15, 18 and 19, I got some puzzling results. (Those pins use channels 0,1,2, and 3 of quad timer TMR3.) Since I set up and started all the PWM outputs in quick sequence, I expected all the outputs to rise within a few microseconds of each other. No Such Luck! All the falling edges were lined up! I found that inserting some delays between initializations would result in a fairly close line up of the leading edges. However, the values and positions of the delays don't seem to relate to the shift in the pulse positions. A key factor seems to be that two PWM outputs with the same pulse width line up nicely. DOH! if the pulses are the same width and the trailing edges line up so will the leading edges! I need to use different pulse widths because the ELINT system combined up to four receiver channels on a single downlink by using different pulse width for each channel. (80,120,160 and 200 microseconds high time with a nominal 1000microsecond Pulse Repitition Interval (PRI).

The simplified demo code is attached as are two scope screenshots. Does anybody know what's going on with the PWM initialization?

Code:
// test of Hardware PWM for POPPY oscilloscope simulation 
const char compileTime [] = "\n\nPWM Phase Test compiled on " __DATE__ " " __TIME__;

#define PWPIN0 19 // yellow
#define PWPIN1 18 // purple
#define PWPIN2 14 // blue
#define PWPIN3 15 // green

#define PRI0 1000   // 1.00 mSec

void InitPWM(bool withdelays){
  analogWriteResolution(10);  // 10 bits = 1024 steps
                              // Common to all channels
  analogWriteFrequency(PWPIN3, PRI0);
  if(withdelays){delayMicroseconds(10);}
  analogWriteFrequency(PWPIN2, PRI0);
  if(withdelays){delayMicroseconds(28);}
  analogWriteFrequency(PWPIN1, PRI0);
  analogWriteFrequency(PWPIN0, PRI0);

  analogWrite(PWPIN3, 200);   // 200 uSec
  analogWrite(PWPIN2, 160);   // 160 uSec
  analogWrite(PWPIN1, 80);    // 80 uSec
  if(withdelays){
    analogWrite(PWPIN0, 80);    // 80 uSec
  } else {
    analogWrite(PWPIN0, 200);    // 80 uSec 
  }
}

void setup() {
  Serial.begin(9600);
  delay(1000);  // wait for PC to connect
  Serial.println(compileTime);
  Serial.println("Starting PWM output");
  InitPWM(true);
}

void loop() {
  // Do 5 seconds with delays, then 5 seconds without them
  InitPWM(true);
  delay(5000);
  InitPWM(false);
  delay(5000);
} // end of loop()
 

Attachments

  • LE_Match.png
    LE_Match.png
    47.3 KB · Views: 13
  • TE_Match.png
    TE_Match.png
    49.8 KB · Views: 13
Quad timer 3 you say?
The quad timers have an enable register with bits for each of their 4 timers, they default to enabled so they start running as soon as their mode is set. Try adding TMR3_ENBL = 0; to the beginning of your InitPWM function and TMR3_ENBL = 0b1111; to the end, so that they all start synchronized.

(If they're not all part of quad timer 3 this obviously won't work...)
 
I want to simulate the display the ground station operators would see if they were receiving two channels of data from each of two satellites. The timing of the intercepted radar pulses could be used with Differential Time Of Arrival (DTOA) techniques to locate the emitting radars.

Does this mean you want two independent pairs of PWM signals, and be able to control the phase difference between the two signals in each pair? Or is there some relationship between all four? The eFlexPWM library has some good examples of controlling phase between signals in sets of two and three, though center-aligned by default.

https://github.com/epsilonrt/eFlexPwm
 
Yes, I want to have two pairs of two traces representing two different satellites receiving pulses from two different radars. One pair, representing the signals from one target will have 80uSec pulse widths. The other two traces will represent the same pair of satellites receiving pulses from a different radar and will be displayed with 160uSec pulses. Since the pulses from each radar will have unique PRIS, the trace from one satellite will not drift with relation to the other, but will be offset by the difference in path lengths from the radar to the satellites. The traces representing the second radar also won't drift with respect to the data from the other satellite but may drift A LOT with respect to the signals from the other radar if its PRI is significantly different from the first radar. Explaining all this is why I want pictures or a movie to show the traces.
 
Quad timer 3 you say?
The quad timers have an enable register with bits for each of their 4 timers, they default to enabled so they start running as soon as their mode is set. Try adding TMR3_ENBL = 0; to the beginning of your InitPWM function and TMR3_ENBL = 0b1111; to the end, so that they all start synchronized.

(If they're not all part of quad timer 3 this obviously won't work...)
I tried bracketing my initialization with the disable/enable code. All it did was remove the effects of the delays inserted between the initializations. The display reverted to having all the trailing edges aligned. I reverted to the RTFM mode and discovered that the aligned trailing edges were exactly as expected by the way the variable-frequency PWM mode is implemented. I've attached a screen shot from the reference manual that cleared things up for me.

tmr manual.png




More from the manual:

Control Register Bit 5 Count Length (Set for T4.x PWM)

This bit determines whether the counter:

• counts to the compare value and then re-initializes itself to the value specified in the LOAD (or
CMPLD2) register, or
• continues counting past the compare value to the binary roll over.

0b - Count until roll over at $FFFF and continue from $0000.

1b - Count until compare, then re-initialize. If counting up, a successful compare occurs when the counter reaches a COMP1 value. If counting down, a successful compare occurs when the counter reaches a COMP2 value. When output mode $4 is used, alternating values of COMP1 and COMP2 are used to generate successful comparisons. For example, the counter counts until a COMP1 value is reached, re-initializes, counts until COMP2 value is reached, re-initializes, counts until COMP1 value is reached, and so on.

(Note that the Output Mode is set to 4 for T4.x PWM)

Note that the Output Mode is set to 4 for T4.x PWM


Getting the output I wanted required these steps:

1. Turning off the output inversion bit in the status and control register.
2. Munging the PRI and High Pulse Width into a new value for analogWrite--basically swapping the COMP1 and COMP2 values.
3. Scaling the duty cycle from fixed uSecond values to the 1024-unit maximum

The results are shown in the attached screen shot. The new code follows:

Code:
// Revised test of Hardware PWM for POPPY oscilloscope simulation
//M. Borgerson 1/30/2026
const char compileTime [] = "\n\nRevised PWM Phase Test compiled on " __DATE__ " " __TIME__;

#define PWPIN0 19 // yellow
#define PWPIN1 18 // purple
#define PWPIN2 14 // blue
#define PWPIN3 15 // green

// Define Pulse Repetiton Intervals in microseconds
#define PRI0 1000   // 1.00 mSec
#define PRI1 1000   // 1.00 mSec
#define PRI2 1000   // 1.00 mSec
#define PRI3 1000   // 1.00 mSec

#define ANRESBITS 10
#define ANSTEPS   1024   // 2 ^ ANRESBITS
const uint16_t pulsewidths[4] = {80, 120, 160, 200};
float PinFreqs[4] = {1e6/PRI0, 1e6/PRI1, 1e6/PRI2, 1e6/PRI3};

// Calculate duty cycle, scale to analog write resolution
void SetAnWrite(uint16_t pin, uint16_t pri, uint16_t pwhi){
uint16_t pwdth;
  pwdth = ANSTEPS*(pri -pwhi)/pri;
  analogWrite(pin, pwdth);
}


// Some debugging output to help me figure out register settings
void ShowTMR3(void){
  Serial.printf("\n\nTMR3 Channel 0 Registers\n");
  Serial.printf("TMR3_LOAD0:   % 6D\n", TMR3_LOAD0); 
  Serial.printf("TMR3_CNTR0:   % 6D\n", TMR3_CNTR0);

  Serial.printf("TMR3_COMP10:  % 6D\n", TMR3_COMP10); 
  Serial.printf("TMR3_COMP20:  % 6D\n", TMR3_COMP20);

  Serial.printf("TMR3_SCTRL0:  0X%04X\n", TMR3_SCTRL0); 
  Serial.printf("TMR3_CTRL0:   0X%04X\n", TMR3_CTRL0);

  Serial.printf("TMR3 Channel 1 Registers\n");
  Serial.printf("TMR3_LOAD1:   % 6D\n", TMR3_LOAD1);
  Serial.printf("TMR3_CNTR1:   % 6D\n", TMR3_CNTR1);

  Serial.printf("TMR3_COMP11:  % 6D\n", TMR3_COMP11); 
  Serial.printf("TMR3_COMP21:  % 6D\n", TMR3_COMP21);

  Serial.printf("TMR3_SCTRL1:  0X%04X\n", TMR3_SCTRL1); 
  Serial.printf("TMR3_CTRL1:   0X%04X\n", TMR3_CTRL1); 
}

void InitPWM(bool showsetup){
  TMR3_ENBL = 0;   // Stop all four TMR3 timers
  analogWriteResolution(ANRESBITS);  // 10 bits = 1024 steps
                              // Common to all channels
  analogWriteFrequency(PWPIN3, PinFreqs[3]); // Using same PRI on all chans for demo
  analogWriteFrequency(PWPIN2, PinFreqs[2]); // PinFreqs are initialized from PRI
  analogWriteFrequency(PWPIN1, PinFreqs[1]);
  analogWriteFrequency(PWPIN0, PinFreqs[0]);
  // Switch the high and low parts of the pulses
  SetAnWrite(PWPIN3,PRI3, pulsewidths[3]);   // 200 uSec high
  SetAnWrite(PWPIN2,PRI2, pulsewidths[2]);   // 160 uSec
  SetAnWrite(PWPIN1,PRI1, pulsewidths[1]);   // 120 uSec
  SetAnWrite(PWPIN0,PRI0, pulsewidths[0]);    // 80 uSec
  // Invert the output bits on channels 0..3
  TMR3_SCTRL0 = TMR3_SCTRL0 ^ 0x0002;
  TMR3_SCTRL1 = TMR3_SCTRL1 ^ 0x0002;
  TMR3_SCTRL2 = TMR3_SCTRL2 ^ 0x0002;
  TMR3_SCTRL3 = TMR3_SCTRL3 ^ 0x0002;
  if(showsetup) ShowTMR3();
  TMR3_ENBL = 0b1111; // Start all channels at same time
  Serial.printf("PWM Started\n");
}

void setup() {
  Serial.begin(9600);
  delay(1000);  // wait for PC to connect
  Serial.println(compileTime);
  Serial.println("Seting up PWM output");
  InitPWM(true); // Show initial values before starting PWM
}

void loop() {
  // Do 5 seconds delay, then show registers 
  delay(5000);
  ShowTMR3();
} // end of loop()
 

Attachments

  • ScreenImg (1).png
    ScreenImg (1).png
    48.2 KB · Views: 13
Updated demo code to display two pairs of traces. One of each pair has a constant phase offset from the other. The one pair has a PRI 100nSec longer than the other, which causes the top pair of traces to scroll with relation to the bottom two.

Code:
// Revised test of Hardware PWM for POPPY oscilloscope simulation
// Add phase differences between traces  and small difference in PRIs
const char compileTime [] = "\n\nPWM Phase Test compiled on " __DATE__ " " __TIME__;

#define PWPIN0 19 // yellow
#define PWPIN1 18 // purple
#define PWPIN2 14 // blue
#define PWPIN3 15 // green

// Define Pulse Repetiton Intervals  in microseconds
#define PRI0 1000   // 1.00 mSec
#define PRI1 1000   // 1.00 mSec
#define PRI2 1000.1   // 1.00 mSec
#define PRI3 1000.1   // 1.00 mSec



#define ANRESBITS 10
#define ANSTEPS   1024   // 2 ^ ANRESBITS
const uint16_t pulsewidths[4] = {80, 120, 160, 200};
float PinFreqs[4] = {1e6/PRI0, 1e6/PRI1, 1e6/PRI2, 1e6/PRI3};

// Calculate duty cycle, scale to analog write resolution
void SetAnWrite(uint16_t pin, uint16_t pri, uint16_t pwhi){
uint16_t pwdth;
  pwdth = ANSTEPS*(pri -pwhi)/pri;
  analogWrite(pin, pwdth);
}


// Some debugging output to help me figure out register settings
void ShowTMR3(void){
  Serial.printf("\n\nTMR3 Channel 0 Registers\n");
  Serial.printf("TMR3_LOAD0:   % 6D\n", TMR3_LOAD0);
  Serial.printf("TMR3_CNTR0:   % 6D\n", TMR3_CNTR0);

  Serial.printf("TMR3_COMP10:  % 6D\n", TMR3_COMP10);
  Serial.printf("TMR3_COMP20:  % 6D\n", TMR3_COMP20);

  Serial.printf("TMR3_SCTRL0:  0X%04X\n", TMR3_SCTRL0);
  Serial.printf("TMR3_CTRL0:   0X%04X\n", TMR3_CTRL0);

  Serial.printf("TMR3 Channel 1 Registers\n");
  Serial.printf("TMR3_LOAD1:   % 6D\n", TMR3_LOAD1);
  Serial.printf("TMR3_CNTR1:   % 6D\n", TMR3_CNTR1);

  Serial.printf("TMR3_COMP11:  % 6D\n", TMR3_COMP11);
  Serial.printf("TMR3_COMP21:  % 6D\n", TMR3_COMP21);

  Serial.printf("TMR3_SCTRL1:  0X%04X\n", TMR3_SCTRL1);
  Serial.printf("TMR3_CTRL1:   0X%04X\n", TMR3_CTRL1);
}

void InitPWM(bool showsetup){
  TMR3_ENBL = 0;   // Stop all four TMR3 timers
  analogWriteResolution(ANRESBITS);  // 10 bits = 1024 steps
                              // Common to all channels
  analogWriteFrequency(PWPIN3, PinFreqs[3]); // Using same PRI on all chans for demo
  analogWriteFrequency(PWPIN2, PinFreqs[2]); // PinFreqs are initialized from PRI
  analogWriteFrequency(PWPIN1, PinFreqs[1]);
  analogWriteFrequency(PWPIN0, PinFreqs[0]);

  // Switch the high and low parts of the pulses and set width
  SetAnWrite(PWPIN3,PRI3, pulsewidths[2]);   // 160 uSec high
  TMR3_SCTRL3 = TMR3_SCTRL3 ^ 0x0002; // Clears the output inversion bit
  TMR3_ENBL = TMR3_ENBL | 0b1000; // Start trace 3

  SetAnWrite(PWPIN1,PRI1, pulsewidths[0]);   // 80 uSec
  TMR3_SCTRL1 = TMR3_SCTRL1 ^ 0x0002;
  TMR3_ENBL = TMR3_ENBL | 0b0010; // Start trace 1 with minimal delay

  delayMicroseconds(60);  // traces 2 and zero will start 60uSec later

  SetAnWrite(PWPIN2,PRI2, pulsewidths[2]);   // 160 uSec
  TMR3_SCTRL2 = TMR3_SCTRL2 ^ 0x0002;
  TMR3_ENBL = TMR3_ENBL | 0b0100; // Start trace  with 60uS delay

  SetAnWrite(PWPIN0,PRI0, pulsewidths[0]);    // 80 uSec
  TMR3_SCTRL0 = TMR3_SCTRL0 ^ 0x0002;
  TMR3_ENBL = TMR3_ENBL | 0b0001; // Start trace  with same 60uS delay

  //delayMicroseconds(30);

  if(showsetup) ShowTMR3();
//  TMR3_ENBL = 0b1111;
  Serial.printf("PWM Started\n");
}


void setup() {
  Serial.begin(9600);
  delay(1000);  // wait for PC to connect
  Serial.println(compileTime);
  Serial.println("Seting up PWM output");
  InitPWM(true);
}

void loop() {
  // Do 5 seconds with delays, then 5 seconds without them
 
  delay(5000);
  ShowTMR3();
 
} // end of loop()

Here are two links to videos:

Satellite transponding ELINT data
This one shows an overview of the satellite ELINT collection process. The ships on the surface send out radar signals that are intercepted by the satellite(s) and transponded to a ground station on Adak Island in the Aleutians chain. This is a fairly old animation made with MatLab and uploaded many months ago.


Scope recording
This short movie shows the results of the Teensy PWM signal output. The movie is a bit jerky as it is a screen capture screen made with the Windows snipping tool. Apparently, my 5-year-old PC has trouble keeping up with network transmission from the scope, through my router to the PC, displaying the MP4 data on the screen, capturing the screen image and storing it in a file. There's probably a better way, but dinner awaits.
 
Since I set up and started all the PWM outputs in quick sequence, I expected all the outputs to rise within a few microseconds of each other. No Such Luck! All the falling edges were lined up!
Doesn't surprise me at all. Pretty standard for a PWM peripheral in fast mode, initial state low, goes high when the compare register matches, resets on the end of cycle. Its been a while since looking at the T4 PWM hardware but I'm pretty sure it can be configured to invert the output sense.
 
Back
Top