Smooth waves when changing frequency in Vector Control - Hyp-ed/hyped-2024 GitHub Wiki

Pulse-Width Modulation (PWM) waves can produce a larger reference sinusoidal wave with variable frequency. For the output to be smooth, the next reference wave must have an offset so that it continues from the same point the previous reference wave left off.

The maths behind it is that $\sin(at + o_1) = \sin(bt + o_2)$ at the point of change, where $a$ and $b$ are the frequencies (multiplied by the multiplier due to second/microsecond conversions) of the old and new reference wave respectively, $t$ is time of change, and $o_1$ and $o_2$ is the old and new offset of the function. We know $a, b,$ and $o_1$ and must find $o_2$.

$$o_2 = \arcsin(\sin(at + o_1)) - bt$$

However, since there are two solutions, we must check the solution we have is the one which the offset creates a wave which has a matching gradient. This ensures a wave is smooth, and even though the frequency changes there are no sudden changes in direction. To do this we take the derivative of the first function

$$\frac{d}{dx} \sin(at + o_1) = \cos(at + o_1)*a$$

and the second one

$$\frac{d}{dx} \sin(bt + o_2) = \cos(bt + o_2)*b = \cos(bt + \arcsin(\sin(at + o_1)) - bt)*b = \cos(\arcsin(\sin(at + o_1)))*b$$

Since we know $a$ and $b$ are always positive (since both the frequency and multiplier are always greater than 0), we can remove it when we check that the sign of both derivatives are the same (doing d1 * d2 >= 0 allows us to check if they are the same sign)

The expression we check for the old function is $\cos(at + o_1)$, and the new one is $\cos(\arcsin(\sin(at + o_1)))$

If they are the same sign, then we are using the correct solution, if not, we must take the second solution, which is calculated as $π - \arcsin(\sin(at + o_1))$

Now we have the correct value for $\arcsin(\sin(at + o_1))$, we just need to subtract $bt$ to get the correct value of $o_2$

Then just return that value.

double new_offset(long time, float old_freq, double old_offset, float new_freq) {
    double new_offset = asin(sin(old_freq*MULTI_CONST*time + old_offset));
    if (cos(old_freq*MULTI_CONST*time + old_offset) * cos(new_offset) < 0) {
	new_offset = M_PI - new_offset;
    }
    new_offset -= new_freq*MULTI_CONST*time;

    return new_offset;
}

(for my memory next year)

old_freq = freq;

time_abs = HAL_GetTick()*1000;
loop_timer = time_abs + TRIANGLE_TIME / 2; // goes from the current time to half the triangle wave
multiplier = MULTI_CONST*freq;
offset = new_offset(time_abs, old_freq, offset, freq);

t = time_abs * multiplier; //scale the value of time period down to (0,2pi) range
PWM_A = TRIANGLE_TIME * amplitude * sin(t + offset); //calculate pulse duty for phase A
PWM_B = TRIANGLE_TIME * amplitude * sin(t + 0.6666 * M_PI + offset); //for phase B
PWM_C = TRIANGLE_TIME * amplitude * sin(t - 0.6666 * M_PI + offset); //for phase C