How to reset Teensy4.0 timers for phase syncing PWM signals

Status
Not open for further replies.

kdharbert

Well-known member
The Teensy4.0 has far more timers than the 3.6 which enables some neat use cases I'm pursuing. However, I need to be able to resync the PWM phase (with respect to an external source) by restarting the timers for each pin. I've seen one other thread addressing this for 3.x. Based on that thread it seems at least one way to do this is to call pinmode with the same settings that pin already had and then call analogwrite again to reset the timer for that pin. Can anyone confirm how this will behave?

I need to be able to sync as many pins as possible as closely as possible, so if there are ways to directly manipulate registers etc to speed the process it would help out a lot. Are there reliable ways to do this easily? Any chance a bank of timers can be reset with one command?
 
Based on that thread it seems at least one way to do this is to call pinmode with the same settings that pin already had and then call analogwrite again to reset the timer for that pin. Can anyone confirm how this will behave?

That will not work. You'll get the same PWM waveforms when the hardware switches back from GPIO to timers controlling those pins.


so if there are ways to directly manipulate registers etc to speed the process it would help out a lot. Are there reliable ways to do this easily? Any chance a bank of timers can be reset with one command?

Yes, there are ways, but I don't believe many people would use the word "easily" regarding reading the FlexPWM chapter in the reference manual.

https://www.pjrc.com/teensy/datasheets.html

The 16 FlexPWM timers are arranged in 4 groups of 4. So you want to use PWM pins controlled by the same group of 4. See this page for details:

https://www.pjrc.com/teensy/td_pulse.html

Scroll down to "PWM Frequency" for the info about which pins are controlled by which timers. You probably want to use pins from those first 4 lines, so you're using all the FlexPWM1 timers.
 
I already found the Teensy PWM page and chose my pins specifically to have different timers. I was able to measure perfect behavior, so I'm over pin\timer conflicts. I just need to phase-correct the waveforms with respect to an external input.

As you suggested, the FlexPWM documentation is definitely too much to bench, but maybe you can help me on the basics:

It seems like resetting the PWM phase would be a one-liner...its just figuring out what that one-liner is. Is it more complicated than this?

You suggested using the same group of four FlexPWM timers, I thought there might be some use to that but I don't understand why. Can you elaborate?
 
In the reference manual, turn to "55.4.2.2 Register Reload Logic" on page 3047.

sc.png

Also on the next page is a similar diagram about synchronization. The point is each group of 4 "submodule" timers can be configured to act independently, which is the way we normally use them, or you can program that group of 4 submodule timers to all sync to the first timer in the group.

