mirror of
https://github.com/synthetos/g2.git
synced 2026-08-19 01:13:34 +08:00
3094 lines
118 KiB
C++
3094 lines
118 KiB
C++
/*
|
|
* canonical_machine.cpp - rs274/ngc canonical machine.
|
|
* This file is part of the g2core project
|
|
*
|
|
* Copyright (c) 2010 - 2017 Alden S Hart, Jr.
|
|
* Copyright (c) 2014 - 2017 Robert Giseburt
|
|
*
|
|
* This file ("the software") is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License, version 2 as published by the
|
|
* Free Software Foundation. You should have received a copy of the GNU General Public
|
|
* License, version 2 along with the software. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* As a special exception, you may use this file as part of a software library without
|
|
* restriction. Specifically, if other files instantiate templates or use macros or
|
|
* inline functions from this file, or you compile this file and link it with other
|
|
* files to produce an executable, this file does not by itself cause the resulting
|
|
* executable to be covered by the GNU General Public License. This exception does not
|
|
* however invalidate any other reasons why the executable file might be covered by the
|
|
* GNU General Public License.
|
|
*
|
|
* THE SOFTWARE IS DISTRIBUTED IN THE HOPE THAT IT WILL BE USEFUL, BUT WITHOUT ANY
|
|
* WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
|
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
|
* SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
|
* OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
/*
|
|
* This code is a loose implementation of Kramer, Proctor and Messina's canonical
|
|
* machining functions as described in the NIST RS274/NGC v3
|
|
*
|
|
* The canonical machine is the layer between the Gcode parser and the motion control
|
|
* code for a specific robot. It keeps state and executes commands - passing the
|
|
* stateless commands to the motion planning layer.
|
|
*/
|
|
/* --- System state contexts - Gcode models ---
|
|
*
|
|
* Useful reference for doing C callbacks http://www.newty.de/fpt/fpt.html
|
|
*
|
|
* There are 3 temporal contexts for system state:
|
|
* - The gcode model in the canonical machine (the MODEL context, held in gm)
|
|
* - The gcode model used by the planner (PLANNER context, held in bf's and mm)
|
|
* - The gcode model used during motion for reporting (RUNTIME context, held in mr)
|
|
*
|
|
* It's a bit more complicated than this. The 'gm' struct contains the core Gcode model
|
|
* context. This originates in the canonical machine and is copied to each planner buffer
|
|
* (bf buffer) during motion planning. Finally, the gm context is passed to the runtime
|
|
* (mr) for the RUNTIME context. So at last count the Gcode model exists in as many as
|
|
* 30 copies in the system. (1+28+1)
|
|
*
|
|
* Depending on the need, any one of these contexts may be called for reporting or by
|
|
* a function. Most typically, all new commends from the gcode parser work form the MODEL
|
|
* context, and status reports pull from the RUNTIME while in motion, and from MODEL when
|
|
* at rest. A convenience is provided in the ACTIVE_MODEL pointer to point to the right
|
|
* context.
|
|
*/
|
|
/* --- Synchronizing command execution ---
|
|
*
|
|
* Some gcode commands only set the MODEL state for interpretation of the current Gcode
|
|
* block. For example, cm_set_feed_rate(). This sets the MODEL so the move time is
|
|
* properly calculated for the current (and subsequent) blocks, so it's effected
|
|
* immediately.
|
|
*
|
|
* "Synchronous commands" are commands that affect the runtime need to be synchronized
|
|
* with movement. Examples include G4 dwells, program stops and ends, and most M commands.
|
|
* These are queued into the planner queue and execute from the queue. Synchronous commands
|
|
* work like this:
|
|
*
|
|
* - Call the cm_xxx_xxx() function which will do any input validation and return an
|
|
* error if it detects one.
|
|
*
|
|
* - The cm_ function calls mp_queue_command(). Arguments are a callback to the _exec_...()
|
|
* function, which is the runtime execution routine, and any arguments that are needed
|
|
* by the runtime. See typedef for *exec in planner.h for details
|
|
*
|
|
* - mp_queue_command() stores the callback and the args in a planner buffer.
|
|
*
|
|
* - When planner execution reaches the buffer it executes the callback w/ the args.
|
|
* Take careful note that the callback executes under an interrupt, so beware of
|
|
* variables that may need to be volatile.
|
|
*
|
|
* Note:
|
|
* - The synchronous command execution mechanism uses 2 vectors in the bf buffer to store
|
|
* and return values for the callback. It's obvious, but impractical to pass the entire
|
|
* bf buffer to the callback as some of these commands are actually executed locally
|
|
* and have no buffer.
|
|
*/
|
|
|
|
#include "g2core.h" // #1
|
|
#include "config.h" // #2
|
|
#include "canonical_machine.h"
|
|
#include "hardware.h"
|
|
#include "controller.h"
|
|
#include "json_parser.h"
|
|
#include "text_parser.h"
|
|
#include "settings.h"
|
|
|
|
#include "plan_arc.h"
|
|
#include "planner.h"
|
|
#include "stepper.h"
|
|
#include "encoder.h"
|
|
#include "spindle.h"
|
|
#include "coolant.h"
|
|
#include "pwm.h"
|
|
#include "report.h"
|
|
#include "gpio.h"
|
|
#include "temperature.h"
|
|
#include "hardware.h"
|
|
#include "util.h"
|
|
#include "settings.h"
|
|
#include "xio.h" // for serial queue flush
|
|
|
|
/***********************************************************************************
|
|
**** STRUCTURE ALLOCATIONS ********************************************************
|
|
***********************************************************************************/
|
|
|
|
cmSingleton_t cm; // canonical machine controller singleton
|
|
|
|
/***********************************************************************************
|
|
**** GENERIC STATIC FUNCTIONS AND VARIABLES ***************************************
|
|
***********************************************************************************/
|
|
|
|
// command execution callbacks from planner queue
|
|
static void _exec_offset(float *value, bool *flag);
|
|
static void _exec_change_tool(float *value, bool *flag);
|
|
static void _exec_select_tool(float *value, bool *flag);
|
|
static void _exec_absolute_origin(float *value, bool *flag);
|
|
static void _exec_program_finalize(float *value, bool *flag);
|
|
|
|
static int8_t _get_axis(const index_t index);
|
|
|
|
/***********************************************************************************
|
|
**** CODE *************************************************************************
|
|
***********************************************************************************/
|
|
|
|
/*************************************
|
|
* Internal getters and setters *
|
|
* Canonical Machine State functions *
|
|
*************************************/
|
|
/*
|
|
* cm_set_motion_state() - adjusts active model pointer as well
|
|
*/
|
|
void cm_set_motion_state(const cmMotionState motion_state)
|
|
{
|
|
cm.motion_state = motion_state;
|
|
|
|
switch (motion_state) {
|
|
case (MOTION_STOP): { ACTIVE_MODEL = MODEL; break; }
|
|
case (MOTION_PLANNING): { ACTIVE_MODEL = RUNTIME; break; }
|
|
case (MOTION_RUN): { ACTIVE_MODEL = RUNTIME; break; }
|
|
case (MOTION_HOLD): { ACTIVE_MODEL = RUNTIME; break; }
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cm_get_machine_state()
|
|
* cm_get_motion_state()
|
|
* cm_get_cycle_state()
|
|
* cm_get_hold_state()
|
|
* cm_get_homing_state()
|
|
*/
|
|
cmMachineState cm_get_machine_state() { return cm.machine_state;}
|
|
cmCycleState cm_get_cycle_state() { return cm.cycle_state;}
|
|
cmMotionState cm_get_motion_state() { return cm.motion_state;}
|
|
cmFeedholdState cm_get_hold_state() { return cm.hold_state;}
|
|
cmHomingState cm_get_homing_state() { return cm.homing_state;}
|
|
|
|
/*
|
|
* cm_get_combined_state() - combines raw states into something a user might want to see
|
|
*
|
|
* Note:
|
|
* On issuing a gcode command we call cm_cycle_start() before the motion gets queued. We don't go
|
|
* to MOTION_RUN until the command is executed by mp_exec_aline(), planned, queued, and started.
|
|
* So MOTION_STOP must actually return COMBINED_RUN to address this case, even though under some
|
|
* circumstances it might actually ne an exception case. Therefore this assertion isn't valid:
|
|
* cm_panic(STAT_STATE_MANAGEMENT_ASSERTION_FAILURE, "mots2"));//"mots is stop but machine is in cycle"
|
|
* return (COMBINED_PANIC);
|
|
*/
|
|
cmCombinedState cm_get_combined_state()
|
|
{
|
|
if (cm.machine_state <= MACHINE_PROGRAM_END) { // replaces first 5 cm.machine_state cases
|
|
return ((cmCombinedState)cm.machine_state); //...where MACHINE_xxx == COMBINED_xxx
|
|
}
|
|
switch(cm.machine_state) {
|
|
case MACHINE_INTERLOCK: { return (COMBINED_INTERLOCK); }
|
|
case MACHINE_SHUTDOWN: { return (COMBINED_SHUTDOWN); }
|
|
case MACHINE_PANIC: { return (COMBINED_PANIC); }
|
|
case MACHINE_CYCLE: {
|
|
switch(cm.cycle_state) {
|
|
case CYCLE_HOMING: { return (COMBINED_HOMING); }
|
|
case CYCLE_PROBE: { return (COMBINED_PROBE); }
|
|
case CYCLE_JOG: { return (COMBINED_JOG); }
|
|
case CYCLE_MACHINING: case CYCLE_OFF: {
|
|
switch(cm.motion_state) {
|
|
case MOTION_STOP: { return (COMBINED_RUN); } // See NOTE_1, above
|
|
case MOTION_PLANNING: { return (COMBINED_RUN); }
|
|
case MOTION_RUN: { return (COMBINED_RUN); }
|
|
case MOTION_HOLD: { return (COMBINED_HOLD); }
|
|
default: {
|
|
cm_panic(STAT_STATE_MANAGEMENT_ASSERTION_FAILURE, "cm_get_combined_state() mots bad");// "mots has impossible value"
|
|
return (COMBINED_PANIC);
|
|
}
|
|
}
|
|
}
|
|
default: {
|
|
cm_panic(STAT_STATE_MANAGEMENT_ASSERTION_FAILURE, "cm_get_combined_state() cycs bad"); // "cycs has impossible value"
|
|
return (COMBINED_PANIC);
|
|
}
|
|
}
|
|
}
|
|
default: {
|
|
cm_panic(STAT_STATE_MANAGEMENT_ASSERTION_FAILURE, "cm_get_combined_state() macs bad"); // "macs has impossible value"
|
|
return (COMBINED_PANIC);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***********************************
|
|
* Model State Getters and Setters *
|
|
***********************************/
|
|
|
|
/* These getters and setters will work on any gm model with inputs:
|
|
* MODEL (GCodeState_t *)&cm.gm // absolute pointer from canonical machine gm model
|
|
* PLANNER (GCodeState_t *)&bf->gm // relative to buffer *bf is currently pointing to
|
|
* RUNTIME (GCodeState_t *)&mr.gm // absolute pointer from runtime mm struct
|
|
* ACTIVE_MODEL cm.am // active model pointer is maintained by state management
|
|
*/
|
|
uint32_t cm_get_linenum(const GCodeState_t *gcode_state) { return gcode_state->linenum;}
|
|
cmMotionMode cm_get_motion_mode(const GCodeState_t *gcode_state) { return gcode_state->motion_mode;}
|
|
uint8_t cm_get_coord_system(const GCodeState_t *gcode_state) { return gcode_state->coord_system;}
|
|
uint8_t cm_get_units_mode(const GCodeState_t *gcode_state) { return gcode_state->units_mode;}
|
|
uint8_t cm_get_select_plane(const GCodeState_t *gcode_state) { return gcode_state->select_plane;}
|
|
uint8_t cm_get_path_control(const GCodeState_t *gcode_state) { return gcode_state->path_control;}
|
|
uint8_t cm_get_distance_mode(const GCodeState_t *gcode_state) { return gcode_state->distance_mode;}
|
|
uint8_t cm_get_arc_distance_mode(const GCodeState_t *gcode_state) { return gcode_state->arc_distance_mode;}
|
|
uint8_t cm_get_feed_rate_mode(const GCodeState_t *gcode_state) { return gcode_state->feed_rate_mode;}
|
|
uint8_t cm_get_tool(const GCodeState_t *gcode_state) { return gcode_state->tool;}
|
|
uint8_t cm_get_block_delete_switch() { return cm.gmx.block_delete_switch;}
|
|
uint8_t cm_get_runtime_busy() { return (mp_get_runtime_busy());}
|
|
float cm_get_feed_rate(const GCodeState_t *gcode_state) { return gcode_state->feed_rate;}
|
|
|
|
void cm_set_motion_mode(GCodeState_t *gcode_state, const uint8_t motion_mode)
|
|
{
|
|
gcode_state->motion_mode = (cmMotionMode)motion_mode;
|
|
}
|
|
|
|
void cm_set_tool_number(GCodeState_t *gcode_state, const uint8_t tool)
|
|
{
|
|
gcode_state->tool = tool;
|
|
}
|
|
|
|
void cm_set_absolute_override(GCodeState_t *gcode_state, const uint8_t absolute_override)
|
|
{
|
|
gcode_state->absolute_override = (cmAbsoluteOverride)absolute_override;
|
|
cm_set_work_offsets(MODEL); // must reset offsets if you change absolute override
|
|
}
|
|
|
|
void cm_set_model_linenum(const uint32_t linenum)
|
|
{
|
|
cm.gm.linenum = linenum; // you must first set the model line number,
|
|
nv_add_object((const char *)"n"); // then add the line number to the nv list
|
|
}
|
|
|
|
stat_t cm_check_linenum() {
|
|
if (cm.gmx.last_line_number != cm.gm.linenum) {
|
|
_debug_trap("line number out of sequence");
|
|
return STAT_LINE_NUMBER_OUT_OF_SEQUENCE;
|
|
}
|
|
cm.gmx.last_line_number = cm.gm.linenum;
|
|
return STAT_OK;
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* COORDINATE SYSTEMS AND OFFSETS
|
|
* Functions to get, set and report coordinate systems and work offsets
|
|
* These functions are not part of the NIST defined functions
|
|
***********************************************************************************/
|
|
/*
|
|
* Notes on Coordinate System and Offset functions
|
|
*
|
|
* All positional information in the canonical machine is kept as absolute coords and in
|
|
* canonical units (mm). The offsets are only used to translate in and out of canonical form
|
|
* during interpretation and response.
|
|
*
|
|
* Managing the coordinate systems & offsets is somewhat complicated. The following affect offsets:
|
|
* - coordinate system selected. 1-9 correspond to G54-G59
|
|
* - absolute override: forces current move to be interpreted in machine coordinates: G53 (system 0)
|
|
* - G92 offsets are added "on top of" the coord system offsets -- if origin_offset_enable == true
|
|
* - G28 and G30 moves; these are run in absolute coordinates
|
|
*
|
|
* The offsets themselves are considered static, are kept in cm, and are supposed to be persistent.
|
|
*
|
|
* To reduce complexity and data load the following is done:
|
|
* - Full data for coordinates/offsets is only accessible by the canonical machine, not the downstream
|
|
* - A fully resolved set of coord and G92 offsets, with per-move exceptions can be captured as "work_offsets"
|
|
* - The core gcode context (gm) only knows about the active coord system and the work offsets
|
|
*/
|
|
|
|
/*
|
|
* cm_get_active_coord_offset() - return the currently active coordinate offset for an axis
|
|
*
|
|
* Takes G5x, G92 and absolute override into account to return the active offset for this move
|
|
*
|
|
* This function is typically used to evaluate and set offsets, as opposed to cm_get_work_offset()
|
|
* which merely returns what's in the work_offset[] array.
|
|
*/
|
|
|
|
float cm_get_active_coord_offset(const uint8_t axis)
|
|
{
|
|
if (cm.gm.absolute_override == ABSOLUTE_OVERRIDE_ON) { // no offset if in absolute override mode
|
|
return (0.0);
|
|
}
|
|
float offset = cm.offset[cm.gm.coord_system][axis] + cm.tl_offset[axis];
|
|
if (cm.gmx.origin_offset_enable == true) {
|
|
offset += cm.gmx.origin_offset[axis]; // includes G5x and G92 components
|
|
}
|
|
return (offset);
|
|
}
|
|
|
|
/*
|
|
* cm_get_work_offset() - return a coord offset from the gcode_state
|
|
*
|
|
* MODEL (GCodeState_t *)&cm.gm // absolute pointer from canonical machine gm model
|
|
* PLANNER (GCodeState_t *)&bf->gm // relative to buffer *bf is currently pointing to
|
|
* RUNTIME (GCodeState_t *)&mr.gm // absolute pointer from runtime mm struct
|
|
* ACTIVE_MODEL cm.am // active model pointer is maintained by state management
|
|
*/
|
|
|
|
float cm_get_work_offset(const GCodeState_t *gcode_state, const uint8_t axis)
|
|
{
|
|
return (gcode_state->work_offset[axis]);
|
|
}
|
|
|
|
/*
|
|
* cm_set_work_offsets() - capture coord offsets from the model into absolute values in the gcode_state
|
|
*
|
|
* MODEL (GCodeState_t *)&cm.gm // absolute pointer from canonical machine gm model
|
|
* PLANNER (GCodeState_t *)&bf->gm // relative to buffer *bf is currently pointing to
|
|
* RUNTIME (GCodeState_t *)&mr.gm // absolute pointer from runtime mm struct
|
|
* ACTIVE_MODEL cm.am // active model pointer is maintained by state management
|
|
*/
|
|
|
|
void cm_set_work_offsets(GCodeState_t *gcode_state)
|
|
{
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
gcode_state->work_offset[axis] = cm_get_active_coord_offset(axis);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cm_get_absolute_position() - get position of axis in absolute coordinates
|
|
*
|
|
* This function accepts as input:
|
|
* MODEL (GCodeState_t *)&cm.gm // absolute pointer from canonical machine gm model
|
|
* RUNTIME (GCodeState_t *)&mr.gm // absolute pointer from runtime mm struct
|
|
*
|
|
* NOTE: Only MODEL and RUNTIME are supported (no PLANNER or bf's)
|
|
* NOTE: Machine position is always returned in mm mode. No units conversion is performed
|
|
*/
|
|
|
|
float cm_get_absolute_position(const GCodeState_t *gcode_state, const uint8_t axis)
|
|
{
|
|
if (gcode_state == MODEL) {
|
|
return (cm.gmx.position[axis]);
|
|
}
|
|
return (mp_get_runtime_absolute_position(axis));
|
|
}
|
|
|
|
/*
|
|
* cm_get_work_position() - return work position in external form
|
|
*
|
|
* ... that means in prevailing units (mm/inch) and with all offsets applied
|
|
*
|
|
* NOTE: This function only works after the gcode_state struct as had the work_offsets setup by
|
|
* calling cm_get_model_coord_offset_vector() first.
|
|
*
|
|
* This function accepts as input:
|
|
* MODEL (GCodeState_t *)&cm.gm // absolute pointer from canonical machine gm model
|
|
* RUNTIME (GCodeState_t *)&mr.gm // absolute pointer from runtime mm struct
|
|
*
|
|
* NOTE: Only MODEL and RUNTIME are supported (no PLANNER or bf's)
|
|
*/
|
|
|
|
float cm_get_work_position(const GCodeState_t *gcode_state, const uint8_t axis)
|
|
{
|
|
float position;
|
|
|
|
if (gcode_state == MODEL) {
|
|
position = cm.gmx.position[axis] - cm_get_active_coord_offset(axis);
|
|
} else {
|
|
position = mp_get_runtime_work_position(axis);
|
|
}
|
|
if (axis <= AXIS_Z) {
|
|
if (gcode_state->units_mode == INCHES) {
|
|
position /= MM_PER_INCH;
|
|
}
|
|
}
|
|
return (position);
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* CRITICAL HELPERS
|
|
* Core functions supporting the canonical machining functions
|
|
* These functions are not part of the NIST defined functions
|
|
***********************************************************************************/
|
|
/*
|
|
* cm_finalize_move() - perform final operations for a traverse or feed
|
|
* cm_update_model_position_from_runtime() - set endpoint position from final runtime position
|
|
*
|
|
* These routines set the point position in the gcode model.
|
|
*
|
|
* Note: As far as the canonical machine is concerned the final position of a Gcode block (move)
|
|
* is achieved as soon as the move is planned and the move target becomes the new model position.
|
|
* In reality the planner will (in all likelihood) have only just queued the move for later
|
|
* execution, and the real tool position is still close to the starting point.
|
|
*/
|
|
|
|
void cm_finalize_move()
|
|
{
|
|
copy_vector(cm.gmx.position, cm.gm.target); // update model position
|
|
}
|
|
|
|
void cm_update_model_position_from_runtime()
|
|
{
|
|
copy_vector(cm.gmx.position, mr.gm.target);
|
|
}
|
|
|
|
/*
|
|
* cm_deferred_write_callback() - write any changed G10 values back to persistence
|
|
*
|
|
* Only runs if there is G10 data to write, there is no movement, and the serial queues are quiescent
|
|
* This could be made tighter by issuing an XOFF or ~CTS beforehand and releasing it afterwards.
|
|
*/
|
|
|
|
stat_t cm_deferred_write_callback()
|
|
{
|
|
if ((cm.cycle_state == CYCLE_OFF) && (cm.deferred_write_flag == true)) {
|
|
cm.deferred_write_flag = false;
|
|
nvObj_t nv;
|
|
for (uint8_t i=1; i<=COORDS; i++) {
|
|
for (uint8_t j=0; j<AXES; j++) {
|
|
sprintf((char *)nv.token, "g%2d%c", 53+i, ("xyzabc")[j]);
|
|
nv.index = nv_get_index((const char *)"", nv.token);
|
|
nv.value = cm.offset[i][j];
|
|
nv_persist(&nv); // Note: only writes values that have changed
|
|
}
|
|
}
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_set_tram() - JSON command to trigger computing the rotation matrix
|
|
* cm_get_tram() - JSON query to determine if the rotation matrix is set (non-identity)
|
|
*
|
|
* There MUST be three valid probes stored.
|
|
*/
|
|
|
|
stat_t cm_set_tram(nvObj_t *nv)
|
|
{
|
|
if ((nv->valuetype == TYPE_BOOL) ||
|
|
(nv->valuetype == TYPE_INT) ||
|
|
(nv->valuetype == TYPE_FLOAT))
|
|
{
|
|
bool do_set = !!((uint32_t)nv->value);
|
|
|
|
// if passed false/0, we will clear the rotation matrix
|
|
if (!do_set) {
|
|
canonical_machine_reset_rotation();
|
|
return (STAT_OK);
|
|
}
|
|
|
|
// check to make sure we have three valid probes in a row
|
|
if ((cm.probe_state[0] == PROBE_SUCCEEDED) &&
|
|
(cm.probe_state[1] == PROBE_SUCCEEDED) &&
|
|
(cm.probe_state[2] == PROBE_SUCCEEDED))
|
|
{
|
|
|
|
// Step 1: Get the normal of the plane formed by the three probes. Naming:
|
|
// d0_{xyz} is the delta between point 0 and point 1
|
|
// d2_{xyz} is the delta between point 2 and point 1
|
|
// n_{xyz} is the unit normal
|
|
|
|
// Step 1a: get the deltas
|
|
float d0_x = cm.probe_results[0][0] - cm.probe_results[1][0];
|
|
float d0_y = cm.probe_results[0][1] - cm.probe_results[1][1];
|
|
float d0_z = cm.probe_results[0][2] - cm.probe_results[1][2];
|
|
float d2_x = cm.probe_results[2][0] - cm.probe_results[1][0];
|
|
float d2_y = cm.probe_results[2][1] - cm.probe_results[1][1];
|
|
float d2_z = cm.probe_results[2][2] - cm.probe_results[1][2];
|
|
|
|
// Step 1b: compute the combined magnitude
|
|
// since sqrt(a)*sqrt(b) = sqrt(a*b), we can save a sqrt in making the unit normal
|
|
float combined_magnitude_inv = 1.0/sqrt(
|
|
(d0_x*d0_x + d0_y*d0_y + d0_z*d0_z)*
|
|
(d2_x*d2_x + d2_y*d2_y + d2_z*d2_z)
|
|
);
|
|
|
|
// Step 1c: compute the cross product and normalize
|
|
float n_x = (d0_z*d2_y - d0_y*d2_z)*combined_magnitude_inv;
|
|
float n_y = (d0_x*d2_z - d0_z*d2_x)*combined_magnitude_inv;
|
|
float n_z = (d0_y*d2_x - d0_x*d2_y)*combined_magnitude_inv;
|
|
|
|
// Step 1d: flip the normal if it's negative
|
|
if (n_z < 0.0) {
|
|
n_x = -n_x;
|
|
n_y = -n_y;
|
|
n_z = -n_z;
|
|
}
|
|
|
|
// Step 2: make the quaternion for the rotation to {0,0,1}
|
|
float p = sqrt(n_x*n_x + n_y*n_y + n_z*n_z);
|
|
float m = sqrt(2.0)*sqrt(p*(p+n_z));
|
|
float q_w = (n_z + p) / m;
|
|
float q_x = -n_y / m;
|
|
float q_y = n_x / m;
|
|
//float q_z = 0; // already optimized out
|
|
|
|
// Step 3: compute the rotation matrix
|
|
float q_wx_2 = q_w * q_x * 2.0;
|
|
float q_wy_2 = q_w * q_y * 2.0;
|
|
float q_xx_2 = q_x * q_x * 2.0;
|
|
float q_xy_2 = q_x * q_y * 2.0;
|
|
float q_yy_2 = q_y * q_y * 2.0;
|
|
|
|
/*
|
|
matrix = {
|
|
{1 - q_yy_2, q_xy_2, q_wy_2, 0},
|
|
{q_xy_2, 1 - q_xx_2, -q_wx_2, 0},
|
|
{-q_wy_2, q_wx_2, 1 - q_xx_2 - q_yy_2, i},
|
|
{0, 0, 0, 1}
|
|
}
|
|
*/
|
|
cm.rotation_matrix[0][0] = 1 - q_yy_2;
|
|
cm.rotation_matrix[0][1] = q_xy_2;
|
|
cm.rotation_matrix[0][2] = q_wy_2;
|
|
|
|
cm.rotation_matrix[1][0] = q_xy_2;
|
|
cm.rotation_matrix[1][1] = 1 - q_xx_2;
|
|
cm.rotation_matrix[1][2] = -q_wx_2;
|
|
|
|
cm.rotation_matrix[2][0] = -q_wy_2;
|
|
cm.rotation_matrix[2][1] = q_wx_2;
|
|
cm.rotation_matrix[2][2] = 1 - q_xx_2 - q_yy_2;
|
|
|
|
// Step 4: compute the z-offset
|
|
cm.rotation_z_offset = (n_x*cm.probe_results[1][0] + n_y*cm.probe_results[1][1]) / n_z + cm.probe_results[1][2];
|
|
} else {
|
|
return (STAT_COMMAND_NOT_ACCEPTED);
|
|
}
|
|
} else {
|
|
return (STAT_INPUT_VALUE_RANGE_ERROR);
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_tram(nvObj_t *nv)
|
|
{
|
|
nv->value = true;
|
|
|
|
if (fp_NOT_ZERO(cm.rotation_z_offset) ||
|
|
fp_NOT_ZERO(cm.rotation_matrix[0][1]) ||
|
|
fp_NOT_ZERO(cm.rotation_matrix[0][2]) ||
|
|
fp_NOT_ZERO(cm.rotation_matrix[1][0]) ||
|
|
fp_NOT_ZERO(cm.rotation_matrix[1][2]) ||
|
|
fp_NOT_ZERO(cm.rotation_matrix[2][0]) ||
|
|
fp_NOT_ZERO(cm.rotation_matrix[2][1]) ||
|
|
fp_NE(1.0, cm.rotation_matrix[0][0]) ||
|
|
fp_NE(1.0, cm.rotation_matrix[1][1]) ||
|
|
fp_NE(1.0, cm.rotation_matrix[2][2]))
|
|
{
|
|
nv->value = false;
|
|
}
|
|
nv->valuetype = TYPE_BOOL;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
|
|
/*
|
|
* cm_set_nxt_line() - JSON command to set the next line number
|
|
* cm_get_nxt_line() - JSON query to get the next expected line number
|
|
*
|
|
* There MUST be three valid probes stored.
|
|
*/
|
|
|
|
stat_t cm_set_nxln(nvObj_t *nv)
|
|
{
|
|
if (nv->valuetype == TYPE_INT || nv->valuetype == TYPE_FLOAT)
|
|
{
|
|
cm.gmx.last_line_number = ((int32_t)nv->value) - 1;
|
|
return (STAT_OK);
|
|
}
|
|
return (STAT_INPUT_VALUE_RANGE_ERROR);
|
|
}
|
|
|
|
stat_t cm_get_nxln(nvObj_t *nv)
|
|
{
|
|
nv->value = cm.gmx.last_line_number+1;
|
|
nv->valuetype = TYPE_INT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
|
|
/*
|
|
* cm_set_model_target() - set target vector in GM model
|
|
*
|
|
* This is a core routine. It handles:
|
|
* - conversion of linear units to internal canonical form (mm)
|
|
* - conversion of relative mode to absolute (internal canonical form)
|
|
* - translation of work coordinates to machine coordinates (internal canonical form)
|
|
* - computation and application of axis modes as so:
|
|
*
|
|
* DISABLED - Incoming value is ignored. Target value is not changed
|
|
* ENABLED - Convert axis values to canonical format and store as target
|
|
* INHIBITED - Same processing as ENABLED, but axis will not actually be run
|
|
* RADIUS - ABC axis value is provided in Gcode block in linear units
|
|
* - Target is set to degrees based on axis' Radius value
|
|
* - Radius mode is only processed for ABC axes. Application to XYZ is ignored.
|
|
*
|
|
* Target coordinates are provided in target[]
|
|
* Axes that need processing are signaled in flag[]
|
|
*/
|
|
|
|
// ESTEE: _calc_ABC is a fix to workaround a gcc compiler bug wherein it runs out of spill
|
|
// registers we moved this block into its own function so that we get a fresh stack push
|
|
// ALDEN: This shows up in avr-gcc 4.7.0 and avr-libc 1.8.0
|
|
|
|
static float _calc_ABC(const uint8_t axis, const float target[])
|
|
{
|
|
if ((cm.a[axis].axis_mode == AXIS_STANDARD) || (cm.a[axis].axis_mode == AXIS_INHIBITED)) {
|
|
return(target[axis]); // no mm conversion - it's in degrees
|
|
}
|
|
|
|
// radius mode
|
|
|
|
return (_to_millimeters(target[axis]) * 360.0 / (2.0 * M_PI * cm.a[axis].radius));
|
|
}
|
|
|
|
void cm_set_model_target(const float target[], const bool flags[])
|
|
{
|
|
uint8_t axis;
|
|
float tmp = 0;
|
|
|
|
// copy position to target so it always starts correctly
|
|
copy_vector(cm.gm.target, cm.gmx.position);
|
|
|
|
// process XYZABC for lower modes
|
|
for (axis=AXIS_X; axis<=AXIS_Z; axis++) {
|
|
if (!flags[axis] || cm.a[axis].axis_mode == AXIS_DISABLED) {
|
|
continue; // skip axis if not flagged for update or its disabled
|
|
} else if ((cm.a[axis].axis_mode == AXIS_STANDARD) || (cm.a[axis].axis_mode == AXIS_INHIBITED)) {
|
|
if (cm.gm.distance_mode == ABSOLUTE_DISTANCE_MODE) {
|
|
cm.gm.target[axis] = cm_get_active_coord_offset(axis) + _to_millimeters(target[axis]);
|
|
} else {
|
|
cm.gm.target[axis] += _to_millimeters(target[axis]);
|
|
}
|
|
}
|
|
}
|
|
// FYI: The ABC loop below relies on the XYZ loop having been run first
|
|
for (axis=AXIS_A; axis<=AXIS_C; axis++) {
|
|
if (!flags[axis] || cm.a[axis].axis_mode == AXIS_DISABLED) {
|
|
continue; // skip axis if not flagged for update or its disabled
|
|
} else {
|
|
tmp = _calc_ABC(axis, target);
|
|
}
|
|
#if MARLIN_COMPAT_ENABLED == true
|
|
// If we are in absolute mode (generally), but the extruder is relative,
|
|
// then we adjust the extruder to a relative position
|
|
if (mst.marlin_flavor && (cm.a[axis].axis_mode == AXIS_RADIUS)) {
|
|
if ((cm.gm.distance_mode == INCREMENTAL_DISTANCE_MODE) || (mst.extruder_mode == EXTRUDER_MOVES_RELATIVE)) {
|
|
cm.gm.target[axis] += tmp;
|
|
}
|
|
else { // if (cm.gmx.extruder_mode == EXTRUDER_MOVES_NORMAL)
|
|
cm.gm.target[axis] = tmp + cm_get_active_coord_offset(axis);
|
|
}
|
|
// TODO
|
|
// else {
|
|
// cm.gm.target[axis] += tmp * cm.gmx.volume_to_filament_length[axis-3];
|
|
// }
|
|
}
|
|
else
|
|
#endif // MARLIN_COMPAT_ENABLED
|
|
if (cm.gm.distance_mode == ABSOLUTE_DISTANCE_MODE) {
|
|
cm.gm.target[axis] = tmp + cm_get_active_coord_offset(axis); // sacidu93's fix to Issue #22
|
|
}
|
|
else {
|
|
cm.gm.target[axis] += tmp;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cm_get_soft_limits()
|
|
* cm_set_soft_limits()
|
|
* cm_test_soft_limits() - return error code if soft limit is exceeded
|
|
*
|
|
* The target[] arg must be in absolute machine coordinates. Best done after cm_set_model_target().
|
|
*
|
|
* Tests for soft limit for any homed axis if min and max are different values. You can set min
|
|
* and max to the same value (e.g. 0,0) to disable soft limits for an axis. Also will not test
|
|
* a min or a max if the value is more than +/- 1000000 (plus or minus 1 million ).
|
|
* This allows a single end to be tested w/the other disabled, should that requirement ever arise.
|
|
*/
|
|
|
|
bool cm_get_soft_limits() { return (cm.soft_limit_enable); }
|
|
void cm_set_soft_limits(bool enable) { cm.soft_limit_enable = enable; }
|
|
|
|
static stat_t _finalize_soft_limits(const stat_t status)
|
|
{
|
|
cm.gm.motion_mode = MOTION_MODE_CANCEL_MOTION_MODE; // cancel motion
|
|
copy_vector(cm.gm.target, cm.gmx.position); // reset model target
|
|
return (cm_alarm(status, "soft_limits")); // throw an alarm
|
|
}
|
|
|
|
stat_t cm_test_soft_limits(const float target[])
|
|
{
|
|
if (cm.soft_limit_enable == true) {
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
if (cm.homed[axis] != true) { continue; } // skip axis if not homed
|
|
if (fp_EQ(cm.a[axis].travel_min, cm.a[axis].travel_max)) { continue; } // skip axis if identical
|
|
if (std::abs(cm.a[axis].travel_min) > DISABLE_SOFT_LIMIT) { continue; } // skip min test if disabled
|
|
if (std::abs(cm.a[axis].travel_max) > DISABLE_SOFT_LIMIT) { continue; } // skip max test if disabled
|
|
|
|
if (target[axis] < cm.a[axis].travel_min) {
|
|
return (_finalize_soft_limits(STAT_SOFT_LIMIT_EXCEEDED_XMIN + 2*axis));
|
|
}
|
|
if (target[axis] > cm.a[axis].travel_max) {
|
|
return (_finalize_soft_limits(STAT_SOFT_LIMIT_EXCEEDED_XMAX + 2*axis));
|
|
}
|
|
}
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*************************************************************************
|
|
* CANONICAL MACHINING FUNCTIONS
|
|
* Values are passed in pre-unit_converted state (from gn structure)
|
|
* All operations occur on gm (current model state)
|
|
*
|
|
* These are organized by section number (x.x.x) in the order they are
|
|
* found in NIST RS274 NGCv3
|
|
************************************************************************/
|
|
|
|
/******************************************
|
|
* Initialization and Termination (4.3.2) *
|
|
******************************************/
|
|
/*
|
|
* canonical_machine_init() - initialize cm struct
|
|
* canonical_machine_reset() - apply startup settings or reset to startup
|
|
* run profile initialization beforehand
|
|
*/
|
|
|
|
void canonical_machine_init()
|
|
{
|
|
// If you can assume all memory has been zeroed by a hard reset you don't need this code:
|
|
// memset(&cm, 0, sizeof(cm)); // do not reset canonicalMachineSingleton once it's been initialized
|
|
memset(&cm, 0, sizeof(cmSingleton_t)); // do not reset canonicalMachineSingleton once it's been initialized
|
|
cm.gm.reset(); // clear all values, pointers and status -- not ALL to zero, however
|
|
|
|
canonical_machine_init_assertions(); // establish assertions
|
|
ACTIVE_MODEL = MODEL; // setup initial Gcode model pointer
|
|
cm_arc_init(); // Note: spindle and coolant inits are independent
|
|
|
|
din_handlers[INPUT_ACTION_STOP].registerHandler(&_hold_handler);
|
|
din_handlers[INPUT_ACTION_FAST_STOP].registerHandler(&_hold_handler);
|
|
din_handlers[INPUT_ACTION_HALT].registerHandler(&_halt_handler);
|
|
din_handlers[INPUT_ACTION_ALARM].registerHandler(&_alarm_handler);
|
|
din_handlers[INPUT_ACTION_PANIC].registerHandler(&_panic_handler);
|
|
din_handlers[INPUT_ACTION_RESET].registerHandler(&_reset_handler);
|
|
}
|
|
|
|
void canonical_machine_reset_rotation() {
|
|
memset(&cm.rotation_matrix, 0, sizeof(float)*3*3);
|
|
// We must make it an identity matrix for no rotation
|
|
cm.rotation_matrix[0][0] = 1.0;
|
|
cm.rotation_matrix[1][1] = 1.0;
|
|
cm.rotation_matrix[2][2] = 1.0;
|
|
cm.rotation_z_offset = 0.0;
|
|
}
|
|
|
|
void canonical_machine_reset()
|
|
{
|
|
// set gcode defaults
|
|
cm_set_units_mode(cm.default_units_mode);
|
|
cm_set_coord_system(cm.default_coord_system); // NB: queues a block to the planner with the coordinates
|
|
cm_select_plane(cm.default_select_plane);
|
|
cm_set_path_control(MODEL, cm.default_path_control);
|
|
cm_set_distance_mode(cm.default_distance_mode);
|
|
cm_set_arc_distance_mode(INCREMENTAL_DISTANCE_MODE);// always the default
|
|
cm_set_feed_rate_mode(UNITS_PER_MINUTE_MODE); // always the default
|
|
cm_reset_overrides(); // set overrides to initial conditions
|
|
|
|
// NOTE: Should unhome axes here
|
|
|
|
// reset requests and flags
|
|
cm.queue_flush_state = FLUSH_OFF;
|
|
cm.end_hold_requested = false;
|
|
cm.limit_requested = 0; // resets switch closures that occurred during initialization
|
|
cm.safety_interlock_disengaged = 0; // ditto
|
|
cm.safety_interlock_reengaged = 0; // ditto
|
|
cm.shutdown_requested = 0; // ditto
|
|
cm.probe_report_enable = PROBE_REPORT_ENABLE;
|
|
|
|
// set initial state and signal that the machine is ready for action
|
|
cm.cycle_state = CYCLE_OFF;
|
|
cm.motion_state = MOTION_STOP;
|
|
cm.hold_state = FEEDHOLD_OFF;
|
|
cm.esc_boot_timer = SysTickTimer.getValue();
|
|
cm.gmx.block_delete_switch = true;
|
|
cm.gm.motion_mode = MOTION_MODE_CANCEL_MOTION_MODE; // never start in a motion mode
|
|
cm.machine_state = MACHINE_READY;
|
|
|
|
canonical_machine_reset_rotation();
|
|
|
|
memset(&cm.probe_state, 0, sizeof(cmProbeState)*PROBES_STORED);
|
|
memset(&cm.probe_results, 0, sizeof(float)*PROBES_STORED*AXES);
|
|
}
|
|
|
|
/*
|
|
* canonical_machine_init_assertions()
|
|
* canonical_machine_test_assertions() - test assertions, return error code if violation exists
|
|
*/
|
|
|
|
void canonical_machine_init_assertions(void)
|
|
{
|
|
cm.magic_start = MAGICNUM;
|
|
cm.magic_end = MAGICNUM;
|
|
cm.gmx.magic_start = MAGICNUM;
|
|
cm.gmx.magic_end = MAGICNUM;
|
|
arc.magic_start = MAGICNUM;
|
|
arc.magic_end = MAGICNUM;
|
|
}
|
|
|
|
stat_t canonical_machine_test_assertions(void)
|
|
{
|
|
if ((BAD_MAGIC(cm.magic_start)) || (BAD_MAGIC(cm.magic_end)) ||
|
|
(BAD_MAGIC(cm.gmx.magic_start)) || (BAD_MAGIC(cm.gmx.magic_end)) ||
|
|
(BAD_MAGIC(arc.magic_start)) || (BAD_MAGIC(arc.magic_end))) {
|
|
return(cm_panic(STAT_CANONICAL_MACHINE_ASSERTION_FAILURE, "canonical_machine_test_assertions()"));
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/**************************
|
|
* Alarms *
|
|
**************************/
|
|
|
|
/********************************************************************************
|
|
* ALARM, SHUTDOWN, and PANIC are nested dolls.
|
|
*
|
|
* cm_alrm() - invoke alarm from command
|
|
* cm_shutd() - invoke shutdown from command
|
|
* cm_pnic() - invoke panic from command
|
|
* cm_clr() - clear alarm or shutdown from command
|
|
*
|
|
* The alarm states can be invoked from the above commands for testing and clearing
|
|
*/
|
|
stat_t cm_alrm(nvObj_t *nv) // invoke alarm from command
|
|
{
|
|
cm_alarm(STAT_ALARM, "sent by host");
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_shutd(nvObj_t *nv) // invoke shutdown from command
|
|
{
|
|
cm_shutdown(STAT_SHUTDOWN, "sent by host");
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_pnic(nvObj_t *nv) // invoke panic from command
|
|
{
|
|
cm_panic(STAT_PANIC, "sent by host");
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_clr(nvObj_t *nv) // clear alarm or shutdown from command line
|
|
{
|
|
cm_clear();
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* _alarm_handler - a gpioDigitalInputHandler to capture pin change events
|
|
* Will be registered at init
|
|
*/
|
|
gpioDigitalInputHandler _alarm_handler {
|
|
[&](const bool state, const inputEdgeFlag edge, const uint8_t triggering_pin_number) {
|
|
if (edge != INPUT_EDGE_LEADING) { return false; }
|
|
|
|
char msg[10];
|
|
sprintf(msg, "input %d", triggering_pin_number);
|
|
cm_alarm(STAT_ALARM, msg);
|
|
|
|
return false; // allow others to see this notice
|
|
},
|
|
5, // priority
|
|
nullptr // next - nullptr to start with
|
|
};
|
|
|
|
/*
|
|
* cm_clear() - clear ALARM and SHUTDOWN states
|
|
* cm_parse_clear() - parse incoming gcode for M30 or M2 clears if in ALARM state
|
|
*
|
|
* Parse clear interprets an M30 or M2 PROGRAM_END as a $clear condition and clear ALARM
|
|
* but not SHUTDOWN or PANIC. Assumes Gcode string has no leading or embedded whitespace
|
|
*/
|
|
|
|
void cm_clear()
|
|
{
|
|
if (cm.machine_state == MACHINE_ALARM) {
|
|
cm.machine_state = MACHINE_PROGRAM_STOP;
|
|
xio_flush_to_command();
|
|
} else if (cm.machine_state == MACHINE_SHUTDOWN) {
|
|
cm.machine_state = MACHINE_READY;
|
|
}
|
|
}
|
|
|
|
void cm_parse_clear(const char *s)
|
|
{
|
|
if (cm.machine_state == MACHINE_ALARM) {
|
|
if (toupper(s[0]) == 'M') {
|
|
if (( (s[1]=='3') && (s[2]=='0') && (s[3]==NUL)) || ((s[1]=='2') && (s[2]==NUL) )) {
|
|
cm_clear();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cm_is_alarmed() - return alarm status code or OK if no alarms
|
|
*/
|
|
|
|
stat_t cm_is_alarmed()
|
|
{
|
|
if (cm.machine_state == MACHINE_ALARM) { return (STAT_COMMAND_REJECTED_BY_ALARM); }
|
|
if (cm.machine_state == MACHINE_SHUTDOWN) { return (STAT_COMMAND_REJECTED_BY_SHUTDOWN); }
|
|
if (cm.machine_state == MACHINE_PANIC) { return (STAT_COMMAND_REJECTED_BY_PANIC); }
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_halt_all() - stop, spindle and coolant immediately
|
|
* cm_halt_motion() - stop motion immediately. Does not affect spindle, coolant, or other IO
|
|
*
|
|
* Stop motors and reset all system states accordingly.
|
|
* Does not de-energize motors as in some cases the motors must remain energized
|
|
* in order to prevent an axis from crashing.
|
|
*/
|
|
|
|
void cm_halt_all(void)
|
|
{
|
|
cm_halt_motion();
|
|
cm_spindle_off_immediate();
|
|
cm_coolant_off_immediate();
|
|
}
|
|
|
|
void cm_halt_motion(void)
|
|
{
|
|
mp_halt_runtime(); // stop the runtime. Do this immediately. (Reset is in cm_clear)
|
|
canonical_machine_reset(); // reset Gcode model
|
|
cm.cycle_state = CYCLE_OFF; // Note: leaves machine_state alone
|
|
cm.motion_state = MOTION_STOP;
|
|
cm.hold_state = FEEDHOLD_OFF;
|
|
}
|
|
|
|
/*
|
|
* _hold_handler - a gpioDigitalInputHandler to capture pin change events
|
|
* Will be registered at init
|
|
*/
|
|
gpioDigitalInputHandler _halt_handler {
|
|
[&](const bool state, const inputEdgeFlag edge, const uint8_t triggering_pin_number) {
|
|
if (edge != INPUT_EDGE_LEADING) { return false; }
|
|
|
|
cm_halt_all();
|
|
|
|
return false; // allow others to see this notice
|
|
},
|
|
5, // priority
|
|
nullptr // next - nullptr to start with
|
|
};
|
|
|
|
/*
|
|
* cm_alarm() - enter ALARM state
|
|
*
|
|
* An ALARM sets the ALARM machine state, starts a feedhold to stop motion, stops the
|
|
* spindle, turns off coolant, clears out queued planner moves and serial input,
|
|
* and rejects new action commands (gcode blocks, SET commands, and other actions)
|
|
* until the alarm is cleared.
|
|
*
|
|
* ALARM is typically entered by a soft limit or a limit switch being hit. In the
|
|
* limit switch case the INPUT_ACTION will override the feedhold - i.e. if the
|
|
* input action is "FAST_STOP" or "HALT" that setting will take precedence over
|
|
* the feedhold native to the alarm function.
|
|
*
|
|
* Gcode and machine state is preserved. It may be possible to recover the job from
|
|
* an alarm, but in many cases this is not possible. Since ALARM attempts to preserve
|
|
* Gcode and machine state it does not END the job.
|
|
*
|
|
* ALARM may also be invoked from the command line using {alarm:n} or $alarm
|
|
* ALARM can be manually cleared by entering: {clear:n}, {clr:n}, $clear, or $clr
|
|
* ALARMs will also clear on receipt of an M30 or M2 command if one is received
|
|
* while draining the host command queue.
|
|
*/
|
|
|
|
stat_t cm_alarm(const stat_t status, const char *msg)
|
|
{
|
|
if ((cm.machine_state == MACHINE_ALARM) || (cm.machine_state == MACHINE_SHUTDOWN) ||
|
|
(cm.machine_state == MACHINE_PANIC)) {
|
|
return (STAT_OK); // don't alarm if already in an alarm state
|
|
}
|
|
cm.machine_state = MACHINE_ALARM;
|
|
cm_request_feedhold(); // stop motion
|
|
cm_request_queue_flush(); // do a queue flush once runtime is not busy
|
|
|
|
// TBD - these functions should probably be called - See cm_shutdown()
|
|
// cm_spindle_control_immediate(SPINDLE_OFF);
|
|
// cm_coolant_off_immediate();
|
|
// cm_spindle_optional_pause(spindle.pause_on_hold);
|
|
// cm_coolant_optional_pause(coolant.pause_on_hold);
|
|
rpt_exception(status, msg); // send alarm message
|
|
|
|
// If "stat" is in the status report, we need to poke it to send.
|
|
sr_request_status_report(SR_REQUEST_TIMED);
|
|
return (status);
|
|
}
|
|
/*
|
|
* cm_shutdown() - enter shutdown state
|
|
*
|
|
* SHUTDOWN stops all motion, spindle and coolant immediately, sets a SHUTDOWN machine
|
|
* state, clears out queued moves and serial input, and rejects new action commands
|
|
* (gcode blocks, SET commands, and some others).
|
|
*
|
|
* Shutdown is typically invoked as an electrical input signal sent to the board as
|
|
* part of an external emergency stop (Estop). Shutdown is meant to augment but not
|
|
* replace the external Estop functions that shut down power to motors, spindles and
|
|
* other moving parts.
|
|
*
|
|
* Shutdown may also be invoked from the command line using {shutd:n} or $shutd
|
|
* Shutdown must be manually cleared by entering: {clear:n}, {clr:n}, $clear, or $clr
|
|
* Shutdown does not clear on M30 or M2 Gcode commands
|
|
*/
|
|
|
|
stat_t cm_shutdown(const stat_t status, const char *msg)
|
|
{
|
|
if ((cm.machine_state == MACHINE_SHUTDOWN) || (cm.machine_state == MACHINE_PANIC)) {
|
|
return (STAT_OK); // don't shutdown if shutdown or panic'd
|
|
}
|
|
cm_halt_motion(); // halt motors (may have already been done from GPIO)
|
|
spindle_reset(); // stop spindle immediately and set speed to 0 RPM
|
|
coolant_reset(); // stop coolant immediately
|
|
temperature_reset(); // turn off heaters and fans
|
|
cm_queue_flush(); // flush all queues and reset positions
|
|
|
|
for (uint8_t i = 0; i < HOMING_AXES; i++) { // unhome axes and the machine
|
|
cm.homed[i] = false;
|
|
}
|
|
cm.homing_state = HOMING_NOT_HOMED;
|
|
|
|
cm.machine_state = MACHINE_SHUTDOWN; // do this after all other activity
|
|
rpt_exception(status, msg); // send exception report
|
|
return (status);
|
|
}
|
|
|
|
/*
|
|
* cm_panic() - enter panic state
|
|
*
|
|
* PANIC occurs if the firmware has detected an unrecoverable internal error
|
|
* such as an assertion failure or a code condition that should never occur.
|
|
* It sets PANIC machine state, and leaves the system inspect able (if possible).
|
|
*
|
|
* PANIC can only be exited by a hardware reset or soft reset (^x)
|
|
*/
|
|
|
|
stat_t cm_panic(const stat_t status, const char *msg)
|
|
{
|
|
_debug_trap(msg);
|
|
|
|
if (cm.machine_state == MACHINE_PANIC) { // only do this once
|
|
return (STAT_OK);
|
|
}
|
|
cm_halt_motion(); // halt motors (may have already been done from GPIO)
|
|
spindle_reset(); // stop spindle immediately and set speed to 0 RPM
|
|
coolant_reset(); // stop coolant immediately
|
|
temperature_reset(); // turn off heaters and fans
|
|
cm_queue_flush(); // flush all queues and reset positions
|
|
|
|
cm.machine_state = MACHINE_PANIC; // don't reset anything. Panics are not recoverable
|
|
rpt_exception(status, msg); // send panic report
|
|
return (status);
|
|
}
|
|
|
|
/*
|
|
* _panic_handler - a gpioDigitalInputHandler to capture pin change events
|
|
* Will be registered at init
|
|
*/
|
|
gpioDigitalInputHandler _panic_handler {
|
|
[&](const bool state, const inputEdgeFlag edge, const uint8_t triggering_pin_number) {
|
|
if (edge != INPUT_EDGE_LEADING) { return false; }
|
|
|
|
char msg[10];
|
|
sprintf(msg, "input %d", triggering_pin_number);
|
|
cm_panic(STAT_PANIC, msg);
|
|
|
|
return false; // allow others to see this notice
|
|
},
|
|
5, // priority
|
|
nullptr // next - nullptr to start with
|
|
};
|
|
|
|
/*
|
|
* _reset_handler - a gpioDigitalInputHandler to capture pin change events
|
|
* Will be registered at init
|
|
*/
|
|
gpioDigitalInputHandler _reset_handler {
|
|
[&](const bool state, const inputEdgeFlag edge, const uint8_t triggering_pin_number) {
|
|
if (edge != INPUT_EDGE_LEADING) { return false; }
|
|
|
|
hw_hard_reset();
|
|
|
|
return false; // this likely won't be seen, but just in case...
|
|
},
|
|
5, // priority
|
|
nullptr // next - nullptr to start with
|
|
};
|
|
|
|
/**************************
|
|
* Representation (4.3.3) *
|
|
**************************/
|
|
|
|
/**************************************************************************
|
|
* Representation functions that affect the Gcode model only (asynchronous)
|
|
*
|
|
* cm_select_plane() - G17,G18,G19 select axis plane
|
|
* cm_set_units_mode() - G20, G21
|
|
* cm_set_distance_mode() - G90, G91
|
|
* cm_set_arc_distance_mode() - G90.1, G91.1
|
|
* cm_set_g10_data() - G10 (delayed persistence)
|
|
*
|
|
* These functions assume input validation occurred upstream.
|
|
*/
|
|
|
|
stat_t cm_select_plane(const uint8_t plane)
|
|
{
|
|
cm.gm.select_plane = (cmCanonicalPlane)plane;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_units_mode(const uint8_t mode)
|
|
{
|
|
cm.gm.units_mode = (cmUnitsMode)mode; // 0 = inches, 1 = mm.
|
|
return(STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_distance_mode(const uint8_t mode)
|
|
{
|
|
cm.gm.distance_mode = (cmDistanceMode)mode; // 0 = absolute mode, 1 = incremental
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_arc_distance_mode(const uint8_t mode)
|
|
{
|
|
cm.gm.arc_distance_mode = (cmDistanceMode)mode; // 0 = absolute mode, 1 = incremental
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_set_g10_data() - G10 L1/L2/L10/L20 Pn (affects MODEL only)
|
|
*
|
|
* This function applies the offset to the GM model but does not persist the offsets
|
|
* during the Gcode cycle. The persist flag is used to persist offsets once the cycle
|
|
* has ended. You can also use $g54x - $g59c config functions to change offsets.
|
|
*
|
|
* It also does not reset the work_offsets which may be accomplished by calling
|
|
* cm_set_work_offsets() immediately afterwards.
|
|
*/
|
|
|
|
stat_t cm_set_g10_data(const uint8_t P_word, const bool P_flag,
|
|
const uint8_t L_word, const bool L_flag,
|
|
const float offset[], const bool flag[])
|
|
{
|
|
if (!L_flag) {
|
|
return (STAT_L_WORD_IS_MISSING);
|
|
}
|
|
|
|
if ((L_word == 2) || (L_word == 20)) {
|
|
// coordinate system offset command
|
|
if ((P_word < G54) || (P_word > COORD_SYSTEM_MAX)) {
|
|
// you can't set G53
|
|
return (STAT_P_WORD_IS_INVALID);
|
|
}
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
if (flag[axis]) {
|
|
if (L_word == 2) {
|
|
cm.offset[P_word][axis] = _to_millimeters(offset[axis]);
|
|
} else {
|
|
// Should L20 take into account G92 offsets?
|
|
cm.offset[P_word][axis] =
|
|
cm.gmx.position[axis] -
|
|
_to_millimeters(offset[axis]) -
|
|
cm.tl_offset[axis];
|
|
}
|
|
// persist offsets once machining cycle is over
|
|
cm.deferred_write_flag = true;
|
|
}
|
|
}
|
|
}
|
|
else if ((L_word == 1) || (L_word == 10)) {
|
|
// tool table offset command. L11 not supported atm.
|
|
if ((P_word < 1) || (P_word > TOOLS)) {
|
|
return (STAT_P_WORD_IS_INVALID);
|
|
}
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
if (flag[axis]) {
|
|
if (L_word == 1) {
|
|
cm.tt_offset[P_word][axis] = _to_millimeters(offset[axis]);
|
|
} else {
|
|
// L10 should also take into account G92 offset
|
|
cm.tt_offset[P_word][axis] =
|
|
cm.gmx.position[axis] - _to_millimeters(offset[axis]) -
|
|
cm.offset[cm.gm.coord_system][axis] -
|
|
(cm.gmx.origin_offset[axis] * cm.gmx.origin_offset_enable);
|
|
}
|
|
// persist offsets once machining cycle is over
|
|
cm.deferred_write_flag = true;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
return (STAT_L_WORD_IS_INVALID);
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/******************************************************************************************
|
|
* Representation functions that affect gcode model and are queued to planner (synchronous)
|
|
*/
|
|
/*
|
|
* cm_set_tl_offset() - G43
|
|
* cm_cancel_tl_offset() - G49
|
|
* cm_set_coord_system() - G54-G59
|
|
* _exec_offset() - callback from planner
|
|
*/
|
|
|
|
stat_t cm_set_tl_offset(const uint8_t H_word, const bool H_flag, const bool apply_additional)
|
|
{
|
|
uint8_t tool;
|
|
if (H_flag) {
|
|
if (H_word > TOOLS) {
|
|
return (STAT_H_WORD_IS_INVALID);
|
|
}
|
|
if (H_word == 0) { // interpret H0 as "current tool", just like no H at all.
|
|
tool = cm.gm.tool;
|
|
} else {
|
|
tool = H_word;
|
|
}
|
|
} else {
|
|
tool = cm.gm.tool;
|
|
}
|
|
if (apply_additional) {
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
cm.tl_offset[axis] += cm.tt_offset[tool][axis];
|
|
}
|
|
} else {
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
cm.tl_offset[axis] = cm.tt_offset[tool][axis];
|
|
}
|
|
}
|
|
float value[] = { (float)cm.gm.coord_system,0,0,0,0,0 };// pass coordinate system in value[0] element
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_offset, value, flags); // second vector (flags) is not used, so fake it
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_cancel_tl_offset()
|
|
{
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
cm.tl_offset[axis] = 0;
|
|
}
|
|
float value[] = { (float)cm.gm.coord_system,0,0,0,0,0 };// pass coordinate system in value[0] element
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_offset, value, flags); // second vector (flags) is not used, so fake it
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_coord_system(const uint8_t coord_system) // set coordinate system sync'd with planner
|
|
{
|
|
cm.gm.coord_system = (cmCoordSystem)coord_system;
|
|
|
|
float value[] = { (float)coord_system,0,0,0,0,0 }; // pass coordinate system in value[0] element
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_offset, value, flags); // second vector (flags) is not used, so fake it
|
|
return (STAT_OK);
|
|
}
|
|
|
|
static void _exec_offset(float *value, bool *flag)
|
|
{
|
|
uint8_t coord_system = ((uint8_t)value[0]); // coordinate system is passed in value[0] element
|
|
float offsets[AXES];
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
|
|
offsets[axis] = cm.offset[coord_system][axis] + cm.tl_offset[axis] +
|
|
(cm.gmx.origin_offset[axis] * cm.gmx.origin_offset_enable);
|
|
}
|
|
mp_set_runtime_work_offset(offsets);
|
|
cm_set_work_offsets(MODEL); // set work offsets in the Gcode model
|
|
}
|
|
|
|
/*
|
|
* cm_set_position() - set the position of a single axis in the model, planner and runtime
|
|
*
|
|
* This command sets an axis/axes to a position provided as an argument.
|
|
* This is useful for setting origins for homing, probing, and other operations.
|
|
*
|
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
* !!!!! DO NOT CALL THIS FUNCTION WHILE IN A MACHINING CYCLE !!!!!
|
|
* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
*
|
|
* More specifically, do not call this function if there are any moves in the planner or
|
|
* if the runtime is moving. The system must be quiescent or you will introduce positional
|
|
* errors. This is true because the planned / running moves have a different reference frame
|
|
* than the one you are now going to set. These functions should only be called during
|
|
* initialization sequences and during cycles (such as homing cycles) when you know there
|
|
* are no more moves in the planner and that all motion has stopped.
|
|
* Use cm_get_runtime_busy() to be sure the system is quiescent.
|
|
*/
|
|
|
|
void cm_set_position(const uint8_t axis, const float position)
|
|
{
|
|
// TODO: Interlock involving runtime_busy test
|
|
cm.gmx.position[axis] = position;
|
|
cm.gm.target[axis] = position;
|
|
mp_set_planner_position(axis, position);
|
|
mp_set_runtime_position(axis, position);
|
|
mp_set_steps_to_runtime_position();
|
|
}
|
|
|
|
/*** G28.3 functions and support ***
|
|
*
|
|
* cm_set_absolute_origin() - G28.3 - model, planner and queue to runtime
|
|
* _exec_absolute_origin() - callback from planner
|
|
*
|
|
* cm_set_absolute_origin() takes a vector of origins (presumably 0's, but not necessarily)
|
|
* and applies them to all axes where the corresponding position in the flag vector is true (1).
|
|
*
|
|
* This is a 2 step process. The model and planner contexts are set immediately, the runtime
|
|
* command is queued and synchronized with the planner queue. This includes the runtime position
|
|
* and the step recording done by the encoders. At that point any axis that is set is also marked
|
|
* as homed.
|
|
*/
|
|
|
|
stat_t cm_set_absolute_origin(const float origin[], bool flag[])
|
|
{
|
|
float value[AXES];
|
|
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
if (flag[axis]) {
|
|
// REMOVED value[axis] = cm.offset[cm.gm.coord_system][axis] + _to_millimeters(origin[axis]); // G2 Issue #26
|
|
value[axis] = _to_millimeters(origin[axis]);
|
|
cm.gmx.position[axis] = value[axis]; // set model position
|
|
cm.gm.target[axis] = value[axis]; // reset model target
|
|
mp_set_planner_position(axis, value[axis]); // set mm position
|
|
}
|
|
}
|
|
mp_queue_command(_exec_absolute_origin, value, flag);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
static void _exec_absolute_origin(float *value, bool *flag)
|
|
{
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
if (flag[axis]) {
|
|
mp_set_runtime_position(axis, value[axis]);
|
|
cm.homed[axis] = true; // G28.3 is not considered homed until you get here
|
|
}
|
|
}
|
|
mp_set_steps_to_runtime_position();
|
|
}
|
|
|
|
/*
|
|
* cm_set_origin_offsets() - G92
|
|
* cm_reset_origin_offsets() - G92.1
|
|
* cm_suspend_origin_offsets() - G92.2
|
|
* cm_resume_origin_offsets() - G92.3
|
|
*
|
|
* G92's behave according to NIST 3.5.18 & LinuxCNC G92
|
|
* http://linuxcnc.org/docs/html/gcode/gcode.html#sec:G92-G92.1-G92.2-G92.3
|
|
*/
|
|
|
|
stat_t cm_set_origin_offsets(const float offset[], const bool flag[])
|
|
{
|
|
// set offsets in the Gcode model extended context
|
|
cm.gmx.origin_offset_enable = true;
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
if (flag[axis]) {
|
|
cm.gmx.origin_offset[axis] = cm.gmx.position[axis] -
|
|
cm.offset[cm.gm.coord_system][axis] -
|
|
cm.tl_offset[axis] -
|
|
_to_millimeters(offset[axis]);
|
|
}
|
|
}
|
|
// now pass the offset to the callback - setting the coordinate system also applies the offsets
|
|
float value[] = { (float)cm.gm.coord_system,0,0,0,0,0 }; // pass coordinate system in value[0] element
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_offset, value, flags);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_reset_origin_offsets()
|
|
{
|
|
cm.gmx.origin_offset_enable = false;
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) {
|
|
cm.gmx.origin_offset[axis] = 0;
|
|
}
|
|
float value[] = { (float)cm.gm.coord_system,0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_offset, value, flags);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_suspend_origin_offsets()
|
|
{
|
|
cm.gmx.origin_offset_enable = false;
|
|
float value[] = { (float)cm.gm.coord_system,0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_offset, value, flags);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_resume_origin_offsets()
|
|
{
|
|
cm.gmx.origin_offset_enable = true;
|
|
float value[] = { (float)cm.gm.coord_system,0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_offset, value, flags);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*****************************
|
|
* Free Space Motion (4.3.4) *
|
|
*****************************/
|
|
/*
|
|
* cm_straight_traverse() - G0 linear rapid
|
|
*/
|
|
|
|
stat_t cm_straight_traverse(const float target[], const bool flags[])
|
|
{
|
|
cm.gm.motion_mode = MOTION_MODE_STRAIGHT_TRAVERSE;
|
|
|
|
// it's legal for a G0 to have no axis words but we don't want to process it
|
|
if (!(flags[AXIS_X] | flags[AXIS_Y] | flags[AXIS_Z] | flags[AXIS_A] | flags[AXIS_B] | flags[AXIS_C])) {
|
|
return(STAT_OK);
|
|
}
|
|
|
|
cm_set_model_target(target, flags);
|
|
ritorno (cm_test_soft_limits(cm.gm.target)); // test soft limits; exit if thrown
|
|
cm_set_work_offsets(&cm.gm); // capture the fully resolved offsets to the state
|
|
cm_cycle_start(); // required for homing & other cycles
|
|
stat_t status = mp_aline(&cm.gm); // send the move to the planner
|
|
cm_finalize_move();
|
|
|
|
if (status == STAT_MINIMUM_LENGTH_MOVE) {
|
|
if (!mp_has_runnable_buffer()) { // handle condition where zero-length move is last or only move
|
|
cm_cycle_end(); // ...otherwise cycle will not end properly
|
|
}
|
|
status = STAT_OK;
|
|
}
|
|
return (status);
|
|
}
|
|
|
|
/*
|
|
* cm_set_g28_position() - G28.1
|
|
* cm_goto_g28_position() - G28
|
|
* cm_set_g30_position() - G30.1
|
|
* cm_goto_g30_position() - G30
|
|
* _goto_stored_position() - helper
|
|
*/
|
|
|
|
stat_t _goto_stored_position(const float stored_position[], // always in mm
|
|
const float intermediate_target[], // in current units (G20/G21)
|
|
const bool flags[]) // all false if no intermediate move
|
|
{
|
|
// Go through intermediate point if one is provided
|
|
while (mp_planner_is_full()); // Make sure you have available buffers
|
|
ritorno(cm_straight_traverse(intermediate_target, flags)); // w/no action if no axis flags
|
|
|
|
// If G20 adjust stored position (always in mm) to inches so traverse will be correct
|
|
float target[AXES]; // make a local stored position as it may be modified
|
|
copy_vector(target, stored_position);
|
|
if (cm.gm.units_mode == INCHES) {
|
|
for (uint8_t i=0; i<AXES; i++) {
|
|
target[i] *= INCHES_PER_MM;
|
|
}
|
|
}
|
|
|
|
// Run the stored position move
|
|
while (mp_planner_is_full()); // Make sure you have available buffers
|
|
|
|
uint8_t saved_distance_mode = cm_get_distance_mode(MODEL);
|
|
cm_set_absolute_override(MODEL, ABSOLUTE_OVERRIDE_ON); // Position was stored in absolute coords
|
|
cm_set_distance_mode(ABSOLUTE_DISTANCE_MODE); // Must run in absolute distance mode
|
|
|
|
bool flags2[] = { 1,1,1,1,1,1 };
|
|
stat_t status = cm_straight_traverse(target, flags2); // Go to stored position
|
|
cm_set_absolute_override(MODEL, ABSOLUTE_OVERRIDE_OFF);
|
|
cm_set_distance_mode(saved_distance_mode); // Restore distance mode
|
|
return (status);
|
|
}
|
|
|
|
stat_t cm_set_g28_position(void)
|
|
{
|
|
copy_vector(cm.gmx.g28_position, cm.gmx.position); // in MM and machine coordinates
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_goto_g28_position(const float target[], const bool flags[])
|
|
{
|
|
return (_goto_stored_position(cm.gmx.g28_position, target, flags));
|
|
}
|
|
|
|
stat_t cm_set_g30_position(void)
|
|
{
|
|
copy_vector(cm.gmx.g30_position, cm.gmx.position); // in MM and machine coordinates
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_goto_g30_position(const float target[], const bool flags[])
|
|
{
|
|
return (_goto_stored_position(cm.gmx.g30_position, target, flags));
|
|
}
|
|
|
|
/********************************
|
|
* Machining Attributes (4.3.5) *
|
|
********************************/
|
|
/*
|
|
* cm_set_feed_rate() - F parameter (affects MODEL only)
|
|
*
|
|
* Normalize feed rate to mm/min or to minutes if in inverse time mode
|
|
*/
|
|
|
|
stat_t cm_set_feed_rate(const float feed_rate)
|
|
{
|
|
if (cm.gm.feed_rate_mode == INVERSE_TIME_MODE) {
|
|
if (fp_ZERO(feed_rate)) {
|
|
return (STAT_GCODE_FEEDRATE_NOT_SPECIFIED);
|
|
}
|
|
cm.gm.feed_rate = 1/feed_rate; // normalize to minutes (NB: active for this gcode block only)
|
|
} else {
|
|
cm.gm.feed_rate = _to_millimeters(feed_rate);
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_set_feed_rate_mode() - G93, G94 (affects MODEL only)
|
|
*
|
|
* INVERSE_TIME_MODE = 0, // G93
|
|
* UNITS_PER_MINUTE_MODE, // G94
|
|
* UNITS_PER_REVOLUTION_MODE // G95 (unimplemented)
|
|
*/
|
|
|
|
stat_t cm_set_feed_rate_mode(const uint8_t mode)
|
|
{
|
|
cm.gm.feed_rate_mode = (cmFeedRateMode)mode;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_set_path_control() - G61, G61.1, G64
|
|
*/
|
|
|
|
stat_t cm_set_path_control(GCodeState_t *gcode_state, const uint8_t mode)
|
|
{
|
|
gcode_state->path_control = (cmPathControl)mode;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
|
|
/*******************************
|
|
* Machining Functions (4.3.6) *
|
|
*******************************/
|
|
/*
|
|
* cm_arc_feed() - SEE plan_arc.cpp
|
|
*/
|
|
|
|
|
|
/*
|
|
* cm_dwell() - G4, P parameter (seconds)
|
|
*/
|
|
stat_t cm_dwell(const float seconds)
|
|
{
|
|
cm.gm.parameter = seconds;
|
|
mp_dwell(seconds);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_straight_feed() - G1
|
|
*/
|
|
stat_t cm_straight_feed(const float target[], const bool flags[])
|
|
{
|
|
// trap zero feed rate condition
|
|
if (fp_ZERO(cm.gm.feed_rate)) {
|
|
return (STAT_GCODE_FEEDRATE_NOT_SPECIFIED);
|
|
}
|
|
cm.gm.motion_mode = MOTION_MODE_STRAIGHT_FEED;
|
|
|
|
if (!(flags[AXIS_X] | flags[AXIS_Y] | flags[AXIS_Z] | flags[AXIS_A] | flags[AXIS_B] | flags[AXIS_C])) {
|
|
return(STAT_OK);
|
|
}
|
|
|
|
cm_set_model_target(target, flags);
|
|
ritorno (cm_test_soft_limits(cm.gm.target)); // test soft limits; exit if thrown
|
|
cm_set_work_offsets(&cm.gm); // capture the fully resolved offsets to the state
|
|
cm_cycle_start(); // required for homing & other cycles
|
|
stat_t status = mp_aline(&cm.gm); // send the move to the planner
|
|
|
|
cm_finalize_move(); // <-- ONLY safe because we don't care about status...
|
|
|
|
if (status == STAT_MINIMUM_LENGTH_MOVE) {
|
|
if (!mp_has_runnable_buffer()) { // handle condition where zero-length move is last or only move
|
|
cm_cycle_end(); // ...otherwise cycle will not end properly
|
|
}
|
|
status = STAT_OK;
|
|
}
|
|
return (status);
|
|
}
|
|
|
|
/*****************************
|
|
* Spindle Functions (4.3.7) *
|
|
*****************************/
|
|
// see spindle.cpp/.h
|
|
|
|
/**************************
|
|
* Tool Functions (4.3.8) *
|
|
**************************/
|
|
/*
|
|
* cm_select_tool() - T parameter
|
|
* _exec_select_tool() - execution callback
|
|
*
|
|
* cm_change_tool() - M6 (This might become a complete tool change cycle)
|
|
* _exec_change_tool() - execution callback
|
|
*
|
|
* Note: These functions don't actually do anything for now, and there's a bug
|
|
* where T and M in different blocks don't work correctly
|
|
*/
|
|
stat_t cm_select_tool(const uint8_t tool_select)
|
|
{
|
|
if (tool_select > TOOLS) {
|
|
return (STAT_T_WORD_IS_INVALID);
|
|
}
|
|
float value[] = { (float)tool_select, 0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_select_tool, value, flags);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
static void _exec_select_tool(float *value, bool *flag)
|
|
{
|
|
cm.gm.tool_select = (uint8_t)value[0];
|
|
}
|
|
|
|
stat_t cm_change_tool(const uint8_t tool_change)
|
|
{
|
|
float value[] = { (float)cm.gm.tool_select,0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_change_tool, value, flags);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
static void _exec_change_tool(float *value, bool *flag)
|
|
{
|
|
cm.gm.tool = (uint8_t)value[0];
|
|
}
|
|
|
|
/***********************************
|
|
* Miscellaneous Functions (4.3.9) *
|
|
***********************************/
|
|
// see coolant.cpp/.h
|
|
|
|
/*
|
|
* cm_message() - queue a RAM string as a message in the response (unconditionally)
|
|
*/
|
|
|
|
void cm_message(const char *message)
|
|
{
|
|
nv_add_string((const char *)"msg", message); // add message to the response object
|
|
}
|
|
|
|
/*
|
|
* cm_reset_overrides() - reset manual feedrate and spindle overrides to initial conditions
|
|
*/
|
|
|
|
void cm_reset_overrides()
|
|
{
|
|
cm.gmx.m48_enable = true;
|
|
cm.gmx.mfo_enable = false; // feed rate overrides
|
|
cm.gmx.mfo_factor = 1.0;
|
|
cm.gmx.mto_enable = false; // traverse overrides
|
|
cm.gmx.mto_factor = 1.0;
|
|
}
|
|
|
|
/*
|
|
static void _exec_feed_override(const bool m48_enable, const bool m50_enable, const float override_factor)
|
|
{
|
|
}
|
|
*/
|
|
|
|
/*
|
|
* cm_m48_enable() - M48, M49
|
|
*
|
|
* M48 is the master enable for manual feedrate override and spindle override
|
|
* If M48 is asserted M50 (mfo), M50.1 (mto) and M51 (sso) settings are in effect
|
|
* If M49 is asserted M50 (mfo), M501. (mto) and M51 (sso) settings are in ignored
|
|
*
|
|
* See http://linuxcnc.org/docs/html/gcode/m-code.html#sec:M48,-M49-Speed-and-Feed-Override-Control
|
|
*/
|
|
/* M48state M48new M50state action (notes):
|
|
* disable disable disable no action, no state change
|
|
* disable disable ENABLE no action, no state change
|
|
*
|
|
* disable ENABLE disable no action, no state change
|
|
* disable ENABLE ENABLE start ramp w/stored P value
|
|
*
|
|
* ENABLE disable disable no action, no state change
|
|
* ENABLE disable ENABLE end ramp
|
|
*
|
|
* ENABLE ENABLE disable no action, no state change
|
|
* ENABLE ENABLE ENABLE no action, no state change
|
|
*/
|
|
stat_t cm_m48_enable(uint8_t enable) // M48, M49
|
|
{
|
|
// handle changes to feed override given new state of m48/m49
|
|
|
|
cm.gmx.m48_enable = enable; // update state
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_mfo_control() - M50 manual feed rate override comtrol
|
|
*
|
|
* M50 enables manual feedrate override and the optional P override parameter.
|
|
* P is expressed as M% to N% of programmed feedrate, typically a value from 0.05 to 2.000.
|
|
* P may also be zero or missing. Behaviors:
|
|
*
|
|
* P < minimum or P > maximum parameter, and not zero. Return error, no state change or action
|
|
* P omitted. Turn on feedrate override to current stored P value
|
|
* P = 0. Turn off feedrate override. (Do not change stored P value
|
|
* P = N. Turn on feedrate override to value of N, preserve new P value
|
|
* See http://www.linuxcnc.org/docs/2.4/html/gcode_main.html#sec:M50:-Feed-Override
|
|
*
|
|
* M48 is set ON on initialization and program end
|
|
* M50 is set OFF on initialization and program end
|
|
* P is set to 1.000 on initialization and program end (there is always a valid value)
|
|
*/
|
|
/* Implementation Notes:
|
|
*
|
|
* To do this correctly need to look not just at new values, but at current state
|
|
* and transitions. See m48 for M48 transitions
|
|
*
|
|
* M48state M50enable M50new M50 endstate: action (notes):
|
|
* disable disable M50 P0 disable no action or state change
|
|
* disable disable M50 ENABLE no action (m48 is disabled)
|
|
* disable disable M50 Pn ENABLE store new P value (no other action)
|
|
*
|
|
* disable ENABLE M50 P0 disable no action
|
|
* disable ENABLE M50 ENABLE no action (m48 is disabled)
|
|
* disable ENABLE M50 Pn ENABLE store new P value (no other action)
|
|
*
|
|
* ENABLE disable M50 P0 disable no action or state change
|
|
* ENABLE disable M50 ENABLE start ramp w/stored P value
|
|
* ENABLE disable M50 Pn ENABLE start ramp w/new P value; store P value
|
|
*
|
|
* ENABLE ENABLE M50 P0 disable end ramp
|
|
* ENABLE ENABLE M50 ENABLE no action
|
|
* ENABLE ENABLE M50 Pn ENABLE start ramp w/new P value; store P value
|
|
* (Note: new ramp will supercede any existing ramp)
|
|
*/
|
|
|
|
stat_t cm_mfo_control(const float P_word, const bool P_flag) // M50
|
|
{
|
|
bool new_enable = true;
|
|
bool new_override = false;
|
|
if (P_flag) { // if parameter is present in Gcode block
|
|
if (fp_ZERO(P_word)) {
|
|
new_enable = false; // P0 disables override
|
|
} else {
|
|
if (P_word < FEED_OVERRIDE_MIN) {
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (P_word > FEED_OVERRIDE_MAX) {
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
cm.gmx.mfo_factor = P_word; // P word is valid, store it.
|
|
new_override = true;
|
|
}
|
|
}
|
|
if (cm.gmx.m48_enable) { // if master enable is ON
|
|
if (new_enable && (new_override || !cm.gmx.mfo_enable)) { // 3 cases to start a ramp
|
|
mp_start_feed_override(FEED_OVERRIDE_RAMP_TIME, cm.gmx.mfo_factor);
|
|
} else if (cm.gmx.mfo_enable && !new_enable) { // case to turn off the ramp
|
|
mp_end_feed_override(FEED_OVERRIDE_RAMP_TIME);
|
|
}
|
|
}
|
|
cm.gmx.mfo_enable = new_enable; // always update the enable state
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_mto_control(const float P_word, const bool P_flag) // M50.1
|
|
{
|
|
bool new_enable = true;
|
|
bool new_override = false;
|
|
if (P_flag) { // if parameter is present in Gcode block
|
|
if (fp_ZERO(P_word)) {
|
|
new_enable = false; // P0 disables override
|
|
} else {
|
|
if (P_word < TRAVERSE_OVERRIDE_MIN) {
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (P_word > TRAVERSE_OVERRIDE_MAX) {
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
cm.gmx.mto_factor = P_word; // P word is valid, store it.
|
|
new_override = true;
|
|
}
|
|
}
|
|
if (cm.gmx.m48_enable) { // if master enable is ON
|
|
if (new_enable && (new_override || !cm.gmx.mfo_enable)) { // 3 cases to start a ramp
|
|
mp_start_traverse_override(FEED_OVERRIDE_RAMP_TIME, cm.gmx.mto_factor);
|
|
} else if (cm.gmx.mto_enable && !new_enable) { // case to turn off the ramp
|
|
mp_end_traverse_override(FEED_OVERRIDE_RAMP_TIME);
|
|
}
|
|
}
|
|
cm.gmx.mto_enable = new_enable; // always update the enable state
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/************************************************
|
|
* Feedhold and Related Functions (no NIST ref) *
|
|
************************************************/
|
|
/*
|
|
* Feedholds, queue flushes and end_holds are all related. The request functions set flags
|
|
* or change state to "REQUESTED". The sequencing callback interprets the flags as so:
|
|
* - A feedhold request received during motion should be honored
|
|
* - A feedhold request received during a feedhold should be ignored
|
|
* - A feedhold request received during a motion stop should be ignored
|
|
*
|
|
* - A queue flush request should only be honored while in a feedhold
|
|
* - Said queue flush request received during a feedhold should be deferred until
|
|
* the feedhold enters a HOLD state (i.e. until deceleration is complete and motors stop).
|
|
* - A queue flush request received during a motion stop should be honored
|
|
*
|
|
* - An end_hold (cycle start) request should only be honored while in a feedhold
|
|
* - Said end_hold request received during a feedhold should be deferred until the
|
|
* feedhold enters a HOLD state (i.e. until deceleration is complete).
|
|
* If a queue flush request is also present the queue flush should be done first
|
|
*
|
|
* Below the request level, feedholds work like this:
|
|
* - The hold is initiated by calling cm_start_hold(). cm.hold_state is set to
|
|
* FEEDHOLD_SYNC, motion_state is set to MOTION_HOLD, and the spindle is turned off
|
|
* (if it it on). The remainder of feedhold
|
|
* processing occurs in plan_exec.c in the mp_exec_aline() function.
|
|
*
|
|
* - MOTION_HOLD and FEEDHOLD_SYNC tells mp_exec_aline() to begin feedhold processing
|
|
* after the current move segment is finished (< 5 ms later). (Cases handled by
|
|
* feedhold processing are listed in plan_exec.c).
|
|
*
|
|
* - FEEDHOLD_SYNC causes the current move in mr to be replanned into a deceleration.
|
|
* If the distance remaining in the executing move is sufficient for a full deceleration
|
|
* then motion will stop in the current block. Otherwise the deceleration phase
|
|
* will extend across as many blocks necessary until one will stop.
|
|
*
|
|
* - Once deceleration is complete hold state transitions to FEEDHOLD_HOLD and the
|
|
* distance remaining in the bf last block is replanned up from zero velocity.
|
|
* The move in the bf block is NOT released (unlike normal operation), as it
|
|
* will be used again to restart from hold.
|
|
*
|
|
* - When cm_end_hold() is called it releases the hold, restarts the move and restarts
|
|
* the spindle if the spindle is active.
|
|
*/
|
|
/* Queue Flush operation
|
|
*
|
|
* This one's complicated. See here first:
|
|
* https://github.com/synthetos/g2/wiki/Alarm-Processing
|
|
* https://github.com/synthetos/g2/wiki/Job-Exception-Handling
|
|
*
|
|
* We want to use queue flush for a few different use cases, as per the above wiki pages.
|
|
* The % behavior implements Exception Handling cases 1 and 2 - Stop a Single Move and
|
|
* Stop Multiple Moves. This is complicated further by the processing in single USB and
|
|
* dual USB being different. Also, the state handling is located in xio.cpp / readline(),
|
|
* controller.cpp _dispatch_kernel() and cm_request_queue_flush(), below.
|
|
* So it's documented here.
|
|
*
|
|
* Single or Dual USB Channels:
|
|
* - If a % is received outside of a feed hold or ALARM state, ignore it.
|
|
* Change the % to a ; comment symbol (xio)
|
|
*
|
|
* Single USB Channel Operation:
|
|
* - Enter a feedhold (!)
|
|
* - Receive a queue flush (%) Both dispatch it and store a marker (ACK) in the input
|
|
* buffer in place of the the % (xio)
|
|
* - Execute the feedhold to a hold condition (plan_exec)
|
|
* - Execute the dispatched % to flush queues (canonical_machine)
|
|
* - Silently reject any commands up to the % in the input queue (controller)
|
|
* - When ETX is encountered transition to STOP state (controller/canonical_machine)
|
|
*
|
|
* Dual USB Channel Operation:
|
|
* - Same as above except that we expect the % to arrive on the control channel
|
|
* - The system will read and dump all commands in the data channel until either a
|
|
* clear is encountered ({clear:n} or $clear), or an ETX is encountered on either
|
|
* channel, but it really should be on the data channel to ensure all queued commands
|
|
* are dumped. It is the host's responsibility to both write the clear (or ETX), and
|
|
* to ensure that it either arrives on the data channel or that the data channel is
|
|
* empty before writing it to the control channel.
|
|
*/
|
|
/*
|
|
* cm_request_feedhold()
|
|
* cm_request_end_hold() - cycle restart
|
|
* cm_request_queue_flush()
|
|
*/
|
|
void cm_request_feedhold(void) {
|
|
// honor request if not already in a feedhold and you are moving
|
|
if ((cm.hold_state == FEEDHOLD_OFF) && (cm.motion_state != MOTION_STOP)) {
|
|
cm.hold_state = FEEDHOLD_REQUESTED;
|
|
}
|
|
}
|
|
|
|
void cm_request_end_hold(void)
|
|
{
|
|
if (cm.hold_state != FEEDHOLD_OFF) {
|
|
cm.end_hold_requested = true;
|
|
}
|
|
}
|
|
|
|
void cm_request_queue_flush()
|
|
{
|
|
if ((cm.hold_state != FEEDHOLD_OFF) && // don't honor request unless you are in a feedhold
|
|
(cm.queue_flush_state == FLUSH_OFF)) { // ...and only once
|
|
cm.queue_flush_state = FLUSH_REQUESTED; // request planner flush once motion has stopped
|
|
|
|
// NOTE: we used to flush the input buffers, but this is handled in xio *prior* to queue flush now
|
|
}
|
|
}
|
|
|
|
/*
|
|
* cm_feedhold_sequencing_callback() - sequence feedhold, queue_flush, and end_hold requests
|
|
*/
|
|
stat_t cm_feedhold_sequencing_callback()
|
|
{
|
|
if (cm.hold_state == FEEDHOLD_REQUESTED) {
|
|
cm_start_hold(); // feed won't run unless the machine is moving
|
|
}
|
|
if (cm.queue_flush_state == FLUSH_REQUESTED) {
|
|
cm_queue_flush(); // queue flush won't run until runtime is idle
|
|
}
|
|
if (cm.end_hold_requested) {
|
|
if (cm.queue_flush_state == FLUSH_OFF) { // either no flush or wait until it's done flushing
|
|
cm_end_hold();
|
|
}
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_has_hold() - return true if a hold condition exists (or a pending hold request)
|
|
* cm_start_hold() - start a feedhhold by signalling the exec
|
|
* cm_end_hold() - end a feedhold by returning the system to normal operation
|
|
* cm_queue_flush() - Flush planner queue and correct model positions
|
|
*/
|
|
bool cm_has_hold()
|
|
{
|
|
return (cm.hold_state != FEEDHOLD_OFF);
|
|
}
|
|
|
|
void cm_start_hold()
|
|
{
|
|
if (mp_has_runnable_buffer()) { // meaning there's something running
|
|
cm_spindle_optional_pause(spindle.pause_on_hold); // pause if this option is selected
|
|
cm_coolant_optional_pause(coolant.pause_on_hold); // pause if this option is selected
|
|
cm_set_motion_state(MOTION_HOLD);
|
|
cm.hold_state = FEEDHOLD_SYNC; // invokes hold from aline execution
|
|
}
|
|
}
|
|
void cm_end_hold()
|
|
{
|
|
if (cm.hold_state == FEEDHOLD_HOLD) {
|
|
cm.end_hold_requested = false;
|
|
mp_exit_hold_state();
|
|
|
|
// State machine cases:
|
|
if (cm.machine_state == MACHINE_ALARM) {
|
|
cm_spindle_off_immediate();
|
|
cm_coolant_off_immediate();
|
|
|
|
} else if (cm.motion_state == MOTION_STOP) { // && (! MACHINE_ALARM)
|
|
cm_spindle_off_immediate();
|
|
cm_coolant_off_immediate();
|
|
cm_cycle_end();
|
|
|
|
} else { // (MOTION_RUN || MOTION_PLANNING) && (! MACHINE_ALARM)
|
|
cm_cycle_start();
|
|
cm_spindle_resume(spindle.dwell_seconds);
|
|
cm_coolant_resume();
|
|
st_request_exec_move();
|
|
}
|
|
}
|
|
}
|
|
void cm_queue_flush()
|
|
{
|
|
if (mp_runtime_is_idle()) { // can't flush planner during movement
|
|
mp_flush_planner();
|
|
|
|
for (uint8_t axis = AXIS_X; axis < AXES; axis++) { // set all positions
|
|
cm_set_position(axis, mp_get_runtime_absolute_position(axis));
|
|
}
|
|
if(cm.hold_state == FEEDHOLD_HOLD) { // end feedhold if we're in one
|
|
cm_end_hold();
|
|
}
|
|
cm.queue_flush_state = FLUSH_OFF;
|
|
qr_request_queue_report(0); // request a queue report, since we've changed the number of buffers available
|
|
}
|
|
}
|
|
|
|
/*
|
|
* _hold_handler - a gpioDigitalInputHandler to capture pin change events
|
|
* Will be registered at init
|
|
*/
|
|
gpioDigitalInputHandler _hold_handler {
|
|
[&](const bool state, const inputEdgeFlag edge, const uint8_t triggering_pin_number) {
|
|
if (edge != INPUT_EDGE_LEADING) { return false; }
|
|
|
|
cm_start_hold();
|
|
|
|
return false; // allow others to see this notice
|
|
},
|
|
5, // priority
|
|
nullptr // next - nullptr to start with
|
|
};
|
|
|
|
|
|
/******************************
|
|
* Program Functions (4.3.10) *
|
|
******************************/
|
|
/* This group implements stop, start, and end functions.
|
|
* It is extended beyond the NIST spec to handle various situations.
|
|
*
|
|
* _exec_program_finalize() - helper
|
|
* cm_cycle_start()
|
|
* cm_cycle_end()
|
|
* cm_program_stop() - M0
|
|
* cm_optional_program_stop() - M1
|
|
* cm_program_end() - M2, M30
|
|
*/
|
|
/*
|
|
* Program and cycle state functions
|
|
*
|
|
* cm_program_stop and cm_optional_program_stop are synchronous Gcode commands
|
|
* that are received through the interpreter. They cause all motion to stop
|
|
* at the end of the current command, including spindle motion.
|
|
*
|
|
* Note that the stop occurs at the end of the immediately preceding command
|
|
* (i.e. the stop is queued behind the last command).
|
|
*
|
|
* cm_program_end is a stop that also resets the machine to initial state
|
|
*
|
|
* cm_program_end() implements M2 and M30
|
|
* The END behaviors are defined by NIST 3.6.1 are:
|
|
* 1a. Origin offsets are set to the default (like G54)
|
|
* 1b. Axis offsets are set to zero (like G92.2)
|
|
* 2. Selected plane is set to CANON_PLANE_XY (like G17)
|
|
* 3. Distance mode is set to MODE_ABSOLUTE (like G90)
|
|
* 4. Feed rate mode is set to UNITS_PER_MINUTE (like G94)
|
|
* 5. Feed and speed overrides are set to ON (like M48)
|
|
* 6. Cutter compensation is turned off (like G40)
|
|
* 7. The spindle is stopped (like M5)
|
|
* 8. The current motion mode is set to G_1 (like G1)
|
|
* 9. Coolant is turned off (like M9)
|
|
*
|
|
* cm_program_end() implments things slightly differently (1a, 8):
|
|
* 1a. Set default coordinate system (uses $gco, not G54)
|
|
* 1b. Axis offsets are SUSPENDED (G92.2)
|
|
* 2. Selected plane is set to default plane ($gpl)
|
|
* 3. Distance mode is set to MODE_ABSOLUTE (like G90)
|
|
* 4. Feed rate mode is set to UNITS_PER_MINUTE (like G94)
|
|
* 5. Not implemented
|
|
* 6. Not implemented
|
|
* 7. The spindle is stopped (like M5)
|
|
* 8. Motion mode is CANCELED like G80 (not set to G1 as per NIST)
|
|
* 9. Coolant is turned off (like M9)
|
|
*/
|
|
|
|
static void _exec_program_finalize(float *value, bool *flag)
|
|
{
|
|
cm_set_motion_state(MOTION_STOP);
|
|
|
|
// Allow update in the alarm state, to accommodate queue flush (RAS)
|
|
if ((cm.cycle_state == CYCLE_MACHINING || cm.cycle_state == CYCLE_OFF) &&
|
|
// (cm.machine_state != MACHINE_ALARM) && // omitted by OMC (RAS)
|
|
(cm.machine_state != MACHINE_SHUTDOWN)) {
|
|
cm.machine_state = (cmMachineState)value[0]; // don't update macs/cycs if we're in the middle of a canned cycle,
|
|
cm.cycle_state = CYCLE_OFF; // or if we're in machine alarm/shutdown mode
|
|
}
|
|
|
|
// reset the rest of the states
|
|
cm.cycle_state = CYCLE_OFF;
|
|
cm.hold_state = FEEDHOLD_OFF;
|
|
mp_zero_segment_velocity(); // for reporting purposes
|
|
|
|
// perform the following resets if it's a program END
|
|
if (((uint8_t)value[0]) == MACHINE_PROGRAM_END) {
|
|
cm_suspend_origin_offsets(); // G92.2 - as per NIST
|
|
// cm_reset_origin_offsets(); // G92.1 - alternative to above
|
|
cm_set_coord_system(cm.default_coord_system); // reset to default coordinate system
|
|
cm_select_plane(cm.default_select_plane); // reset to default arc plane
|
|
cm_set_distance_mode(cm.default_distance_mode);
|
|
cm_set_arc_distance_mode(INCREMENTAL_DISTANCE_MODE); // always the default
|
|
cm_spindle_off_immediate(); // M5
|
|
cm_coolant_off_immediate(); // M9
|
|
cm_set_feed_rate_mode(UNITS_PER_MINUTE_MODE); // G94
|
|
cm_set_motion_mode(MODEL, MOTION_MODE_CANCEL_MOTION_MODE);// NIST specifies G1 (MOTION_MODE_STRAIGHT_FEED), but we cancel motion mode. Safer.
|
|
cm_reset_overrides(); // reset feedrate the spindle overrides
|
|
temperature_reset(); // turn off all heaters and fans
|
|
}
|
|
sr_request_status_report(SR_REQUEST_IMMEDIATE); // request a final and full status report (not filtered)
|
|
}
|
|
|
|
void cm_cycle_start()
|
|
{
|
|
if (cm.cycle_state == CYCLE_OFF) { // don't (re)start homing, probe or other canned cycles
|
|
cm.machine_state = MACHINE_CYCLE;
|
|
cm.cycle_state = CYCLE_MACHINING;
|
|
qr_init_queue_report(); // clear queue reporting buffer counts
|
|
}
|
|
}
|
|
|
|
void cm_cycle_end()
|
|
{
|
|
if(cm.cycle_state == CYCLE_MACHINING) {
|
|
float value[] = { (float)MACHINE_PROGRAM_STOP, 0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
_exec_program_finalize(value, flags);
|
|
}
|
|
}
|
|
|
|
void cm_canned_cycle_end()
|
|
{
|
|
cm.cycle_state = CYCLE_OFF;
|
|
float value[] = { (float)MACHINE_PROGRAM_STOP, 0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
_exec_program_finalize(value, flags);
|
|
}
|
|
|
|
void cm_program_stop()
|
|
{
|
|
float value[] = { (float)MACHINE_PROGRAM_STOP, 0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_program_finalize, value, flags);
|
|
}
|
|
|
|
void cm_optional_program_stop()
|
|
{
|
|
float value[] = { (float)MACHINE_PROGRAM_STOP, 0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_program_finalize, value, flags);
|
|
}
|
|
|
|
void cm_program_end()
|
|
{
|
|
float value[] = { (float)MACHINE_PROGRAM_END, 0,0,0,0,0 };
|
|
bool flags[] = { 1,0,0,0,0,0 };
|
|
mp_queue_command(_exec_program_finalize, value, flags);
|
|
}
|
|
|
|
|
|
/*
|
|
* cm_json_command() - M100
|
|
*/
|
|
stat_t cm_json_command(char *json_string)
|
|
{
|
|
return mp_json_command(json_string);
|
|
}
|
|
|
|
/*
|
|
* cm_json_command_immediate() - M100.1
|
|
*/
|
|
stat_t cm_json_command_immediate(char *json_string)
|
|
{
|
|
return mp_json_command_immediate(json_string);
|
|
}
|
|
|
|
/*
|
|
* cm_json_wait() - M102
|
|
*/
|
|
stat_t cm_json_wait(char *json_string)
|
|
{
|
|
return mp_json_wait(json_string);
|
|
}
|
|
|
|
|
|
/**************************************
|
|
* END OF CANONICAL MACHINE FUNCTIONS *
|
|
**************************************/
|
|
|
|
/***********************************************************************************
|
|
* CONFIGURATION AND INTERFACE FUNCTIONS
|
|
* Functions to get and set variables from the cfgArray table
|
|
* These functions are not part of the NIST defined functions
|
|
***********************************************************************************/
|
|
|
|
// Strings for writing settings as nvObj string values
|
|
// Ref: http://www.avrfreaks.net/index.php?name=PNphpBB2&file=printview&t=120881&start=0
|
|
|
|
#ifdef __TEXT_MODE
|
|
|
|
static const char msg_units0[] = " in"; // used by generic print functions
|
|
static const char msg_units1[] = " mm";
|
|
static const char msg_units2[] = " deg";
|
|
static const char *const msg_units[] = { msg_units0, msg_units1, msg_units2 };
|
|
#define DEGREE_INDEX 2
|
|
|
|
static const char msg_am00[] = "[disabled]";
|
|
static const char msg_am01[] = "[standard]";
|
|
static const char msg_am02[] = "[inhibited]";
|
|
static const char msg_am03[] = "[radius]";
|
|
static const char *const msg_am[] = { msg_am00, msg_am01, msg_am02, msg_am03};
|
|
|
|
static const char msg_g20[] = "G20 - inches mode";
|
|
static const char msg_g21[] = "G21 - millimeter mode";
|
|
static const char *const msg_unit[] = { msg_g20, msg_g21 };
|
|
|
|
static const char msg_stat0[] = "Initializing"; // combined state (stat) uses this array
|
|
static const char msg_stat1[] = "Ready";
|
|
static const char msg_stat2[] = "Alarm";
|
|
static const char msg_stat3[] = "Stop";
|
|
static const char msg_stat4[] = "End";
|
|
static const char msg_stat5[] = "Run";
|
|
static const char msg_stat6[] = "Hold";
|
|
static const char msg_stat7[] = "Probe";
|
|
static const char msg_stat8[] = "Cycle";
|
|
static const char msg_stat9[] = "Homing";
|
|
static const char msg_stat10[] = "Jog";
|
|
static const char msg_stat11[] = "Interlock";
|
|
static const char msg_stat12[] = "Shutdown";
|
|
static const char msg_stat13[] = "Panic";
|
|
static const char *const msg_stat[] = { msg_stat0, msg_stat1, msg_stat2, msg_stat3,
|
|
msg_stat4, msg_stat5, msg_stat6, msg_stat7,
|
|
msg_stat8, msg_stat9, msg_stat10, msg_stat11,
|
|
msg_stat12, msg_stat13 };
|
|
|
|
static const char msg_macs0[] = "Initializing";
|
|
static const char msg_macs1[] = "Ready";
|
|
static const char msg_macs2[] = "Alarm";
|
|
static const char msg_macs3[] = "Stop";
|
|
static const char msg_macs4[] = "End";
|
|
static const char msg_macs5[] = "Cycle";
|
|
static const char msg_macs6[] = "Interlock";
|
|
static const char msg_macs7[] = "SHUTDOWN";
|
|
static const char msg_macs8[] = "PANIC";
|
|
static const char *const msg_macs[] = { msg_macs0, msg_macs1, msg_macs2, msg_macs3,
|
|
msg_macs4, msg_macs5, msg_macs6, msg_macs7,
|
|
msg_macs8 };
|
|
|
|
static const char msg_cycs0[] = "Off";
|
|
static const char msg_cycs1[] = "Machining";
|
|
static const char msg_cycs2[] = "Homing";
|
|
static const char msg_cycs3[] = "Probe";
|
|
static const char msg_cycs4[] = "Jog";
|
|
static const char *const msg_cycs[] = { msg_cycs0, msg_cycs1, msg_cycs2, msg_cycs3, msg_cycs4 };
|
|
|
|
static const char msg_mots0[] = "Stop";
|
|
static const char msg_mots1[] = "Planning";
|
|
static const char msg_mots2[] = "Run";
|
|
static const char msg_mots3[] = "Hold";
|
|
static const char *const msg_mots[] = { msg_mots0, msg_mots1, msg_mots2, msg_mots3 };
|
|
|
|
static const char msg_hold0[] = "Off";
|
|
static const char msg_hold1[] = "Requested";
|
|
static const char msg_hold2[] = "Sync";
|
|
static const char msg_hold3[] = "Decel Continue";
|
|
static const char msg_hold4[] = "Decel to Zero";
|
|
static const char msg_hold5[] = "Decel Done";
|
|
static const char msg_hold6[] = "Pending";
|
|
static const char msg_hold7[] = "Hold";
|
|
static const char *const msg_hold[] = { msg_hold0, msg_hold1, msg_hold2, msg_hold3,
|
|
msg_hold4, msg_hold5, msg_hold6, msg_hold7 };
|
|
|
|
static const char msg_home0[] = "Not Homed";
|
|
static const char msg_home1[] = "Homed";
|
|
static const char msg_home2[] = "Homing";
|
|
static const char *const msg_home[] = { msg_home0, msg_home1, msg_home2 };
|
|
|
|
static const char msg_g53[] = "G53 - machine coordinate system";
|
|
static const char msg_g54[] = "G54 - coordinate system 1";
|
|
static const char msg_g55[] = "G55 - coordinate system 2";
|
|
static const char msg_g56[] = "G56 - coordinate system 3";
|
|
static const char msg_g57[] = "G57 - coordinate system 4";
|
|
static const char msg_g58[] = "G58 - coordinate system 5";
|
|
static const char msg_g59[] = "G59 - coordinate system 6";
|
|
static const char *const msg_coor[] = { msg_g53, msg_g54, msg_g55, msg_g56, msg_g57, msg_g58, msg_g59 };
|
|
|
|
static const char msg_g00[] = "G0 - linear traverse";
|
|
static const char msg_g01[] = "G1 - linear feed";
|
|
static const char msg_g02[] = "G2 - clockwise arc feed";
|
|
static const char msg_g03[] = "G3 - counter clockwise arc feed";
|
|
static const char msg_g80[] = "G80 - cancel motion mode (none active)";
|
|
static const char *const msg_momo[] = { msg_g00, msg_g01, msg_g02, msg_g03, msg_g80 };
|
|
|
|
static const char msg_g17[] = "G17 - XY plane";
|
|
static const char msg_g18[] = "G18 - XZ plane";
|
|
static const char msg_g19[] = "G19 - YZ plane";
|
|
static const char *const msg_plan[] = { msg_g17, msg_g18, msg_g19 };
|
|
|
|
static const char msg_g61[] = "G61 - exact path mode";
|
|
static const char msg_g6a[] = "G61.1 - exact stop mode";
|
|
static const char msg_g64[] = "G64 - continuous mode";
|
|
static const char *const msg_path[] = { msg_g61, msg_g6a, msg_g64 };
|
|
|
|
static const char msg_g90[] = "G90 - absolute distance mode";
|
|
static const char msg_g91[] = "G91 - incremental distance mode";
|
|
static const char *const msg_dist[] = { msg_g90, msg_g91 };
|
|
|
|
static const char msg_g901[] = "G90.1 - absolute distance mode";
|
|
static const char msg_g911[] = "G91.1 - incremental distance mode (default mode)";
|
|
static const char *const msg_admo[] = { msg_g901, msg_g911 };
|
|
|
|
static const char msg_g93[] = "G93 - inverse time mode";
|
|
static const char msg_g94[] = "G94 - units-per-minute mode (i.e. feedrate mode)";
|
|
static const char msg_g95[] = "G95 - units-per-revolution mode";
|
|
static const char *const msg_frmo[] = { msg_g93, msg_g94, msg_g95 };
|
|
|
|
#else
|
|
|
|
#define msg_units NULL
|
|
#define msg_unit NULL
|
|
#define msg_stat NULL
|
|
#define msg_macs NULL
|
|
#define msg_cycs NULL
|
|
#define msg_mots NULL
|
|
#define msg_hold NULL
|
|
#define msg_home NULL
|
|
#define msg_coor NULL
|
|
#define msg_momo NULL
|
|
#define msg_plan NULL
|
|
#define msg_path NULL
|
|
#define msg_dist NULL
|
|
#define msg_admo NULL
|
|
#define msg_frmo NULL
|
|
#define msg_am NULL
|
|
|
|
#endif // __TEXT_MODE
|
|
|
|
|
|
/***** AXIS HELPERS *****************************************************************
|
|
* _get_axis() - return axis # or -1 if not an axis (works for mapped motors as well)
|
|
* _coord() - return coordinate system number or -1 if error
|
|
* cm_get_axis_char() - return ASCII char for axis given the axis number
|
|
* cm_get_axis_type() - return linear axis (0), rotary axis (1) or error (-1)
|
|
*/
|
|
|
|
/* _get_axis()
|
|
*
|
|
* Cases that are handled by _get_axis():
|
|
* - sys/... value is a system parameter (global), there is no axis
|
|
* - xam any axis parameter will return the axis number
|
|
* - 1ma any motor parameter will return the mapped axis for that motor
|
|
* - 1su an example of the above
|
|
* - mpox readouts
|
|
* - g54x offsets
|
|
* - tlx tool length offset
|
|
* - tt1x tool table
|
|
* - tt32x tool table
|
|
* - _tex diagnostic parameters
|
|
*/
|
|
|
|
static int8_t _get_axis(const index_t index)
|
|
{
|
|
// test if this is a SYS parameter (global), in which case there will be no axis
|
|
if (strcmp("sys", cfgArray[index].group) == 0) {
|
|
return (AXIS_TYPE_SYSTEM);
|
|
}
|
|
|
|
// if the leading character of the token is a number it's a motor
|
|
char c = cfgArray[index].token[0];
|
|
if (isdigit(cfgArray[index].token[0])) {
|
|
return(st_cfg.mot[c-0x31].motor_map);
|
|
}
|
|
|
|
// otherwise it's an axis. Or undefined, which is usually a global.
|
|
char *ptr;
|
|
char axes[] = {"xyzabc"};
|
|
if ((ptr = strchr(axes, c)) == NULL) { // test the character in the 0 and 3 positions
|
|
if ((ptr = strchr(axes, cfgArray[index].token[3])) == NULL) { // to accommodate 'xam' and 'g54x' styles
|
|
return (AXIS_TYPE_UNDEFINED);
|
|
}
|
|
}
|
|
return (ptr - axes);
|
|
}
|
|
|
|
/**** not used yet ****
|
|
static int8_t _coord(char *token) // extract coordinate system from 3rd character
|
|
{
|
|
char *ptr;
|
|
char coord_list[] = {"456789"};
|
|
|
|
if ((ptr = strchr(coord_list, token[2])) == NULL) { // test the 3rd character against the string
|
|
return (-1);
|
|
}
|
|
return (ptr - coord_list);
|
|
}
|
|
*/
|
|
|
|
char cm_get_axis_char(const int8_t axis)
|
|
{
|
|
char axis_char[] = "XYZABC";
|
|
if ((axis < 0) || (axis > AXES)) return (' ');
|
|
return (axis_char[axis]);
|
|
}
|
|
|
|
cmAxisType cm_get_axis_type(const index_t index)
|
|
{
|
|
int8_t axis = _get_axis(index);
|
|
if (axis == AXIS_TYPE_UNDEFINED) { return (AXIS_TYPE_UNDEFINED); }
|
|
if (axis == AXIS_TYPE_SYSTEM) { return (AXIS_TYPE_SYSTEM); }
|
|
if (axis >= AXIS_A) { return (AXIS_TYPE_ROTARY); }
|
|
return (AXIS_TYPE_LINEAR);
|
|
}
|
|
|
|
/**** Functions called directly from cfgArray table - mostly wrappers ****
|
|
* _get_msg_helper() - helper to get string values
|
|
*
|
|
* cm_get_stat() - get combined machine state as value and string
|
|
* cm_get_macs() - get raw machine state as value and string
|
|
* cm_get_cycs() - get raw cycle state as value and string
|
|
* cm_get_mots() - get raw motion state as value and string
|
|
* cm_get_hold() - get raw hold state as value and string
|
|
* cm_get_home() - get raw homing state as value and string
|
|
*
|
|
* cm_get_unit() - get units mode as integer and display string
|
|
* cm_get_coor() - get goodinate system
|
|
* cm_get_momo() - get runtime motion mode
|
|
* cm_get_plan() - get model plane select
|
|
* cm_get_path() - get model path control mode
|
|
* cm_get_dist() - get model distance mode
|
|
* cm_get_admo() - get model arc distance mode
|
|
* cm_get_frmo() - get model feed rate mode
|
|
* cm_get_tool() - get tool
|
|
* cm_get_feed() - get feed rate
|
|
* cm_get_mline()- get model line number for status reports
|
|
* cm_get_line() - get active (model or runtime) line number for status reports
|
|
* cm_get_vel() - get runtime velocity
|
|
* cm_get_ofs() - get current work offset (runtime)
|
|
* cm_get_pos() - get current work position (runtime)
|
|
* cm_get_mpos() - get current machine position (runtime)
|
|
*
|
|
* cm_print_pos()- print work position (with proper units)
|
|
* cm_print_mpos()- print machine position (always mm units)
|
|
* cm_print_coor()- print coordinate offsets with linear units
|
|
* cm_print_corr()- print coordinate offsets with rotary units
|
|
*/
|
|
|
|
// Add the string for the enum to the nv, but leave it as a TYPE_INT
|
|
stat_t _get_msg_helper(nvObj_t *nv, const char *const msg_array[], uint8_t value)
|
|
{
|
|
nv->value = (float)value;
|
|
nv->valuetype = TYPE_INT;
|
|
return(nv_copy_string(nv, (const char *)GET_TEXT_ITEM(msg_array, value)));
|
|
}
|
|
|
|
stat_t cm_get_stat(nvObj_t *nv) { return(_get_msg_helper(nv, msg_stat, cm_get_combined_state()));}
|
|
stat_t cm_get_macs(nvObj_t *nv) { return(_get_msg_helper(nv, msg_macs, cm_get_machine_state()));}
|
|
stat_t cm_get_cycs(nvObj_t *nv) { return(_get_msg_helper(nv, msg_cycs, cm_get_cycle_state()));}
|
|
stat_t cm_get_mots(nvObj_t *nv) { return(_get_msg_helper(nv, msg_mots, cm_get_motion_state()));}
|
|
stat_t cm_get_hold(nvObj_t *nv) { return(_get_msg_helper(nv, msg_hold, cm_get_hold_state()));}
|
|
stat_t cm_get_home(nvObj_t *nv) { return(_get_msg_helper(nv, msg_home, cm_get_homing_state()));}
|
|
|
|
stat_t cm_get_unit(nvObj_t *nv) { return(_get_msg_helper(nv, msg_unit, cm_get_units_mode(ACTIVE_MODEL)));}
|
|
stat_t cm_get_coor(nvObj_t *nv) { return(_get_msg_helper(nv, msg_coor, cm_get_coord_system(ACTIVE_MODEL)));}
|
|
stat_t cm_get_momo(nvObj_t *nv) { return(_get_msg_helper(nv, msg_momo, cm_get_motion_mode(ACTIVE_MODEL)));}
|
|
stat_t cm_get_plan(nvObj_t *nv) { return(_get_msg_helper(nv, msg_plan, cm_get_select_plane(ACTIVE_MODEL)));}
|
|
stat_t cm_get_path(nvObj_t *nv) { return(_get_msg_helper(nv, msg_path, cm_get_path_control(ACTIVE_MODEL)));}
|
|
stat_t cm_get_dist(nvObj_t *nv) { return(_get_msg_helper(nv, msg_dist, cm_get_distance_mode(ACTIVE_MODEL)));}
|
|
stat_t cm_get_admo(nvObj_t *nv) { return(_get_msg_helper(nv, msg_admo, cm_get_arc_distance_mode(ACTIVE_MODEL)));}
|
|
stat_t cm_get_frmo(nvObj_t *nv) { return(_get_msg_helper(nv, msg_frmo, cm_get_feed_rate_mode(ACTIVE_MODEL)));}
|
|
|
|
stat_t cm_get_toolv(nvObj_t *nv)
|
|
{
|
|
nv->value = (float)cm_get_tool(ACTIVE_MODEL);
|
|
nv->valuetype = TYPE_INT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_mline(nvObj_t *nv)
|
|
{
|
|
nv->value = (float)cm_get_linenum(MODEL);
|
|
nv->valuetype = TYPE_INT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_line(nvObj_t *nv)
|
|
{
|
|
nv->value = (float)cm_get_linenum(ACTIVE_MODEL);
|
|
nv->valuetype = TYPE_INT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_vel(nvObj_t *nv)
|
|
{
|
|
if (cm_get_motion_state() == MOTION_STOP) {
|
|
nv->value = 0;
|
|
} else {
|
|
nv->value = mp_get_runtime_velocity();
|
|
if (cm_get_units_mode(RUNTIME) == INCHES) {
|
|
nv->value *= INCHES_PER_MM;
|
|
}
|
|
}
|
|
nv->precision = GET_TABLE_WORD(precision);
|
|
nv->valuetype = TYPE_FLOAT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_feed(nvObj_t *nv)
|
|
{
|
|
nv->value = cm_get_feed_rate(ACTIVE_MODEL);
|
|
if (cm_get_units_mode(ACTIVE_MODEL) == INCHES) {
|
|
nv->value *= INCHES_PER_MM;
|
|
}
|
|
nv->precision = GET_TABLE_WORD(precision);
|
|
nv->valuetype = TYPE_FLOAT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_pos(nvObj_t *nv)
|
|
{
|
|
nv->value = cm_get_work_position(ACTIVE_MODEL, _get_axis(nv->index));
|
|
nv->precision = GET_TABLE_WORD(precision);
|
|
nv->valuetype = TYPE_FLOAT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_mpo(nvObj_t *nv)
|
|
{
|
|
nv->value = cm_get_absolute_position(ACTIVE_MODEL, _get_axis(nv->index));
|
|
nv->precision = GET_TABLE_WORD(precision);
|
|
nv->valuetype = TYPE_FLOAT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_ofs(nvObj_t *nv)
|
|
{
|
|
nv->value = cm_get_work_offset(ACTIVE_MODEL, _get_axis(nv->index));
|
|
nv->precision = GET_TABLE_WORD(precision);
|
|
nv->valuetype = TYPE_FLOAT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_get_tof(nvObj_t *nv)
|
|
{
|
|
nv->value = cm.tl_offset[_get_axis(nv->index)];
|
|
nv->precision = GET_TABLE_WORD(precision);
|
|
nv->valuetype = TYPE_FLOAT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* AXIS GET AND SET FUNCTIONS
|
|
*
|
|
* cm_get_am() - get axis mode w/enumeration string
|
|
* cm_set_am() - set axis mode w/exception handling for axis type
|
|
* cm_set_hi() - set homing input
|
|
*/
|
|
|
|
stat_t cm_get_am(nvObj_t *nv)
|
|
{
|
|
get_ui8(nv);
|
|
return(_get_msg_helper(nv, msg_am, nv->value));
|
|
}
|
|
|
|
stat_t cm_set_am(nvObj_t *nv) // axis mode
|
|
{
|
|
if (cm_get_axis_type(nv->index) == 0) { // linear
|
|
if (nv->value > AXIS_MODE_MAX_LINEAR) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
} else {
|
|
if (nv->value > AXIS_MODE_MAX_ROTARY) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
}
|
|
set_ui8(nv);
|
|
return(STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_hi(nvObj_t *nv)
|
|
{
|
|
if (nv->value < 0) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (nv->value > D_IN_CHANNELS) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
set_ui8(nv);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/**** Velocity and Jerk functions
|
|
* cm_get_axis_jerk() - returns jerk for an axis
|
|
* cm_set_axis_jerk() - sets the jerk for an axis, including recirpcal and cached values
|
|
*
|
|
* cm_set_vm() - set velocity max value - called from dispatch table
|
|
* cm_set_fr() - set feedrate max value - called from dispatch table
|
|
* cm_set_jm() - set jerk max value - called from dispatch table
|
|
* cm_set_jh() - set jerk homing value - called from dispatch table
|
|
*
|
|
* Jerk values can be rather large, often in the billions. This makes for some pretty big
|
|
* numbers for people to deal with. Jerk values are stored in the system in truncated format;
|
|
* values are divided by 1,000,000 then reconstituted before use.
|
|
*
|
|
* The set_xjm() nad set_xjh() functions will accept either truncated or untruncated jerk
|
|
* numbers as input. If the number is > 1,000,000 it is divided by 1,000,000 before storing.
|
|
* Numbers are accepted in either millimeter or inch mode and converted to millimeter mode.
|
|
*
|
|
* The axis_jerk() functions expect the jerk in divided-by 1,000,000 form
|
|
*/
|
|
float cm_get_axis_jerk(const uint8_t axis)
|
|
{
|
|
return (cm.a[axis].jerk_max);
|
|
}
|
|
|
|
// Precompute sqrt(3)/10 for the max_junction_accel.
|
|
// See plan_line.cpp -> _calculate_junction_vmax() notes for details.
|
|
static const float _junction_accel_multiplier = sqrt(3.0)/10.0;
|
|
|
|
// Important note: Actual jerk is stored jerk * JERK_MULTIPLIER, and
|
|
// Time Quanta is junction_integration_time / 1000.
|
|
// We no longer incorporate jerk into this, since it can be channged per-move.
|
|
void _cm_recalc_max_junction_accel(const uint8_t axis) {
|
|
float T = cm.junction_integration_time / 1000.0;
|
|
float T2 = T*T;
|
|
|
|
cm.a[axis].max_junction_accel = _junction_accel_multiplier * T2 * JERK_MULTIPLIER;
|
|
}
|
|
|
|
void cm_set_axis_jerk(const uint8_t axis, const float jerk)
|
|
{
|
|
cm.a[axis].jerk_max = jerk;
|
|
}
|
|
|
|
stat_t cm_set_vm(nvObj_t *nv)
|
|
{
|
|
uint8_t axis = _get_axis(nv->index);
|
|
if ((axis == AXIS_A) || (axis == AXIS_B) || (axis == AXIS_C)) {
|
|
ritorno(set_fltp(nv));
|
|
} else {
|
|
ritorno(set_flup(nv));
|
|
}
|
|
cm.a[axis].recip_velocity_max = 1/nv->value;
|
|
return(STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_fr(nvObj_t *nv)
|
|
{
|
|
uint8_t axis = _get_axis(nv->index);
|
|
if ((axis == AXIS_A) || (axis == AXIS_B) || (axis == AXIS_C)) {
|
|
ritorno(set_fltp(nv));
|
|
} else {
|
|
ritorno(set_flup(nv));
|
|
}
|
|
cm.a[axis].recip_feedrate_max = 1/nv->value;
|
|
return(STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_jm(nvObj_t *nv)
|
|
{
|
|
if (nv->value < JERK_INPUT_MIN) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (nv->value > JERK_INPUT_MAX) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
set_flu(nv);
|
|
cm_set_axis_jerk(_get_axis(nv->index), nv->value);
|
|
return(STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_jh(nvObj_t *nv)
|
|
{
|
|
if (nv->value < JERK_INPUT_MIN) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (nv->value > JERK_INPUT_MAX) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
set_flu(nv);
|
|
return(STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_jt(nvObj_t *nv)
|
|
{
|
|
stat_t status = STAT_OK;
|
|
|
|
if (nv->value < JUNCTION_INTEGRATION_MIN) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (nv->value > JUNCTION_INTEGRATION_MAX) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
set_flt(nv);
|
|
|
|
// Must recalculate the max_junction_accel now that the time quanta has changed.
|
|
for (uint8_t axis=0; axis<AXES; axis++) {
|
|
_cm_recalc_max_junction_accel(axis);
|
|
}
|
|
return(status);
|
|
}
|
|
|
|
/*
|
|
* cm_set_mfo() - set manual feedrate override factor
|
|
* cm_set_mto() - set manual traverse override factor
|
|
*/
|
|
|
|
stat_t cm_set_mfo(nvObj_t *nv)
|
|
{
|
|
if (nv->value < FEED_OVERRIDE_MIN) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (nv->value > FEED_OVERRIDE_MAX) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
set_flt(nv);
|
|
return(STAT_OK);
|
|
}
|
|
|
|
stat_t cm_set_mto(nvObj_t *nv)
|
|
{
|
|
if (nv->value < TRAVERSE_OVERRIDE_MIN) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_LESS_THAN_MIN_VALUE);
|
|
}
|
|
if (nv->value > TRAVERSE_OVERRIDE_MAX) {
|
|
nv->valuetype = TYPE_NULL;
|
|
return (STAT_INPUT_EXCEEDS_MAX_VALUE);
|
|
}
|
|
set_flt(nv);
|
|
return(STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* cm_get_so() - get spring factor offset
|
|
*
|
|
*/
|
|
|
|
stat_t cm_get_so(nvObj_t *nv)
|
|
{
|
|
if (cm_get_motion_state() == MOTION_STOP) {
|
|
nv->value = 0;
|
|
} else {
|
|
nv->value = mp_get_runtime_spring_value(_get_axis(nv->index));
|
|
if (cm_get_units_mode(RUNTIME) == INCHES) {
|
|
nv->value *= INCHES_PER_MM;
|
|
}
|
|
}
|
|
nv->precision = GET_TABLE_WORD(precision);
|
|
nv->valuetype = TYPE_FLOAT;
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* Commands
|
|
*
|
|
* cm_run_qf() - flush planner queue
|
|
* cm_run_home() - run homing sequence
|
|
*/
|
|
|
|
stat_t cm_run_qf(nvObj_t *nv)
|
|
{
|
|
cm_request_queue_flush();
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_run_home(nvObj_t *nv)
|
|
{
|
|
if (fp_TRUE(nv->value)) {
|
|
float axes[] = { 1,1,1,1,1,1 };
|
|
bool flags[] = { 1,1,1,1,1,1 };
|
|
cm_homing_cycle_start(axes, flags);
|
|
}
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/*
|
|
* Debugging Commands
|
|
*
|
|
* cm_dam() - dump active model
|
|
*/
|
|
|
|
stat_t cm_dam(nvObj_t *nv)
|
|
{
|
|
xio_writeline("Active model:\n");
|
|
cm_print_vel(nv);
|
|
cm_print_feed(nv);
|
|
cm_print_line(nv);
|
|
cm_print_stat(nv);
|
|
cm_print_macs(nv);
|
|
cm_print_cycs(nv);
|
|
cm_print_mots(nv);
|
|
cm_print_hold(nv);
|
|
cm_print_home(nv);
|
|
cm_print_unit(nv);
|
|
cm_print_coor(nv);
|
|
cm_print_momo(nv);
|
|
cm_print_plan(nv);
|
|
cm_print_path(nv);
|
|
cm_print_dist(nv);
|
|
cm_print_frmo(nv);
|
|
cm_print_tool(nv);
|
|
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* AXIS JOGGING
|
|
***********************************************************************************/
|
|
|
|
float cm_get_jogging_dest(void)
|
|
{
|
|
return cm.jogging_dest;
|
|
}
|
|
|
|
stat_t cm_run_jogx(nvObj_t *nv)
|
|
{
|
|
set_flt(nv);
|
|
cm_jogging_cycle_start(AXIS_X);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_run_jogy(nvObj_t *nv)
|
|
{
|
|
set_flt(nv);
|
|
cm_jogging_cycle_start(AXIS_Y);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_run_jogz(nvObj_t *nv)
|
|
{
|
|
set_flt(nv);
|
|
cm_jogging_cycle_start(AXIS_Z);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
stat_t cm_run_joga(nvObj_t *nv)
|
|
{
|
|
set_flt(nv);
|
|
cm_jogging_cycle_start(AXIS_A);
|
|
return (STAT_OK);
|
|
}
|
|
|
|
/***********************************************************************************
|
|
* TEXT MODE SUPPORT
|
|
* Functions to print variables from the cfgArray table
|
|
***********************************************************************************/
|
|
|
|
#ifdef __TEXT_MODE
|
|
|
|
/* model state print functions */
|
|
|
|
static const char fmt_vel[] = "Velocity:%17.3f%s/min\n";
|
|
static const char fmt_feed[] = "Feed rate:%16.3f%s/min\n";
|
|
static const char fmt_line[] = "Line number:%10lu\n";
|
|
static const char fmt_stat[] = "Machine state: %s\n"; // combined machine state
|
|
static const char fmt_macs[] = "Raw machine state: %s\n"; // raw machine state
|
|
static const char fmt_cycs[] = "Cycle state: %s\n";
|
|
static const char fmt_mots[] = "Motion state: %s\n";
|
|
static const char fmt_hold[] = "Feedhold state: %s\n";
|
|
static const char fmt_home[] = "Homing state: %s\n";
|
|
static const char fmt_unit[] = "Units: %s\n"; // units mode as ASCII string
|
|
static const char fmt_coor[] = "Coordinate system: %s\n";
|
|
static const char fmt_momo[] = "Motion mode: %s\n";
|
|
static const char fmt_plan[] = "Plane: %s\n";
|
|
static const char fmt_path[] = "Path Mode: %s\n";
|
|
static const char fmt_dist[] = "Distance mode: %s\n";
|
|
static const char fmt_admo[] = "Arc Distance mode: %s\n";
|
|
static const char fmt_frmo[] = "Feed rate mode: %s\n";
|
|
static const char fmt_tool[] = "Tool number %d\n";
|
|
static const char fmt_g92e[] = "G92 enabled %d\n";
|
|
|
|
void cm_print_vel(nvObj_t *nv) { text_print_flt_units(nv, fmt_vel, GET_UNITS(ACTIVE_MODEL));}
|
|
void cm_print_feed(nvObj_t *nv) { text_print_flt_units(nv, fmt_feed, GET_UNITS(ACTIVE_MODEL));}
|
|
void cm_print_line(nvObj_t *nv) { text_print(nv, fmt_line);} // TYPE_INT
|
|
void cm_print_tool(nvObj_t *nv) { text_print(nv, fmt_tool);} // TYPE_INT
|
|
void cm_print_g92e(nvObj_t *nv) { text_print(nv, fmt_g92e);} // TYPE_INT
|
|
void cm_print_stat(nvObj_t *nv) { text_print_str(nv, fmt_stat);} // print all these as TYPE_STRING
|
|
void cm_print_macs(nvObj_t *nv) { text_print_str(nv, fmt_macs);} // See _get_msg_helper() for details
|
|
void cm_print_cycs(nvObj_t *nv) { text_print_str(nv, fmt_cycs);}
|
|
void cm_print_mots(nvObj_t *nv) { text_print_str(nv, fmt_mots);}
|
|
void cm_print_hold(nvObj_t *nv) { text_print_str(nv, fmt_hold);}
|
|
void cm_print_home(nvObj_t *nv) { text_print_str(nv, fmt_home);}
|
|
void cm_print_unit(nvObj_t *nv) { text_print_str(nv, fmt_unit);}
|
|
void cm_print_coor(nvObj_t *nv) { text_print_str(nv, fmt_coor);}
|
|
void cm_print_momo(nvObj_t *nv) { text_print_str(nv, fmt_momo);}
|
|
void cm_print_plan(nvObj_t *nv) { text_print_str(nv, fmt_plan);}
|
|
void cm_print_path(nvObj_t *nv) { text_print_str(nv, fmt_path);}
|
|
void cm_print_dist(nvObj_t *nv) { text_print_str(nv, fmt_dist);}
|
|
void cm_print_admo(nvObj_t *nv) { text_print_str(nv, fmt_admo);}
|
|
void cm_print_frmo(nvObj_t *nv) { text_print_str(nv, fmt_frmo);}
|
|
|
|
static const char fmt_gpl[] = "[gpl] default gcode plane%10d [0=G17,1=G18,2=G19]\n";
|
|
static const char fmt_gun[] = "[gun] default gcode units mode%5d [0=G20,1=G21]\n";
|
|
static const char fmt_gco[] = "[gco] default gcode coord system%3d [1-6 (G54-G59)]\n";
|
|
static const char fmt_gpa[] = "[gpa] default gcode path control%3d [0=G61,1=G61.1,2=G64]\n";
|
|
static const char fmt_gdi[] = "[gdi] default gcode distance mode%2d [0=G90,1=G91]\n";
|
|
|
|
void cm_print_gpl(nvObj_t *nv) { text_print(nv, fmt_gpl);} // TYPE_INT
|
|
void cm_print_gun(nvObj_t *nv) { text_print(nv, fmt_gun);} // TYPE_INT
|
|
void cm_print_gco(nvObj_t *nv) { text_print(nv, fmt_gco);} // TYPE_INT
|
|
void cm_print_gpa(nvObj_t *nv) { text_print(nv, fmt_gpa);} // TYPE_INT
|
|
void cm_print_gdi(nvObj_t *nv) { text_print(nv, fmt_gdi);} // TYPE_INT
|
|
|
|
/* system parameter print functions */
|
|
|
|
static const char fmt_jt[] = "[jt] junction integrgation time%6.2f\n";
|
|
static const char fmt_ct[] = "[ct] chordal tolerance%17.4f%s\n";
|
|
static const char fmt_sl[] = "[sl] soft limit enable%12d [0=disable,1=enable]\n";
|
|
static const char fmt_lim[] ="[lim] limit switch enable%10d [0=disable,1=enable]\n";
|
|
static const char fmt_saf[] ="[saf] safety interlock enable%6d [0=disable,1=enable]\n";
|
|
|
|
void cm_print_jt(nvObj_t *nv) { text_print(nv, fmt_jt);} // TYPE FLOAT
|
|
void cm_print_ct(nvObj_t *nv) { text_print_flt_units(nv, fmt_ct, GET_UNITS(ACTIVE_MODEL));}
|
|
void cm_print_sl(nvObj_t *nv) { text_print(nv, fmt_sl);} // TYPE_INT
|
|
void cm_print_lim(nvObj_t *nv){ text_print(nv, fmt_lim);} // TYPE_INT
|
|
void cm_print_saf(nvObj_t *nv){ text_print(nv, fmt_saf);} // TYPE_INT
|
|
|
|
static const char fmt_m48e[] = "[m48e] overrides enabled%11d [0=disable,1=enable]\n";
|
|
static const char fmt_mfoe[] = "[mfoe] manual feed override enab%3d [0=disable,1=enable]\n";
|
|
static const char fmt_mfo[] = "[mfo] manual feedrate override%8.3f [0.05 < mfo < 2.00]\n";
|
|
static const char fmt_mtoe[] = "[mtoe] manual traverse over enab%3d [0=disable,1=enable]\n";
|
|
static const char fmt_mto[] = "[mto] manual traverse override%8.3f [0.05 < mto < 1.00]\n";
|
|
static const char fmt_tram[] = "[tram] is coordinate space rotated to be tram %s\n";
|
|
static const char fmt_nxln[] = "[nxln] the next line number expected is %10d\n";
|
|
|
|
void cm_print_m48e(nvObj_t *nv) { text_print(nv, fmt_m48e);} // TYPE_INT
|
|
void cm_print_mfoe(nvObj_t *nv) { text_print(nv, fmt_mfoe);} // TYPE INT
|
|
void cm_print_mfo(nvObj_t *nv) { text_print(nv, fmt_mfo);} // TYPE FLOAT
|
|
void cm_print_mtoe(nvObj_t *nv) { text_print(nv, fmt_mtoe);} // TYPE INT
|
|
void cm_print_mto(nvObj_t *nv) { text_print(nv, fmt_mto);} // TYPE FLOAT
|
|
void cm_print_tram(nvObj_t *nv) { text_print(nv, fmt_tram);}; // TYPE BOOL
|
|
void cm_print_nxln(nvObj_t *nv) { text_print(nv, fmt_nxln);}; // TYPE INT
|
|
|
|
/*
|
|
* axis print functions
|
|
*
|
|
* _print_axis_ui8() - helper to print an integer value with no units
|
|
* _print_axis_flt() - helper to print a floating point linear value in prevailing units
|
|
* _print_pos_helper()
|
|
*
|
|
* cm_print_am()
|
|
* cm_print_fr()
|
|
* cm_print_vm()
|
|
* cm_print_tm()
|
|
* cm_print_tn()
|
|
* cm_print_jm()
|
|
* cm_print_jh()
|
|
* cm_print_ra()
|
|
* cm_print_hi()
|
|
* cm_print_hd()
|
|
* cm_print_lv()
|
|
* cm_print_lb()
|
|
* cm_print_zb()
|
|
*
|
|
* cm_print_pos() - print position with unit displays for MM or Inches
|
|
* cm_print_mpo() - print position with fixed unit display - always in Degrees or MM
|
|
* cm_print_tram() - print if the coordinate system is rotated
|
|
*/
|
|
|
|
static const char fmt_Xam[] = "[%s%s] %s axis mode%18d %s\n";
|
|
static const char fmt_Xfr[] = "[%s%s] %s feedrate maximum%11.0f%s/min\n";
|
|
static const char fmt_Xvm[] = "[%s%s] %s velocity maximum%11.0f%s/min\n";
|
|
static const char fmt_Xtm[] = "[%s%s] %s travel maximum%17.3f%s\n";
|
|
static const char fmt_Xtn[] = "[%s%s] %s travel minimum%17.3f%s\n";
|
|
static const char fmt_Xjm[] = "[%s%s] %s jerk maximum%15.0f%s/min^3 * 1 million\n";
|
|
static const char fmt_Xjh[] = "[%s%s] %s jerk homing%16.0f%s/min^3 * 1 million\n";
|
|
static const char fmt_Xra[] = "[%s%s] %s radius value%20.4f%s\n";
|
|
static const char fmt_Xsf[] = "[%s%s] %s spring offset factor%20.4f%s\n";
|
|
static const char fmt_Xsm[] = "[%s%s] %s spring offset max%20.4f%s\n";
|
|
static const char fmt_Xso[] = "[%s%s] %s spring offset%20.4f%s\n";
|
|
static const char fmt_Xhi[] = "[%s%s] %s homing input%15d [input 1-N or 0 to disable homing this axis]\n";
|
|
static const char fmt_Xhd[] = "[%s%s] %s homing direction%11d [0=search-to-negative, 1=search-to-positive]\n";
|
|
static const char fmt_Xsv[] = "[%s%s] %s search velocity%12.0f%s/min\n";
|
|
static const char fmt_Xlv[] = "[%s%s] %s latch velocity%13.2f%s/min\n";
|
|
static const char fmt_Xlb[] = "[%s%s] %s latch backoff%18.3f%s\n";
|
|
static const char fmt_Xzb[] = "[%s%s] %s zero backoff%19.3f%s\n";
|
|
static const char fmt_cofs[] = "[%s%s] %s %s offset%20.3f%s\n";
|
|
static const char fmt_cpos[] = "[%s%s] %s %s position%18.3f%s\n";
|
|
|
|
static const char fmt_pos[] = "%c position:%15.3f%s\n";
|
|
static const char fmt_mpo[] = "%c machine posn:%11.3f%s\n";
|
|
static const char fmt_ofs[] = "%c work offset:%12.3f%s\n";
|
|
static const char fmt_tof[] = "%c tool length offset:%12.3f%s\n";
|
|
static const char fmt_hom[] = "%c axis homing state:%2.0f\n";
|
|
|
|
static void _print_axis_ui8(nvObj_t *nv, const char *format)
|
|
{
|
|
sprintf(cs.out_buf, format, nv->group, nv->token, nv->group, (uint8_t)nv->value);
|
|
xio_writeline(cs.out_buf);
|
|
}
|
|
|
|
static void _print_axis_flt(nvObj_t *nv, const char *format)
|
|
{
|
|
char *units;
|
|
if (cm_get_axis_type(nv->index) == 0) { // linear
|
|
units = (char *)GET_UNITS(MODEL);
|
|
} else {
|
|
units = (char *)GET_TEXT_ITEM(msg_units, DEGREE_INDEX);
|
|
}
|
|
sprintf(cs.out_buf, format, nv->group, nv->token, nv->group, nv->value, units);
|
|
xio_writeline(cs.out_buf);
|
|
}
|
|
|
|
static void _print_axis_coord_flt(nvObj_t *nv, const char *format)
|
|
{
|
|
char *units;
|
|
if (cm_get_axis_type(nv->index) == 0) { // linear
|
|
units = (char *)GET_UNITS(MODEL);
|
|
} else {
|
|
units = (char *)GET_TEXT_ITEM(msg_units, DEGREE_INDEX);
|
|
}
|
|
sprintf(cs.out_buf, format, nv->group, nv->token, nv->group, nv->token, nv->value, units);
|
|
xio_writeline(cs.out_buf);
|
|
}
|
|
|
|
static void _print_pos(nvObj_t *nv, const char *format, uint8_t units)
|
|
{
|
|
char axes[] = {"XYZABC"};
|
|
uint8_t axis = _get_axis(nv->index);
|
|
if (axis >= AXIS_A) { units = DEGREES;}
|
|
sprintf(cs.out_buf, format, axes[axis], nv->value, GET_TEXT_ITEM(msg_units, units));
|
|
xio_writeline(cs.out_buf);
|
|
}
|
|
|
|
static void _print_hom(nvObj_t *nv, const char *format)
|
|
{
|
|
char axes[] = {"XYZABC"};
|
|
uint8_t axis = _get_axis(nv->index);
|
|
sprintf(cs.out_buf, format, axes[axis], nv->value);
|
|
xio_writeline(cs.out_buf);
|
|
}
|
|
|
|
void cm_print_am(nvObj_t *nv) // print axis mode with enumeration string
|
|
{
|
|
sprintf(cs.out_buf, fmt_Xam, nv->group, nv->token, nv->group, (uint8_t)nv->value,
|
|
GET_TEXT_ITEM(msg_am, (uint8_t)nv->value));
|
|
xio_writeline(cs.out_buf);
|
|
}
|
|
|
|
void cm_print_fr(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xfr);}
|
|
void cm_print_vm(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xvm);}
|
|
void cm_print_tm(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xtm);}
|
|
void cm_print_tn(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xtn);}
|
|
void cm_print_jm(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xjm);}
|
|
void cm_print_jh(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xjh);}
|
|
void cm_print_ra(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xra);}
|
|
|
|
void cm_print_sf(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xsf);}
|
|
void cm_print_sm(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xsm);}
|
|
void cm_print_so(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xso);}
|
|
|
|
void cm_print_hi(nvObj_t *nv) { _print_axis_ui8(nv, fmt_Xhi);}
|
|
void cm_print_hd(nvObj_t *nv) { _print_axis_ui8(nv, fmt_Xhd);}
|
|
void cm_print_sv(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xsv);}
|
|
void cm_print_lv(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xlv);}
|
|
void cm_print_lb(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xlb);}
|
|
void cm_print_zb(nvObj_t *nv) { _print_axis_flt(nv, fmt_Xzb);}
|
|
|
|
void cm_print_cofs(nvObj_t *nv) { _print_axis_coord_flt(nv, fmt_cofs);}
|
|
void cm_print_cpos(nvObj_t *nv) { _print_axis_coord_flt(nv, fmt_cpos);}
|
|
|
|
void cm_print_pos(nvObj_t *nv) { _print_pos(nv, fmt_pos, cm_get_units_mode(MODEL));}
|
|
void cm_print_mpo(nvObj_t *nv) { _print_pos(nv, fmt_mpo, MILLIMETERS);}
|
|
void cm_print_ofs(nvObj_t *nv) { _print_pos(nv, fmt_ofs, MILLIMETERS);}
|
|
void cm_print_tof(nvObj_t *nv) { _print_pos(nv, fmt_tof, MILLIMETERS);}
|
|
void cm_print_hom(nvObj_t *nv) { _print_hom(nv, fmt_hom);}
|
|
|
|
#endif // __TEXT_MODE
|