Calculating Timer Variables in .NETMF - STM32F4 (Cerberus)

Hi Guys,

I am having real trouble with this issue. Microsoft.SPOT.Hardware.PWM allows one to start PWM with:


PWM MyPWM = new PWM(Cpu.PWMChannel.PWM_2, Frequency, DucyCycle, false);
MyPWM.Start();

I want to be able to calculate the Prescaler and Period the same as does the PWM Class.

Can someone please help with information on how .NETMF calculates the Period and Prescaler.

I can calculate these values and at above 2KHz they seem to work, below 2KHz they are all wrong.

I have found this code in GHI’s Open Source and this does help:


        private static unsafe bool PWM_ApplyConfiguration(PWM_CHANNEL channel, GPIO_PIN pin, uint period, uint duration, PWM_SCALE_FACTOR scale, bool invert)
        {
            int timer = g_STM32F4_PWM_Timer[channel];
            int tchnl = g_STM32F4_PWM_Channel[channel];
            TIM_TypeDef* treg = g_STM32F4_PWM_Ports[timer - 1];

            uint p = period;
            uint d = duration;
            uint s = scale;

            if (d > p) d = p;

            // set pre, p, & d such that:
            // pre * p = PWM_CLK * period / scale
            // pre * d = PWM_CLK * duration / scale

            uint clk = PWM1_CLK_HZ;
            if ((uint)treg == 0x10000) clk = PWM2_CLK_HZ; // APB2

            uint pre = clk / s; // prescaler
            if (pre == 0)
            { // s > PWM_CLK
                uint sm = s / ONE_MHZ; // scale in MHz
                clk = PWM1_CLK_MHZ;      // clock in MHz
                if ((uint)treg == 0x10000) clk = PWM2_CLK_MHZ; // APB2
                if (p > 0xFFFFFFFF / PWM_MAX_CLK_MHZ)
                { // avoid overflow
                    pre = clk;
                    p /= sm;
                    d /= sm;
                }
                else
                {
                    pre = 1;
                    p = p * clk / sm;
                    d = d * clk / sm;
                }
            }
            else
            {
                while (pre > 0x10000)
                { // prescaler too large
                    if (p >= 0x80000000) return false;
                    pre >>= 1;
                    p <<= 1;
                    d <<= 1;
                }
            }
            if (timer != 2 && timer != 5)
            { // 16 bit timer
                while (p >= 0x10000)
                { // period too large
                    if (pre > 0x8000) return false;
                    pre <<= 1;
                    p >>= 1;
                    d >>= 1;
                }
            }
            treg->PSC = pre - 1;
            treg->ARR = p - 1;

            if (timer == 2)
            {
                if (tchnl == 0)
                    treg->CCR1 = d;
                else if (tchnl == 1)
                    treg->CCR2 = d;
                else if (tchnl == 2)
                    treg->CCR3 = d;
                else if (tchnl == 3)
                    treg->CCR4 = d;
            }
            else
                *(uint)&((uint)&treg->CCR1)[tchnl] = d;

            uint invBit = TIM_CCER_CC1P << (4 * tchnl);
            if (invert)
            {
                treg->CCER |= invBit;
            }
            else
            {
                treg->CCER &= ~invBit;
            }
            return true;
        }

Microsoft have some nice code in Microsoft.SPOT.Hardware.PWM but nothing on how they calculate the Prescaler. The Period that is calculated in the methods in here is not the same Period that gets Commited to the ARR Register.

// Decompiled with JetBrains decompiler
// Type: Microsoft.SPOT.Hardware.PWM
// Assembly: Microsoft.SPOT.Hardware.PWM, Version=4.2.0.1, Culture=neutral, PublicKeyToken=null
// MVID: 82F07598-B86F-4A6D-B02A-633AEDCF7C8F
// Assembly location: C:\Program Files (x86)\Microsoft .NET Micro Framework\v4.2\Assemblies\le\Microsoft.SPOT.Hardware.PWM.dll

using System;
using System.Runtime.CompilerServices;

namespace Microsoft.SPOT.Hardware
{
  public class PWM : IDisposable
  {
    private readonly Cpu.Pin m_pin;
    private readonly Cpu.PWMChannel m_channel;
    private uint m_period;
    private uint m_duration;
    private bool m_invert;
    private PWM.ScaleFactor m_scale;

    public Cpu.Pin Pin
    {
      get
      {
        return this.m_pin;
      }
    }

    public double Frequency
    {
      get
      {
        return PWM.FrequencyFromPeriod((double) this.m_period, this.m_scale);
      }
      set
      {
        this.m_period = PWM.PeriodFromFrequency(value, out this.m_scale);
        this.Commit();
      }
    }

    public double DutyCycle
    {
      get
      {
        return PWM.DutyCycleFromDurationAndPeriod((double) this.m_period, (double) this.m_duration);
      }
      set
      {
        this.m_duration = PWM.DurationFromDutyCycleAndPeriod(value, (double) this.m_period);
        this.Commit();
      }
    }

    public uint Period
    {
      get
      {
        return this.m_period;
      }
      set
      {
        this.m_period = value;
        this.Commit();
      }
    }

    public uint Duration
    {
      get
      {
        return this.m_duration;
      }
      set
      {
        this.m_duration = value;
        this.Commit();
      }
    }

    public PWM.ScaleFactor Scale
    {
      get
      {
        return this.m_scale;
      }
      set
      {
        this.m_scale = value;
        this.Commit();
      }
    }

    public PWM(Cpu.PWMChannel channel, double frequency_Hz, double dutyCycle, bool invert)
    {
      HardwareProvider hwProvider = HardwareProvider.HwProvider;
      if (hwProvider == null)
        throw new InvalidOperationException();
      this.m_pin = hwProvider.GetPwmPinForChannel(channel);
      this.m_channel = channel;
      this.m_period = PWM.PeriodFromFrequency(frequency_Hz, out this.m_scale);
      this.m_duration = PWM.DurationFromDutyCycleAndPeriod(dutyCycle, (double) this.m_period);
      this.m_invert = invert;
      try
      {
        this.Init();
        this.Commit();
        Port.ReservePin(this.m_pin, true);
      }
      catch
      {
        this.Dispose(false);
      }
    }

    public PWM(Cpu.PWMChannel channel, uint period, uint duration, PWM.ScaleFactor scale, bool invert)
    {
      HardwareProvider hwProvider = HardwareProvider.HwProvider;
      if (hwProvider == null)
        throw new InvalidOperationException();
      this.m_pin = hwProvider.GetPwmPinForChannel(channel);
      this.m_channel = channel;
      this.m_period = period;
      this.m_duration = duration;
      this.m_scale = scale;
      this.m_invert = invert;
      try
      {
        this.Init();
        this.Commit();
        Port.ReservePin(this.m_pin, true);
      }
      catch
      {
        this.Dispose(false);
      }
    }

    ~PWM()
    {
      this.Dispose(false);
    }

    public void Dispose()
    {
      this.Dispose(true);
    }

    [MethodImpl(MethodImplOptions.InternalCall)]
    public void Start();

    [MethodImpl(MethodImplOptions.InternalCall)]
    public void Stop();

    [MethodImpl(MethodImplOptions.InternalCall)]
    public static extern void Start(PWM[] ports);

    [MethodImpl(MethodImplOptions.InternalCall)]
    public static extern void Stop(PWM[] ports);

    protected void Dispose(bool disposing)
    {
      try
      {
        this.Stop();
      }
      catch
      {
      }
      finally
      {
        this.Uninit();
        Port.ReservePin(this.m_pin, false);
      }
    }

    [MethodImpl(MethodImplOptions.InternalCall)]
    protected void Commit();

    [MethodImpl(MethodImplOptions.InternalCall)]
    protected void Init();

    [MethodImpl(MethodImplOptions.InternalCall)]
    protected void Uninit();

    private static uint PeriodFromFrequency(double f, out PWM.ScaleFactor scale)
    {
      if (f >= 1000.0)
      {
        scale = PWM.ScaleFactor.Nanoseconds;
        return (uint) (1000000000.0 / f + 0.5);
      }
      else if (f >= 1.0)
      {
        scale = PWM.ScaleFactor.Microseconds;
        return (uint) (1000000.0 / f + 0.5);
      }
      else
      {
        scale = PWM.ScaleFactor.Milliseconds;
        return (uint) (1000.0 / f + 0.5);
      }
    }

    private static uint DurationFromDutyCycleAndPeriod(double dutyCycle, double period)
    {
      if (period <= 0.0)
        throw new ArgumentException();
      if (dutyCycle < 0.0)
        return 0U;
      if (dutyCycle > 1.0)
        return 1U;
      else
        return (uint) (dutyCycle * period);
    }

    private static double FrequencyFromPeriod(double period, PWM.ScaleFactor scale)
    {
      return (double) scale / period;
    }

    private static double DutyCycleFromDurationAndPeriod(double period, double duration)
    {
      if (period <= 0.0)
        throw new ArgumentException();
      if (duration < 0.0)
        return 0.0;
      if (duration > period)
        return 1.0;
      else
        return duration / period;
    }

    public enum ScaleFactor : uint
    {
      Milliseconds = 1000U,
      Microseconds = 1000000U,
      Nanoseconds = 1000000000U,
    }
  }
}

I have added some examples in the attached picture. Any help on this would be much appreciated.

All the Best

Chris

For Illustratory purposes I have put some code together from the fore mentioned code examples:


using System;
using System.Linq;
using System.Text;
using System.Collections.Generic;

namespace ConsoleApplication1
{
    class Program
    {
        #region Fields...

        // For Debugging Purposes...
        private static bool debug = true;

        private static uint m_period;

        private static ScaleFactor m_scale;

        private Clock m_busclock;

        private static uint m_prescaler;

        private static uint m_duration;

        private static bool m_invert;

        #endregion

        #region Properties...

        public Clock ClockSpeed
        {
            get { return m_busclock; }
            set { m_busclock = value; }
        }

        public double Frequency
        {
            get
            {
                return FrequencyFromPeriod((double)m_period, m_scale);
            }
            set
            {
                m_period = PeriodFromFrequency(value, out m_scale);
                //this.Commit();
            }
        }

        public uint Prescaler
        {
            get { return m_prescaler; }
            set { m_prescaler = value; }
        }

        public uint Period
        {
            get
            {
                return m_period;
            }
            set
            {
                m_period = value;
                //this.Commit();
            }
        }

        public double DutyCycle
        {
            get
            {
                return DutyCycleFromDurationAndPeriod((double)m_period, (double)m_duration);
            }
            set
            {
                m_duration = DurationFromDutyCycleAndPeriod(value, (double)m_period);
                //this.Commit();
            }
        }

        public uint Duration
        {
            get
            {
                return m_duration;
            }
            set
            {
                m_duration = value;
                //this.Commit();
            }
        }

        public bool InvertPin
        {
            get { return m_invert; }
            set { m_invert = value; }
        }

        public ScaleFactor Scale
        {
            get
            {
                return m_scale;
            }
            set
            {
                m_scale = value;
                //this.Commit();
            }
        }

        #endregion

        public enum ScaleFactor : uint
        {
            Microseconds = 0xf4240,
            Milliseconds = 0x3e8,
            Nanoseconds = 0x3b9aca00
        }