Also pay close attention to the MCTRL register documented starting on page 3134. The 4 bit LDOK field is particularly important. Each submodule timer is controller by 6 double buffered registers. These 4 bits control the PWM setting can load. Again, normally we use these 4 submodules are independent timers, so when writing to this register we use only values 1, 2, 4, 8 with LDOK, to update exactly 1 of the submodules at a time (but all the PWM values for that submodule take effect at that moment, regardless of the code's speed to write to those 6 registers). You will probably use LDOK differently.

I can't tell you exactly how to use the many advanced but complex FlexPWM features. In fact, I don't even have a perfectly clear idea of what you're really trying to accomplish. The best I can do is help point you to the specific parts of the FlexPWM documentation that are most likely the features you need. Hopefully this can at least help give you a foothold in the mountainous FlexPWM chapter.
 
Also, as you read the FlexPWM chapter you'll see info about fractional cycle stuff. Teensy 4.0 & 4.1 do *NOT* have that feature. You can ignore all that fractional cycle info. All those fractional value registers with "FRAC" in the middle of their names are not actually implemented.

NXP could have made this clearer, or they could have left all that irrelevant info out of the manual, but they didn't. At least knowing this now can save you some time. Ignore all that fractional value documentation.
 
Thanks for the replies. My use case is simple: I need to have a pin output a square wave at a particular frequency and be able to restart the duty cycle so I can sync its phase based on external input. I already used AnalogWriteFrequency to set the frequency, I just need a command to handle the reset. Nothing fancy past that.

So far we've only discussed the FlexTimers. Is there a quick explanation of the difference between the FlexTimers and QuadTimers? Any chance they might be easier to use?
 
I took another run at the documentation and I'm still short of a solution for what I thought would be a simple problem of resetting a timer count to zero.

I asked about pinmode and analogwrite above...is there any chance analogwritefrequency might be able to used to reset the timer count?

Also, it seems plausible that there's a terminology conflict here. I said I wanted to reset the timer, but I meant that I wanted to set the timer count to zero and not reconfigure the timer in any other way.
 
Maybe you can use the FORCE bit in the submodule CTRL2 register? And of course write zero to the INIT register, or whatever value you want the counter to become when you write a 1 to that bit.

sc1.png


sc2.png
 
Excellent. This appears to be exactly what I need, especially with the motor commutator explanation included.

I'll need some help with the code. I poked around the Teensy4 core files and for code fragments to work from, but nothing popped out at me.
So far, I'm working from this thread:
https://community.nxp.com/thread/490619
Are the register manipulations going to take on the same form?

According to the doc fragment above, it seems I will need to set FORCE and FORCE_SEL bits only once and then FRCEN when I need the count set to zero. After the setting to zero, count will increment as normal to generate the PWM wave forms without the need for further register manipulation. Is this correct?
 
I'll need some help with the code.

Please be specific when you need more specific help. Show the complete code you're already trying that isn't yet working. "Complete" means anyone where can copy it into Arduino and run it on their Teensy to see the same results you're getting.
 
I need code examples for the FlexPWM register manipulation. I don't have any code for that yet. I posted a link to stuff I found online above, but it is for a different NXP part. I've pasted a code fragment below. The code fragment references the same register names as the doc snippets above, so it seems like the code needed for the NXP parts in the Teensy 4 will be quite similar. My question above is to confirm this and fish for any obvious changes people are aware of.



static void FlexPWM_1_Init(void) // 10khz PWM for inverter
{
FlexPWM_1.OUTEN.R = 0x770; // enable A and B outputs on submodule 0,1,2

/* Submodule 0 Initialisation */
FlexPWM_1.SUB[0].CTRL1.R = 0x0404; // full cycle reload, every opportunity,IPBus/1, LDMOD=1
FlexPWM_1.SUB[0].CTRL2.R = 0xa080; // debug and force enable
FlexPWM_1.SUB[0].INIT.R = 0;
FlexPWM_1.SUB[0].VAL1.R = 1000; // PWM modulo
FlexPWM_1.SUB[0].VAL2.R = 0; // PWM 0A rising edge
FlexPWM_1.SUB[0].VAL3.R = 250; // PWM 0A falling edge
FlexPWM_1.SUB[0].VAL4.R = 0; // PWM 0B rising edge
FlexPWM_1.SUB[0].VAL5.R = 250; // PWM 0B falling edge
FlexPWM_1.SUB[0].DTCNT0.R = 0; // deadtime values
FlexPWM_1.SUB[0].DTCNT1.R = 0; // deadtime values
FlexPWM_1.SUB[0].DISMAP.R = 0x0000; // disable fault pin condition

/* Submodule 1 Initialisation */
FlexPWM_1.SUB[1].CTRL1.R = 0x0404; // full cycle reload, every opportunity,IPBus/1, LDMOD=1
FlexPWM_1.SUB[1].CTRL2.R = 0xa080; // debug and force enable
FlexPWM_1.SUB[1].INIT.R = 0;
FlexPWM_1.SUB[1].VAL1.R = 1000;//500; // PWM modulo
FlexPWM_1.SUB[1].VAL2.R = 0; // PWM 1A rising edge
FlexPWM_1.SUB[1].VAL3.R = 500; // PWM 1A falling edge
FlexPWM_1.SUB[1].VAL4.R = 0; // PWM 1B rising edge
FlexPWM_1.SUB[1].VAL5.R = 500; // PWM 1B falling edge
FlexPWM_1.SUB[1].DTCNT0.R = 0; // deadtime values
FlexPWM_1.SUB[1].DTCNT1.R = 0; // deadtime values
FlexPWM_1.SUB[1].DISMAP.R = 0x0000; // disable fault pin condition

/* Submodule 2 Initialisation */
FlexPWM_1.SUB[2].CTRL1.R = 0x0404; // full cycle reload, every opportunity,IPBus/1, LDMOD=1
FlexPWM_1.SUB[2].CTRL2.R = 0xa080; // debug and force enable
FlexPWM_1.SUB[2].INIT.R = 0;
FlexPWM_1.SUB[2].VAL1.R = 1000;//500; // PWM modulo
FlexPWM_1.SUB[2].VAL2.R = 0; // PWM 2A rising edge
FlexPWM_1.SUB[2].VAL3.R = 250; // PWM 2A falling edge
FlexPWM_1.SUB[2].VAL4.R = 0; // PWM 2B rising edge
FlexPWM_1.SUB[2].VAL5.R = 250; // PWM 2B falling edge
FlexPWM_1.SUB[2].DTCNT0.R = 0; // deadtime values
FlexPWM_1.SUB[2].DTCNT1.R = 0; // deadtime values
FlexPWM_1.SUB[2].DISMAP.R = 0x0000; // disable fault pin condition

FlexPWM_1.MCTRL.R = 0x0f00; // Submodule 1 PWM generator enabled
FlexPWM_1.MCTRL.B.LDOK = 0xf; /* Load the PRSC bits of CTRL1 and the INIT, and VALx registers into a set of buffers */
}
 
You have to start with something, even if just an empty program with just 1 analogWrite() line. Then we could at least see which pin number! (and we can start talking about actual registers instead of hypotheticals without knowing which flexpwm and which submodule)

If you can't write any code yet, start with just an empty program with 1 or 2 analogWrite() line and connect whatever equipment you have to monitor the PWM signal. If a scope or logic analyzer, take a screenshot. The key point is to get started with something, rather than endlessly talk about documentation and hypothetical scenarios.
 
Ok...the full program is below.
The two pins I'm seeking to reset are 22 and 23 which are both on FlexPWM4.

The attempt to resync PWM happens after this line:
if((command_buf & 0x000000000000FF00) == 0x0000000000000000){ //resync CW

I can see the test output 'Y' in the serial, indicating that the if statement is entered.

I have my test code for pinmode, analogwrite, and analogwrite frequency present...but all seem to do nothing.

With a scope, I can see the PWM pattern 'scroll' by WRT to the sync command very steadily. This is just the expected clock skew and no resyncing is observed.



#include<ADC.h>
#include<ADC_Module.h>

#include<i2c_t3.h>
#include<SPI.h>
#include<stdio.h>

//#include "filter_coefficients.h"

long int CPU_MARK;
long int CPU_CYCS;

char buf[20];

int sample_count=-1;
int tone_count=-1;

IntervalTimer piezoTimer;
IntervalTimer pingTimer;
IntervalTimer US_Tx_Timer;

//working

//reserve pin1 for future return channel
const int BEAT_ACK_PIN = 2;
const int RX_433_PIN = 0;
unsigned int byte_433=0;

int loop_count=0;
long int beat_count = 0;
int isr_count = 0;

unsigned long long command_buf=0;
unsigned long long command_long=0;
unsigned int command_byte=0;
unsigned int recieved_bytes=0;

int sampling_rate=200000; //ideally a multiple of both tone_freq and tone_freq+diff_freq
const int LUT_size=12500;//at least 1/32 of a sec long = 6250 samples
int COS_LUT[12500];

int tone_freq;
int tone_freqs[2]={6240, 2240};//must be a multiple of diff freq. set for targets =20.
int diff_freq=32;

int begin_count=0;

int ping_count=0;

int address_pin[4]={2,3,4,5};
unsigned long long target_address=0;

int US_Tx_PINs=4;
int US_Tx_PIN[]={23,22,14,15};
uint32_t US_Tx_Freq[]={40000,40032,39488,39456};

void setup() {

ARM_DEMCR |= ARM_DEMCR_TRCENA;
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

for(int i=0;i<4;i++){
pinMode(address_pin,INPUT_PULLUP);
target_address=(target_address<<1)+digitalRead(address_pin);
}

pinMode(RX_433_PIN,INPUT);
pinMode(BEAT_ACK_PIN,OUTPUT);

for(int i=0;i<US_Tx_PINs;i++){
pinMode(US_Tx_PIN,OUTPUT);
analogWriteFrequency(US_Tx_PIN,US_Tx_Freq);
}


tone_freq=tone_freqs[digitalRead(address_pin[3])];
compute_LUT();

Serial.begin(9600);
Serial1.begin(4800);

sprintf(buf,"%08llX ",target_address);
Serial.println(buf);
Serial.println(tone_freq);
analogWriteResolution(8);


//digitalWrite(US_TX_PIN,1);
piezoTimer.begin(SAMPLE_ISR, 1000000/(sampling_rate));
US_Tx_Timer.begin(US_Tx_ISR, 1000000/80000);

for(int i=0;i<US_Tx_PINs;i++){
// analogWrite(US_Tx_PIN,128);

}


}

void US_Tx_ISR(void){
if(ping_count>0){
if(ping_count--==0){
digitalWrite(US_Tx_PIN[0],0);
}
else{
digitalWrite(US_Tx_PIN[0],ping_count%2);
}
}
if(tone_count>0){
if(tone_count--==0){
//stop tone
}
}
}

void SAMPLE_ISR(){
CPU_CYCS = ARM_DWT_CYCCNT-CPU_MARK;
CPU_MARK = ARM_DWT_CYCCNT;



//change this...
if (sample_count < LUT_size*100 && (sample_count!=-1)){ //~3 seconds

// analogWrite(DAC_PIN0,COS_LUT[(sample_count)%LUT_size]);
sample_count++;
}

}

void loop() {

loop_count++;
if(Serial1.available()){
while(Serial1.available()){
byte_433=(unsigned int)Serial1.read();
command_buf=command_buf<<8;
command_buf=command_buf+byte_433;
//Serial.println(byte_433);
command_byte++;
recieved_bytes++;
}
if(command_buf == (0xFFFFFFFFFFFFFFFF)){//start byte
command_byte=0;
command_long=command_buf;
digitalWrite(BEAT_ACK_PIN,HIGH);
begin_count++;
}
}
if(command_byte>7) {

digitalWrite(BEAT_ACK_PIN,HIGH);
//process command
if(command_buf!=0x00){

sprintf(buf,"%08llX ",command_buf);
// Serial.println(buf);

if((command_buf & 0x00000000000000FF) == target_address){ //found this target's address...
beat_count++;

if((command_buf & 0x000000000000FF00) == 0x0000000000000000){ //resync CW
tone_count=3*80000;
//PWM

for(int i=0;i<US_Tx_PINs;i++){
pinMode(US_Tx_PIN,INPUT);///how to make the timer reset???
pinMode(US_Tx_PIN,OUTPUT);
analogWrite(US_Tx_PIN,128);
analogWriteFrequency(US_Tx_PIN,US_Tx_Freq);
}
Serial.println('Y');
}
else if((command_buf & 0x000000000000FF00) == 0x0000000000000100){ //stop CW
sample_count=-1;
}
else if((command_buf & 0x000000000000FF00) == 0x0000000000001000){ //begin ping
Serial.println('X');

//tone(US_Tx_PIN[0],40000,1); //this line is not working.
sample_count=-1;
ping_count=20;
}
command_long=command_buf;
}

}
command_byte=0;
}
else{
digitalWrite(BEAT_ACK_PIN,LOW);
}

if(loop_count % 500000 ==0){
Serial.print(begin_count);
Serial.print(' ');
Serial.print(beat_count);
Serial.print(' ');

sprintf(buf,"%08llX ",command_long);
Serial.print(buf);
//Serial.print("%llux",command_long);

Serial.print(' ');

Serial.println(beat_count);

}

}

void compute_LUT(){
for(int i=0;i<LUT_size;i++){
//COS_LUT=2047+floor(1024*cos(2*PI*i*(tone_freq)/sampling_rate))+floor(1024*cos(2*PI*i*(tone_freq)/sampling_rate));
COS_LUT=1600+floor(350*cos(2*PI*i*(tone_freqs[0])/sampling_rate))+
floor(350*cos(2*PI*i*(tone_freqs[0]+diff_freq)/sampling_rate))+
floor(350*cos(2*PI*i*(tone_freqs[1])/sampling_rate))+
floor(350*cos(2*PI*i*(tone_freqs[1]+diff_freq)/sampling_rate));
//COS_LUT=2047+floor(2047*cos(2*PI*i*6880/44100));
}

}

unsigned int interpolate(int val){
int inval=val;
int outval;
outval=0;
for(int i=0;i<16;i++){
outval=outval+((inval & 0x00000001)<<(2*i));
outval=outval+((inval & 0x00000001)<<(2*i+1));
inval=inval>>1;
}
return outval;

}
 
First, consider the PWM pins you've chosen.

Code:
int US_Tx_PIN[] = {23, 22, 14, 15};

2 of these are on FlexPWM4, but the other 2 are on QuadTimer3, which isn't even the same type of timer!

Details about which pins are controlled by which timers can be found on this page.

https://www.pjrc.com/teensy/td_pulse.html

and also with more detail in this comments in this code:

https://github.com/PaulStoffregen/c...83a6fd5888e8660b0fae078297f/teensy4/pwm.c#L19

As I tried to explain earlier, you should choose pins which all are controlled by the same FlexPWM timer.


Second, this is a large program with a lot of extra stuff not essential to getting the PWM sync to work. Please trim it down. Also, make the command triggered by the serial monitor, so we can help you without having to connect another board that sends a command to Serial1. The easier you make this program for anyone here to run and see the results, the better we'll be able to help you.
 
As I said, only 23 and 22 are under test presently and they are both on FlexPWM4.
I'll trim the code down and post it, but at the same I'll still need syntax for the register writes eventually. Any help there?
 
Here's slimmer code:
Pressing enter in the serial interface executes the attempted timer reset on pins 22 and 23. If that works, you should observe the phase difference between their waveforms reset to zero and their their individual phases reset to zero. Instead, I observe no change.


long int CPU_MARK;
long int CPU_CYCS;

int US_Tx_PINs=2;
int US_Tx_PIN[]={23,22};
uint32_t US_Tx_Freq[]={40000,40032};

int loop_count=0;
void setup() {

ARM_DEMCR |= ARM_DEMCR_TRCENA;
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;

for(int i=0;i<US_Tx_PINs;i++){
pinMode(US_Tx_PIN,OUTPUT);
analogWriteFrequency(US_Tx_PIN,US_Tx_Freq);
}

Serial.begin(9600);

}

void loop() {

loop_count++;
if(Serial.available()){
Serial.println(Serial.read());
for(int i=0;i<US_Tx_PINs;i++){
pinMode(US_Tx_PIN,INPUT);///how to make the timer reset???
pinMode(US_Tx_PIN,OUTPUT);
analogWrite(US_Tx_PIN,128);
analogWriteFrequency(US_Tx_PIN,US_Tx_Freq);
}

}

if(loop_count % 500000 ==0){
Serial.println(loop_count);

}

}
 
Are you using an oscilloscope or logic analyzer to view the waveforms on pins 22 or 23? Or if not directly viewing the waveforms, how will you confirm whether this works?
 
Ok, I ran it here and verified the FRCEN & FORCE bits really do work with my oscilloscope. I added code to pulse pin 3, so I can trigger the scope on the moment it writes to those bits.

file.png

Code:
long int CPU_MARK;
long int CPU_CYCS;

int US_Tx_PINs = 2;
int US_Tx_PIN[] = {23, 22};
uint32_t US_Tx_Freq[] = {40000, 40032};

int loop_count = 0;
void setup() {
  pinMode(3, OUTPUT); // pin 3 to trigger oscilloscope
  ARM_DEMCR |= ARM_DEMCR_TRCENA;
  ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
  for (int i = 0; i < US_Tx_PINs; i++) {
    analogWriteFrequency(US_Tx_PIN[i], US_Tx_Freq[i]);
    analogWrite(US_Tx_PIN[i], 128);
  }
  Serial.begin(9600);
}

void loop() {
  loop_count++;
  if (Serial.available()) {
    Serial.println(Serial.read());
    digitalWriteFast(3, HIGH);
    FLEXPWM4_SM0CTRL2 |= FLEXPWM_SMCTRL2_FRCEN | FLEXPWM_SMCTRL2_FORCE; // pin 22
    FLEXPWM4_SM1CTRL2 |= FLEXPWM_SMCTRL2_FRCEN | FLEXPWM_SMCTRL2_FORCE; // pin 23
    delayMicroseconds(10);
    digitalWriteFast(3, LOW);
    delayMicroseconds(10);
  }
  if (loop_count % 500000 == 0) {
    Serial.println(loop_count);
  }
}
 
Interrupts in general or just interrupts pertaining to those pins?
I'll be using Intervaltimer objects, and the PWM adjustments may need to be made within one of those ISRs, so it looks like I'll need to address this.
Lemme know what I can read.
 
This is great, I've been looking for examples of using the registers to control the PWM timers. Can you point me to the required library/header files for the "FlexPWM_1.SUB[0]" functions ?
 
Status
Not open for further replies.
Back
Top