diff --git a/g2core.atsln b/g2core.atsln old mode 100755 new mode 100644 index 73d8f923..93cd6ae1 --- a/g2core.atsln +++ b/g2core.atsln @@ -3,9 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 VisualStudioVersion = 14.0.23107.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "g2core", "g2core\g2core.cppproj", "{44EA8FEC-55D7-4149-8A78-A574FC26BF51}" - ProjectSection(ProjectDependencies) = postProject - {D7779B24-5CD6-4A1B-893C-2CAE8CF7A3A0} = {D7779B24-5CD6-4A1B-893C-2CAE8CF7A3A0} - EndProjectSection EndProject Project("{E66E83B9-2572-4076-B26E-6BE79FF3018A}") = "Motate", "Motate\Motate.cppproj", "{D7779B24-5CD6-4A1B-893C-2CAE8CF7A3A0}" EndProject diff --git a/g2core/g2core.cppproj b/g2core/g2core.cppproj index cec892fb..974faa35 100644 --- a/g2core/g2core.cppproj +++ b/g2core/g2core.cppproj @@ -1,11 +1,11 @@ - + 2.0 7.0 com.Atmel.ARMGCC.CPP {44ea8fec-55d7-4149-8a78-a574fc26bf51} - ATSAMS70N19 + ATSAM3X8C none Executable CPP @@ -68,12 +68,12 @@ - 10000000 + 2000000 SWD com.atmel.avrdbg.tool.atmelice - J41800006366 + J41800030015 Atmel-ICE True @@ -100,9 +100,9 @@ True true - J41800006366 - 0xA11D0A00 - 10000000 + J41800030015 + 0x284E0A60 + 2000000 diff --git a/g2core/g2core_info.h b/g2core/g2core_info.h index d81a3420..f9a5344a 100644 --- a/g2core/g2core_info.h +++ b/g2core/g2core_info.h @@ -21,7 +21,7 @@ #ifndef G2CORE_INFO_H_ONCE #define G2CORE_INFO_H_ONCE -#define G2CORE_FIRMWARE_BUILD 100.12 // Fix UART flow control +#define G2CORE_FIRMWARE_BUILD 100.12 // Changes to USB serial for SAMS70 and SAM8X targets #ifdef GIT_VERSION #define G2CORE_FIRMWARE_BUILD_STRING GIT_VERSION #else diff --git a/g2core/plan_exec.cpp b/g2core/plan_exec.cpp index 7360058d..cebaf810 100644 --- a/g2core/plan_exec.cpp +++ b/g2core/plan_exec.cpp @@ -49,86 +49,186 @@ static void _init_forward_diffs(float v_0, float v_1); /************************************************************************* * mp_plan_move() - call ramping function to plan moves ahead of the exec * - * This should NOT normally be called directly! Instead call st_request_plan_move(). + * mp_plan_move() performs just-in-time forward planning immediately before + * lines and commands are queued to the move execution runtime (exec). + * It examines the currently running buffer and its adjacent buffers to: + * - Stop the system from re-planning or planning something that's not prepped + * - Plan the next available ALINE (movement) block past the COMMAND blocks + * - Skip past/ or pre-plan COMMAND blocks while labeling them as PLANNED + * + * Returns STAT_OK if exec should be called to start (or continue) movement, + * or exit with no action (STAT_NOOP) if exec does not need to be called. + * + **** WARNING **** + **** This function should NOT be called directly! Instead call + **** st_request_plan_move(), which mediates access. Mp_plan_move() is called + **** aggressively from multiple places and multiple interrupt levels, + **** and has a relatively low interrupt level to call its own. + */ +/* + * Forward Planning Background + * + * - Forward planning only occurs once, JIT just ahead of the exec + Forward planning only ever originates from the run buffer (mb.r), only occurs once for each block + * - + * - 'Bootstrap' refers to startup condition w/buffers arriving before movement starts + * - see planner.h / bufferState enum for shorthand used + * - The + */ +/* + * Forward Planning Cases + * + * + * CASE: + * 0. NOT_PREPPED. No lines or commands in planner buffer. Exit with no action + * This case also handles case before a run buffer can be assigned + * + * 1. Bootstrap cases, lines only, N lines in buffer, 2 blocks (JIT planning queue only has plan and run) + * run_buffer next_buffer + * a. NOT_PREPPED don't care Action: exit no action (backplanner is still running) + * b. PREPPED don't care Action: plan line, exit OK (prepped means backplanned) + * c. PLANNED don't care Action: exit no action (cannot plan next buffer beyond current PLANNED buffer) + * + * 1'. Bootstrap cases, lines only, N lines in buffer, N blocks (deeper JIT planning queue) + * run_buffer next_buffer + * a. NOT_PREPPED don't care Action: exit no action (backplanner is still running) + * b. PREPPED don't care Action: plan line, exit OK (prepped means backplanned) + * c. PLANNED, NOT_PREPPED Action: exit no action (cannot plan next buffer beyond current PLANNED buffer) + * d. PLANNED, PREPPED Action: plan the next block into the next planner block if the + * next planner block is NOT the run BLOCK. This can iterate to more blocks + * + * 2. Running cases, lines only, N lines in buffer, 2 blocks + * run_buffer next_buffer + * a. RUNNING NOT_PREPPED Action: exit no action (backplanner is still running) + * b. RUNNING PREPPED Action: plan buffer, exit OK (do a JIT plan) + * c. RUNNING PLANNED Action: exit no action + + * 3. Bootstrap cases, lines and commands mixed, N lines in buffer, 2 blocks + (Note: NOT_PREPPED can be either line or command) + + * run_buffer next/last buffer + * a. NOT_PREPPED (any state) Action: exit no action + * b. PREPPED-LINE (any state) Action: plan line, exit OK + * c. PLANNED-LINE NOT_PREPPED Action: exit no action (cannot plan next buffer beyond current PLANNED buffer) + * d. PLANNED-LINE PREPPED Action: plan the next block into the next planner block if the + * a. PREPPED-CMD(s) NOT_PREPPED Action: set contiguous PREPPED-COMMANDs to PLANNED, exit OK + * b. PREPPED-CMD(s) PREPPED-LINE Action: set contiguous PREPPED-COMMANDs to PLANNED, + * carry forward initial exit_velocity (see note *) + * iterate to PREPPED-LINE, plan line, exit OK. + * + * (*) Use mr.entry_velocity for the run velocity, which is almost always 0, but could be non-0 in a race condition. + * + * 4. Running cases, lines and commands mixed, N lines in buffer, 2 blocks + * All running cases start with first block is running (Note: NOT_PREPPED can be either line or command) + * run_buffer next N buffers terminating buffer + * a. RUNNING-LINE PREPPED-COMAND(s) NOT_PREPPED Action: Call mp_plan_command(), exit OK + * b. RUNNING-LINE PREPPED-COMAND(s) PREPPED-LINE Action: Mark all commands as PLANNED and exit OK + * Plan PREPPED-LINE using exit_velocity of run block + * (We trust exit velocity here because the backplanner has already handled this) + * c. RUNNING-LINE PREPPED-LINE (identical to 2b) Action: plan buffer, exit OK (do a JIT plan) + * d. RUNNING-LINE PLANNED-LINE (identical to 2c) Action: exit no action + * + * e. RUNNING-LINE PLANNED-COMMAND9s) Action: iterate over the PLANNED and PREPPED commands, skipping PLANNED + * f. PLANNED-COMMAND (anything) + * + * g. RUNNING-COMMAND (anything) as cases 4a - 4e, but use mr.entry_velocity + */ + +/* PSEUDOCODE + * + * + * Test the running buffer for early exit conditions. + * (In the motion startup (bootstrap) condition the "running" block is not actually running yet) + * If nothing is actually running, ext immediately (prevents a race condition) + * + * Test the planning buffer + * Explanation: There are 2 cases where this can occur: + (1) nothing is actually running + (2) something is running + The case changes which buffer is passed to calculate _ramps(), and what the entry_velocity is. + + * Code: See if the running buffer is -in-fact- running + * If so, move off the running buffer to the next buffer + * Set the entry velocity for be the exit velocity of the running block * */ + +static stat_t _plan_command(mpBuf_t *bf) +{ + bf->buffer_state = MP_BUFFER_PLANNED; // report that we "planned" something... + return (STAT_OK); +} + stat_t mp_plan_move() { mpBuf_t *bf; - // NULL means nothing's running - this is OK - if ((bf = mp_get_run_buffer()) == NULL) { + // Examine current running buffer for early exit conditions + if ((bf = mp_get_run_buffer()) == NULL) { // NULL means nothing's running - this is OK st_prep_null(); return (STAT_NOOP); } - if (bf->buffer_state < MP_BUFFER_PREPPED) { - // Get outta here. - // We did nothing - return (STAT_NOOP); + return (STAT_NOOP); // get outta here - we did nothing } - - if (bf->block_type != BLOCK_TYPE_ALINE) { - // Nothing to see here... - - bf->buffer_state = MP_BUFFER_PLANNED; - - // report that we "planned" something... +///* + if (bf->block_type != BLOCK_TYPE_ALINE) { // nothing to see here... + bf->buffer_state = MP_BUFFER_PLANNED; // report that we "planned" something... return (STAT_OK); } +//*/ + // Examine the next plannable block(s) NB: at this point bf == bf.r + float entry_velocity = mr.entry_velocity; // used for - // We default to the planning block - mpBlockRuntimeBuf_t* block = mr.p; - - // Default to the planning buffer - float entry_velocity = mr.entry_velocity; - - // At this point, bf == bf.r if (bf->buffer_state == MP_BUFFER_RUNNING) { - // Update bf to bf->nx, set entry_* to mr.r->exit_*. bf = bf->nx; - entry_velocity = mr.r->exit_velocity; + entry_velocity = mr.r->exit_velocity; // set entry_* to mr.r->exit_* if (bf->buffer_state < MP_BUFFER_PREPPED) { - // Get outta here. - // We did nothing - return (STAT_NOOP); + return (STAT_NOOP); // get outta here - we did nothing } - +/* + while (bf->buffer_state < MP_BUFFER_PLANNED) { + if (mp_get_next_buffer(bf)->buffer_state < MP_BUFFER_PREPPED) { // i.e. EMPTY + return (STAT_OK); // ...and invoke an EXEC call + } + return (STAT_NOOP); // get outta here - we did nothing + } +*/ if (bf->block_type != BLOCK_TYPE_ALINE) { - // Nothing to see here... - - bf->buffer_state = MP_BUFFER_PLANNED; - // report that we "planned" something... - return (STAT_OK); + return (_plan_command(bf)); } } if (bf->buffer_state == MP_BUFFER_PLANNED) { - // Get outta here. - // We did nothing - return (STAT_NOOP); + return (STAT_NOOP); // get outta here - we did nothing } + // Pass in the bf buffer that will "link" with the planned block + // The block and the buffer are implicitly linked for exec_aline() + // Calculate ramps for the current planning block and the next PREPPED buffer + // The PREPPED buffer will be set to PLANNED later... + // // Note that that can only be one PLANNED move at a time. // This is to help sync mr.p to point to the next planned mr.bf - // mr.p is only advanced in mp_exec_aline, after mp.r = mr.p. + // mr.p is only advanced in mp_exec_aline(), after mp.r = mr.p. + // This code aligns the buffers and the blocks for exec_aline(). + mpBlockRuntimeBuf_t* block = mr.p; // set a local planning block to the current planning block mp_calculate_ramps(block, bf, entry_velocity); +// mp_calculate_ramps(mr.p, bf, entry_velocity); if (block->exit_velocity > block->cruise_velocity) { - __asm__("BKPT"); // exit > cruise after calculate_block + __asm__("BKPT"); // exit > cruise after calculate_block } if (block->head_length < 0.00001 && block->body_length < 0.00001 && block->tail_length < 0.00001) { - __asm__("BKPT"); // zero or negative length block + __asm__("BKPT"); // zero or negative length block } - bf->buffer_state = MP_BUFFER_PLANNED; + bf->buffer_state = MP_BUFFER_PLANNED; //...here bf->plannable = false; - - // report that we planned something... - return (STAT_OK); + return (STAT_OK); // report that we planned something... } /************************************************************************* @@ -246,12 +346,33 @@ stat_t mp_exec_move() * _NEW - trigger initialization * _RUN1 - run the first part * _RUN2 - run the second part + * + * Important distinction to note: + * - mp_plan move() is called for every type of move + * - mp_exec_move() is called for every type of move + * - mp_exec_aline() is only called for alines */ -/* Note: - * For a version of these routines that execute using the original equation-of-motion - * math (as opposed to the forward difference math) please refer to build 357.xx or earlier. - * Builds 358 onward have only forward difference code. ALso, the Kahan corrections for the - * forward differencing were also been removed shortly after as they were not needed. +/* Synchronization of run BUFFER and run BLOCK + * + * The runtime uses 2 structures for the current move or commend, the run BUFFER + * from the planner queue (mb), and the run BLOCK from the runtime singleton (mr). + * These are synchronized implicitly, but not explicitly referenced, as pointers + * can lead to race conditions. See plan_zoid.cpp / mp_calculate_ramps() for more details + * + * Mp_exec_aline() makes a huge assumption: When it comes time to get a new run block + * (mr.r) it assumes the planner block (mr.p) has been fully planned (JIT planning), + * and is ready for use as the new run block. + * + * When mp_exec_aline() needs to grab a new planner buffer for a new move or command + * (i.e. block state is inactive) it swaps (rolls) the run and planner BLOCKS so that + * mr.p (planner block) is now the mr.r (run block), and the old mr.r block becomes + * available for planning; it becomes mr.p block. + * + * At the same time, it's when finished with its current run buffer (mb.r), it has already + * advanced to the next buffer. mp_exec_move() does this at the end of previous move. + * Or in the bootstrap case, there never was a previous mb.r, so the current one is OK. + * + * As if by magic, the new mb.r aligns with the run block that was just moved in from the planning block */ /**** NOTICE ** NOTICE ** NOTICE **** @@ -290,8 +411,9 @@ stat_t mp_exec_aline(mpBuf_t *bf) mr.section = SECTION_HEAD; mr.section_state = SECTION_NEW; - mr.r = mr.p; - mr.p = mr.p->nx; + // This is the only place in the system where mr.r and mr.p are allowed to be changed + mr.r = mr.p; // we are now going to run the planning block + mr.p = mr.p->nx; // re-use the old running block as the new planning block // Assumptions that are required for this to work: // entry velocity <= cruise velocity && cruise velocity >= exit velocity diff --git a/g2core/plan_line.cpp b/g2core/plan_line.cpp index 3ff78f26..76c35a63 100644 --- a/g2core/plan_line.cpp +++ b/g2core/plan_line.cpp @@ -194,6 +194,7 @@ stat_t mp_aline(GCodeState_t* gm_in) if (fp_ZERO(length)) { sr_request_status_report(SR_REQUEST_TIMED_FULL); // Was SR_REQUEST_IMMEDIATE_FULL return (STAT_MINIMUM_LENGTH_MOVE); +// return (STAT_OK); //+++++ test this } // get a cleared buffer and copy in the Gcode model state @@ -285,13 +286,13 @@ static mpBuf_t* _plan_block(mpBuf_t* bf) } } _calculate_override(bf); // adjust cruise_vmax for feed/traverse override - // bf->plannable_time = bf->pv->plannable_time; // set plannable time - excluding current move + // bf->plannable_time = bf->pv->plannable_time; // set plannable time - excluding current move bf->buffer_state = MP_BUFFER_IN_PROCESS; // +++++ Why do we have to do this here? // bf->pv_group = bf->pv; - bf->hint = NO_HINT; // ensure we've cleared the hints + bf->hint = NO_HINT; // ensure we've cleared the hints // Time: 12us-41us if (bf->nx->plannable) { // read in new buffers until EMPTY return (bf->nx); @@ -319,15 +320,15 @@ static mpBuf_t* _plan_block(mpBuf_t* bf) bf->iterations++; bf->plannable = bf->plannable && !optimal; // Don't accidentally enable plannable! - // Let's be mindful that for ward planning may change exit_vmax, and our exit velocity may be lowered + // Let's be mindful that forward planning may change exit_vmax, and our exit velocity may be lowered braking_velocity = min(braking_velocity, bf->exit_vmax); // We *must* set cruise before exit, and keep it at least as high as exit. bf->cruise_velocity = max(braking_velocity, bf->cruise_velocity); bf->exit_velocity = braking_velocity; - // We have two places where it could be a mixed decel or an asymetric bump, - // dpending on if the pv->exit_vmax is the same as bf.cruise_vmax + // We have two places where it could be a mixed decel or an asymmetric bump, + // depending on if the pv->exit_vmax is the same as bf.cruise_vmax bool test_decel_or_bump = false; // command blocks diff --git a/g2core/plan_zoid.cpp b/g2core/plan_zoid.cpp old mode 100755 new mode 100644 index 8e9c3025..28a54ee4 --- a/g2core/plan_zoid.cpp +++ b/g2core/plan_zoid.cpp @@ -80,7 +80,7 @@ static float _get_meet_velocity(const float v_0, * * Note we use three data structures: mr, bf, and block. * - * bf holds the data from aline and back-planning. For the most part it is immuatable. + * bf holds the data from aline and back-planning. For the most part it is immutable. * * block is the data for forward-planning. There are only two block structures, and they * are for the current block and the next block. @@ -94,8 +94,8 @@ static float _get_meet_velocity(const float v_0, * * All values of block are expected to be setup by mp_calculate_ramps. * - * Quick cheat-sheet on which is in bf and whcih is in block: - * bf: + * Quick cheat-sheet on which is in buffer (bf) and which is in block: + * buffer (bf): * block_type * hint * {cruise,exit}_vmax @@ -290,9 +290,9 @@ void mp_calculate_ramps(mpBlockRuntimeBuf_t* block, mpBuf_t* bf, const float ent // PERFECT_DECELERATION // MIXED_DECELERATION - // All that remians is ASYMMETRIC_BUMP and SYMMETRIC_BUMP. + // All that remains is ASYMMETRIC_BUMP and SYMMETRIC_BUMP. // We don't really care if it's symmetric, since the first test that _get_meet_velocity - // does is for a symmetic move. It's cheaper to just let it do that then to try and prevent it. + // does is for a symmetric move. It's cheaper to just let it do that then to try and prevent it. // *** Requested-Fit cases (2) *** diff --git a/g2core/planner.cpp b/g2core/planner.cpp old mode 100755 new mode 100644 index 853d9586..69aac4ab --- a/g2core/planner.cpp +++ b/g2core/planner.cpp @@ -44,7 +44,7 @@ * functions. Data from the Gcode model is transferred to the motion planner by the mp_xxxx() * functions called by the canonical machine. * - * The planner should only use data in the planner model. When a move (block) is ready for + * The planner should only use data in the planner model. When a move (buffer) is ready for * execution the relevant data from the planner is transferred to the runtime model, * which should also be isolated. * @@ -470,8 +470,8 @@ bool mp_is_phat_city_time() * mp_planner_callback()'s job is to invoke backward planning intelligently. * The flow of control and division of responsibilities for planning is: * - * - mp_aline() receives new Gcode blocks and initializes the local variables - * for the new block. + * - mp_aline() receives new Gcode moves and initializes the local variables + * for the new buffer. * * - mp_planner_callback() is called regularly from the main loop. * It's job is to determine whether or not to call mp_plan_block_list(), @@ -480,9 +480,9 @@ bool mp_is_phat_city_time() * mp_planner_callback() also manages planner state - whether the planner * is IDLE, in STARTUP or in one of the running states. * - * - _plan_block_list() / _planblock() is the backward planning function. + * - _plan_block() is the backward planning function for a single buffer. * - * - Forward planning is just-in-time by the execution runtime + * - Just-in-time forward planning is performed by mp_plan_move() in the plan_exec.cpp runtime executive * * Some Items to note: * @@ -542,12 +542,9 @@ void mp_replan_queue(mpBuf_t *bf) { do { if (bf->buffer_state >= MP_BUFFER_PLANNED) { - // revert from PLANNED state - bf->buffer_state = MP_BUFFER_PREPPED; - } else { - // If it's not "planned" then it's either PREPPED or earlier. - // We don't need to adjust it. - break; + bf->buffer_state = MP_BUFFER_PREPPED; // revert from PLANNED state + } else { // If it's not "planned" then it's either PREPPED or earlier. + break; // We don't need to adjust it. } } while ((bf = mp_get_next_buffer(bf)) != mb.r); diff --git a/g2core/planner.h b/g2core/planner.h old mode 100755 new mode 100644 index 1863b00a..664d19a1 --- a/g2core/planner.h +++ b/g2core/planner.h @@ -25,6 +25,126 @@ * 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. */ +/* + * --- Background on the Planner --- + * + * The planner is a complicated beast that takes a lot of things into account. + * Planner documentation is scattered about and co-located with the functions + * that perform the actions. Key files are: + * + * - planner.h - This file has defines, structures and prototypes. What you would expect + * - planner.cpp - Core and common functions, queue handling, JSON and command handlers + * - plan_line.cpp - Move planning and queuing, backward planning functions + * - plan_zoid.cpp - Move forward planning, velocity contour calculations and crazy math + * - plan_exec.cpp - Runtime execution functions, calls zoid's forward planning functions + * - stepper.cpp/h - Real-time step generation, segment loading, pulls from plan_exec + * - plan_arc.cpp/h- Arc calculation and runtime functions - layer above the rest of this + * + * --- Overview of Operation --- + * + * At high level the planner's job is to reconstruct smooth motion from a set of linear + * approximations while observing and operating within the physical constraints of the + * machine and the physics of motion. Gcode - which consists of a series of into linear + * motion segments - is interpreted, queued to the planner, and joined together to produce + * continuous, synchronized motion. Non-motion commands such as pauses (dwells) and + * peripheral controls such as spindles can also be synchronized in the queue. Arcs are + * just a special case consisting of many linear moves. Arcs are not interpreted directly. + * + * The planner sits in the middle of three system layers: + * - The Gcode interpreter and canonical machine (the 'model'), which feeds... + * - The planner - taking generic commands from the model and queuing them for... + * - The runtime layer - pulling from the planner and driving stepper motors or other devices + * + * The planner queue is the heart of the planner. It's a circular list of ~48 complex structures + * that carry the state of the system needs to execute a linear motion, run a pre-planned command, + * like turning on a spindle, or executing an arbitrary JSON command such as an active comment. + * + * The queue can be viewed as a list of instructions that will execute in exact sequence. + * Some instructions control motion and need to be joined to their forward and backwards + * neighbors so that position, velocity, acceleration, and jerk constraints are not + * violated when moving from one motion to the next. + * + * Others are "commands" that are actually just function callbacks that happen to execute + * at a particular point in time (synchronized with motion commands). Commands can control + * anything you can reasonably program, such as digital IO, serial communications, or + * interpreted commands encoded in JSON. + * + * The buffers in the planner queue are treated as a 'closure' - with all state needed for + * proper execution carried in the planner structure. This is important as it keeps + * model state coherent in a heavily pipelined system. The local copy of the Gcode + * model is carried in the gm structure that is part of each planner buffer. + * See header notes in planner.cpp for more details. + * + * The planner is entered by calling one of: + * - mp_aline() - plan and queue a move with acceleration management + * - mp_dwell() - plan and queue a pause (dwell) to the planner queue + * - mp_queue_command() - queue a canned command + * - mp_json_command() - queue a JSON command for run-time interpretation and execution (M100) + * - mp_json_wait() - queue a JSON wait for run-time interpretation and execution (M101) + * - + * In addition, cm_arc_feed() valaidates and sets up a arc paramewters and calls mp_aline() + * repeatedly to spool out the arc segments into the planner queue. + * + * All the above queueing commands other than mp_aline() are relatively trivial; they just + * post callbacks into the next available planner buffer. Command functions are in 2 parts: + * the part that posts to the queue, and the callback that is executed when the command is + * finally reached in the queue - the _exec(). + * + * All mp_aline() does is some preliminary math and then posts an initialized buffer to + * the planner queue. The rest of the move planning operations takes place in background; + * via mp_planner_callback() called from the main loop, and as 'pulls' from the runtime + * stepper operations. + * + * Motion planning is separated into backward planning and forward planning stages. + * Backward planning is initiated by mp_planner_callback() which is called repeatedly + * from the main loop. Backwards planning is performed by mp_plan_block_list() and + * _plan_block(). It starts at the most recently arrived Gcode block. Backward + * planning can occur multiple times for a given buffer, as new moves arriving + * can make the motion profile more optimal. + * + * Backward planning uses velocity and jerk constraints to set maximum entry, + * travel (cruise) and exit velocities for the moves in the queue. In addition, + * it observes the maximum cornering velocities that adjoining moves can sustain + * in a corner or a 'kink' to ensure that the jerk limit of any axis participating + * in the move is not violated. See mp_planner_callback() header comments for more detail. + * + * Forward planning is performed just-in-time and only once, right before the + * planner runtime needs the next buffer. Forward planning provides the final + * contouring of the move. It is invoked by mp_plan_move() and executed by + * mp_calculate_ramps() in plan_zoid.cpp. + * + * Planner timing operates at a few different levels: + * + * - New lines of ASCII containing commands and moves arriving from the USB are + * parsed and executed as the lowest priority background task from the main loop. + * + * - Backward planning is invoked by a main loop callback, so it also executes as + * a background task, albeit a higher priority one. + * + * - Forward planning and the ultimate preparation of the move for the runtime runs + * as an interrupt as a 'pull' from the planner queue that uses a series of + * interrupts at progressively lower priorities to ensure that the next planner + * buffer is ready before the runtime runs out of forward-planned moves and starves. + * + * Some other functions performed by the planner include: + * + * - Velocity throttling to ensure that very short moves do not execute faster + * than the serial interface can deliver them + * + * - Feedhold and resume operations + * + * - Feed rate override functions and replanning + * + * Some terms that are useful that we try to use consistently: + * + * - buffer - in this context a planner buffer holding a move or a command: mb._ or bf + * - block - a data structure for planning or runtime control. See mp_calculate_ramps() comments + * - move - a linear Gcode move, typically from a G0 or G1 code + * - command - a non-move executable in the planner + * - group - a collection of moves or commands that are treated as a unit + * - line - a line of ASCII gcode or arbitrary text + * - bootstrap - the startup period where the planner collects moves but does not yet execute them + */ #ifndef PLANNER_H_ONCE #define PLANNER_H_ONCE @@ -172,7 +292,12 @@ typedef enum { #define UPDATE_MP_DIAGNOSTICS { mp.plannable_time_ms = mp.plannable_time*60000; } /* - * Planner structures + * Planner structures + * + * You should be aware of the distinction between 'buffers' and 'blocks' + * Please refer to header comments in for important details on buffers and blocks + * - plan_zoid.cpp / mp_calculate_ramps() + * - plan_exec.cpp / mp_exec_aline() */ struct mpBuffer_to_clear { @@ -368,9 +493,9 @@ typedef struct mpMotionRuntimeSingleton { // persistent runtime variables float encoder_steps[MOTORS]; // encoder position in steps - ideally the same as commanded_steps float following_error[MOTORS]; // difference between encoder_steps and commanded steps - mpBlockRuntimeBuf_t *r; // what's running - mpBlockRuntimeBuf_t *p; // what's being planned, p might == r - mpBlockRuntimeBuf_t bf[2]; // the buffer + mpBlockRuntimeBuf_t *r; // block that is running + mpBlockRuntimeBuf_t *p; // block that is being planned, p might == r + mpBlockRuntimeBuf_t bf[2]; // buffer holding the two blocks float entry_velocity; // entry values for the currently running block