Author Topic: Calculating SPWM values on the fly. Is it possible with my MCU?  (Read 4425 times)

0 Members and 1 Guest are viewing this topic.

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8110
  • Country: fi
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #25 on: October 04, 2022, 01:58:27 pm »
Therefore like many suggested I will use a look up table of 360 Values and go from there

And then a table of 90 values, for 1° resolution, as was already suggested.

If at all possible, I always suggest using angles where full rotation matches the numerical range of the fundamental numerical type. This makes wrap-arounds trivial to handle and offers significant speed benefit, too. You can convert from/to degrees whenever degrees are needed, but this is usually only in UIs. Internally, you rarely actually need such weird unit as 1/360 circle. Only us humans are used to it.

If you need to keep count of position (including count of full rotations, plus sub-position within current rotation), it won't get any easier: highest bits denote the number of full counts. E.g. uint64_t can count +/- 2 billion full rotations plus sub-position with 360/(2^32) degree resolution.

You can even do things like interpreting the same variable as unsigned or signed, depending on if you want 0..360 deg or -180..+180 deg indication - latter is useful for differences between two angles.
« Last Edit: October 04, 2022, 02:01:37 pm by Siwastaja »
 
The following users thanked this post: Glenn0010

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4391
  • Country: dk
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #26 on: October 04, 2022, 01:58:50 pm »
I'm assuming you are driving a motor. If so, consider this:

Brushed motors, and simple BLDC motors are driven with full DC bus voltage of either polarity, in typically 6 steps per electrical rotation, i.e., every 60 degrees. And they are mostly just fine, a some torque ripple can be observed.

Now if you choose to spend just 256 bytes of RAM to implement a 8-bit resolution sine table with 256 elements, no interpolation, no "only store one quarter of cycle" tricks - you have 12600% better amplitude resolution, and 4200% better temporal resolution, than those simple brushed or BLDC implementations. It is quite obvious you won't be able to see any further improvement in vibration etc. even if you increase accuracy further. In fact, such budget implementation is already nearly overkill!

And the implementation is trivial: make the 256-step table, and use uint8_t to denote rotor angle so that 0 - 255 maps to 0 - 358.59 degrees. You get natural wrap-around of angles while calculating, in CPU registers, without any if-elses. Practical use for this wrap-around is you can go between sine and cosine by just adding/subtracting 64 (equiv. to 90 degrees) from the index. Then just index the table with this uint8_t angle directly. No range-checking necessary. And the performance is just excellent.

Beware of some C footguns, however, namely signed integer subtraction overflowing is undefined behavior (unsigned is OK, and defined to work as modulo operation as explained above), and integer promotion rules.


and if feeling lazy, just use the CMSIS DSP lib, https://github.com/ARM-software/CMSIS/blob/master/CMSIS/DSP_Lib/Source/FastMathFunctions/arm_sin_q15.c

 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #27 on: October 04, 2022, 02:47:16 pm »
If you have hardware floating-point support (STM32F302R8 does):

A polynomial sine approximation can give you the precision you need with very little effort – and you can drop the π multiplier also.

First, we split the sine wave into four phases.  This gives us just the initial rising edge, one fourth of the full period, to approximate.  If we choose full period to be 4, then this rising edge is between 0 and 1.  Because sine is an odd function (f(-x) = -f(x)), the approximation is valid between -1 and +1, and is always an odd function (a polynomial with only odd powers of x).

Pick one of the below, and rename it approx_sine_internal().  (I didn't bother to look up the exact optimal ones, I've got them somewhere on backup media; these are plain polynomial least squares fits to first quarter sine scaled to period 4.)
Code: [Select]
// -1.0f <= x <= 1.0f, for a sine wave with period 4.0f

// 21.5 bits of precision; absolute error less than 1/2917777 or so
static inline float  approx_sine_internal_5(const float x)
{
    const float  xx = -x*x;
    return x*(1.5707963f + xx*(0.6459635f + xx*(0.079689085f + xx*(0.0046731215f + xx*0.00015125547f))));
}

