How TinyG Keeps Real Time - synthetos/TinyG GitHub Wiki
How TinyG keeps real time on an 8-bit processor
TinyG runs on an Atmel ATxmega192A3: an 8-bit processor at 32 MHz with no floating-point unit (FPU). It still parses JSON and G-code text, converts numbers to floating point, plans jerk-limited motion, and produces step pulses with precise timing. It can do this because the work is split by how urgent it is, and the slow work never runs in the time-critical path.
The pipeline
Work flows down four stages. Each one runs earlier, and with more slack, than the one below it.
| Stage | Where it runs | Arithmetic | Deadline |
|---|---|---|---|
| Receive | Serial receive interrupt | Copy a byte into a ring buffer | One character time |
| Parse | Main loop | Text, JSON, string to float | None: lines wait in the buffer |
| Plan | Main loop | Software floating point | The time the queued moves take to run |
| Prepare segments | Low-priority interrupt | Floating point, once per segment | About 5 ms (a segment) |
| Step | High-priority timer interrupt, 50 kHz | Integer additions only | 20 µs (one tick) |
Receive
Characters arrive in an interrupt that only copies each byte into a ring buffer. Nothing is interpreted here.
Parse: once per line, with no deadline
The main loop (the controller, a cooperative "superloop") reads a complete line from the buffer and parses it: JSON or text commands, or G-code. This is where strings are converted to floating-point numbers, one conversion per number on the line. It is relatively slow on an 8-bit processor, but nothing is waiting on it. Motion that is already planned keeps running while a line is parsed, and the next line simply waits in the buffer.
Plan: well ahead of the motion
Each parsed move goes into the planner queue (up to 32 moves). The planner computes the jerk-limited speed profile for the queued moves in software floating point. Because it works on moves that will run later, its deadline is the time the whole queue takes to execute, usually tenths of a second to seconds, not microseconds. A move is replanned as new moves arrive behind it.
Prepare segments: the first time-sensitive step
The running move is cut into short segments, nominally 5 ms each (2.5 ms minimum). For each segment, a low-priority interrupt converts the planned speed into what the step generator needs: integer step counts and a rate for every motor. This is the last place floating point is used, and it happens a few hundred times a second, not tens of thousands.
Step: integers only
The step interrupt runs at a constant 50 kHz. On each tick it adds a precomputed integer to each motor's accumulator and pulses the motor when it overflows (a DDA, or Bresenham, step generator). It never touches floating point, strings or JSON. Its cost per tick is small and fixed, which is what keeps the pulse timing exact. Details: Stepper Module.
Why this works
- The expensive work is done early and once. A number in a G-code line is converted from text once, when the line is parsed. A move's profile is computed once (and refined as later moves arrive), not on every step.
- Queues absorb the slack. The serial ring buffer and the planner queue let the slow stages run at their own pace. As long as the host keeps sending and the planner stays a few moves ahead, the step interrupt never waits.
- The hard real-time code is tiny and integer-only. Its worst case is short and predictable, so it can preempt everything else without disturbing it.
Limits
The design has limits, and they show up as speed, not as errors:
- Very dense G-code (thousands of tiny moves) can keep the planner and parser fully busy. The machine then runs slower than the programmed feed rather than skipping steps. The feed-limit advisor reports this, see Runtime session (rt).
- The host must keep the queue fed. If lines arrive too slowly, the planner runs out of moves and the machine stops between them. Line mode (
$rxm=1) is the recommended way to stream; see Sending Data to TinyG using RX Packet Mode. - The step rate has a ceiling. The step generator's 50 kHz clock limits how fast any motor can step; the planner keeps moves below it.
For the code itself, see Introduction to the TinyG Code Base.