Forum Rule: Always post complete source code & details to reproduce any issue!
Results 1 to 24 of 24

Thread: PWM @ 1ghz 47%-53% duty issue

  1. #1
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50

    PWM @ 1ghz 47%-53% duty issue

    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. #2
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    2,453
    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.

  3. #3
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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

  4. #4
    Senior Member+ Theremingenieur's Avatar
    Join Date
    Feb 2014
    Location
    Colmar, France
    Posts
    2,453
    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.

  5. #5
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,303
    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.

    Click image for larger version. 

Name:	file.png 
Views:	15 
Size:	28.0 KB 
ID:	16747

    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.

  6. #6
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    Cheers. Thanks for the pointers. Iíll start reading into this.

  7. #7
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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?

  8. #8
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    Quote Originally Posted by Theremingenieur View Post
    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
    Click image for larger version. 

Name:	54usSYnc.jpg 
Views:	8 
Size:	101.5 KB 
ID:	16748

  9. #9
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,303
    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.

  10. #10
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    Quote Originally Posted by PaulStoffregen View Post
    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;
      }
    }

  11. #11
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,303
    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 by PaulStoffregen; 06-11-2019 at 11:01 AM.

  12. #12
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    Quote Originally Posted by PaulStoffregen View Post
    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?

  13. #13
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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.

  14. #14
    Member
    Join Date
    Mar 2019
    Location
    Bordeaux / France
    Posts
    69
    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

  15. #15
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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.

  16. #16
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,303
    You could connect a wire from pin 3 (or whatever pin creates the signal) to pin 13, and use the FreqCount library to count the number of actual cycles.

  17. #17
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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.

  18. #18
    Senior Member+ defragster's Avatar
    Join Date
    Feb 2015
    Posts
    9,019
    Quote Originally Posted by martianredskies View Post

    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);
      }
    }

  19. #19
    Member
    Join Date
    Mar 2019
    Location
    Bordeaux / France
    Posts
    69
    Quote Originally Posted by martianredskies View Post
    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/17532...IR-_PDOR/page3

  20. #20
    Senior Member PaulStoffregen's Avatar
    Join Date
    Nov 2012
    Posts
    20,303
    Quote Originally Posted by martianredskies View Post
    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?

  21. #21
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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

  22. #22
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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.

  23. #23
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    Quote Originally Posted by Tactif CIE View Post
    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

  24. #24
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    50
    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.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •