Changing the PWM frequency for pedagogical purpose

Hello OwnTech,

I’d like to know if anybody has experience with changing the PWM frequency of the SPIN microcontroller. In a teaching context (introduction to power electronics), the idea would be to witness the effect changing the PWM frequency, in particular classical effect on the current ripple through an inductor (ΔI proportional to 1/f).

If I remember correctly previous discussions with @luiz_villa or @jalinei, I think the difficulty lays in the interplay between PWM signal generation and the synchronization of ADC sampling instants. However, in my target application, I can run open loop and measure voltages and current with probes, so it’s fine if the ADC measurements are broken :wink: !

Is there a code example for this?

Pierre

Hello Pierre,

Actually it will affect measurements if the modulation is left aligned. In center aligned mode, measurements should still be fine.

In your case you don’t want to measure at a precise instant on the PWM period, you can actually sweep through the period (in left aligned mode), and get an image of the ripple current with the onboard instruments. This is implemented in the Ripple Current example.

To show the effect of a frequency change on the inductor current, I suggest you to use in your setup_routine the function initVariableFrequency.

Once initialized, you can change the frequency at your will, using setFrequency(for instance when pressing a button).

This way you should be able to retrieve multiple plots showing the ripple current for different frequencies.

Let me know if it does what you want :slight_smile:

Thanks for the feedback.

It seems there some amount of shady magic going on with initVariableFrequency, in the sense that a min frequency must be declared and also with the warning “this may compromise the resolution of the PWM.”. But OK I’ll experiment with it.

Quick question about setFrequency: doc says “The new frequency can’t be inferior to the the one set in the initialization step.” → is it “can’t be inferior to the the minimum one” ?

[EDIT]

Another question: @jalinei, in your answer you assume I want to vary the frequency during operation. But if I just want to have a fixed PWM frequency, only different from the 200 kHz default, do I also need to call initVariableFrequency ?

Hehehe - Short answer, yes, setFrequency is enough to set any fixed frequency you want. This function computes the prescaler is order that you get the best time resolution available for that frequency. Now, as this prescaler affects the minimal frequency that can be achieved, at runtime, we can not ensure that the user will be able to switch to lower frequencies.

This is why initVariableFrequency has an under the hood logic that compute the maximal time resolution that matches both the initial frequency and the minimal frequency that will be used during runtime.

@jalinei, I tried to add either setFrequency and/or initFixedFrequency in my GitHub - pierre-haessig/ownverter-dcdc: Microcontroller code for using the OwnTech OwnVerter board as a DC/DC converter code, but in the end I didn’t find a combination which works. I got either:

  • frequency is still 200 kHz (while I tried setFrequency(50000))
  • pwm doesn’t work (the system isn’t crashed, but the power on action has no effect)

So it’s not clear to me how they should be used. In particular, within the setup_routine() there are several other intialization-style calls (shield.power.initBuck(ALL); , shield.sensors.enableDefaultOwnverterSensors();…).

Is there a constraint as to when these functions should be called?

Hello @pierre-haessig

Your question is very interesting and has multiple elements to it.

Why it does not work

