PWM @ 1ghz 47%-53% duty issue

Status
Not open for further replies.

martianredskies

Well-known member
I'm generating a PWM clock that varies between 1mhz to 191khz according to a midi cc controller. Ive been reading up on duty-cycles in variable pwm situations and have tried to design this in respect to the f_bus (60mhz t3.6), my understanding is that even divisions of f_bus allow pwm to stay at 50% duty?

What I've done is filled a 127 slot array with even divisions of 60,000,000 (60, 62, 64, 66, etc)

Code:
void setup() {

  Serial.begin(9600);

  for (byte i = 0; i < 128; i++) {
    double divider = (i * 2) + 60;
    clock_Array [i] = 60000000 / divider;
  }

I've verified reading out the array after to serial that it is filling to with the right values, i.e;

16:49:38.783 -> 1000000.0000000000
16:49:38.783 -> 967741.9354838709
16:49:38.783 -> 937500.0000000000
16:49:38.783 -> 909090.9090909090
16:49:38.783 -> 882352.9411764706
16:49:38.783 -> 857142.8571428571
16:49:38.783 -> 833333.3333333333

The issue is that I am not measuring 50% duty cycle near the high end of the clock, the duty reads duty+ 46.9%, duty - 53.1%. As i slow down the pwm clock the duty gets closer to 50/50, but not at the high end. Also, is this normal for a pwm out signal @ 1mhz, was hoping for something more clean. See;

https://ibb.co/8bsZCsj

Everything is going well for the most part, but i am getting some glitches in the instrument I'm controlling, sometimes it crashes. Trying to rule out duty issues.

the code i'm using to write on midi cc changes is simply;

Code:
analogWrite(PWMpin1, 0);
analogWriteFrequency(PWMpin1, clock_Array[value]); //
analogWrite(PWMpin1, 127);

Any ideas about the irregular duty cycle at 1mhz end of the scale?

Thanks in advance
 
2 issues: First, there are visible "ringing" peaks, your oscilloscope probe might not be well compensated and thus the measurement slightly wronged. Second, with using analoigWriteFrequency and analogWrite, you will always get small rounding errors since the FTM modulo and compare values can only be integers. If the mod value is an uneven number, there is no cmp value for an exact 50:50 duty cycle.
If I were you, I'd not use these high-level functions but write optimized values directly to the timer registers.
 
so ignore the analogWriteFrequency and analogWrite functions and create a pwm signal directly by writing to the registers? This is a bit over my head, but I'm good at running with examples. If I have a PWM signal coming out pin 3 (FTM1), any pointers on creating a basic 50/50 1mhz clock. I've tried searching the forums but theres not any examples i can see.

Thanks for the reply
 
The strategy is:
1 Setting the FTM1 clock source to F_BUS
2 Setting the FTM1 MOD register to 59 (60-1) because the 0 counts, will give 1MHz clock
3 Setting the FTM1_CH0 CMP register to 29 or 30 (do not remember if the 0 counts here or not, try which is closer to 50:50)
Complete the timer setup and pin mux according to the source code of the analogWrite() function in the Teensyduino core files.
 
I tried this just now (since you didn't give a complete program for testing):

Code:
void setup() {
  analogWriteFrequency(6, 1000000);
  analogWrite(6, 128);
}

void loop() {
}

Here's the signal my oscilloscope sees on pin 6.

file.png

Admittedly 49.88% isn't *exactly* 50%, but pretty close.... nothing like 46.9% you're seeing.

Maybe there's something wrong with your code? Or maybe you're not measuring accurately, like using a hand-held multimeter which isn't capable of accurate duty cycle measurements at higher frequencies?

EDIT: oh, I see now you're using a Hantek USB scope, and you're using 127 for analogWrite. Use 128 for 50%, and keep in mind those Hantek USB products are notorious for poor quality measurements on higher frequency signals.
 
I’ll use 128, thanks. Maybe the hantek is off calibration, i did open it up and adjust the variable capacitors for its 1khz test signal a few weeks back. Do people calibrate using higher test frequencies, 1mhz?
 
The strategy is:
1 Setting the FTM1 clock source to F_BUS
2 Setting the FTM1 MOD register to 59 (60-1) because the 0 counts, will give 1MHz clock
3 Setting the FTM1_CH0 CMP register to 29 or 30 (do not remember if the 0 counts here or not, try which is closer to 50:50)
Complete the timer setup and pin mux according to the source code of the analogWrite() function in the Teensyduino core files.

I'm trying another approach first just because my knowledge of registers is infantile atm. Since the t3.6 is now generating the 1mhz master clock upstream in the instrument, the keyboard assigner CMOS ic still is producing the 54us sync clock (1us pulse). All the signals in the keyboard happen within this 54us period, so I wanted to read the 54us sync clock @ a t3.6 input pin and use it as an interrupt. However, for the life of me i cannot get it to work.

The sync input pin is setup with;

Code:
#define SYNC_CK_IN 27 // variable for 54us Sync clock in pin

I've defined a volatile variable;

Code:
volatile unsigned int globalFiredFlag = 0;

in void.setup i've setup the interrupt function;

Code:
pinMode(SYNC_CK_OUT, OUTPUT);
pinMode(SYNC_CK_IN, INPUT);
attachInterrupt(digitalPinToInterrupt(SYNC_CK_IN), ISR_FUNCTION, RISING);

interrupt function;

Code:
void ISR_FUNCTION() {
globalFiredFlag = 1;
}

and then in the main loop i have simple test, just trying to drive another output pin;

Code:
void loop() {
  if (globalFiredFlag == 1) {
  digitalWriteFast(SYNC_CK_OUT, HIGH);
  globalFiredFlag = 0;
  }
  else{
    digitalWriteFast(SYNC_CK_OUT, LOW);
  }
  
}

I've tested the pin is fine using digitalReadFast and digitalWriteFast to mirror the input to the output, and the scope signal looks ok (hovering slightly above 0v), but it's just not being detected. According to the forum the above test should work as others said it worked fine, but for me, nope.

Thanks
54usSYnc.jpg
 
Again, you've shown pieces of code, but no single complete program which I or others can copy into Arduino and run on a Teensy to see the problem. This time, I'm not going to write a program or try to guess how to put these pieces together (even if that'd be trivial). Please, follow our "Forum Rule". This type of conversation is perfectly good, but you *also* need put a complete copy of the test program in your message.
 
Again, you've shown pieces of code, but no single complete program which I or others can copy into Arduino and run on a Teensy to see the problem. This time, I'm not going to write a program or try to guess how to put these pieces together (even if that'd be trivial). Please, follow our "Forum Rule". This type of conversation is perfectly good, but you *also* need put a complete copy of the test program in your message.

Hi Paul,

thanks, heres the code in full. I'm just trying to get the interrupt to write a pin high/low, siiliar if you were polling with digitialReadFast and digitalWriteFast. I assume the avr headers are only relvant to the 8-bit teensy's, but it's in the documentation section on interupts.

Code:
#include <MIDI.h>
#include <midi_UsbTransport.h>

#include <avr/io.h>
#include <avr/interrupt.h>

template<class T> inline Print &operator <<(Print &obj, T arg) {
  obj.print(arg);
  return obj;
}

#define LED 13 // variable for LED pin
#define K1_IN 11 // variable for K1 in pin
#define K2_IN 12 // variable for K2 in pin
#define K3_IN 24 // variable for K3 in pin
#define K4_IN 25 // variable for K4 in pin
#define MASTER_CK_IN 26 // variable for Master 1mhz clock in pin
#define SYNC_CK_IN 27 // variable for 54us Sync clock in pin
#define USI_POLY_OUT 28 // variable for Serial Poly Out pin
#define SI_ORGAN_OUT 29 // variable for Serial Organ Out pin
#define SYNC_CK_OUT 32 // variable for Serial Organ Out pin

const int PWMpin1 = 3; //FTM1 timer

volatile unsigned int globalFiredFlag = 0;

boolean pwmduration;

float clock_Array[128];

void setup() {

  Serial.begin(9600);

  for (byte i = 0; i < 128; i++) {
    float divider = (i * 2) + 60;
    clock_Array [i] = 60000000 / divider;
  }

  pwmduration = true;

  attachInterrupt(digitalPinToInterrupt(SYNC_CK_IN), ISR_FUNCTION, RISING);

  usbMIDI.setHandleControlChange(OnCC); // set handle for MIDI continuous controller messages
  pinMode(PWMpin1, OUTPUT);
  pinMode(LED, OUTPUT); // set LED pin to output
  pinMode(K1_IN, INPUT);
  pinMode(K2_IN, INPUT);
  pinMode(K3_IN, INPUT);
  pinMode(K4_IN, INPUT);
  pinMode(MASTER_CK_IN, INPUT);
  pinMode(SYNC_CK_IN, INPUT);
  pinMode(USI_POLY_OUT, OUTPUT);
  pinMode(SI_ORGAN_OUT, OUTPUT);
  pinMode(SYNC_CK_OUT, OUTPUT);

  analogWrite(PWMpin1, 0);
  analogWriteFrequency(PWMpin1, 1000000); // set freq to 1mhz
  analogWrite(PWMpin1, 128);
}

void loop() {
  usbMIDI.read(); // read the USB MIDI bus every loop
  if (globalFiredFlag == 1) {
  digitalWriteFast(SYNC_CK_OUT, HIGH);
  globalFiredFlag = 0;
  }
  else{
    digitalWriteFast(SYNC_CK_OUT, LOW);
  }
  
}

void ISR_FUNCTION() {
globalFiredFlag = 1;
}

void OnCC(byte channel, byte controller, byte value) {
  if (controller == 1) {
    pwmduration = false;
    analogWrite(PWMpin1, 0);
    analogWriteFrequency(PWMpin1, clock_Array[value]); //
    analogWrite(PWMpin1, 128);
    pwmduration = true;
  }
}
 
Use of pinMode() after attachInterrupt() is effectively turning off the interrupt capability of that pin. Move the attachInterrupt function to the end of setup(), or anywhere after pinMode(SYNC_CK_IN, INPUT);

Also, something to keep in mind is the massive variability of pulse widths this sort of code can produce. Using digitalWriteFast under optimal conditions can create pulses which are much too short for your Hantek USB scope. Even my 200 MHz Keysight DSOX4024A scope struggles to measure the fastest possible pulses. But structuring this code this way in loop() can also result in quite long pulses, depending on the timing of the USB code and yield() and other stuff, not to mention how it'll change as you add more to your loop() function.
 
Last edited:
Use of pinMode() after attachInterrupt() is effectively turning off the interrupt capability of that pin. Move the attachInterrupt function to the end of setup(), or anywhere after pinMode(SYNC_CK_IN, INPUT);

Also, something to keep in mind is the massive variability of pulse widths this sort of code can produce. Using digitalWriteFast under optimal conditions can create pulses which are much too short for your Hantek USB scope. Even my 200 MHz Keysight DSOX4024A scope struggles to measure the fastest possible pulses. But structuring this code this way in loop() can also result in quite long pulses, depending on the timing of the USB code and yield() and other stuff, not to mention how it'll change as you add more to your loop() function.

Thanks, that worked adding it at the end. I'll keep the main loop relatively clean, does usbMIDI.read need to be in there or can it be flagged also by interrupts? Is the idea to keep as much out of the main loop as possible and just trigger functions?
 
Is it possible to watch a pwm output (lets say up to 1mhz) and increment a counter on every positive clock pulse?

I’m using pin 3 FTM1 timer (linked to only pin 3 & 4) to generate a variable pwm clock up to 1mhz. I’d like to use a counter (on every positive cycle) to read a array location.
 
Dunno if it's meaningful in your case, but this is what I do to generate a 1MHZ signal with 50% duty for a 6502 cpu

Code:
#define SIG_CLOCK 33 // is PORT E BIT 24 so E24
volatile uint32_t count;
volatile bool isr_flag;

void setup() {
    pinMode(SIG_CLOCK, OUTPUT);
    FTM0_SC = 0;
    FTM0_CNT = 0;
    FTM0_MOD = 29; 2 MHZ
    count = 0;
    isr_flag = false;
    NVIC_SET_PRIORITY(IRQ_FTM0, 64);
    NVIC_ENABLE_IRQ(IRQ_FTM0);
    FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0) | FTM_SC_TOIE;
}

FASTRUN void ftm0_isr(void) {
    FTM0_SC &= ~FTM_SC_TOF; // clear the interrupt overflow flag
    if (isr_flag) {
       GPIOE_PDOR |= (1 << 24); // Clock HI
       count++;
    } else {
       GPIOE_PDOR &= ~(1 << 24); // Clock LOW
    }
    isr_flag = !isr_flag;
}

This kind of code gives me a 1MHZ 50% duty on my cheap logic analyzer.
With the count variable you count the HI cycles
I've extracted the code above from my code, so take it as pseudo code, it's un tested and I don't even know if it's done the right way...
Any advice on FTM ISRs welcome
 
Thanks Tactif,

Examples are always very helpful. Which teensy model did you use this for? I’ll see if i can do a search in the datasheet to see if i can easily adapt this to pin 3.
 
i think i’m gonna have to learn about using FTM timers, as Theramingenieur mentioned. It’s just really heavy stuff for a novice, and not many examples around to get the ball rolling.

getting an incremental counter from the same source that is generating the pwm, as it happens, that’s the challenge. I think i’m only gonna have frustration trying to read it from another pin at 1mhz speeds.
 

I think i’m only gonna have frustration trying to read it from another pin at 1mhz speeds.

There is an example of FreqCount on the web that makes it work easily. I've done it recently without trouble. And 1 MHz is well below pushing the count in any way - the web page suggests "up to 65 MHz with Teensy 3.0 & 3.1"

Example Program
Open from the menu: File > Examples > FreqCount > Serial_Output
Code:
#include <FreqCount.h>

void setup() {
  Serial.begin(57600);
  FreqCount.begin(1000);
}

void loop() {
  if (FreqCount.available()) {
    unsigned long count = FreqCount.read();
    Serial.println(count);
  }
}
 
i think i’m gonna have to learn about using FTM timers, as Theramingenieur mentioned. It’s just really heavy stuff for a novice, and not many examples around to get the ball rolling.

getting an incremental counter from the same source that is generating the pwm, as it happens, that’s the challenge. I think i’m only gonna have frustration trying to read it from another pin at 1mhz speeds.

Yeah, learning about the insides of the MPU is also very fun even if sometimes difficult. I struggled a little with the FTM (but not that much) and that was worth the try : not only I need go generate a stable clock but also have to run code when the clock goes HI or LO, with very few nanoseconds for that. So optimization is critical in my case, so critical that I even consider to write some parts in assembler, another field for learning.

What is cool with the Teensy eco system is that we can rely on well written libraries but also, by need or by taste, go deeper and does things by ourselves.

To answer your previous question : I use a Teensy 3.6 running at 180MHZ.

BTW if you want to know how pins and PORTS are related, have a look here : https://forum.pjrc.com/threads/1753...DR-D-B-registers-vs-ARM-GPIO_PDIR-_PDOR/page3
 
It’s just really heavy stuff for a novice, and not many examples around to get the ball rolling.

FreqCount is designed to be fairly easy to use. Sounds like you don't want to hear our advice to give it a try, that you feel you must learn the low level timers even when an easy to use library might be able to meet your needs?

I'm guessing this also means you wouldn't consider the FreqCount source code as an example? It does exactly what you're asking, configure a timer to count pulses. Why would you ignore that code and then complain you can't find an example?
 
Maybe i misinterpreted what it can do. From the link you sent, which I read through a couple times, it seemed like it could only display a frequency measurement. Like if you feed it a 1mhz clock it would tell you its a 1mhz clock.

I really am open to anything that works, if it doesn’t have to be low-level then that is a win. Any examples of using it to increment a counter i’m interested to read.

Thanks
 
Thanks defragster. I am buffering the pwm output so i’ll see if i can drive it directly from pin 3 while still feeding the clock to the rest of the signal chain.
 
Dunno if it's meaningful in your case, but this is what I do to generate a 1MHZ signal with 50% duty for a 6502 cpu

Code:
#define SIG_CLOCK 33 // is PORT E BIT 24 so E24
volatile uint32_t count;
volatile bool isr_flag;

void setup() {
    pinMode(SIG_CLOCK, OUTPUT);
    FTM0_SC = 0;
    FTM0_CNT = 0;
    FTM0_MOD = 29; 2 MHZ
    count = 0;
    isr_flag = false;
    NVIC_SET_PRIORITY(IRQ_FTM0, 64);
    NVIC_ENABLE_IRQ(IRQ_FTM0);
    FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0) | FTM_SC_TOIE;
}

FASTRUN void ftm0_isr(void) {
    FTM0_SC &= ~FTM_SC_TOF; // clear the interrupt overflow flag
    if (isr_flag) {
       GPIOE_PDOR |= (1 << 24); // Clock HI
       count++;
    } else {
       GPIOE_PDOR &= ~(1 << 24); // Clock LOW
    }
    isr_flag = !isr_flag;
}

This kind of code gives me a 1MHZ 50% duty on my cheap logic analyzer.
With the count variable you count the HI cycles
I've extracted the code above from my code, so take it as pseudo code, it's un tested and I don't even know if it's done the right way...
Any advice on FTM ISRs welcome

This worked for me on pin33. Thanks for the leg up! Gave me a very nice 1mhz signal. I love examples :) Using that code and the port mappings in your link, I was able to modify this to work on pin3. The full changed code would be;

Code:
#define SIG_CLOCK 3 // is PORT A BIT 12 so A12
volatile uint32_t count;
volatile bool isr_flag;

void setup() {
    pinMode(SIG_CLOCK, OUTPUT);
    FTM1_SC = 0;
    FTM1_CNT = 0;
    FTM1_MOD = 29; // 1 MHZ
    count = 0;
    isr_flag = false;
    NVIC_SET_PRIORITY(IRQ_FTM1, 64);
    NVIC_ENABLE_IRQ(IRQ_FTM1);
    FTM1_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0) | FTM_SC_TOIE;
}

FASTRUN void ftm0_isr(void) {
    FTM1_SC &= ~FTM_SC_TOF; // clear the interrupt overflow flag
    if (isr_flag) {
       GPIOA_PDOR |= (1 << 12); // Clock HI
       count++;
    } else {
       GPIOA_PDOR &= ~(1 << 12); // Clock LOW
    }
    isr_flag = !isr_flag;
}

Many many thanks again. I don't fully understand it, but just enough to manipulate it :)
 
Also wanted to mention, this is a much more responsive & steady clock for the critical timing of my project. Before, I was using midi cc to alter the speed of the 1mhz clock using
Code:
analogWriteFrequency(4, xxx);
and used midi cc to alter the frequency variable. This was causing random crashes of the keyboard I'm retrofitting, usually when i was playing notes on the built in keyboard while modulating the analogwritefrequency, I also got some clicks in the sound. I put this down to a delay in the pwm changing speed, maybe the clock drops out for a cycle or two? But using the code above, it's like butter. I now write to midi value to FTM1_MOD variable, and its seemless. No crashes, no clicks or pops in the instrument. I'm very happy with the results.
 
Status
Not open for further replies.
Back
Top