// 19.1 bits of precision; absolute error less than 1/578525 or so
static inline float  approx_sine_internal_4(const float x)
{
    const float  xx = -x*x;
    return x*(1.5707923f + xx*(0.64590585f + xx*(0.07946442f + xx*0.0043524974f)));
}

// 12.6 bits of precision; absolute error less than 1/6284
static inline float  approx_sine_internal_3(const float x)
{
    const float  xx = -x*x;
    return x*(1.5704362f + xx*(0.6427036f + xx*0.07242646f));
}

// 6.8 bits of precision; absolute error less than 1/111
static inline float  approx_sine_internal_2(const float x)
{
    const float  xx = -x*x;
    return x*(1.5531573f + xx*0.5621494f);
}
I'd suggest approx_sine_internal_4(), which has a computational cost of five multiplications, four additions, and one negation, but the best, approx_sine_internal_5(), only costs one multiplication and one addition more, giving over 21 bits of precision.

Note that the above covers only half of the full period, from -1 to +1: approx_sine_internal(-1) == -1, approx_sine_internal(0) == 0, and approx_sine_internal(1) = 1.

Next, we need a function that calculates the sine given period N and phase p, 0≤pN, edited:
Code: [Select]
float approx_sine(float period, float phase)
{
    float  x = (2.0f * period - 4.0f * phase) / period; /* == 2 - 4*phase/period, shift by half period and negate */

    if (x > 1.0f)
        x = 2.0f - x;
    else
    if (x < -1.0f)
        x = -2.0f - x;

    return approx_sine_internal(x);
}
The idea here is that we shift the phase by half period (which negates the output, seen in the default case, the last return statement), so we get a symmetric range around zero.  The outermost quadrants do need to be shifted by half a period again (which again negates the output, cancelling the earlier negation).

There isn't any easy but precise way to avoid the division by period, unless your period is always the same.  Check your MCU documentation on how slow the floating-point division is; it typically is much slower than a (floating-point) multiplication, but not too slow to be used in this instance (as you do need hardware FP support to use this approach).

For comparison with library-built-in sinf function, use e.g.
Code: [Select]
float expected_sine(float period, float phase)
{
    return sinf(6.283185307179586f * phase  / period);
}
« Last Edit: October 04, 2022, 05:26:51 pm by Nominal Animal »
 
The following users thanked this post: Glenn0010

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #28 on: October 04, 2022, 03:18:47 pm »
Edited: If it matters, Compiler Explorer produces the following assembly with trunk GCC with -O2 -mthumb -mtune=cortex-m4, using approx_sine_internal_5(), with my guesses on the cycle counts:
Code: [Select]
approx_sine(float, float):          ; Cycles
    vmov.f32    s15, #4.0e+0        ;  1
    vadd.f32    s14, s0, s0         ;  1+1
    vmls.f32    s14, s1, s15        ;  3
    vdiv.f32    s15, s14, s0        ; 14
    vmov.f32    s14, #1.0e+0        ;  1
    vcmpe.f32   s15, s14            ;  1
    vmrs        APSR_nzcv, FPSCR    ;  1
    ble         .L8                 ;  2-4
    vmov.f32    s14, #2.0e+0        ;  1+1
.L9:
    vsub.f32    s15, s14, s15       ;  1+1
.L4:
    vnmul.f32   s14, s15, s15       ;  1
    vldr.32     s12, .L10           ;  2
    vldr.32     s13, .L10+4         ;  2
    vldr.32     s0, .L10+8          ;  2
    vmla.f32    s13, s14, s12       ;  3
    vldr.32     s12, .L10+12        ;  2
    vmla.f32    s12, s13, s14       ;  3
    vldr.32     s13, .L10+16        ;  2
    vmla.f32    s13, s12, s14       ;  3+1
    vmla.f32    s0, s13, s14        ;  3+1
    vmul.f32    s0, s0, s15         ;  1
    bx          lr
