Marlin Firmware processes G-code commands as they arrive from multiple sources, including the SD card and one or more serial ports such as USB-connected hosts, WiFi, Bluetooth, and so on.
Marlin is also continuously processing the commands at the front of the queue, converting them into signals for many physical actuators such as motors, heaters, lasers, and RGB LEDs.
The firmware needs to maintain continuity and timing so the command senders remain unblocked, while still performing physical movements and other actions in real-time, respecting the physical limits of stepper motors and other peripherals.
To keep things flowing Marlin feeds a single queue of G-code commands from all inputs, inserting them in the order received. Movement commands immediately go into the Planner Buffer, if there is room. The buffering of a move is considered the completion of the command, so if a non-movement command has to occur after a move is done, and not just after a move is buffered, then there has to be an M400
to wait for the Planner Buffer to finish.
Whenever the command queue gets full the sender needs to wait for space to open up, and the host may need to re-send the last command again. Marlin does some handshaking to keep the host informed during a print job, described below.
An opposite problem called "planner starvation" occurs when Marlin receives many short and fast moves in a row so the Planner Buffer gets completed very quickly. In this case the host can't send commands fast enough to prevent the Planner Buffer from emptying out. Planner starvation causes obvious stuttering and is commonly seen on overloaded deltabots during small curves. Marlin has strategies to mitigate this issue, but sometimes a model has to be re-sliced (or the G-code has to be post-processed with Arc Welder) just to stay within the machine's inherent limits.
Here's a basic flowchart of Marlin command processing:
+------+ Marlin's GCodeQueue
| | +--------------------------------------+ +-----------+
| Host | | SerialState RingBuffer | | |
| | Marlin | NUM_SERIAL BUF_SIZE | | Marlin |
+--+---+ R/TX_BUFFER_SIZE | +---+ +------------------+ | | |
| +------------+ | | | | | | | G-Code |
| | | | | | | MAX_CMD_SIZE +-+-----> processor |
| | Platform | | | | On EOL | +--------------+ | r_pos | |
+-------------> serial's +-----------> +--------> | G-code | | | +-----------+
| buffer | | | | w_pos | | command | | |
| | | | | | | line | | |
+------------+ | +---+ | +--------------+ | |
| Line buffer | x BUF_SIZE | |
| | | |
| | | |
| | | |
| | | |
| +------------------+ |
| |
| |
| |
+--------------------------------------+
Marlin is a single-threaded application with a main loop()
that manages the command queue and an idle()
routine that manages the hardware. The command queue is handled in two stages:
idle()
routine reads all inputs and attempts to enqueue any completed command lines.loop()
gets the command at the front the G-code queue (if any) and runs it. Each G-code command blocks the main loop, preventing the queue from advancing until it returns. To keep essential tasks and the UI running, any commands that run a long process need to call idle()
frequently.To maintain synchronization Marlin replies "ok
" to the host as soon as the command has been enqueued. This lets the host know that it can send another command, and well-behaved hosts will wait for this message. With ADVANCED_OK
enabled the ok
message includes extra information (such as the number of slots left in the queue).
If no data is available on the serial buffer, Marlin can be configured to periodically send a "wait
" message to the host. This was the only method of "host keepalive" provided in Marlin 1.0, but today the better options are HOST_KEEPALIVE
and ADVANCED_OK
.
Some limitations to the design are evident:
G29
causes commands to pile up and to fill the queue, making the host wait.MAX_CMD_SIZE
with a default value of 96.GCodeQueue
. This doesn't do anything to improve the processing rate of Marlin since only one command can be dispatched per loop iteration.BUF_SIZE
is not just apocryphal. The default value of 4 is typically just fine for a single serial port. (And, if you decide to send a G25
to pause the machine, the wait will be much shorter!)