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

Thread: phase shift PWM channels on FTM0 for LED dimming

  1. #1
    Junior Member
    Join Date
    Jan 2018
    Posts
    5

    phase shift PWM channels on FTM0 for LED dimming

    Hi,

    I am using a teensy 3.2 to control 8 x 100W LEDs. The LEDs are connected via Boost converters to a common power supply (24V). The boost converter allows to dim the LED via a PWM input. Each converter gets its own PWM signal from the teensy to allow for different brightness levels per LED. I am using all channels of FTM0 on pins 5,6,9,10,20,21,22 and 23. Currently the PWM has a frequency of 6kHz and a resolution of 4095 steps.
    Dimming per channel works although I have the problem that all channels have their rising edge at the same time and thus introduce a huge spike for the power supply. This introduces a big ripple on my supply line which causes some flickering down the road.
    My idea was to phase shift the rising edge per channel or group of channels to even out the load the power supply sees within the duty cycle.
    e.g. chan0 has ist rising edge on 0%, chan 1 at 12.5%,chan2 at 25% ..chan7 at 87.5%

    I looked into the FTM0 timer functions and assume something like this could be possible by changing the FTM0_CnV channel value or maybe using one channel for the rising edge and one for the falling edge and thus having 4 phase shifted PWMs.
    As I am quite new to the Flexible Timer structure that teensy I'd appreciate if someone more knowledgeable could give me a hint or an example of a phase shifted PWM. Or any other tip on how I could even out the load for the power supply.

    thanks!

    Links:
    Boost converter Led Senser Xtreme
    https://pcb-components.de/led-aufwae...0v-detail.html
    LED Bridgelux Vero29 BXRC-56G10K0-L-24
    https://www.digikey.com/products/en?...C-56G10K0-L-24

  2. #2
    Junior Member
    Join Date
    Jan 2018
    Posts
    5
    I found a way to have 4 channels of phase shifted PWM on FTM0. The code snippet in this thread helped a lot
    https://forum.pjrc.com/threads/42158...ight=pwm+phase I adapted it to use FTM0 and included a little logic to allow for 100% PWM on combined channels with an offset (see set_pwm()). This is needed as the value to set the output for channel n (e.g. FTM0_C0V) needs to be smaller than channel n+1 (e.g. FTM0_C1V). In order to achieve this I flip the polarity of the PWM by setting the FTM_CSC_ELSB or FTM_CSC_ELSB bit in FTM0_C0SC (see 36.3.6 Channel (n) Status And Control (FTMx_CnSC) p.783 in the manual of the MK20DX256 manual).

    Somehow I need to stop the timer, set new values and restart the timer to define a new duty cycle on the output. Simply setting FTM0_C0V and FTM0_C1V doesn't work. Otherwise the output keeps its initial duty cycle. Does anyone have a clue why?

    Code:
    /*
    PWM output on pins 22 (ch0+1), 9(ch2+3), 6(ch4+5) and 21(ch6+7)
    PWM on pin 22 is cycling between 0..100%, see function fade() and set_pwm()
    */
    
    const uint16_t kMaxPWMValue = 8192;
    uint16_t counter = 0;
    int16_t sign = 1;
    
    void setup()
    {
      Serial.begin(38400);
      delay(1000);
      FTM0_SC = 0;    // halt timer
      FTM0_COMBINE = FTM_COMBINE_COMBINE0 | FTM_COMBINE_COMBINE1 | FTM_COMBINE_COMBINE2 | FTM_COMBINE_COMBINE3; // ch 2+3 = adjustable high/low points
      
      FTM0_C0V = 4096; // start 50%
      FTM0_C1V = 6143; // end 75%
      
      FTM0_C2V = 6144; // start 75%
      FTM0_C3V = 8192; // end 100%
      
      FTM0_C4V = 0;    // start 0%
      FTM0_C5V = 2047; // end 25%
      
      FTM0_C6V = 2048; // start 25%
      FTM0_C7V = 4097; // end 50%
      
      // 36.3.6 Channel (n) Status And Control (FTMx_CnSC) p.783 
      // FTM0_C0SC = 0b00001000; 0x8, set (rising) channel 0, clear (falling) at channel 1
      FTM0_C0SC |= FTM_CSC_ELSB; 
      FTM0_C0SC &= ~FTM_CSC_ELSA; 
      
      // FTM0_C0SC = 0b00000100; // 0x4, clear (falling) at channel 0, set (rising) at channel 0, 
      // FTM0_C0SC |= FTM_CSC_ELSA; 
      // FTM0_C0SC &= ~FTM_CSC_ELSB; 
      
      FTM0_CNT = 0;   // reset timer
      FTM0_MOD = 8192-1; // timer period (MOD+1 clocks of F_BUS)
      FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // start timer
    
      CORE_PIN22_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch0+1
      CORE_PIN9_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch2+3
      CORE_PIN6_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch4+5
      CORE_PIN21_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch 6+7
    }
    
    void set_pwm(uint16_t _pwm_value, uint16_t _offset){
      uint32_t value_set, value_clear;
      uint8_t  flip_pwm;
    
      if((_pwm_value + _offset) > kMaxPWMValue){
        value_set = _pwm_value + _offset - kMaxPWMValue;
        value_clear = _offset;
        flip_pwm = 1;
      } else {
        value_set = _offset;
        value_clear = _pwm_value + _offset;
        flip_pwm = 0;
      }
    
      FTM0_SC = 0;    // halt timer
      if(flip_pwm){
        
      FTM0_C0SC |= FTM_CSC_ELSA; 
      FTM0_C0SC &= ~FTM_CSC_ELSB; 
      } else {
        
      FTM0_C0SC |= FTM_CSC_ELSB; 
      FTM0_C0SC &= ~FTM_CSC_ELSA;
      }
      FTM0_C0V = value_set;
      FTM0_C1V = value_clear;
      FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // restart timer
    }
    
    void fade()
    {
        int pwm_value; 
    
        if(counter >= kMaxPWMValue){
            sign = -1;
        } else if (counter == 0){
            sign = 1;
        }
    
        counter += sign;
    
        set_pwm(counter, 4096);
        delay(1);
    }
    
    void loop()
    {
      fade();
    }

  3. #3
    Junior Member
    Join Date
    Jan 2018
    Posts
    5
    Someone sent me a private message asking if I solved the problem, yes I did. Seeing that I didn't fully answer my own question here's the complete solution I came up with. Copy and pasted this code together, so I hope I included everything. Each PWM output is phase shifted by 25%. This allowed me to dim eight 150W LEDs in pairs of two and distribute the load on the power supply more evenly over the PWM period.

    Code:
    /*
    Teensy 3.2
    PWM output on pins 22 (ch0+1), 9(ch2+3), 6(ch4+5) and 21(ch6+7)
    PWM cycling between 0..100%, phase shifted by 25% between each channel pair, see function fade() and set_pwm()
    */
    
    const uint16_t kMaxPWMValue = 8192;
    uint16_t counter = 0;
    int16_t sign = 1;
    
    void setup(){
        initPWM();
    }
    
    void initPWM(){
        FTM0_SC = 0; // halt timer
        // enable combinded mode on ch n and ch n+1
        // adjustable high/low points via ch n value and ch n+1 value
        FTM0_COMBINE = FTM_COMBINE_COMBINE0 | FTM_COMBINE_COMBINE1 | FTM_COMBINE_COMBINE2 | FTM_COMBINE_COMBINE3; 
    
        FTM0_C0V = 0; // start 50%
        FTM0_C1V = 0; // end 75%
    
        FTM0_C2V = 0; // start 75%
        FTM0_C3V = 0; // end 100%
    
        FTM0_C4V = 0;    // start 0%
        FTM0_C5V = 0; // end 25%
    
        FTM0_C6V = 0; // start 25%
        FTM0_C7V = 0; // end 50%
    
        // 36.3.6 Channel (n) Status And Control (FTMx_CnSC) p.783
        // FTM0_C0SC = 0b00001000; 0x8, set (rising) channel 0, clear (falling) at channel 1
        FTM0_C0SC |= FTM_CSC_ELSB;
        FTM0_C0SC &= ~FTM_CSC_ELSA;
    
        FTM0_C2SC |= FTM_CSC_ELSB;
        FTM0_C2SC &= ~FTM_CSC_ELSA;
    
        FTM0_C4SC |= FTM_CSC_ELSB;
        FTM0_C4SC &= ~FTM_CSC_ELSA;
    
        FTM0_C6SC |= FTM_CSC_ELSB;
        FTM0_C6SC &= ~FTM_CSC_ELSA;
    
        FTM0_CNT = 0;                            // reset timer
        FTM0_MOD = 8192 - 1;                     // timer period (MOD+1 clocks of F_BUS)
        FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // start timer
    
        CORE_PIN22_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch0+1
        CORE_PIN23_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch0+1
        CORE_PIN9_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch2+3
        CORE_PIN10_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch2+3
        CORE_PIN6_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch4+5
        CORE_PIN20_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch4+5
        CORE_PIN21_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch 6+7
        CORE_PIN5_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch 6+7
    }
    
    
    void inline set_pwm(uint16_t _pwm_value, uint8_t _channel)
    {
        uint32_t value_set, value_clear, value_offset;
        uint8_t flip_pwm;
    
        if (_channel > 3)
        {
            _channel = 3;
        }
    
        value_offset = _channel * 1024; // if mod is 4095
    
        if ((_pwm_value + value_offset) > kMaxPWMValue)
        {
            value_set = _pwm_value + value_offset - kMaxPWMValue;
            value_clear = value_offset;
            flip_pwm = 1;
        }
        else
        {
            value_set = value_offset;
            value_clear = _pwm_value + value_offset;
            flip_pwm = 0;
        }
    
        FTM0_SC = 0; // halt timer
    
        switch (_channel)
        {
        case 0:
            if (flip_pwm)
            {
                FTM0_C0SC |= FTM_CSC_ELSA;
                FTM0_C0SC &= ~FTM_CSC_ELSB;
            }
            else
            {
                FTM0_C0SC |= FTM_CSC_ELSB;
                FTM0_C0SC &= ~FTM_CSC_ELSA;
            }
            FTM0_C0V = value_set;
            FTM0_C1V = value_clear;
            break;
    
        case 1:
            if (flip_pwm)
            {
                FTM0_C2SC |= FTM_CSC_ELSA;
                FTM0_C2SC &= ~FTM_CSC_ELSB;
            }
            else
            {
                FTM0_C2SC |= FTM_CSC_ELSB;
                FTM0_C2SC &= ~FTM_CSC_ELSA;
            }
            FTM0_C2V = value_set;
            FTM0_C3V = value_clear;
            break;
    
        case 2:
            if (flip_pwm)
            {
                FTM0_C4SC |= FTM_CSC_ELSA;
                FTM0_C4SC &= ~FTM_CSC_ELSB;
            }
            else
            {
                FTM0_C4SC |= FTM_CSC_ELSB;
                FTM0_C4SC &= ~FTM_CSC_ELSA;
            }
            FTM0_C4V = value_set;
            FTM0_C5V = value_clear;
            break;
    
        case 3:
            if (flip_pwm)
            {
                FTM0_C6SC |= FTM_CSC_ELSA;
                FTM0_C6SC &= ~FTM_CSC_ELSB;
            }
            else
            {
                FTM0_C6SC |= FTM_CSC_ELSB;
                FTM0_C6SC &= ~FTM_CSC_ELSA;
            }
            FTM0_C6V = value_set;
            FTM0_C7V = value_clear;
            break;
        }
        FTM0_SC = FTM_SC_CLKS(1) | FTM_SC_PS(0); // restart timer
    }
    
    void fade()
    {
        int pwm_value; 
    
        if(counter >= kMaxPWMValue){
            sign = -1;
        } else if (counter == 0){
            sign = 1;
        }
    
        counter += sign;
    
        set_pwm(counter, 0); // channel 0/1, pin 22
        set_pwm(counter, 1); // channel 2/3, pin 9
        set_pwm(counter, 2); // channel 4/5, pin 6
        set_pwm(counter, 3); // channel 6/7, pin 21
        delay(1);
    }
    
    void loop()
    {
      fade();
    }

  4. #4
    Junior Member
    Join Date
    Sep 2019
    Posts
    3
    Tanks a lot lacsap,
    I didn't know that is was possible to attach so many pins with only one timer, very usefull.
    I see that there is always start/stop timer. I ll try it on my final application to see if it is ambarrassing or not. I ll post it on my initial thread.
    Thank you for your usefull answer : i ll be able, if it works in my case, to control more wave signals.
    Sincerly.
    jpv

  5. #5
    Junior Member
    Join Date
    Jan 2018
    Posts
    5
    If I remember correctly "normally" each of those channels would be used for one PWM, having 4 Timers with 2 thresholds each (FTM_CSC_ELSA and FTM_CSC_ELSB) allows for 8 separate PWM signals in normal operation.
    In my code there are 4 PWM phases using the two channels (FTM_CSC_ELSA and FTM_CSC_ELSB) per timer for unique rising and falling edges. I connected two pins to the same timer channels as my pcb design already had all 8 PWM output pins exposed on a connector and I didn't want to change the design.

    Here's the pin config, e.g. pin22/23 share the same timer channels and phase.
    Code:
        CORE_PIN22_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch0+1
        CORE_PIN23_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch0+1
        CORE_PIN9_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch2+3
        CORE_PIN10_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch2+3
        CORE_PIN6_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch4+5
        CORE_PIN20_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch4+5
        CORE_PIN21_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE; // ch 6+7
        CORE_PIN5_CONFIG = PORT_PCR_MUX(4) | PORT_PCR_DSE | PORT_PCR_SRE;  // ch 6+7

  6. #6
    Junior Member
    Join Date
    Sep 2019
    Location
    Essex, UK
    Posts
    8
    An alternate solution would be to offload the PWM function to a PCA9685 16 channel 12 bit chip - these natively support phase shifting of the outputs to not only reduce total power draw but to also minimise EMI -
    https://www.nxp.com/products/power-m...roller:PCA9685
    Pre-built modules can be easily sourced from Banggood/Ebay or even Amazon

  7. #7
    Junior Member
    Join Date
    Jan 2018
    Posts
    5
    Yes indeed, looks like an interesting IC. Per datasheet of the PCA9685 it can operate with a programmable PWM freq between 24 Hz to 1526 Hz, with the above "raw" implementation you could use higher PWM freqs if needed. If I remeber correctly I drove my LEDs somewhere in the range of 4-6kHz to lessen interference with high speed cameras.

Posting Permissions

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