.L8:
    vmov.f32    s14, #-1.0e+0       ;  1+1
    vcmpe.f32   s15, s14            ;  1
    vmrs        APSR_nzcv, FPSCR    ;  1
    bpl         .L4                 ;  2-4
    vmov.f32    s14, #-2.0e+0       ;  1
    b           .L9                 ;  2-4

.L10:
    .word   958306901
    .word   999891196
    .word   1070141402
    .word   1034105864
    .word   1059413469
with cycle counts based on Cortex-M4 Processor Technical Reference Manual.

If I counted right, the (edited version of) with approx_sine() takes on the order of 55-65 cycles per approx_sine(period, phase) call.  On STM32F302R8 at 72 MHz, that would be about 0.76-0.90 µs.

Using approx_sine_internal_4() instead of approx_sine_internal_5(), you save five cycles (0.07µs) or so.
« Last Edit: October 04, 2022, 05:44:19 pm by Nominal Animal »
 
The following users thanked this post: Glenn0010

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #29 on: October 04, 2022, 04:11:19 pm »
Hi Thanks for that description, it's helpful and simple.
So I want to have a fundamental output frequency ranging from 0.1 to 100 Hz.

To get the desired fundamental frequency I can vary the speed at which I go through the LUT. Either skip samples or stay at the same sample for a n amount of switching cycles depending on the switching frequency I selected.
 

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4391
  • Country: dk
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #30 on: October 04, 2022, 04:31:15 pm »
Hi Thanks for that description, it's helpful and simple.
So I want to have a fundamental output frequency ranging from 0.1 to 100 Hz.

To get the desired fundamental frequency I can vary the speed at which I go through the LUT. Either skip samples or stay at the same sample for a n amount of switching cycles depending on the switching frequency I selected.

or make it a DDS. angle is 32bit int, add a number to that every cycle, how much you add sets the frequency. Use the MSBs of the angle to index the LUT 





 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #31 on: October 04, 2022, 10:26:30 pm »
For three phases, I'd do as T3sl4co1l suggested, but using a sine approximation to get 20+ bits of precision:
Code: [Select]
/* sinf(x*PI) for a full period -1 <= x <= 1, 20+ bits of precision. */
static inline float approx_sine_internal(float x)
{
    const float  xx = -x*x;
    return x*(3.1415918f + xx*(5.167685f + xx*(2.5499268f + xx*(0.59839785f + xx*(0.08060531f + xx*0.0060412507f)))));
}

float  phase;            /* 0..1 */
float  step;             /* Desired frequency divided by PWM frequency, 0..1 */
float  sinA, sinB, sinC; /* Three-phase outputs */

void update_phases(void)
{
    /* Advance phase by one step, wrapping phase to [0,1) */
    phase += step;
    phase -= (int)phase;

    float   y = phase - (phase >= 0.5f); /* -0.5 <= y < 0.5 */
    float   x = y + 0.25f - (y >= 0.25f); /* -0.5 <= x < 0.5, quarter phase further than y */
    float   temp_im = approx_sine_internal(y + y);
    float   temp_re = approx_sine_internal(x + x);
    float   temp_a = -0.5f * temp_im;
    float   temp_b =  0.8660254f * temp_re;
    sinA = temp_im;
    sinB = temp_a + temp_b;
    sinC = temp_a - temp_b;
}
Each call to update_phase() advances phase by step, wrapping it to between -1 and +1.
The complex phase is temp_re+temp_im*I, which gets rotated 120° and 240° forward (or -240° and -120° backwards) via complex number multiplication (except that the above only calculates the imaginary part), and their imaginary components assigned to sinA, sinB, and sinC.

The required complex math is simple:
    e2πpi = cos(2πp) + i sin(2πp)
    ei120° = e-i240° = -0.5 + i sqrt(3/4) ≃ -0.5 + i 0.8660254
    e-i120° = ei240° = -0.5 - i sqrt(3/4) ≃ -0.5 - i 0.8660254
    (a + i A)(b + i B) = (a b - A B) + i (a B + A b)

