Force leg switch state UP or DOWN (duty = 0.0 or duty 1.0)

Hello,
With an ownverter board, I’m trying to do a three-phase sqaure wave inverter for pedagogical purpose.

Here is the three-phase duty cycle computation code, where v_angle is the phase angle (in [0, 2PI]) which is incremented at each control step:

if (v_angle <= PI) duty_a = 1.0;
else duty_a = 0.0;
if (ot_modulo_2pi(v_angle - 2.0/3.0*PI) <= PI) duty_b = 1.0;
else duty_b = 0.0;
if (ot_modulo_2pi(v_angle - 4.0/3.0*PI) <= PI) duty_c = 1.0;
else duty_c = 0.0;

With this code, I indeed get the nice fixed levels current which are typical of three-phase square wave inverter. However, the voltage isn’t clean of switchings. It’s as if the duty is not exactly 0. but perhaps some small > 0 value (or slightly <1.0 value).

So my questions are:

  • in the underlying shield.power.setDutyCycle(LEG1, duty_a) function, is there some adjustment which prevents using duty = 0.0 and/or 1.0 ?
  • Is there a better approach to fix leg switch state UP or DOWN?

Are you using the last version of core ?

This commit introduced the possibility of having full 100% and full 0% PWM.

I think your are looking for this function :

Let me know if it helps :slight_smile:

Thanks for the feedback. About the Core version I’m using, I suppose it’s fine because my repo is a fork of commit commit 73f15dd (July 11, 2025) (fork in the crude sense of starting a fresh repo from the workspace files of another…)

What I don’t understand is: what is the default value of min and max limits of the duty cycle, before calling setDutyCycleMin/Max

Because in the BLDC example I took inspiration from, there is no call to these in the setup routine. Or is it hidden in a static app configuration file?

EDIT:

in relation to the question of the default duty cycle bounds, I see that the docstring of setDutyCycle states in the parameters description:

  • duty_value The duty cycle value to set (a floating-point number between 0.1 and 0.9).
  • Absolute duty-cycle limits for every timer unit are initialized inside _period_ckpsc() in zephyr/modules/owntech_hrtim_driver/zephyr/src/hrtim.c (lines 332-340), where tu->pwm_conf.duty_min/duty_max pull from the prescaler-dependent lookup tables HRTIM_MIN_PER_and_CMP_REG_VALUES and HRTIM_MAX_PER_and_CMP_REG_VALUES, and the user-facing defaults are set to 10 %/90 % of the computed period (duty_min_user, duty_max_user, plus their float versions).

  • Those defaulted duty_min_user/duty_max_user values are what higher layers use when clamping calls such as Power::setDutyCycle() in zephyr/modules/owntech_shield_api/zephyr/src/Power.cpp (lines 223-258), so unless your code overrides them via the API (e.g., setDutyCycleMin/Max), the 10 %–90 % defaults coming from hrtim.c govern the effective min/max duty cycle.

Hello @pierre-haessig ,

Indeed, the default values of duty cycle are:

  • Vduty MIN = 0.1
  • Vduty MAX = 0.9

You can use the function below for instance to set the value to zero.

shield.power.setDutyCycleMin(LEG1,0.0) 

Should we add explicitly somewhere that the min and max are 0.1 and 0.9 ?

Thanks for both feedbacks.

I have added the call to setDutyCycleMin and Max in my three-phase islanded inverter application GitHub - pierre-haessig/ownverter-islanded at completed (using the completed branch, since main is the student version with a few blanks to be filled.)

This unlocks the ability to use extreme duty cycle values. However, this led to the following issues which I only reported today

There are issues in two cases:

  • sinusoidal duty cycles PWM
  • square wave modulation (only using d=0.0 or d=1.0)

with variants between the two cases:

  • sinusoidal duty cycles PWM: glitch is visible in the pwm signal (CHA1 and CHA2), so that an HRTIM issue is likely
  • square wave modulation: glitch is not visibile in the pwm signal, but still in the current and voltages. So perhaps the problem is not with HRTIM

Sinusoidal case:

Square wave case:

Yes!

My suggestion:

  • setDutyCycle doc should mention that duty_value should be between min and max value as per set in setDutyCycleMin/Max (adding ref to these function)
  • Then in the doc of setDutyCycleMin/Max, the default values should be givent

Hello @pierre-haessig,

The issue was a little deeper. I explain it in detail in
this pull request.

The TLDR is that the 1/0 takes place after a comparison, and the value of the comparison was wrong. I updated it. It should work seamlessly now.

Please:

  • add my fork of the Core to your VSCode git graph,
  • checkout my branch,
  • put your main.cpp code in it and
  • put the results here