FRF #
Brief #
frf measures the frequency response of the mechanics with the position loop running (stepped sine) and fits a two-mass model to it, mainly to find the coupling anti-resonance for conf0.j_lpf. It is loaded by conf/template/id_frf.txt, which replaces id_lpf’s ring down and is run before id_pid (j_lpf changes the plant the gains are tuned against). It sits between pid0 and the torque controller (after biquad0 if that is loaded):
frf0.in = pid0.torque_cmd,pmsm_ttc0.torque = frf0.outfrf0.pos = fb_switch0.vel_fbvel1.torque = frf0.outandvel2.torque = frf0.out: the observers must see the injectionfrf0.rt_prio = 8.6- creep:
frf0.en = fault0.en_out,frf0.start = fault0.en_pid,frf0.pos_fb = fb_switch0.pos_fb,pid0.pos_ext_cmd = frf0.pos_cmd,pid0.vel_ext_cmd = frf0.vel_cmd,pid0.acc_ext_cmd = frf0.acc_cmd frf0.amp = 1,frf0.creep_vel = 3,frf0.creep_acc = 20,frf0.min_pos = -10,frf0.max_pos = 10,conf0.max_pos_error = 0;term0showsfrf0.freqandfrf0.inj.
The template links start to fault0.en_pid, so enabling the drive starts the sweep once the position loop runs and disabling stops it (the result pins keep the sweep). Unlinked, start with frf0.start = 1; frf0.start = 0 aborts a running sweep (with creep it first drives back to the start position), or clears the state after a sweep; the result pins keep the last sweep. The table, the fit and a conf0.j_lpf line print in the terminal.
Component Explanation #
The sweep and the creep run in rt, printing and the fit in nrt.
- Stepped sine:
- A torque sine of
ampis added to the torque command,out = in + inj, atpointslog-spaced frequencies fromf_starttof_end. - At each frequency it waits
settlecycles, then correlates over whole periods (cycles), so the correlation rejects DC and the other harmonics. The signals areu = out(torque into the current loop, injection included),c = in(the controller’s torque) andvel = minus(pos, last pos) / period. velis the raw motor-side velocity: an observer such asvel1would mix its own model into the result. The half sample delay of the difference is taken out of the phase.- Plant
G = vel / u(rad/s per Nm). Open loopL = -c / uat the injection frequency: the loop gain seen at the torque command. gj = abs(G) * wis 1 / inertia: the total inertia below the coupling ring, the motor inertia above it, a dip at the anti-resonance and a peak at the resonance.- Each point prints as it comes in:
f,abs(G), G phase,gj,abs(L), L phase.
- Creep:
- Friction sticks the motor at standstill and the low frequencies then read wrong, so the axis has to move. With
creep_vel > 0frfdrives it itself throughpid0’s ext commands (asidsdoes): trapezoid moves back and forth atcreep_velandcreep_accbetween start +min_posand start +max_pos. - It measures only while it cruises; after each reversal the current point starts over. When the sweep is done it drives back to the start, then
stategoes to 2. - While
en <= 0the profile followspos_fb, so enabling never jumps. Losingenduring a sweep stops it and its injection where it is (state3), with or without creep. - The lowest point has to fit in one cruise:
(max_pos - min_pos) / creep_vel - creep_vel / creep_accmust exceed its settle plus measure time (the template: about 3 s atf_start5 Hz). Otherwise, or withmax_pos <= 0,min_pos >= 0or the drive disabled at the start, it stops at once (state3). creep_vel3 rad/s keeps the load sliding through the sine near the anti-resonance, where it swings most (1 rad/s read the mode 10 % low in the simulation).creep_vel0 leaves the motion to someone else (a LinuxCNC jog) and measures all the time.
Crossover: after the sweep
f_crossis the last point whereabs(L)falls below 1, interpolated in log (abs(L)can dip under 1 around the anti-resonance and come back), andpm = 180 + L phasethere. Ifabs(L)is still above 1 at the end of the band there is none (0).Two-mass fit:
- The complex inertia
J(jw) = 1 / (jw G)of the points up tofit_maxis fitted withJ = j_mot + j_load * (2 z wa s + wa^2) / (s^2 + 2 z wa s + wa^2) + b / s(motor and load inertia, anti-resonancewa, its dampingz, viscousb), after taking out a pure delay (current loop and feedback). - The model is linear in
j_mot,j_loadandb, so each (wa,z, delay) on a grid is one small weighted least squares (weight1 / abs(J), so every point counts in proportion); the best one with positive inertias wins. Grid: 160 anti-resonances log-spaced from 0.7 times the lowest fitted frequency to the highest, 16 dampings from 0.02 to 0.8, 8 delays from 0 to 1.5 ms. One grid row runs pernrtpass, sonrtstays responsive. - It needs at least 6 points. The fit replaces picking the extremes of
gj, which a second mode or noise moves.f_antiis the corner forpid0.j_lpf(whatids’ ring down measured). - Verdict printed: no coupling resonance in the band (
f_antiabove 95 % of the band orj_load < 0.1 j_mot) givesconf0.j_lpf = 0;f_antiat the low end asks for a lowerf_start;fit_err > 0.15is a poor fit (more than one mode or noise); otherwise it printsconf0.j_lpf = f_anti.
- Results (commit message, Y axis bench): crossover 45 Hz, phase margin 47 deg, anti-resonance about 14 Hz, peak 26 Hz.
ampdefaults to 0 (no injection): set it (the template uses 1 Nm).- Only one
frfper drive: the fit arrays are shared (file statics), so a second instance stops at once (state3) and prints that only the first frf instance can run.
Pins #
| Name | Description |
|---|---|
| in | input, Controller torque command (Nm), pid0.torque_cmd |
| out | output, Torque command with the injection (Nm), to the torque controller |
| pos | input, Motor-side position for the velocity (rad), fb_switch0.vel_fb |
| inj | output, Injected torque sine (Nm) |
| start | input, 1 = start the sweep, 0 = abort or clear |
| amp | parameter, Injection amplitude (Nm, default 0) |
| f_start | parameter, First frequency (Hz, default 5, min 0.5) |
| f_end | parameter, Last frequency (Hz, default 60) |
| points | parameter, Number of log-spaced frequencies (default 40, 1 to 48) |
| settle | parameter, Cycles before measuring (default 5, at least 0.05 s) |
| cycles | parameter, Cycles measured, whole periods (default 10, at least 0.1 s) |
| freq | output, Current frequency (Hz), 0 when idle |
| state | output, 0 idle, 1 running, 2 done, 3 stopped (en lost, stroke too short or not the first frf), start = 0 clears it |
| en | input, Drive enable, fault0.en_out: the profile follows pos_fb while disabled (default 1 when unlinked) |
| pos_fb | input, Position feedback (rad), fb_switch0.pos_fb |
| pos_cmd | output, Creep position command (rad), to pid0.pos_ext_cmd |
| vel_cmd | output, Creep velocity command (rad/s), to pid0.vel_ext_cmd |
| acc_cmd | output, Creep acceleration command (rad/s^2), to pid0.acc_ext_cmd |
| creep_vel | parameter, Creep velocity (rad/s, default 0 = no motion of its own) |
| creep_acc | parameter, Creep acceleration (rad/s^2, default 20, min 0.1) |
| min_pos | parameter, Lower end of the stroke about the start position (rad, default -10, must be below 0) |
| max_pos | parameter, Upper end of the stroke about the start position (rad, default 10, must be above 0) |
| fit_max | parameter, Fit the points up to this frequency (Hz, default 0 = all) |
| f_anti | output, Fitted anti-resonance (Hz), the pid0.j_lpf corner |
| f_res | output, Fitted resonance f_anti * sqrt(1 + j_load / j_mot) (Hz) |
| zeta | output, Fitted damping of the anti-resonance |
| j_mot | output, Fitted inertia above the ring (kgm^2) |
| j_load | output, Fitted inertia behind the coupling (kgm^2) |
| b | output, Fitted viscous friction (Nm s/rad) |
| delay | output, Fitted delay (s) |
| fit_err | output, RMS fit error, fraction of abs(J) |
| f_cross | output, Crossover frequency where abs(L) = 1 (Hz), 0 = none in the band |
| pm | output, Phase margin at f_cross (deg) |
References #
Source: .c File