Looking at the code ARM trunk gcc with -Os -mthumb -mtune=cortex-m4 generates using Compiler explorer, it looks like each call to update_phase() takes about 120 cycles, or about 1.7 µs when running at 72 MHz.  Calling it 100,000 times a second would consume 17% (plus interrupt overhead); say about 20%, of available CPU time.

(I did not optimize the six coefficients, they're just the nonlinear least squares fit to the first sine half-wave.  I have optimized such coefficient before, using a directed walk and nextafterf(), to minimize the absolute error within two full periods (-1 to +1) in the parameter space near these coefficients.)
 
The following users thanked this post: Glenn0010

Online PCB.Wiz

  • Super Contributor
  • ***
  • Posts: 1473
  • Country: au
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #32 on: October 05, 2022, 12:41:46 am »

So I want to have a fundamental output frequency ranging from 0.1 to 100 Hz.
To get the desired fundamental frequency I can vary the speed at which I go through the LUT. Either skip samples or stay at the same sample for a n amount of switching cycles depending on the switching frequency I selected.

Yes, exactly. That is what DDS systems do. The adder value varies, and sweeps the pointer through the LUT at varying rates.
At very low frequencies, you do read the same sample multiple times and at higher frequencies (where the table is too large), some are skipped.
Another benefit of this DDS approach, is the samples picked do not need to repeat every sine cycle, which is what allows DDS to have very fine Hz steps.

As an example, DSS at 100.00kHz with a target of 50.50Hz will take 1980 samples 80% of the time  & 1981 samples 20% of the time, for a ~1ppm error from 50.50Hz
 

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #33 on: October 05, 2022, 07:23:08 am »
Hi All,

I'd like to give my thanks out to all of you for all the detailed help you've given me. I spent some time on it yesterday still using the calculation method and got it down to 5.8 us execution time. I used the sint library provided here which I believe is simpler method to calculating the sine value similar to what some of you suggested. Details on this can be found here: https://prog.world/stm32-about-sine.

For the moment all I need to do is generate this PWM and the I'm using COMP2 with the DAC as a reference to generate an interrupt when a certain current threshold is exceeded. At this point, I turn off the PWM in the COMP2 interrupt.

That is to say that I don't necessarily need to optimize the code but would like to so I can learn a bit more. When I've got more time I will look at all the options you guys suggested and come up with a more optimized code.
 

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #34 on: October 05, 2022, 07:27:35 am »

So I want to have a fundamental output frequency ranging from 0.1 to 100 Hz.
To get the desired fundamental frequency I can vary the speed at which I go through the LUT. Either skip samples or stay at the same sample for a n amount of switching cycles depending on the switching frequency I selected.

Yes, exactly. That is what DDS systems do. The adder value varies, and sweeps the pointer through the LUT at varying rates.
At very low frequencies, you do read the same sample multiple times and at higher frequencies (where the table is too large), some are skipped.
Another benefit of this DDS approach, is the samples picked do not need to repeat every sine cycle, which is what allows DDS to have very fine Hz steps.

As an example, DSS at 100.00kHz with a target of 50.50Hz will take 1980 samples 80% of the time  & 1981 samples 20% of the time, for a ~1ppm error from 50.50Hz

Thanks for this, could you point me to any resources explaining DDS as I'm not familiar with it
 

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #35 on: October 05, 2022, 08:00:06 am »
Hi Thanks for that description, it's helpful and simple.
So I want to have a fundamental output frequency ranging from 0.1 to 100 Hz.

To get the desired fundamental frequency I can vary the speed at which I go through the LUT. Either skip samples or stay at the same sample for a n amount of switching cycles depending on the switching frequency I selected.

or make it a DDS. angle is 32bit int, add a number to that every cycle, how much you add sets the frequency. Use the MSBs of the angle to index the LUT

Hi I'm not familiar with DDS but I think I understand what you're getting at. However if I have a LUT with 256 values, won't I need to use the 8 MSB of the 32 bit angle variable to index the array?

Cheers

GG
 

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4391
  • Country: dk
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #36 on: October 05, 2022, 08:38:27 am »
Hi Thanks for that description, it's helpful and simple.
So I want to have a fundamental output frequency ranging from 0.1 to 100 Hz.

To get the desired fundamental frequency I can vary the speed at which I go through the LUT. Either skip samples or stay at the same sample for a n amount of switching cycles depending on the switching frequency I selected.

or make it a DDS. angle is 32bit int, add a number to that every cycle, how much you add sets the frequency. Use the MSBs of the angle to index the LUT

Hi I'm not familiar with DDS but I think I understand what you're getting at. However if I have a LUT with 256 values, won't I need to use the 8 MSB of the 32 bit angle variable to index the array?

yes, if your 256 value LUT is 360 degrees of sine use the 8 MSB of the 32 bit angle variable to index the array

 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8110
  • Country: fi
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #37 on: October 05, 2022, 10:25:11 am »
Is this a motor controller? If yes, then everything else in the algorithm is much more interesting and important than how you calculate sin().

Are you doing BLDC FOC, or something totally different?
 

Online Siwastaja

  • Super Contributor
  • ***
  • Posts: 8110
  • Country: fi
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #38 on: October 05, 2022, 10:28:51 am »
Hi I'm not familiar with DDS but I think I understand what you're getting at. However if I have a LUT with 256 values, won't I need to use the 8 MSB of the 32 bit angle variable to index the array?

The only reason you would use 32-bit (or even 64-bit) angle variable is so that small incremental changes do not get lost in rounding errors. If you have a 32-bit angle variable, you can still use, say, 256-element table (or any other 2^n length). Just use the 8 most significant bits of the angle variable:

static const sin_lut[256] = {generated by whatever};

static uint32_t angle;
sin = sin_lut[angle>>24];
angle += 1; // do this 17 million times and the index will increase by 1. Such tiny steps will not get lost!
 

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #39 on: October 05, 2022, 12:13:52 pm »
Is this a motor controller? If yes, then everything else in the algorithm is much more interesting and important than how you calculate sin().

Are you doing BLDC FOC, or something totally different?

I want to create a basic spwm output that will drive a power state that will accelerate from 0 Hz to say 50 Hz to drive an induction machine in open loop.

Then I will use this power stage to test for conducted and radiated emissions. And see what effect the power stage characteristics (layout, switching speed, filtering etc)  has on these tests.

Then I would move on to see what effect the smps that generates the power rails has on these tests etc.

So the point of this is just to drive an induction machine in open loop and to keep the control as simple as possible.
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #40 on: October 05, 2022, 12:24:33 pm »
If it matters any, I think I prefer the look-up method too.  I posted the floating-point stuff because it is an useful option, when one simply does not have the Flash/ROM (or RAM) available for a look-up table large enough, and OP does have hardware floating point available.

If anyone is interested, I can show exactly why and how the extra bits in in the index actually act as a pulse density modulation register.  As mentioned before, it is not just to allow smaller steps, it also adds to the practical precision, via optimal "jitter" in the generated intervals.

When using the interrupt to adjust the next period, I would recommend one precalculates one period in advance, and in the interrupt, immediately sets that period, and then precalculates the next period, before returning from the interrupt handler.  This does increase the latency (reaction time to speed/velocity changes) by one PWM period, though, and affects how you implement your PID control loop.  If such a delay throws you into a loop ;D look into predictor-corrector methods on how to deal with it (wrt. integration and derivation).  Oscillation in the controlled variable (angular velocity in this case here) is often an indicator of issues in feedback, and the added latency is one way the feedback may be off.

With an open loop (no feedback), the latency is basically irrelevant.
 

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4391
  • Country: dk
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #41 on: October 05, 2022, 08:22:30 pm »
When using the interrupt to adjust the next period, I would recommend one precalculates one period in advance, and in the interrupt, immediately sets that period, and then precalculates the next period, before returning from the interrupt handler. 

The STM32 timers can do that in HW. There's a bit to enable period and compare registers to be buffered until the update event at the end of the pwm period
 
The following users thanked this post: Nominal Animal

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #42 on: October 06, 2022, 10:02:03 am »
Hi folks.

I've Implemented the LUT method using DDS (Direct Digital Synthesis) as you folks suggested. Firstly I didn't know about this method, but now that I've read up on it I've found its very clever way to achieve the desired result and get very high resolution.

However I've still not got it to work 100% yet. I'm having issues at higher Fsw.

I have a 256 position LUT with the max value being 36000. This is the maximum Timer ARR value that I will have at 2 kHz with a 72 MHz timer clock. (72,000,000 / 2,000) = 36,000

I then calculate the phase offset for the phase accumulator and a scaling value for the PWM so that I can convert the values in the look up table to match the new ARR values for different switching frequencies.

Code: [Select]

//calcualtions for PWM geneation
Phase_Offset = (Fout*(pow(2,32))/Fsw);
MaxPWM_Value = (int) (72000000 / Fsw);
PWM_Scaling =  (36000/MaxPWM_Value); //dividing 36000 by MaxPWM_Value to scale down to right pwm value for different Fsw
TIM1->ARR = MaxPWM_Value; //  ARR =  Clock / (PRC * Fsw) = 72 MHz / (1 * Fsw) = ARR
So this works nicely at 2kHz you can see here I am generating a 50Hz sine wave. CH2 = PWM, CH3 PWM filtered through a LPF. CH1 = TIM 1 ISR execution time, TIM1 ISR that increments the LUT executes in about 550ns



This is the code in the ISR
Code: [Select]
// interrupt occurs at switching frequency
void TIM1_UP_TIM16_IRQHandler(void)
{
TIM1->SR = ~TIM_SR_UIF; // Clear interrupt

GPIOB->BSRR = (1<<2); // turn on


PWM_U = ((SPWM_LUT[Phase_Accumulator>>24])/PWM_Scaling);
TIM1->CCR1 = PWM_U;

Phase_Accumulator = Phase_Accumulator+Phase_Offset;


GPIOB->BSRR = (1<<(2+16)); // turn off

}

However, at higher Fsw, the Sine Wave Starts to distort and gets clamped towards 0 as shown below.

At 10 kHz we start seeing some clamping happening



At 100 kHz it is very visible



So this seems very odd to me. So I thought the PWM values weren't being generated properly, however it looks like they are, as I put all my equations in excel and they are correct.

At 100 kHz I know one of the PWM values will be 51. Therefore I set an I/O pin to go to high when the PWM value is 51. The I/O pin is now connected to CH1

Code: [Select]
void TIM1_UP_TIM16_IRQHandler(void)
{
TIM1->SR = ~TIM_SR_UIF; // Clear interrupt

PWM_U = ((SPWM_LUT[Phase_Accumulator>>24])/PWM_Scaling);
if (PWM_U==51)
{
GPIOB->BSRR = (1<<2); // turn on
}else GPIOB->BSRR = (1<<(2+16)); // turn off


TIM1->CCR1 = PWM_U;

Phase_Accumulator = Phase_Accumulator+Phase_Offset;

}

This is what I get.



As can be seen even though I/O pin goes high for CH1 indicating the PWM value is 51, meaning there should be an output on CH2, however there is no output on CH2. Why would this be?

The pin stays high for a good 80 us so this should be plenty of time


Any thoughts?


« Last Edit: October 06, 2022, 10:05:49 am by Glenn0010 »
 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #43 on: October 06, 2022, 10:26:17 am »
Most likely the PWM counter has passed the value before your code assigns it to CC1, and thus never triggers.  (The hardware uses == triggering, not >= .  And the interrupt does not stop the PWM counter; it continues running all the time.)

Like I wrote in #40, you need to be one period ahead of the actual PWM.  See #41, and find out how you can use the internal CC1 buffering, so that on the new value is automatically loaded at the end of the previous period, and the ISR just assigns/buffers the value for the next period.
 

Offline T3sl4co1l

  • Super Contributor
  • ***
  • Posts: 21606
  • Country: us
  • Expert, Analog Electronics, PCB Layout, EMC
    • Seven Transistor Labs
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #44 on: October 06, 2022, 11:51:10 am »
I have a 256 position LUT with the max value being 36000. This is the maximum Timer ARR value that I will have at 2 kHz with a 72 MHz timer clock. (72,000,000 / 2,000) = 36,000

I then calculate the phase offset for the phase accumulator and a scaling value for the PWM so that I can convert the values in the look up table to match the new ARR values for different switching frequencies.

Code: [Select]

//calcualtions for PWM geneation
Phase_Offset = (Fout*(pow(2,32))/Fsw);
MaxPWM_Value = (int) (72000000 / Fsw);
PWM_Scaling =  (36000/MaxPWM_Value); //dividing 36000 by MaxPWM_Value to scale down to right pwm value for different Fsw
TIM1->ARR = MaxPWM_Value; //  ARR =  Clock / (PRC * Fsw) = 72 MHz / (1 * Fsw) = ARR

Yipe!  Note the return type of pow().  The compiler may infer the value of this constant integer expression, but it has to return a float (or double!), and convert that back to int in the end.

Use this instead:

Code: [Select]
#include <inttypes.h>
...
Phase_Offset = ((int64_t)Fout << 32) / Fsw;

If you don't feel like using inttypes, long long (instead of just int) should be the same size on this platform.

Tim
Seven Transistor Labs, LLC
Electronic design, from concept to prototype.
Bringing a project to life?  Send me a message!
 

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #45 on: October 06, 2022, 12:19:42 pm »
Most likely the PWM counter has passed the value before your code assigns it to CC1, and thus never triggers.  (The hardware uses == triggering, not >= .  And the interrupt does not stop the PWM counter; it continues running all the time.)

Like I wrote in #40, you need to be one period ahead of the actual PWM.  See #41, and find out how you can use the internal CC1 buffering, so that on the new value is automatically loaded at the end of the previous period, and the ISR just assigns/buffers the value for the next period.
So I have done as you suggested to no avail. I was already using the buffered CCR1 and have advanced the calculation by one.

This cannot be the issue. So with a LUT of 256 Values at 100 kHz and 50 Hz fundamental, the LUT index should increment by 1 roughly every 8 switching cycles meaning that we have the value PWM value 51 in this case there for 8 switching cycles and this can be seen in the measurement done above, however , during this, I am getting absolutley no PWM output at all.

I'm totally confused.
 

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #46 on: October 06, 2022, 12:22:44 pm »

If you don't feel like using inttypes, long long (instead of just int) should be the same size on this platform.

Tim

So I tried what you suggested to no avail. I don't think the calculation is the issue here. The PWM calculations are being generated correctly as I have checked.

As I explained to Nominal Animal.

With a LUT of 256 Values at 100 kHz and 50 Hz fundamental, the LUT index should increment by 1 roughly every 8 switching cycles meaning that we have the PWM value of 51 in the measurement above for 8 switching , however , during this, I am getting absolutely no PWM output at all.

This is what is confusing to me
 

Offline langwadt

  • Super Contributor
  • ***
  • Posts: 4391
  • Country: dk
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #47 on: October 06, 2022, 02:03:13 pm »

If you don't feel like using inttypes, long long (instead of just int) should be the same size on this platform.

Tim

So I tried what you suggested to no avail. I don't think the calculation is the issue here. The PWM calculations are being generated correctly as I have checked.

As I explained to Nominal Animal.

With a LUT of 256 Values at 100 kHz and 50 Hz fundamental, the LUT index should increment by 1 roughly every 8 switching cycles meaning that we have the PWM value of 51 in the measurement above for 8 switching , however , during this, I am getting absolutely no PWM output at all.

This is what is confusing to me


missing an offset? the PWM should end up in the range 0-MaxPWM_Value, so really: MaxPWM_Value/2  +/- MaxPWM_Value/2   

 

Online Nominal Animal

  • Super Contributor
  • ***
  • Posts: 6172
  • Country: fi
    • My home page and email address
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #48 on: October 06, 2022, 02:07:48 pm »
With embedded C, you'll want to use <stdint.h> (which is provided by the C compiler) and not <inttypes.h> (which is provided by the standard C library, and may not be available in freestanding implementations).

For Cortex-M4, there are two good options on calculating the phase offset.  First is the 64-bit integer arithmetic T3sl4co1l suggested,
    Phase_Offset = ((int64_t)Fout << 32) / Fsw;
although I'd use (uint64_t) and <stdint.h> instead.  The other is to use float,
    Phase_Offset = 4294967296.0f * (float)Fout / (float)Fsw;
where the (float) casts are not strictly necessary, and I only use them to help us humans with reduced cognitive load.

The former approach is exact, and compiles to a __aeabi_uldivmod() call.  On Cortex-M4, the exact implementation depends on the compiler, but is typically implemented using binary division, not hardware division operations.  It can be a surprisingly slow operation, but it depends on the values of Fout and Fsw.

The latter approach is limited to 24 bits of precision, but uses the hardware floating-point unit.  That is, if Fout < Fsw < 16777216, it is exact.  It takes about 35 cycles to run.

The one I'd use, depends on what Fsw is.  If I'd use the float approach, I'd also add 0.5f to Phase_Offset before it is (implictly) cast to an integer type, for correct rounding.

(Note, Phase_Offset is a misleading name; consider something like Phase_Step instead.)

So I have done as you suggested to no avail. I was already using the buffered CCR1
Double-check, please.

If CCR1 is not auto-loaded from the buffered value when the PWM cycle restarts, then the symptoms match: at a low enough PWM value, the counter has advanced past the assigned value, and will not trigger.

You can verify this by replacing your function with
Code: [Select]
// interrupt occurs at switching frequency
void TIM1_UP_TIM16_IRQHandler(void)
{
TIM1->SR = ~TIM_SR_UIF;
TIM1->CCR1 = 51;
}
and seeing if you get any PWM output on the scope.  Increment the value (51, above) until you do; the idea is to find the smallest number that you can reliably see the PWM output on the scope.

You then add a small delay just before the TIM1->CCR1 line.
If that makes the PWM again disappear on the scope, you are not using a buffered CCR1.
If even a quarter-of-PWM-period delay makes no change, you are using a buffered CCR1.
« Last Edit: October 06, 2022, 02:10:40 pm by Nominal Animal »
 

Offline Glenn0010Topic starter

  • Regular Contributor
  • *
  • Posts: 214
  • Country: mt
Re: Calculating SPWM values on the fly. Is it possible with my MCU?
« Reply #49 on: October 06, 2022, 02:16:58 pm »
Hi Folks,

So I figured it out. I was just being stupid

Turns out it was working fine and I forgot that I had enabled dead time on Timer 1. I only pickup this code again after a year or so and I forgot I had put in dead time.

The dead time was set to 1.7us across frequency hence why you see the sine wave getting dragged down. Because 1.7 us is a much larger portion of 10 us versus say 500 us, that's why we this difference between 2 and 100 kHz.

I found the issue by doing this:  I set a pin high whenever we should have 100% duty cycle. I trigger the scope using this pin. Then I checked the duty cycle and notices it wasn't anywhere near 100% (it was 83%) which made me realize it was dead time.  Sure enough when I put dead time to its lowest value of 13.9 ns, it works fine now at 100 kHz too.

I guess now I understand how to do deadtime compensation. To account for dead time I can increase the duty cycle of each pulse by the amount of dead time. and this should make it more "sinusoidal"

Cheers for the help you folks have been giving
 
The following users thanked this post: pardo-bsso, Nominal Animal


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf