diff --git a/configs/sim/axis/lathe_multispindle/lathe.hal b/configs/sim/axis/lathe_multispindle/lathe.hal index 1d9ea534687..bdd85d2d258 100644 --- a/configs/sim/axis/lathe_multispindle/lathe.hal +++ b/configs/sim/axis/lathe_multispindle/lathe.hal @@ -23,15 +23,17 @@ net spindle-phase-A sim_encoder_0.phase-A => encoder_0.phase-A net spindle-phase-B sim_encoder_0.phase-B => encoder_0.phase-B net spindle-phase-Z sim_encoder_0.phase-Z => encoder_0.phase-Z -# assume 120 ppr = 480 counts/rev for the spindle -setp sim_encoder_0.ppr 120 +# quadrature encoder gives 4 * ppr counts/rev +# the count rate (rpm/60 * ppr * 4) must stay below the base thread +# frequency, ppr 6 works up to 25000 rpm at a 100000 ns base period +setp sim_encoder_0.ppr 6 # iocontrol output is in rpm, but sim-encoder speed is rps setp sim_encoder_0.scale 60 # scale encoder output to read in revolutions # (that way thread pitches can be straightforward, # a 20 tpi thread would multiply the encoder output # by 1/20, etc) -setp encoder_0.position-scale 480 +setp encoder_0.position-scale 24 # encoder reset control # hook up motion controller's sync output @@ -46,15 +48,17 @@ net spindle-phase-A1 sim_encoder_1.phase-A => encoder_1.phase-A net spindle-phase-B1 sim_encoder_1.phase-B => encoder_1.phase-B net spindle-phase-Z1 sim_encoder_1.phase-Z => encoder_1.phase-Z -# assume 120 ppr = 480 counts/rev for the spindle -setp sim_encoder_1.ppr 120 +# quadrature encoder gives 4 * ppr counts/rev +# the count rate (rpm/60 * ppr * 4) must stay below the base thread +# frequency, ppr 6 works up to 25000 rpm at a 100000 ns base period +setp sim_encoder_1.ppr 6 # iocontrol output is in rpm, but sim-encoder speed is rps setp sim_encoder_1.scale 60 # scale encoder output to read in revolutions # (that way thread pitches can be straightforward, # a 20 tpi thread would multiply the encoder output # by 1/20, etc) -setp encoder_1.position-scale 480 +setp encoder_1.position-scale 24 # encoder reset control # hook up motion controller's sync output diff --git a/configs/sim/gmoccapy/lathe_configs/spindle_sim.hal b/configs/sim/gmoccapy/lathe_configs/spindle_sim.hal index 7b97c3df0ad..0958e38be49 100644 --- a/configs/sim/gmoccapy/lathe_configs/spindle_sim.hal +++ b/configs/sim/gmoccapy/lathe_configs/spindle_sim.hal @@ -23,15 +23,17 @@ net spindle-phase-A sim_encoder_0.phase-A => encoder_0.phase-A net spindle-phase-B sim_encoder_0.phase-B => encoder_0.phase-B net spindle-phase-Z sim_encoder_0.phase-Z => encoder_0.phase-Z -# assume 120 ppr = 480 counts/rev for the spindle -setp sim_encoder_0.ppr 12 +# quadrature encoder gives 4 * ppr counts/rev +# the count rate (rpm/60 * ppr * 4) must stay below the base thread +# frequency, ppr 6 works up to 25000 rpm at a 100000 ns base period +setp sim_encoder_0.ppr 6 # iocontrol output is in rpm, but sim-encoder speed is rps setp sim_encoder_0.scale 60 # scale encoder output to read in revolutions # (that way thread pitches can be straightforward, # a 20 tpi thread would multiply the encoder output # by 1/20, etc) -setp encoder_0.position-scale 48 +setp encoder_0.position-scale 24 # encoder reset control # hook up motion controller's sync output diff --git a/configs/sim/gmoccapy/non_trivial_kinematics/scara/spindle_sim.hal b/configs/sim/gmoccapy/non_trivial_kinematics/scara/spindle_sim.hal index 7398669b3ba..79d344392d4 100644 --- a/configs/sim/gmoccapy/non_trivial_kinematics/scara/spindle_sim.hal +++ b/configs/sim/gmoccapy/non_trivial_kinematics/scara/spindle_sim.hal @@ -23,15 +23,17 @@ net spindle-phase-A sim_encoder_0.phase-A => encoder_0.phase-A net spindle-phase-B sim_encoder_0.phase-B => encoder_0.phase-B net spindle-phase-Z sim_encoder_0.phase-Z => encoder_0.phase-Z -# assume 120 ppr = 480 counts/rev for the spindle -setp sim_encoder_0.ppr 12 +# quadrature encoder gives 4 * ppr counts/rev +# the count rate (rpm/60 * ppr * 4) must stay below the base thread +# frequency, ppr 2 works up to 75000 rpm at a 100000 ns base period +setp sim_encoder_0.ppr 2 # iocontrol output is in rpm, but sim-encoder speed is rps setp sim_encoder_0.scale 60 # scale encoder output to read in revolutions # (that way thread pitches can be straightforward, # a 20 tpi thread would multiply the encoder output # by 1/20, etc) -setp encoder_0.position-scale 48 +setp encoder_0.position-scale 8 # encoder reset control # hook up motion controller's sync output diff --git a/configs/sim/gmoccapy/spindle_sim.hal b/configs/sim/gmoccapy/spindle_sim.hal index 7b97c3df0ad..0cde4ba50ca 100644 --- a/configs/sim/gmoccapy/spindle_sim.hal +++ b/configs/sim/gmoccapy/spindle_sim.hal @@ -23,15 +23,17 @@ net spindle-phase-A sim_encoder_0.phase-A => encoder_0.phase-A net spindle-phase-B sim_encoder_0.phase-B => encoder_0.phase-B net spindle-phase-Z sim_encoder_0.phase-Z => encoder_0.phase-Z -# assume 120 ppr = 480 counts/rev for the spindle -setp sim_encoder_0.ppr 12 +# quadrature encoder gives 4 * ppr counts/rev +# the count rate (rpm/60 * ppr * 4) must stay below the base thread +# frequency, ppr 2 works up to 75000 rpm at a 100000 ns base period +setp sim_encoder_0.ppr 2 # iocontrol output is in rpm, but sim-encoder speed is rps setp sim_encoder_0.scale 60 # scale encoder output to read in revolutions # (that way thread pitches can be straightforward, # a 20 tpi thread would multiply the encoder output # by 1/20, etc) -setp encoder_0.position-scale 48 +setp encoder_0.position-scale 8 # encoder reset control # hook up motion controller's sync output diff --git a/docs/src/man/man9/sim_encoder.9.adoc b/docs/src/man/man9/sim_encoder.9.adoc index 22666604009..81ee9ca7d6a 100644 --- a/docs/src/man/man9/sim_encoder.9.adoc +++ b/docs/src/man/man9/sim_encoder.9.adoc @@ -57,6 +57,13 @@ The *sim-encoder.N.* format is shown in the following descriptions. The desired speed of the encoder, in user units per per second. This is divided by *scale*, and the result is used as the encoder speed in revolutions per second. +**sim-encoder.**_N_**.saturated** bit out:: + True while the commanded *speed* exceeds the maximum the pulse + generator can produce, which is one quadrature count per execution of + *make-pulses*. The outputs then keep toggling at that maximum rate + instead of following the command. The maximum speed in user units is + the *make-pulses* thread frequency divided by 4 times *ppr*, + multiplied by *scale*. == PARAMETERS diff --git a/src/hal/components/sim_encoder.c b/src/hal/components/sim_encoder.c index 0351b835ece..0d9e1a7d452 100644 --- a/src/hal/components/sim_encoder.c +++ b/src/hal/components/sim_encoder.c @@ -97,8 +97,10 @@ typedef struct { hal_real_t scale; /* pin: pulses per revolution */ hal_real_t speed; /* pin: speed in revs/second */ hal_sint_t rawcounts; /* pin: raw counts */ + hal_bool_t saturated; /* pin: speed capped at thread rate limit */ double old_scale; /* internal, used to detect changes */ double scale_mult; /* internal, reciprocal of scale */ + char clamp_warned; /* internal, one-shot frequency cap warning */ } sim_enc_t; /* ptr to array of sim_enc_t structs in shared memory, 1 per channel */ @@ -231,9 +233,10 @@ void rtapi_app_exit(void) to frequency to an accumulator. When the accumulator overflows (or underflows), it is time to increment (or decrement) the state of the output pins. - The add value is limited to +/-2^30, and overflows are detected - at bit 30, not bit 31. This means that with add_val at it's max - (or min) value, and overflow (or underflow) occurs on every cycle. + The add value is limited to a magnitude of less than 2^31, and + overflows are detected at bit 31. This means that with add_val at + it's max (or min) value, and overflow (or underflow) occurs on every + cycle. */ static void make_pulses(void *arg, long period) @@ -349,14 +352,34 @@ static void update_speed(void *arg, long period) rev_sec = hal_get_real(sim_enc->speed) * sim_enc->scale_mult; /* convert speed command (revs per sec) to counts/sec */ freq = rev_sec * (hal_get_ui32(sim_enc->ppr)) * 4.0; - /* limit the commanded frequency */ - if (freq > maxf) { - freq = maxf; - } else if (freq < -maxf) { - freq = -maxf; + /* calculate new addval, limiting its magnitude to less than 2^31. + make_pulses() uses bit 31 of addval as the direction bit, so an + addval of +2^31 (freq = +maxf, the old freq clamp value) would + be misread as negative and the generated counts would run + backwards (negative velocity at high commanded speed) */ + double aval = freq * freqscale; + int clamped = 0; + if (aval > 2147483647.0) { + aval = 2147483647.0; + clamped = 1; + } else if (aval < -2147483648.0) { + aval = -2147483648.0; + clamped = 1; } - /* calculate new addval */ - sim_enc->addval = freq * freqscale; + if (clamped && !sim_enc->clamp_warned) { + double maxspeed = maxf * 0.25 / hal_get_ui32(sim_enc->ppr) + * hal_get_real(sim_enc->scale); + if (freq < 0.0) { + maxspeed = -maxspeed; + } + sim_enc->clamp_warned = 1; + rtapi_print_msg(RTAPI_MSG_WARN, + "SIM_ENCODER: WARNING: channel %d speed %.3f exceeds maximum %.3f for ppr %d at %ld ns thread period, output capped\n", + n, hal_get_real(sim_enc->speed), maxspeed, + hal_get_ui32(sim_enc->ppr), periodns); + } + hal_set_bool(sim_enc->saturated, clamped); + sim_enc->addval = aval; sim_enc++; } /* done */ @@ -416,9 +439,16 @@ static int export_sim_enc(sim_enc_t * addr, char *prefix) if (retval != 0) { return retval; } + /* export pin for speed cap indication */ + retval = hal_pin_new_bool(comp_id, HAL_OUT, &(addr->saturated), 0, + "%s.saturated", prefix); + if (retval != 0) { + return retval; + } /* init internal vars */ addr->old_scale = 0.0; addr->scale_mult = 1.0; + addr->clamp_warned = 0; /* init the state variables */ addr->accum = 0; addr->addval = 0;