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

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

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

    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,412
    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
    40
    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,412
    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,057
    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:	6 
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
    40
    Cheers. Thanks for the pointers. Iíll start reading into this.

  7. #7
    Member
    Join Date
    May 2019
    Location
    Brisbane, QLD
    Posts
    40
    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
    40
    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:	2 
Size:	101.5 KB 
ID:	16748

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

Posting Permissions

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