OBS #
Brief #
The obs component estimates the flux angle and speed from voltage and
current, for a PMSM and an induction motor alike. It generalises
sensorless (which stays for old configs) and fixes its undamped loop.
Model #
In a frame at angle pos turning at vel, the back emf is
ed = ud - r id - ld d(id)/dt + vel lq iq eq = uq - r iq - lq d(iq)/dt - vel ld id
PMSM: ld, lq, and the emf is vel * psi on q. Induction motor: ld = lq = sigma*Ls, and the emf is the rotor flux’s, vel * lmr * i_mr on q. Either way a frame lagging the flux by d reads ed = -e sin d, eq = e cos d times the sign of vel, so the angle error is -sign(vel) ed / |e|. ed/eq alone would also lock half a turn off.
Frames #
The feedback (hv0.id_fb …) is in the frame hv0 used, pos_ref (angle0.pos)
plus vel_ref * adv (hv0.adv). obs rotates it into its own frame first, so
it can run as a shadow while angle0 follows the encoder (src 0 or 1), and
pos_err = pos - (pos_ref + vel_ref adv) shows how far it is from the
frame hv0 used, the encoder’s angle in shadow. Run
obs before angle0 (lower rt_prio): it then reads the angle the feedback was
taken with. A constant pos_err growing with speed is timing, not model, and
adv trims it. hv0 adds vel * adv again, so commutate from pos_c, which
takes it off: in shadow pos_c is the encoder’s angle.
Loop #
A PI phase locked loop on the angle error: pos += (vel + kp err) T,
vel += ki err T, kp = 1.4 bw, ki = bw^2. Below e_min volts of emf the error
is not trusted: the loop holds its speed (ok = 0). Start it from a known
angle and direction (track, or I/f): at standstill the sign of vel is a
guess. track = 1 sets pos to the feedback frame, pos_ref + vel_ref *
adv (the frame the free running pos is given in, so pos_err reads 0),
and vel to vel_ref, for a sequencer that hands over to it.
vel_m is the rotor speed, (vel - slip) / polecount. vel only follows a
slip step through the loop, so the slip is passed through a copy of the
loop before it is taken off; taken straight, a step in iq would dip vel_m
for about 1 / bw, the speed loop would ask for more iq, and with little
flux (deep field weakening) that rings at about the observer bw. With
track = 1 the slip is taken straight.
max_vel (default 3000 rad/s electrical, 477 Hz) clamps vel. On the F4
(5 kHz) that is about 10 samples per electrical period, which is also
about where the F4 observer runs out. There is no faster path: an F3
observer overran the F3 rt on the spindle (fault 9) and was removed, so keep
conf0.max_vel below this. On X the angle error stayed flat (0.015-0.021 rad)
from 200 to 800 rad/s electrical.
Pins #
| Name | Description |
|---|---|
| r | parameter, winding resistance [ohm] |
| r_w | parameter, d loss growing with speed [ohm per rad/s electrical], r + r_w * abs(vel), 0 = none |
| ld | parameter, d inductance [H] (induction motor: sigma*Ls) |
| lq | parameter, q inductance [H], 0 = ld |
| polecount | parameter, pole pairs |
| bw | parameter, loop bandwidth [rad/s] |
| kl | parameter, current derivative low pass, 0..0.99 |
| e_min | parameter, emf below which the angle is not trusted [V] |
| max_vel | parameter, speed clamp [rad/s electrical] |
| adv | parameter, feedback frame lead over pos_ref [s], hv0.adv |
| id | input, hv0.id_fb |
| iq | input, hv0.iq_fb |
| ud | input, hv0.ud_fb |
| uq | input, hv0.uq_fb |
| pos_ref | input, angle the feedback frame was at, angle0.pos |
| vel_ref | input, angle0.vel |
| slip | input, induction motor slip [rad/s electrical], 0 on a PMSM |
| track | input, 1 = pos follows pos_ref + vel_ref * adv, vel follows vel_ref |
| pos_chk | input, encoder angle to compare with while commutating from obs |
| pos | output, flux angle in the feedback frame [rad electrical] |
| pos_c | output, pos - vel * adv, the angle to commutate with, angle0.pos_obs |
| vel | output, synchronous speed [rad/s electrical] |
| vel_m | output, rotor speed [rad/s mechanical], (vel - slip) / polecount, slip with the loop’s lag |
| ed | output, d emf in the observer’s frame [V] |
| eq | output, q emf [V] |
| err | output, angle error the loop sees [rad] |
| pos_err | output, pos - (pos_ref + vel_ref * adv) [rad] |
| chk_err | output, pos - (pos_chk + vel_ref * adv) [rad] |
| ok | output, 1 = emf above e_min for 50 ms |
References #
Source: .c File