        public enum Clock : uint
        {
            // https://my.st.com/public/STe2ecommunities/mcu/Lists/cortex_mx_stm32/Flat.aspx?RootFolder=%2fpublic%2fSTe2ecommunities%2fmcu%2fLists%2fcortex%5fmx%5fstm32%2fSTM32%20Series%20%2d%20Calculations%20of%20Timer%20Variables&FolderCTID=0x01200200770978C69A1141439FE559EB459D7580009C4E14902C3CDE46A77F0FFD06506F5B&TopicsView=https%3A%2F%2Fmy%2Est%2Ecom%2Fpublic%2FSTe2ecommunities%2Fmcu%2FLists%2Fcortex%5Fmx%5Fstm32%2FAllItems%2Easpx&currentviews=22
            // See: Post: 10/5/2014 2:43 PM
            // On the F4 running at 168 MHz, APB1 is typically 42 MHz (DIV4) and APB2 84 MHz (DIV2), 
            // and the respective TIMCLKs are 84 MHz, and 168 MHz...
            APB1 = 84000000,
            APB2 = 168000000
        }


        static void Main(string[] args)
        {
            double Frequency = 1;
            double DucyCycle = 0.5;


            CalculateValues(Frequency, DucyCycle);

            Console.ReadLine();
        }

        private static void CalculateValues(double frequency_Hz, double dutyCycle)
        {
            m_period = PeriodFromFrequency(frequency_Hz, out m_scale);
            m_duration = DurationFromDutyCycleAndPeriod(dutyCycle, (double)m_period);

            #region Fields...

            uint period = m_period;
            uint duration = m_duration;

            double mperiod = period;
            double mduration = duration;

            uint scale = ((uint)m_scale);

            uint clk;
            uint prescaler;

            #endregion

            #region System Vars...

            int timer = 8;
            uint treg = 0x20000;

            uint PWM1_CLK_HZ;
            uint PWM2_CLK_HZ;
            uint ONE_MHZ = 1000000;
            uint PWM1_CLK_MHZ = 84;
            uint PWM2_CLK_MHZ = 42;

            uint PWM_MAX_CLK_MHZ;

            uint SYSTEM_CYCLE_CLOCK_HZ = 168000000;  // 168MHz
            uint SYSTEM_APB1_CLOCK_HZ = 42000000;   // 42MHz
            uint SYSTEM_APB2_CLOCK_HZ = 84000000;   // 84MHz


            if (SYSTEM_APB1_CLOCK_HZ == SYSTEM_CYCLE_CLOCK_HZ)
            {
                PWM1_CLK_HZ = (SYSTEM_APB1_CLOCK_HZ);
            }
            else
            {
                PWM1_CLK_HZ = (SYSTEM_APB1_CLOCK_HZ * 2);
                PWM1_CLK_MHZ = (PWM1_CLK_HZ / ONE_MHZ);
            }


            if (SYSTEM_APB2_CLOCK_HZ == SYSTEM_CYCLE_CLOCK_HZ)
            {
                PWM2_CLK_HZ = (SYSTEM_APB2_CLOCK_HZ);
            }
            else
            {
                PWM2_CLK_HZ = (SYSTEM_APB2_CLOCK_HZ * 2);
                PWM2_CLK_MHZ = (PWM2_CLK_HZ / ONE_MHZ);
            }

            if (PWM2_CLK_MHZ > PWM1_CLK_MHZ)
            {
                PWM_MAX_CLK_MHZ = PWM2_CLK_MHZ;
            }
            else
            {
                PWM_MAX_CLK_MHZ = PWM1_CLK_MHZ;
            }

            if (duration > period) duration = period;

            // APB2
            if ((uint)treg == 0x10000)
            {
                clk = PWM2_CLK_HZ;
            }
            else
            {
                clk = PWM1_CLK_HZ;
            }


            #endregion

            #region Assign Variables...

            prescaler = clk / scale;
            
            // scale in MHz
            uint sm = scale / ONE_MHZ;

            if ((uint)treg == 0x10000)
            {
                clk = PWM2_CLK_MHZ;
            }
            else
            {
                clk = PWM1_CLK_MHZ;
            }

            #endregion

            // set pre, p, & d such that:
            // pre * p = PWM_CLK * period / scale
            // pre * d = PWM_CLK * duration / scale

            Console.WriteLine("*************************************************");
            Console.WriteLine("Freq: " + frequency_Hz);
            Console.WriteLine("Pre: " + prescaler);
            Console.WriteLine("Period: " + mperiod);
            Console.WriteLine("Clk: " + clk);
            Console.WriteLine("Scale: " + scale);

            if (prescaler == 0)
            {
                if (period > 0xFFFFFFFF / PWM_MAX_CLK_MHZ)
                { 
                    // avoid overflow
                    prescaler = clk;
                    period /= sm;
                    duration /= sm;
                }
                else
                {
                    Console.WriteLine("Setting Values from here...");
                    prescaler = 1;
                    period = period * clk / sm;
                    duration = duration * clk / sm;
                }
            }
            else
            {
                while (prescaler > 0x10000)
                { 
                    // prescaler too large
                    // if (period >= 0x80000000) return false;
                    prescaler >>= 1;
                    period <<= 1;
                    duration <<= 1;
                }
            }
            if (timer != 2 && timer != 5)
            { 
                // 16 bit timer
                while (period >= 0x10000)
                { 
                    // period too large
                    // if (prescaler > 0x8000) return false;
                    prescaler <<= 1;
                    period >>= 1;
                    duration >>= 1;
                }
            }

            Console.WriteLine("*************************************************");
            Console.WriteLine("Calculated PSC: " + (((clk * period) / sm) - 1));    //   *** This is what I need to know, this calculation...
            Console.WriteLine("*************************************************");
            Console.WriteLine("PSC: " + (prescaler));
            Console.WriteLine("ARR: " + (period - 1));
            Console.WriteLine("CCRx: " + duration);
            Console.WriteLine("*************************************************");
        }

