Kinetis, FTM, and complementary combined PWM

Kinetis, FTM, and complementary combined PWM

  • 27 May 0

Lately I’ve been working on a Kinetis K70 cortex M4 micro controller. It’s one of my favorite micro controllers by the way. I needed to control a 3 phases BLDC motor.  Obviously, there was going to be some PWM signals involved!

There are two ways to generate PWM signals with a kinetis MCU, either using the “classic” TPM-compatible registers (that’s when the bit FTMEN in  FTMx_MODE register si cleared), or the FTM mode (FTMEN = 1). Using the TPM-compatible mode is rather simple and straight forward, but offer limited functionalities, compared to FTM mode.

This tutorial assume you’re familiar with the following subjects:

  • Kinetis micro controller
  • C Programming
  • PWM signals
  • Motor drivers and H-Bridges

 

FTM mode adds new interesting features, that lets you do cool stuff, like combining two PWM channels to generate complementary drive signals. That’s extremely useful when you need to drive both High and Low end Mosfets of a half bridge. IT even lets you add a dead time, to prevent short-circuits and fatigue on output mosfets. Unfortunately, configuring PWMs in FTM mode is not as straight forward as in classic TPM mode.

I spend last 10 hours searching for a good-quick-simple-yet-working example, and i couldn’t find any. But worry no more google: Next time someone is searching for a kinetis complementary PWM example, you can send them here!

Initialization function

So, let’s get to the bottom of this. Here is a source code that initializes 3 pairs of complementary PWM.

void FTM0_Init(void)
{
  /* SIM_SCGC6: FTM0=1 */
  SIM_SCGC6 |= SIM_SCGC6_FTM0_MASK;

  /* FTM0_MODE: WPDIS=1 */
  FTM0_MODE |= FTM_MODE_WPDIS_MASK;    /* Disable write protection */
  FTM0_C0SC = 0x00U;
  FTM0_C1SC = 0x00U;
  FTM0_C2SC = 0x00U;
  FTM0_C3SC = 0x00U;
  FTM0_C4SC = 0x00U;
  FTM0_C5SC = 0x00U;
  FTM0_C6SC = 0x00U;
  FTM0_C7SC = 0x00U;

  FTM0_SC &= (uint32_t)~(uint32_t)((FTM_SC_TOF_MASK | FTM_SC_CPWMS_MASK));
  
  FTM0_MODE |= FTM_MODE_FTMEN_MASK;
  
  FTM0_COMBINE = (uint32_t)((FTM0_COMBINE & (uint32_t)~(uint32_t)(
                  FTM_COMBINE_FAULTEN3_MASK |
                  FTM_COMBINE_SYNCEN3_MASK |
                  FTM_COMBINE_DTEN3_MASK |
                  FTM_COMBINE_COMP3_MASK |
                  FTM_COMBINE_FAULTEN2_MASK |
                  FTM_COMBINE_FAULTEN1_MASK |
                  FTM_COMBINE_FAULTEN0_MASK |
                  FTM_COMBINE_SYNCEN0_MASK |
                  FTM_COMBINE_DECAPEN0_MASK
                 )) | (uint32_t)(
                  
                  FTM_COMBINE_SYNCEN2_MASK |
                  FTM_COMBINE_DTEN2_MASK |
                  FTM_COMBINE_COMP2_MASK |
                  FTM_COMBINE_COMBINE2_MASK |
                  
                  FTM_COMBINE_SYNCEN1_MASK |
                  FTM_COMBINE_DTEN1_MASK |
                  FTM_COMBINE_COMP1_MASK |
                  FTM_COMBINE_COMBINE1_MASK |
                  
                  FTM_COMBINE_SYNCEN0_MASK |
                  FTM_COMBINE_DTEN0_MASK |
                  FTM_COMBINE_COMP0_MASK |
                  FTM_COMBINE_COMBINE0_MASK
                 ));

  FTM0_C0SC = FTM_CnSC_ELSB_MASK;
  FTM0_C1SC = FTM_CnSC_ELSB_MASK;
  FTM0_C2SC = FTM_CnSC_ELSB_MASK;
  FTM0_C3SC = FTM_CnSC_ELSB_MASK;
  FTM0_C4SC = FTM_CnSC_ELSB_MASK;
  FTM0_C5SC = FTM_CnSC_ELSB_MASK;
  
  FTM0_C0V = FTM_CnV_VAL(0);
  FTM0_C1V = FTM_CnV_VAL(100);
  
  FTM0_C2V = FTM_CnV_VAL(0);
  FTM0_C3V = FTM_CnV_VAL(100);
  
  FTM0_C4V = FTM_CnV_VAL(0);
  FTM0_C5V = FTM_CnV_VAL(100);

  FTM0_MOD = 1000; 
  FTM0_CNTIN &= (uint32_t)~(uint32_t)(FTM_CNTIN_INIT(0xFFFF));
  FTM0_DEADTIME = 10;
  FTM0_CNT = (FTM_CNT_COUNT(0));
  FTM0_MODE = (uint32_t)((FTM0_MODE & (uint32_t)~(uint32_t)(
               FTM_MODE_FAULTIE_MASK |
               FTM_MODE_FAULTM(0x03) |
               FTM_MODE_PWMSYNC_MASK |
               0xFFFFFF00U
              )) | (uint32_t)(
               FTM_MODE_INIT_MASK |
               FTM_MODE_FTMEN_MASK
              ));
  
  FTM0_SYNCONF |= (FTM_SYNCONF_SWWRBUF_MASK |  
                          FTM_SYNCONF_SWRSTCNT_MASK |  
                          FTM_SYNCONF_SYNCMODE_MASK  
                          );  
  
  /* FTM0_SC: TOF=0,TOIE=0,CPWMS=0,CLKS=1,PS=0 */
  FTM0_SC = (uint32_t)((FTM0_SC & (uint32_t)~(uint32_t)(
             FTM_SC_TOF_MASK |
             FTM_SC_TOIE_MASK |
             FTM_SC_CPWMS_MASK |
             FTM_SC_CLKS(0x02) |
             FTM_SC_PS(0x07)
            )) | (uint32_t)(
             FTM_SC_CLKS(0x01)
            ));
}

I’ll let you look up the meaning of each line using the datasheet if you’re curious. After all, i wont add more that what the datasheet says.

Note that in the code above, modulo is set to 1000, and all PWM duty cycles are set to 100 (or 10% or 1000).

Updating PWM duty cycle

Now, the second more important part of code that you need, is the one used to update the values of the duty cycle. Note that when the FTM mode is enabled, and when channels are combined in pairs, the value of the duty cycle is defined by this formula: Cn+1VCnV,  where CxV is the counter compare value, and “n” is the number of of the PWM channel.

FTM0_C1V = 500;
FTM0_C0V = 0;
	FTM0_SYNC |= FTM_SYNC_SWSYNC_MASK;

Those 3 lines of code set the duty cycle to 50% (500/1000). Last line is quite important, as this forces the values written in the registers to be updated.

Conclusion

This is more a cheat sheet than a tutorial. Just copy paste the code, modify to your needs and enjoy. I far from being a kinetis MCU expert, but i was frustrated i couldn’t easily find fully working examples of that rather basic functionality. I am sure there are many ways to to do what i’ve done with that code – I encourage you to dig on your own and let me know if you have remarks or suggestions.

Thanks for reading!


Leave A Comment