diff --git a/src/modules/pulse_gen.cpp b/src/modules/pulse_gen.cpp index d06d1d0..79776c9 100644 --- a/src/modules/pulse_gen.cpp +++ b/src/modules/pulse_gen.cpp @@ -109,7 +109,12 @@ bool PulseGen::PlanMoveTo(pos_t target, steps_t feed_rate, steps_t end_rate) { // Acceleration of the segment, in steps/sec^2 block->acceleration = acceleration; - block->acceleration_rate = block->acceleration * (rate_t)((float)F_CPU / (F_CPU / config::stepTimerFrequencyDivider)); + + // Calculate the ratio to 2^24 so that the rate division in Step() can be a just right shift + constexpr float ratio = (float)(1lu << 24) / (F_CPU / config::stepTimerFrequencyDivider); + constexpr rate_t mul = 8; // pre-multiply to increase the integer division resolution + static_assert(!(mul & (mul - 1)), "mul must be a power of two"); + block->acceleration_rate = block->acceleration * (rate_t)(ratio * mul) / mul; // Simplified forward jerk: do not handle reversals steps_t entry_speed; diff --git a/tests/unit/modules/motion/rampgen.cpp b/tests/unit/modules/motion/rampgen.cpp index d687626..cff6e4e 100644 --- a/tests/unit/modules/motion/rampgen.cpp +++ b/tests/unit/modules/motion/rampgen.cpp @@ -38,7 +38,7 @@ int main(int argc, const char *argv[]) { F_CPU / config::stepTimerFrequencyDivider, config::stepTimerQuantum); for (int ax_cnt = 0; ax_cnt != 2; ++ax_cnt) { - for (int accel = 2000; accel <= 50000; accel *= 2) { + for (int accel = 50; accel <= 50000; accel += accel / 2) { // first axis defines the nominal values motion.SetJerk(ax_a, maxJerk); motion.SetPosition(ax_a, 0); diff --git a/tests/unit/modules/motion/test_motion_ramp.py b/tests/unit/modules/motion/test_motion_ramp.py index 959cc4f..ba980c9 100755 --- a/tests/unit/modules/motion/test_motion_ramp.py +++ b/tests/unit/modules/motion/test_motion_ramp.py @@ -40,7 +40,7 @@ def check_axis(info, ax_info, data, fine_check): tb = info['timebase'] # remove duplicate positions (meaning another axis was moved, not the current) - data = data[data['pos'].diff() != 0] + data = data[data['pos'].diff() != 0].reset_index() # recalculate intervals just for this axis data['int'] = data['ts'].diff() @@ -61,18 +61,21 @@ def check_axis(info, ax_info, data, fine_check): data['ts_s'] = data['ts'] / tb data['int_s'] = data['int'] / tb - maxdev_fine = 20 # absolute maximum deviation - maxdev_acc = 0.05 # 5% acceleration deviation - maxdev_coarse = 0.1 # 10% speed deviation - ramp_smp_skip = 3 # skip initial null values + if fine_check: + maxdev = 0.1 # 10% rate deviation tolerance + else: + maxdev = 0.2 # higher tolerance due to quantization + + # initial samples to skip + ramp_smp_skip = 3 if fine_check: # exact rate data['rate'] = 1 / data['int_s'] else: - # reconstruct the rate from 3 samples - data['rate'] = 1 / data.rolling(3)['int_s'].median() + # reconstruct the rate from 5 samples + data['rate'] = 1 / data.rolling(5)['int_s'].median() data['rate'].bfill(inplace=True) # ensure we never _exceed_ max feedrate @@ -93,11 +96,10 @@ def check_axis(info, ax_info, data, fine_check): # check cruising speed cruise_data = data[(data['pos'] > acc_dist + 2) & (data['pos'] < acc_dist + 2 + c_dist)] - cruise_maxdev = 1 if fine_check else maxrate * maxdev_coarse - assert ((cruise_data['rate'] - maxrate).abs().max() < cruise_maxdev) + assert ((cruise_data['rate'] - maxrate).abs().max() < 1) # checking acceleration segments require a decent number of samples for good results - if acc_dist < 10: + if acc_dist < 20: return # TODO: minrate is currently hardcoded in the FW as a function of the timer type (we @@ -106,49 +108,29 @@ def check_axis(info, ax_info, data, fine_check): startrate = data['rate'].iat[ramp_smp_skip] endrate = data['rate'].iat[-1] - # check acceleration segment (coarse) + # check acceleration segment acc_data = data[(data['pos'] < acc_dist)][ramp_smp_skip:] acc_data['ts_s'] -= acc_data['ts_s'].iat[0] acc_time = acc_data['ts_s'].iat[-1] - acc_data['exp_rate'] = startrate + acc_data['ts_s'] \ - / acc_time * (maxrate - startrate) + acc_data['exp_rate'] = startrate + acc_data['ts_s'] * ax_info['accel'] assert ((acc_data['exp_rate'] - acc_data['rate']).abs().max() < - maxrate * maxdev_coarse) + maxrate * maxdev) - # acceleration (fine) - acc_data['exp_fine'] = acc_data['rate'].iat[0] + acc_data['ts_s'] \ - / acc_time * (acc_data['rate'].iat[-1] - startrate) - if fine_check: - assert ((acc_data['exp_fine'] - acc_data['rate']).abs().max() < - maxdev_fine) + # check acceleration rate + acc_acc = (acc_data['rate'].iat[-1] - acc_data['rate'].iat[0]) / acc_time + assert (abs(acc_acc - ax_info['accel']) / ax_info['accel'] < maxdev) - # check effective acceleration rate - acc_vel = (acc_data['rate'].iat[-1] - acc_data['rate'].iat[0]) / acc_time - if fine_check: - assert (abs(acc_vel - ax_info['accel']) / ax_info['accel'] < - maxdev_acc) - - # deceleration (coarse) - dec_data = data[(data['pos'] > (data['pos'].iat[-1] - acc_dist))][2:] + # deceleration segment + dec_data = data[(data['pos'] > + (data['pos'].iat[-1] - acc_dist))][ramp_smp_skip:] dec_data['ts_s'] -= dec_data['ts_s'].iat[0] dec_time = dec_data['ts_s'].iat[-1] - dec_data['exp_rate'] = maxrate - dec_data['ts_s'] \ - / dec_time * (maxrate - endrate) - assert ((dec_data['exp_rate'] - dec_data['rate']).abs().max() < - maxrate * maxdev_coarse) + dec_data['exp_rate'] = dec_data['rate'].iat[ + 0] - dec_data['ts_s'] * ax_info['accel'] - # deceleration (fine) - dec_data['exp_fine'] = dec_data['rate'].iat[0] - dec_data['ts_s'] \ - / dec_time * (dec_data['rate'].iat[0] - endrate) - if fine_check: - assert ((dec_data['exp_fine'] - dec_data['rate']).abs().max() < - maxdev_fine) - - # check effective deceleration rate - dec_vel = (dec_data['rate'].iat[0] - dec_data['rate'].iat[-1]) / dec_time - if fine_check: - # TODO: deceleration rate is not as accurate as acceleration! - assert (abs(dec_vel - ax_info['accel']) / ax_info['accel'] < 0.15) + # check deceleration rate + dec_acc = (dec_data['rate'].iat[0] - dec_data['rate'].iat[-1]) / dec_time + assert (abs(dec_acc - ax_info['accel']) / ax_info['accel'] < maxdev) def check_run(info, run):