phase shift PWM channels on FTM0 for LED dimming

Status
Not open for further replies.

lacsap

Member
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-aufwa...d-senser-xtreme-200-2050ma-6v-30v-detail.html
LED Bridgelux Vero29 BXRC-56G10K0-L-24
https://www.digikey.com/products/en?keywords= BXRC-56G10K0-L-24
 
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-PWM-phase-shift-help?highlight=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();
}
 
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();
}
 
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
 
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
 
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.
 
Status
Not open for further replies.
Back
Top