The frequency of the Twist and OwnVerter boards is currently set on the dts file:

	powershield: power-shield{
        compatible = "power-leg";
        default-frequency = <200000>;
        min-frequency = <50000>;

These values are automatically retrieved by the power_init.cpp file:

uint32_t timer_frequency = DT_PROP(POWER_SHIELD_ID, default_frequency);

uint32_t timer_min_frequency = DT_PROP(POWER_SHIELD_ID, min_frequency);

These variables are used in turn in the PowerAPI::initMode function:

        /* Configure PWM frequency */
        spin.pwm.initVariableFrequency(timer_frequency, timer_min_frequency);

which is called at the setup_routine()function by:

    shield.power.initBuck(ALL);

When we call the spin.pwm.initFixedFrequency(value)function, we update values within a structure of the spin that is found within the hrtim.c file:

void hrtim_frequency_set(uint32_t frequency_set, uint32_t frequency_min)
{
    HRTIM_MINIM_FREQUENCY = frequency_min;

    timerMaster.pwm_conf.frequency = frequency_set;
    tu_channel[PWMA]->pwm_conf.frequency = frequency_set;
    tu_channel[PWMB]->pwm_conf.frequency = frequency_set;
    tu_channel[PWMC]->pwm_conf.frequency = frequency_set;
    tu_channel[PWMD]->pwm_conf.frequency = frequency_set;
    tu_channel[PWME]->pwm_conf.frequency = frequency_set;
    tu_channel[PWMF]->pwm_conf.frequency = frequency_set;

    timerMaster.pwm_conf.min_frequency = frequency_min;
    tu_channel[PWMA]->pwm_conf.min_frequency = frequency_min;
    tu_channel[PWMB]->pwm_conf.min_frequency = frequency_min;
    tu_channel[PWMC]->pwm_conf.min_frequency = frequency_min;
    tu_channel[PWMD]->pwm_conf.min_frequency = frequency_min;
    tu_channel[PWME]->pwm_conf.min_frequency = frequency_min;
    tu_channel[PWMF]->pwm_conf.min_frequency = frequency_min;
}

However, this change is indeed overwritten by the values that are present in the device tree of the twist board.

Thus, if I write on the setup_routine() of the voltage mode buck example:

void setup_routine()
{
    /* Buck voltage mode */
    spin.pwm.initFixedFrequency(50000); :
    shield.power.initBuck(ALL);

    shield.sensors.enableDefaultTwistSensors();

    pid.init(pid_params);

    /* Then declare tasks */
    uint32_t app_task_number = task.createBackground(loop_application_task);
    uint32_t com_task_number = task.createBackground(loop_communication_task);
    task.createCritical(loop_critical_task, 100);

    /* Finally, start tasks */
    task.startBackground(app_task_number);
    task.startBackground(com_task_number);
    task.startCritical();
}

I get a frequency of 200kHz regardless:

How to get it working (variable frequency)

However, if you change your communication function to:

void loop_communication_task()
{
    received_serial_char = console_getchar();
    switch (received_serial_char)
    {
    case 'h':
        /*----------SERIAL INTERFACE MENU----------------------- */
        printk(" ________________________________________ \n"
               "|     ---- MENU buck voltage mode ----   |\n"
               "|     press i : idle mode                |\n"
               "|     press p : power mode               |\n"
               "|     press u : voltage reference UP     |\n"
               "|     press d : voltage reference DOWN   |\n"
               "|________________________________________|\n\n");
        /*------------------------------------------------------ */
        break;
    case 'i':
        printk("idle mode\n");
        mode = IDLEMODE;
        break;
    case 'p':
        printk("power mode\n");
        mode = POWERMODE;
        break;
    case 'u':
        frequency += 500;
        spin.pwm.setFrequency(frequency);
        
        break;
    case 'd':
        frequency -= 500;
        spin.pwm.setFrequency(frequency);
        break;
    default:
        break;
    }
}

You will indeed vary your frequency natively between 200kHz and 50kHz, as it is 144kHzbelow.

So, as a conclusion:

  • The variable frequency is supported by default.
  • For now, it is defined on the device tree of your power shield
  • If you wish to make a different frequency, you will have to change the device tree (as of now)
  • If you want us to change this and create a function that overwrites the frequency, please create an issue on the Core and we’ll take care of it.

Hello @luiz_villa, I’ve made some new tests today.

Application code is:

(it derives from from OwnTech Power API Core repository, commit 73f15dd (July 11, 2025) GitHub - owntech-foundation/Core at 73f15dd248662b70359ffdcf07029cba2ff017f0 · GitHub)

What works:

  • call to spin.pwm.setFrequency(150000) changes the frequency to 150 kHz, if, in the setup, there is a call to spin.pwm.initVariableFrequency(frequency, frequency_min) coming after shield.power.initBuck(ALL)

So in summary the sequence which works is:

// setting: frequency_min = 50e3; frequency = 200e3; i.e. 50 kHz and 200 kHz
shield.power.initBuck(ALL);
spin.pwm.initVariableFrequency(frequency, frequency_min);
spin.pwm.setFrequency(150000);

What doesn’t work:

  • calling spin.pwm.initVariableFrequency(frequency, frequency_min) before shield.power.initBuck(ALL)
  • (and, if I remember well, calling setFrequency() without the proper call to initVariableFrequency doesn’t work either)
  • [STRANGE ISSUE] when reducing the frequency, for a given fixed duty cycle, I get the proper pulse adjustment (that is the pulse width is scaled down proportionally to the switching period), until crossing some frequency threshold below which the pulse width isn’t reduced (I don’t remember exactly how: perhaps it stays constant… to be verified).

Typically at 100 kHz, the maximum reachable duty cycle is about 45% (or is it the minimum?.. to be verified) with the application code above (GitHub - pierre-haessig/ownverter-dcdc: Microcontroller code for using the OwnTech OwnVerter board as a DC/DC converter · GitHub).

Now, looking at the commits to Core repo, I found the recent July 2026 commit [bug] Updates the power API to correct a frequency update related bug… · owntech-foundation/Core@aab0ae5 · GitHub from PR [bug] Updates the power API to correct a frequency update related bug. by luizvilla · Pull Request #156 · owntech-foundation/Core · GitHub, which may be related to this problem. Surprisingly though, the symptom described in the PR and commit message isn’t exactly what I faced:

When frequency is updated, the duty cycle is not.

This means that when updating the duty cycle the period is not refreshed and the values passed to the spin API were wrong.

→ in my case (without this commit), I the got the correct duty cycle update after setFrequency, at least on some frequency range (say above 150 kHz or so). So it would be interesting to get details on the setting in which the bug symptom was noticed.

More generally, my question is:

Is it recommended to update the Core code of my application to the main branch (latest commit: 31 July 2026 Commits · owntech-foundation/Core · GitHub)? Is it stable enough?