        private static uint PeriodFromFrequency(double f, out ScaleFactor scale)
        {
            if (f >= 1000.0)
            {
                scale = ScaleFactor.Nanoseconds;
                return (uint)((1000000000.0 / f) + 0.5);
            }
            if (f >= 1.0)
            {
                scale = ScaleFactor.Microseconds;
                return (uint)((1000000.0 / f) + 0.5);
            }
            scale = ScaleFactor.Milliseconds;
            return (uint)((1000.0 / f) + 0.5);
        }

        private uint PrescalerFromFrequencyAndClock(double f)
        {
            return (uint)(((uint)(this.ClockSpeed)) / (((this.Period - 1) / (1 / f)) + 1));
        }

        private static double FrequencyFromPeriod(double period, ScaleFactor scale)
        {
            return (((double)scale) / period);
        }

        private static uint DurationFromDutyCycleAndPeriod(double dutyCycle, double period)
        {
            if (period <= 0.0)
            {
                throw new ArgumentException();
            }
            if (dutyCycle < 0.0)
            {
                return 0;
            }
            if (dutyCycle > 1.0)
            {
                return 1;
            }
            return (uint)(dutyCycle * period);
        }

        private static double DutyCycleFromDurationAndPeriod(double period, double duration)
        {
            if (period <= 0.0)
            {
                throw new ArgumentException();
            }
            if (duration < 0.0)
            {
                return 0.0;
            }
            if (duration > period)
            {
                return 1.0;
            }
            return (duration / period);
        }

    }
}


This code does produce correct ARR and CCRx values.

It does not always produce correct Prescaler.

EG1:
Frequency: 1Hz
Prescaler: 2687
Period: 62499
CCRx: 31250

The code however gives a value for the prescaler of: 1344

EG2:
Frequency: 1840Hz
Prescaler: 1
Period: 45651
CCRx: 22826

The code however gives a value for the prescaler of: 1 Which is correct.

EG1:
Frequency: 123Hz
Prescaler: 167
Period: 8129
CCRx: 4065

The code however gives a value for the prescaler of: 84

Found it: Prescaler = ((((84000000 * 2) / ((period) / (1 / frequency_Hz))) + 0.5) - 1));

APB2 is 168000000 but is prescaled, via a separate prescaler by 2. So we need (84000000 * 2) to work at Bus Speed.

All the best

Chris

2 Likes