#include "axis.h" #include "emcmotcfg.h" // EMCMOT_MAX_AXIS #include "rtapi.h" #include "rtapi_math.h" #include "simple_tp.h" typedef struct { double pos_cmd; /* commanded axis position */ double teleop_vel_cmd; /* commanded axis velocity */ double max_pos_limit; /* upper soft limit on axis pos */ double min_pos_limit; /* lower soft limit on axis pos */ double vel_limit; /* upper limit of axis speed */ double acc_limit; /* upper limit of axis accel */ simple_tp_t teleop_tp; /* planner for teleop mode motion */ int old_ajog_counts; /* prior value, used for deltas */ int kb_ajog_active; /* non-zero during a keyboard jog */ int wheel_ajog_active; /* non-zero during a wheel jog */ int locking_joint; /* locking_joint number, -1 ==> notused */ double ext_offset_vel_limit; /* upper limit of axis speed for ext offset */ double ext_offset_acc_limit; /* upper limit of axis accel for ext offset */ int old_eoffset_counts; simple_tp_t ext_offset_tp; /* planner for external coordinate offsets*/ } emcmot_axis_t; typedef struct { hal_float_t *pos_cmd; /* RPI: commanded position */ hal_float_t *teleop_vel_cmd; /* RPI: commanded velocity */ hal_float_t *teleop_pos_cmd; /* RPI: teleop traj planner pos cmd */ hal_float_t *teleop_vel_lim; /* RPI: teleop traj planner vel limit */ hal_bit_t *teleop_tp_enable; /* RPI: teleop traj planner is running */ hal_s32_t *ajog_counts; /* WPI: jogwheel position input */ hal_bit_t *ajog_enable; /* RPI: enable jogwheel */ hal_float_t *ajog_scale; /* RPI: distance to jog on each count */ hal_float_t *ajog_accel_fraction; /* RPI: to limit wheel jog accel */ hal_bit_t *ajog_vel_mode; /* RPI: true for "velocity mode" jogwheel */ hal_bit_t *kb_ajog_active; /* RPI: executing keyboard jog */ hal_bit_t *wheel_ajog_active; /* RPI: executing handwheel jog */ hal_bit_t *eoffset_enable; hal_bit_t *eoffset_clear; hal_s32_t *eoffset_counts; hal_float_t *eoffset_scale; hal_float_t *external_offset; hal_float_t *external_offset_requested; } axis_hal_t; typedef struct { axis_hal_t axis[EMCMOT_MAX_AXIS]; /* data for each axis */ } axis_hal_data_t; static emcmot_axis_t axis_array[EMCMOT_MAX_AXIS]; static axis_hal_data_t *hal_data = NULL; // Mark strings for translation, but defer translation to userspace #define _(s) (s) void axis_init_all(void) { int n; for (n = 0; n < EMCMOT_MAX_AXIS; n++) { emcmot_axis_t *axis = &axis_array[n]; axis->locking_joint = -1; } } void axis_initialize_external_offsets(void) { int n; axis_hal_t *axis_data; for (n = 0; n < EMCMOT_MAX_AXIS; n++) { axis_data = &hal_data->axis[n]; *(axis_data->external_offset) = 0; *(axis_data->external_offset_requested) = 0; axis_array[n].ext_offset_tp.pos_cmd = 0; axis_array[n].ext_offset_tp.curr_pos = 0; axis_array[n].ext_offset_tp.curr_vel = 0; } } #define CALL_CHECK(expr) do { \ int _retval; \ _retval = expr; \ if (_retval) return _retval; \ } while (0); static int export_axis(int mot_comp_id, char c, axis_hal_t * addr) { int msg; msg = rtapi_get_msg_level(); rtapi_set_msg_level(RTAPI_MSG_WARN); CALL_CHECK(hal_pin_bit_newf(HAL_IN, &(addr->ajog_enable), mot_comp_id,"axis.%c.jog-enable", c)); CALL_CHECK(hal_pin_float_newf(HAL_IN, &(addr->ajog_scale), mot_comp_id,"axis.%c.jog-scale", c)); CALL_CHECK(hal_pin_s32_newf(HAL_IN, &(addr->ajog_counts), mot_comp_id,"axis.%c.jog-counts", c)); CALL_CHECK(hal_pin_bit_newf(HAL_IN, &(addr->ajog_vel_mode), mot_comp_id,"axis.%c.jog-vel-mode", c)); CALL_CHECK(hal_pin_bit_newf(HAL_OUT, &(addr->kb_ajog_active), mot_comp_id,"axis.%c.kb-jog-active", c)); CALL_CHECK(hal_pin_bit_newf(HAL_OUT, &(addr->wheel_ajog_active), mot_comp_id,"axis.%c.wheel-jog-active", c)); CALL_CHECK(hal_pin_float_newf(HAL_IN,&(addr->ajog_accel_fraction), mot_comp_id,"axis.%c.jog-accel-fraction", c)); *addr->ajog_accel_fraction = 1.0; // fraction of accel for wheel ajogs rtapi_set_msg_level(msg); return 0; } int axis_init_hal_io(int mot_comp_id) { int n, retval; hal_data = hal_malloc(sizeof(axis_hal_data_t)); if (!hal_data) { rtapi_print_msg(RTAPI_MSG_ERR, _("MOTION: axis_hal_data hal_malloc() failed\n")); return -1; } // export axis pins and parameters for (n = 0; n < EMCMOT_MAX_AXIS; n++) { char c = "xyzabcuvw"[n]; axis_hal_t *axis_data = &(hal_data->axis[n]); CALL_CHECK(hal_pin_float_newf(HAL_OUT, &axis_data->pos_cmd, mot_comp_id, "axis.%c.pos-cmd", c)); CALL_CHECK(hal_pin_float_newf(HAL_OUT, &axis_data->teleop_vel_cmd, mot_comp_id, "axis.%c.teleop-vel-cmd", c)); CALL_CHECK(hal_pin_float_newf(HAL_OUT, &axis_data->teleop_pos_cmd, mot_comp_id, "axis.%c.teleop-pos-cmd", c)); CALL_CHECK(hal_pin_float_newf(HAL_OUT, &axis_data->teleop_vel_lim, mot_comp_id, "axis.%c.teleop-vel-lim", c)); CALL_CHECK(hal_pin_bit_newf(HAL_OUT, &axis_data->teleop_tp_enable, mot_comp_id, "axis.%c.teleop-tp-enable",c)); CALL_CHECK(hal_pin_bit_newf(HAL_IN, &axis_data->eoffset_enable, mot_comp_id, "axis.%c.eoffset-enable", c)); CALL_CHECK(hal_pin_bit_newf(HAL_IN, &axis_data->eoffset_clear, mot_comp_id, "axis.%c.eoffset-clear", c)); CALL_CHECK(hal_pin_s32_newf(HAL_IN, &axis_data->eoffset_counts, mot_comp_id, "axis.%c.eoffset-counts", c)); CALL_CHECK(hal_pin_float_newf(HAL_IN, &axis_data->eoffset_scale, mot_comp_id, "axis.%c.eoffset-scale", c)); CALL_CHECK(hal_pin_float_newf(HAL_OUT, &axis_data->external_offset, mot_comp_id, "axis.%c.eoffset", c)); CALL_CHECK(hal_pin_float_newf(HAL_OUT, &axis_data->external_offset_requested, mot_comp_id, "axis.%c.eoffset-request", c)); retval = export_axis(mot_comp_id, c, axis_data); if (retval) { rtapi_print_msg(RTAPI_MSG_ERR, _("MOTION: axis %c pin/param export failed\n"), c); return -1; } } return 0; } void axis_output_to_hal(double *pcmd_p[]) { int n; // output axis info to HAL for scoping, etc for (n = 0; n < EMCMOT_MAX_AXIS; n++) { emcmot_axis_t *axis = &axis_array[n]; axis_hal_t *axis_data = &hal_data->axis[n]; *(axis_data->teleop_vel_cmd) = axis->teleop_vel_cmd; *(axis_data->teleop_pos_cmd) = axis->teleop_tp.pos_cmd; *(axis_data->teleop_vel_lim) = axis->teleop_tp.max_vel; *(axis_data->teleop_tp_enable) = axis->teleop_tp.enable; *(axis_data->kb_ajog_active) = axis->kb_ajog_active; *(axis_data->wheel_ajog_active) = axis->wheel_ajog_active; // hal pins: axis.L.pos-cmd reported without applied offsets: *(axis_data->pos_cmd) = *pcmd_p[n] - axis->ext_offset_tp.curr_pos; } } void axis_set_max_pos_limit(int axis_num, double maxLimit) { axis_array[axis_num].max_pos_limit = maxLimit; } void axis_set_min_pos_limit(int axis_num, double minLimit) { axis_array[axis_num].min_pos_limit = minLimit; } void axis_set_vel_limit(int axis_num, double vel) { axis_array[axis_num].vel_limit = vel; } void axis_set_acc_limit(int axis_num, double acc) { axis_array[axis_num].acc_limit = acc; } void axis_set_ext_offset_vel_limit(int axis_num, double vel) { axis_array[axis_num].ext_offset_vel_limit = vel; } void axis_set_ext_offset_acc_limit(int axis_num, double acc) { axis_array[axis_num].ext_offset_acc_limit = acc; } void axis_set_locking_joint(int axis_num, int joint) { axis_array[axis_num].locking_joint = joint; } double axis_get_min_pos_limit(int axis_num) { return axis_array[axis_num].min_pos_limit; } double axis_get_max_pos_limit(int axis_num) { return axis_array[axis_num].max_pos_limit; } double axis_get_vel_limit(int axis_num) { return axis_array[axis_num].vel_limit; } double axis_get_acc_limit(int axis_num) { return axis_array[axis_num].acc_limit; } double axis_get_teleop_vel_cmd(int axis_num) { return axis_array[axis_num].teleop_vel_cmd; } int axis_get_locking_joint(int axis_num) { return axis_array[axis_num].locking_joint; } double axis_get_compound_velocity(void) { double v2 = 0.0; int n; for (n = 0; n < EMCMOT_MAX_AXIS; n++) { emcmot_axis_t *axis = &axis_array[n]; if (axis->teleop_tp.active) { v2 += axis->teleop_vel_cmd * axis->teleop_vel_cmd; } } if (v2 > 0.0) return sqrt(v2); return 0.0; } double axis_get_ext_offset_curr_pos(int axis_num) { return axis_array[axis_num].ext_offset_tp.curr_pos; } void axis_jog_cont(int axis_num, double vel, long servo_period) { emcmot_axis_t *axis = &axis_array[axis_num]; if (vel > 0.0) { axis->teleop_tp.pos_cmd = axis->max_pos_limit; } else { axis->teleop_tp.pos_cmd = axis->min_pos_limit; } axis->teleop_tp.max_vel = fabs(vel); axis->teleop_tp.max_acc = axis->acc_limit; axis->kb_ajog_active = 1; axis->teleop_tp.enable = 1; } void axis_jog_incr(int axis_num, double offset, double vel, long servo_period) { emcmot_axis_t *axis = &axis_array[axis_num]; double tmp1; if (vel > 0.0) { tmp1 = axis->teleop_tp.pos_cmd + offset; } else { tmp1 = axis->teleop_tp.pos_cmd - offset; } if (tmp1 > axis->max_pos_limit) { return; } if (tmp1 < axis->min_pos_limit) { return; } axis->teleop_tp.pos_cmd = tmp1; axis->teleop_tp.max_vel = fabs(vel); axis->teleop_tp.max_acc = axis->acc_limit; axis->kb_ajog_active = 1; axis->teleop_tp.enable = 1; } void axis_jog_abs(int axis_num, double offset, double vel) { emcmot_axis_t *axis = &axis_array[axis_num]; double tmp1; axis->kb_ajog_active = 1; if (axis->wheel_ajog_active) { return; } if (vel > 0.0) { tmp1 = axis->teleop_tp.pos_cmd + offset; } else { tmp1 = axis->teleop_tp.pos_cmd - offset; } if (tmp1 > axis->max_pos_limit) { return; } if (tmp1 < axis->min_pos_limit) { return; } axis->teleop_tp.pos_cmd = tmp1; axis->teleop_tp.max_vel = fabs(vel); axis->teleop_tp.max_acc = axis->acc_limit; axis->kb_ajog_active = 1; axis->teleop_tp.enable = 1; } bool axis_jog_abort(int axis_num, bool immediate) { bool aborted = 0; emcmot_axis_t *axis = &axis_array[axis_num]; if (axis->teleop_tp.enable) { aborted = 1; } axis->teleop_tp.enable = 0; axis->kb_ajog_active = 0; axis->wheel_ajog_active = 0; if (immediate) { axis->teleop_tp.curr_vel = 0.0; } return aborted; } bool axis_jog_abort_all(bool immediate) { int n; bool aborted = 0; for (n = 0; n < EMCMOT_MAX_AXIS; n++) { if (axis_jog_abort(n, immediate)) {aborted = 1;} } return aborted; } bool axis_jog_is_active(void) { int n; emcmot_axis_t *axis; for (n = 0; n < EMCMOT_MAX_AXIS; n++) { axis = &axis_array[n]; if (axis->kb_ajog_active || axis->wheel_ajog_active) { return 1; } } return 0; } void axis_handle_jogwheels(bool motion_teleop_flag, bool motion_enable_flag, bool homing_is_active) { int axis_num; emcmot_axis_t *axis; axis_hal_t *axis_data; int new_ajog_counts, delta; double distance, pos, stop_dist; static int first_pass = 1; /* used to set initial conditions */ for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { double aaccel_limit; axis = &axis_array[axis_num]; axis_data = &hal_data->axis[axis_num]; // disallow accel bogus fractions if ( (*(axis_data->ajog_accel_fraction) > 1) || (*(axis_data->ajog_accel_fraction) < 0) ) { aaccel_limit = axis->acc_limit; } else { aaccel_limit = *(axis_data->ajog_accel_fraction) * axis->acc_limit; } new_ajog_counts = *(axis_data->ajog_counts); delta = new_ajog_counts - axis->old_ajog_counts; axis->old_ajog_counts = new_ajog_counts; if ( first_pass ) { continue; } if ( delta == 0 ) { //just update counts continue; } if (!motion_teleop_flag) { axis->teleop_tp.enable = 0; return; } if (!motion_enable_flag) { continue; } if ( *(axis_data->ajog_enable) == 0 ) { continue; } if (homing_is_active) { continue; } if (axis->kb_ajog_active) { continue; } if (axis->locking_joint >= 0) { rtapi_print_msg(RTAPI_MSG_ERR, "Cannot wheel jog a locking indexer AXIS_%c\n", "XYZABCUVW"[axis_num]); continue; } distance = delta * *(axis_data->ajog_scale); pos = axis->teleop_tp.pos_cmd + distance; if ( *(axis_data->ajog_vel_mode) ) { double v = axis->vel_limit; /* compute stopping distance at max speed */ stop_dist = v * v / ( 2 * aaccel_limit); /* if commanded position leads the actual position by more than stopping distance, discard excess command */ if ( pos > axis->pos_cmd + stop_dist ) { pos = axis->pos_cmd + stop_dist; } else if ( pos < axis->pos_cmd - stop_dist ) { pos = axis->pos_cmd - stop_dist; } } if (pos > axis->max_pos_limit) { break; } if (pos < axis->min_pos_limit) { break; } axis->teleop_tp.pos_cmd = pos; axis->teleop_tp.max_vel = axis->vel_limit; axis->teleop_tp.max_acc = aaccel_limit; axis->wheel_ajog_active = 1; axis->teleop_tp.enable = 1; } first_pass = 0; } void axis_sync_teleop_tp_to_carte_pos(int extfactor, double *pcmd_p[]) { int n; // expect extfactor = -1 || 0 || +1 for (n = 0; n < EMCMOT_MAX_AXIS; n++) { axis_array[n].teleop_tp.curr_pos = *pcmd_p[n] + extfactor * axis_array[n].ext_offset_tp.curr_pos; } } void axis_sync_carte_pos_to_teleop_tp(int extfactor, double *pcmd_p[]) { int n; // expect extfactor = -1 || 0 || +1 for (n = 0; n < EMCMOT_MAX_AXIS; n++) { *pcmd_p[n] = axis_array[n].teleop_tp.curr_pos + extfactor * axis_array[n].ext_offset_tp.curr_pos; } } void axis_apply_ext_offsets_to_carte_pos(int extfactor, double *pcmd_p[]) { int n; // expect extfactor = -1 || 0 || +1 for (n = 0; n < EMCMOT_MAX_AXIS; n++) { *pcmd_p[n] = *pcmd_p[n] + extfactor * axis_array[n].ext_offset_tp.curr_pos; } } hal_bit_t axis_plan_external_offsets(double servo_period, bool motion_enable_flag, bool all_homed) { static int first_pass = 1; int n; emcmot_axis_t *axis; axis_hal_t *axis_data; int new_eoffset_counts, delta; static int last_eoffset_enable[EMCMOT_MAX_AXIS]; double ext_offset_epsilon; hal_bit_t eoffset_active; eoffset_active = 0; for (n = 0; n < EMCMOT_MAX_AXIS; n++) { axis = &axis_array[n]; // coord,teleop updates done in get_pos_cmds() axis->ext_offset_tp.max_vel = axis->ext_offset_vel_limit; axis->ext_offset_tp.max_acc = axis->ext_offset_acc_limit; axis_data = &hal_data->axis[n]; new_eoffset_counts = *(axis_data->eoffset_counts); delta = new_eoffset_counts - axis->old_eoffset_counts; axis->old_eoffset_counts = new_eoffset_counts; *(axis_data->external_offset) = axis->ext_offset_tp.curr_pos; axis->ext_offset_tp.enable = 1; if ( first_pass ) { *(axis_data->external_offset) = 0; continue; } // Use stopping criterion of simple_tp.c: ext_offset_epsilon = TINY_DP(axis->ext_offset_tp.max_acc, servo_period); if (fabs(*(axis_data->external_offset)) > ext_offset_epsilon) { eoffset_active = 1; } if ( !*(axis_data->eoffset_enable) ) { axis->ext_offset_tp.enable = 0; // Detect disabling of eoffsets: // At very high accel, simple planner may terminate with // a larger position value than occurs at more realistic accels. if (last_eoffset_enable[n] && (fabs(*(axis_data->external_offset)) > ext_offset_epsilon) && motion_enable_flag && axis->ext_offset_tp.enable) { // to stdout only: rtapi_print_msg(RTAPI_MSG_NONE, "*** Axis_%c External Offset=%.4g eps=%.4g\n" "*** External Offset disabled while NON-zero\n" "*** To clear: re-enable & zero or use Machine-Off\n", "XYZABCUVW"[n], *(axis_data->external_offset), ext_offset_epsilon); } last_eoffset_enable[n] = 0; continue; // Note: if not eoffset_enable // then planner disabled and no pos_cmd updates // useful for eoffset_pid hold } last_eoffset_enable[n] = 1; if (*(axis_data->eoffset_clear)) { axis->ext_offset_tp.pos_cmd = 0; *(axis_data->external_offset_requested) = 0; continue; } if (delta == 0) { continue; } if (!all_homed) { continue; } if (!motion_enable_flag) { continue; } axis->ext_offset_tp.pos_cmd += delta * *(axis_data->eoffset_scale); *(axis_data->external_offset_requested) = axis->ext_offset_tp.pos_cmd; } // for n first_pass = 0; return eoffset_active; } /* For each axis, return -1 if over negative limit, 1 if over positive limit, or 0 if in range */ void axis_check_constraints(double pos[], int failing_axes[]) { int axis_num; double eps = 1e-308; for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num += 1) { double nl = axis_array[axis_num].min_pos_limit; double pl = axis_array[axis_num].max_pos_limit; failing_axes[axis_num] = 0; if ( (fabs(pos[axis_num]) < eps) && (fabs(axis_array[axis_num].min_pos_limit) < eps) && (fabs(axis_array[axis_num].max_pos_limit) < eps) ) { continue; } if (pos[axis_num] < (nl - 0.000000000001)) { // see pull request #1047 failing_axes[axis_num] = -1; } if (pos[axis_num] > (pl + 0.000000000001)) { // see pull request #1047 failing_axes[axis_num] = 1; } } } int axis_update_coord_with_bound(double *pcmd_p[], double servo_period) { int n; int ans = 0; emcmot_axis_t *axis; double save_pos_cmd[EMCMOT_MAX_AXIS]; double save_offset_cmd[EMCMOT_MAX_AXIS]; for (n = 0; n < EMCMOT_MAX_AXIS; n++) { axis = &axis_array[n]; save_pos_cmd[n] = *pcmd_p[n]; save_offset_cmd[n] = axis->ext_offset_tp.pos_cmd; simple_tp_update(&(axis->ext_offset_tp), servo_period); } axis_apply_ext_offsets_to_carte_pos(+1, pcmd_p); // add external offsets for (n = 0; n < EMCMOT_MAX_AXIS; n++) { axis = &axis_array[n]; //workaround: axis letters not in [TRAJ]COORDINATES // have min_pos_limit == max_pos_lim == 0 if ( (0 == axis->max_pos_limit) && (0 == axis->min_pos_limit) ) { continue; } if (axis->ext_offset_tp.curr_pos == 0) { continue; // don't claim violation if no offset } if (*pcmd_p[n] >= axis->max_pos_limit) { // hold carte_pos_cmd at the limit: *pcmd_p[n] = axis->max_pos_limit; // stop growth of offsetting position: axis->ext_offset_tp.curr_pos = axis->max_pos_limit - save_pos_cmd[n]; if (axis->ext_offset_tp.pos_cmd > save_offset_cmd[n]) { axis->ext_offset_tp.pos_cmd = save_offset_cmd[n]; } axis->ext_offset_tp.curr_vel = 0; ans++; continue; } if (*pcmd_p[n] <= axis->min_pos_limit) { *pcmd_p[n] = axis->min_pos_limit; axis->ext_offset_tp.curr_pos = axis->min_pos_limit - save_pos_cmd[n]; if (axis->ext_offset_tp.pos_cmd < save_offset_cmd[n]) { axis->ext_offset_tp.pos_cmd = save_offset_cmd[n]; } axis->ext_offset_tp.curr_vel = 0; ans++; } } if (ans > 0) { return 1; } return 0; } static int update_teleop_with_check(int axis_num, simple_tp_t *the_tp, double servo_period) { // 'the_tp' is the planner to update // the tests herein apply to the sum of the offsets for both // planners (teleop_tp and ext_offset_tp) double save_curr_pos; emcmot_axis_t *axis = &axis_array[axis_num]; save_curr_pos = the_tp->curr_pos; simple_tp_update(the_tp, servo_period); //workaround: axis letters not in [TRAJ]COORDINATES // have min_pos_limit == max_pos_lim == 0 if ( (0 == axis->max_pos_limit) && (0 == axis->min_pos_limit) ) { return 0; } if ( (axis->ext_offset_tp.curr_pos + axis->teleop_tp.curr_pos) >= axis->max_pos_limit) { // positive error, restore save_curr_pos the_tp->curr_pos = save_curr_pos; the_tp->curr_vel = 0; return 1; } if ( (axis->ext_offset_tp.curr_pos + axis->teleop_tp.curr_pos) <= axis->min_pos_limit) { // negative error, restore save_curr_pos the_tp->curr_pos = save_curr_pos; the_tp->curr_vel = 0; return 1; } return 0; } int axis_calc_motion(double servo_period) { int axis_num; int violated_teleop_limit = 0; emcmot_axis_t *axis; for (axis_num = 0; axis_num < EMCMOT_MAX_AXIS; axis_num++) { axis = &axis_array[axis_num]; // teleop_tp.max_vel is always positive if (axis->teleop_tp.max_vel > axis->vel_limit) { axis->teleop_tp.max_vel = axis->vel_limit; } if (update_teleop_with_check(axis_num, &(axis->teleop_tp), servo_period)) { violated_teleop_limit = 1; } else { axis->teleop_vel_cmd = axis->teleop_tp.curr_vel; axis->pos_cmd = axis->teleop_tp.curr_pos; } if (!axis->teleop_tp.active) { axis->kb_ajog_active = 0; axis->wheel_ajog_active = 0; } if (axis->ext_offset_tp.enable) { if (update_teleop_with_check(axis_num, &(axis->ext_offset_tp), servo_period)) { violated_teleop_limit = 1; } } } return violated_teleop_limit; }