This page describes how FluidNC - and in fact any GRBL-derived controller - processes GCode commands to cause a machine to move. The process involves multiple stages including input, GCode parsing, motion planning/replanning, segment generation, and step generation.
Naively, people often thing that a line of GCode is received, then executed causing the machine to move. Then when the motion is complete, an "ok" is returned to the sender program. That is incorrect. If it worked that way, motion would be jerky, with the machine coming to a stop between GCode lines. That would not result in good parts. Motion needs to smooth and coordinated between successive lines of GCode, so there is a complicated pipeline of processing to accomplish it.
A sender is a user interface program that feeds lines of GCode to FluidNC or Grbl. Senders also typically receive status reports and present the user with information about the state of the machine and the progress of GCode execution. Senders must be careful to feed the GCode at an appropriate rate to avoid overrunning Grbl's input buffer. Some senders choose to send a line and wait for an acknowlegment before sending the next line. Others track the amount of space available in the Line Buffer, sending multiple lines to try and stay ahead of the processing.
Grbl can receive a certain number of characters - typically 128 or 256 depending on the processor - into a serial line buffer before passing the line to later stages. Any individual GCode line must be shorter than the length of that buffer. If there are several short lines, it is possible to send more than one at once into the buffer, being careful to track which lines have already been received and processed.
When the Line Buffer has a complete line, the line is handed off to the GCode Parser. The GCode Parser interprets the GCode commands in that line, schedules the corresponding actions, and then returns an acknowledgment to the sender. The acknowledgment is either "ok", meaning that the line was accepted without any errors, or an "error: N" message indicating that there was a problem. In either case, upon receipt of that acknowledgment, the sender knows that the line is no longer in the buffer, so that amount of space (the line length) is now available in the Line Buffer for sending more lines.
Some GCode commands are executed immediately and completed before the ok is returned. Examples of immediate commands include coolant on/off and dwells (delays). Motion commands are scheduled for later execution and the ok is returned when they are scheduled, before the motion actually occurs. This lets Grbl connect sequences of motion commands into a coordinated smooth motion, without jerking to a halt after every command.
For immediate commands, the sender can be sure that the operation is complete when the ok is received. For motion commands, the ok does not indicate completion, but rather that the command was received without error and it is okay to send the next one.
The GCode Parser forward motions to the Motion Control module. Motions include rapid linear moves, feedrate linear moves, arcs, and some special motions like homing and probing. The Motion Control module peforms some transformations with the help of the Kinematics module. The transformations can include things like breaking arcs into a sequence of small line segments and coordinate transforms like changing from a simple cartesian coordinate system into a different system for non-rectilinear machines. The output of Motion Control is a sequence of "plan lines" in the machine's motor coordinate space.
It is possible that the Motion Control module can "stall" the GCode Parser, causing it to delay the ok acknowledgment to the sender. For example, if an arc is broken up into many small segments and the downstream Planner does not have space for them all, Motion Control will wait until the Planner can receive all the segments, and the GCode Parser will not acknowledge until Motion Control has finished. But that does not mean that the requested motion is complete, only that the Planner has received it.
The Planner converts a sequence of linear motions at specified rates into motion that is possible for the machine, taking into account limits on how fast the machine can actually move and accelerate without stalling. It must know how fast the machine is currently moving, the desired speed, and the machine's axis-dependent limitations. The computation is quite complicated, especially considering changes in direction (corners). The calculation has to take into account not only individual motion segments, but also the coordination of adjacent segments so that motion is smooth across the entire sequence.
Once motion has been planned, the job is not necessarily done. It is possible that an already-planned motion will need to be replanned in light of new information. That new information could include the arrival of a new plan line from Motion Control, a speed override from the user (via a realtime character from the sender), or a request to stop as a result of a feedhold command from the user, a stop from a limit switch, or a pause from a safety door switch.
In addition to motion, the Planner also handles some aspects of spindle speed control.
The Planner stores its calculations in a queue of "planner blocks". Each block has entry speed, acceleration, distance, direction, and other auxiliary information, thus representing a linear (in motor space) motion at a given starting speed and acceleration. The information can be changed later if a "replan" is necessary, for example if the system has to be stopped for some reason. The maximum number of planner blocks that can exist at one time is given by the "planner_blocks" configuration item (default 16, minimum 10, maximum 120). Blocks are added to the queue as information comes in from Motion Control and removed when the Segment Generator is ready for a new block.
If GCode line numbers are enabled, each planner block has the line number of the GCode line that caused it to be created, so the correspondence between GCode lines and motion can be tracked up to the planner stage - but no farther.
When the system is not otherwise busy, it calls the Segment Generator which tries to keep the Segment Queue full by computing a set of segments from information in the Planner Queue. Each planner block specifies motion with a given accelerating (or decelerating, or constant velocity), but later stages of the pipeline cannot directly perform acceleration. Acceleration must be broken down into smaller constant-velocity "segments", increasing (or decreasing) the velocity for each egment as necessary.
Under special circumstances like feedhold or other motion changes that are not represented by the GCode program, the Segment Generator can take motion commands from "system" sources instead of from the Planner queue. That is used for forcing the system to pause, restarting after a previous pause, parking motions during safety door handling, jogging, homing and probing. Resuming is particularly tricky because the current motion state differs from the expected initial speed as represented by the next planner block.
In some circumstances, the Segment Generator can also override the motion specified by a planner block.
Each segment thus generated specifies that a certain number of stepping events is to be executed with a calculated time interval between the events. Note that a "stepping event" does not correspond directly to a step pulse on any given motor, because the motors are usually running at different speeds at any given time. Rather, at each stepping event, the system decides which which of the motors need to step now.
At each stepping event, triggered by an interrupt, the Step Generator determines whether to issue step pulses for each of the motors. It uses the Bresenham algorithm to issue pulses at the nearest available time compared to the theoretical ideal time. It removes an entry from the Segment Queue as necessary, converting its information into a set of data structures than can be accessed quickly so as to provide high-speed realtime step pulses.
The Step Generator keeps a count of pulses that have been issued to each motor, incrementing or decrementing the count according to the direction of travel. That per-motor pulse count is the fundamental view of the current system position, accurate to within a few microseconds.
When the sender requests a status report by issuing the realtime character '?', FluidNC responds with a report that contains, among other information, the current position and possibly the GCode line number. The position is very accurate because it is derived from pulse counts at the very end of the pipeline. Line numbers are less accurate because they are propagated only as far as the end of the Planner Queue, but do not reflect delays due to the Segment Queue. In general there is no easy way to tell exactly when a given GCode line has fully executed, unless you add synchronizing commands in the GCode stream. Synchronizing commands like dwells wait for all motion to cease before proceeding.
You can tell that all motion has ceased, for example at the end of a program, by waiting for a status report to indicate Idle state.
As an alternative to periodic polling with '?', FluidNC (but not Grbl) offers an autoreporting feature. With autoreporting enabled, FluidNC will automatically issue a status reporting on certain state changes, and when moving, at a configurable interval. This can result in a very-responsive user interface experience.