Troubleshooting The CNC – July 15, 2026
Forest CNC: Tool-Change Macro Debugging & Communication Troubleshooting
Overview
Over several sessions we extended the CNC12 Acorn Mill v4.50 setup on the Velocity CNC router beyond the VCP button work: we built two new MDI commands for manually correcting tool-tracking state, added a lightweight logging system to four macro files, fixed two real bugs in the finger-rack tool-change macro (mfunc6.mac), simplified the spindle warm-up macro, and opened (but did not close) an investigation into an intermittent Ethernet communication issue between the CNCPC and the Acorn board.
What We Built
| Addition | Location | Function |
|---|---|---|
M52 |
New macro | Displays which tool CNC12 currently believes is in the spindle (#150), no motion |
M53 |
New macro | Requests tool 0 (empty rack pocket), triggering the existing put-away sequence in mfunc6.mac |
#150 = X |
MDI command | Manually corrects the tracked tool-in-spindle number with zero motion, for when the software’s tracking and the physical spindle disagree |
| Logging | mfunc6.mac, mfunc48.mac, mfunc52.mac, mfunc53.mac |
Each writes a timestamped START/END entry to a shared macro_log.txt |
| Bug fix | mfunc6.mac |
Z now fully retracts before any X/Y travel at the end of a tool change |
| Bug fix | mfunc6.mac |
Replaced a GOTO label that intermittently failed to resolve with a safer M99 early-return pattern |
| Simplification | mfunc48.mac |
Removed all axis motion from the spindle warm-up macro; it now ramps up in place, wherever the spindle happens to be |
Part 1: Manually Correcting Tool State
The finger-rack tool-change macro tracks which tool is physically in the spindle using a single variable, #150. If the spindle is manually opened and a different tool inserted by hand, #150 no longer matches reality, and the software has no way to know that on its own.
The fix is a plain variable assignment, no macro required:
#150 = 2
Typed directly into MDI, this updates the tracked tool number with zero motion. To read it back, we built M52:
IF #150 EQ 0 THEN M225 #100 "Tool in spindle: 0 (empty)"
IF #150 EQ 1 THEN M225 #100 "Tool in spindle: 1"
...
Lesson learned: M225‘s numeric argument is a display duration, not a value to print. Our first attempt tried M225 #150 "Current value of #150 is:", hoping the variable’s value would get inserted into the message. It doesn’t. M225‘s first argument is a timer variable (seconds to display, 0 for indefinite) – it has no built-in text substitution feature at all. The only reliable way to display a variable’s value in a message box is a chain of IF value EQ n THEN M225 ... comparisons, one per possible value.
Part 2: Two Real Bugs in the Tool-Change Macro
Bug 1 – Z was not retracting before X/Y travel. The original ending sequence used G28, which returns to a stored reference point. That reference point had Z set to 0 – not a safe retracted height – so the machine attempted to travel in X/Y while also dropping Z toward table height. Fix: retract Z explicitly with M25 (Move to Z Home, a stock CNC12 command that reads the machine’s actual configured home reference) as its own step, before commanding any X/Y motion:
M25 ; retract Z to home first, on its own line
G53 G0 X0 Y0 ; only now move X/Y to machine home
Bug 2 – a GOTO label intermittently failed to resolve. The macro used GOTO 500 in two places to skip the tool-change sequence when graphing, searching, or already at the requested tool. Under a specific condition (requesting the already-loaded tool, tool 0), this threw Block N500 not found and aborted – even though the N500 label was sitting right there in the file. Rather than chase why the label lookup failed, we replaced both instances with M99 (Return from Macro), reasoning that since the file is a macro, “nothing to do, exit now” was a valid substitute for the jump.
Update, July 20, 2026: this was wrong, and it broke real tool changes. M99 means “return from a called subroutine.” The O9100 through O9108 tool-rack position lookups later in the same file are legitimately called via M98, so M99 is correct there. But at the top of the macro and in the same-tool skip check, the file is running as the M6 handler itself, not as something called via M98. Both spots are wrapped in IF #50001 guards specifically to stop CNC12’s lookahead parser from resolving the branch early, and GOTO respects that in a way M99 does not. In practice this meant a real tool change (requesting a tool different from the one already in the spindle) would abort the M6 macro before the spindle ever received a stop command. No fault code, no error message. The job would load, backplot, then quietly return to the home screen with the tool never dropped off.
The fix was to put GOTO 500 back in both spots, keeping everything else (the logging calls, the corrected Z-retract-before-XY sequence from Bug 1) unchanged. Confirmed working via macro_log.txt, which now shows M6 Tool Change - END (full change) for a genuine tool swap, not just the same-tool no-op case.
Part 3: Adding Macro Logging
Centroid’s macro language includes built-in data-logging M-codes, the same ones used internally by Centroid’s own systest.cnc diagnostic program:
M121 "c:\cncm\macro_log.txt" ; open file, append mode
M127 ; record a timestamp
M123 ;Some comment text ; record that comment to the file
We added a START entry at the top of each of the four macros and an END entry at every exit point, giving a running, timestamped audit trail of exactly when each macro ran and how it exited (a full tool change vs. a same-tool no-op, for example). This later turned out to be essential for correlating macro activity against communication problems (see Part 5).
Lesson learned: test the logging mechanism on the simplest macro first. We verified the pattern on M52 (pure read, no motion) before adding it to the more complex, multi-exit-point mfunc6.mac – a good habit any time you’re bolting something new onto code that already works.
Part 4: Simplifying the Spindle Warm-Up Macro
The original warm-up macro moved the spindle 12 inches in X and -Y before ramping up RPM, on the assumption the spindle was always starting from home. That assumption doesn’t hold if the operator presses the button from wherever the spindle happens to be sitting. Two revisions:
- First, we removed an incremental
G0 Z0.25move that had triggered a (correctly-functioning)907 Z axis travel exceededsafety fault when Z was already near the top of its travel. - On reflection, we removed the X/Y move entirely as well. The macro now does nothing but ramp the spindle through its six RPM stages in place – no positioning assumptions of any kind.
Part 5: An Open Investigation – Ethernet Communication Reliability
While debugging the macro issues above, msg_log.txt surfaced a separate, longer-running problem: recurring 944 MPU requested resend / 946 PC resending pairs between the CNCPC and the Acorn board, present in the log as far back as February. Troubleshooting so far:
- Replaced the Ethernet cable. Resends continued at a similar rate afterward – cable alone doesn’t appear to be the (sole) cause.
- Distinguished startup handshake resends from mid-session resends. Every session start shows a resend/reset pair; this turned out to be the operator’s own habit of pressing Reset at startup, not a software-triggered fault. The resends occurring well after startup, with no operator action nearby, are the ones worth treating as real signal.
- Found and ruled out a specific native ATC touch-off pathway. Centroid’s own diagnostic logic gates automatic tool-height measurement on Parameter 6 and Parameter 160 both being non-zero. Parameter 6 is set; Parameter 160 is not – so that particular mechanism isn’t active here, even though it looked promising for a while.
- Found, then partially ruled out, a possible EMI correlation. Comparing the macro log against the communication log, both instances of a full tool change (unclamp/clamp solenoid firing) were followed within seconds by a resend burst – while multiple “same tool, no-op” tool-change calls (no solenoid activity) showed no nearby resend activity at all. However, a physical inspection of the Ethernet cable’s routing found no proximity to solenoid or power wiring, which weakens (without fully eliminating) the most direct version of this theory.
- Caught one severe escalation. One session showed resends climbing from occasional (every few minutes) to dozens within the same second, sustained for over ten minutes, before tapering off on its own. Given the severity, we’re treating this as a genuine reliability concern rather than background noise, and have paused running production jobs until it’s better understood.
This remained unresolved as of July 15. See the update below for what we found.
Part 6: Update, July 20, 2026 – Root Cause Found and Fixed
Five days later, the machine failed again with a Z-axis position loss during a job. Reviewing the logs from that failure turned up two separate issues, one hardware and one software, that had been tangled together the whole time.
The Ethernet issue: Centroid Tech Bulletin 270. The 452 Ethernet Communication Error message referenced in this control’s own message log points directly to Centroid’s TB270 (“452 PC Data Receive Error / Ethernet Error, CNCPC to Controller”), which identifies electrical noise on the Ethernet link as the most common cause and specifies exact requirements: a shielded CAT5 cable, run directly from the CNCPC to the control board, with no switch, hub, bulkhead connector, or USB adapter in between. The earlier cable replacement noted above did not meet this specification. Replacing it again with a properly shielded cable, run directly with no hub, produced an immediate and dramatic drop in resend activity (from hundreds or thousands per session down to single digits during idle testing), and no further escalations to a full 452 error or emergency stop across several subsequent sessions, including a full air-cut job. Resend counts still rise somewhat under sustained cutting motion (roughly 100 to 150 in a 10 minute run), so this is being monitored rather than considered fully closed, but the severe failure mode is gone.
The real cause of the Z-axis loss: the M99 change documented above was a regression, not a fix. See the correction in Part 2, Bug 2 above. Once GOTO 500 was restored, tool changes completed reliably and consistently, confirmed via repeated macro_log.txt entries showing M6 Tool Change - END (full change) rather than a silent abort.
With both issues addressed, the full VCP skin (spindle warm-up, the WCS/zero buttons, and the tool-check/put-away macros) was reapplied to the live control folder and verified button by button, followed by a complete air-cut job with no faults. A known-good backup of the working cncm folder was taken immediately afterward.
Key Lessons Learned
Key Lessons Learned
- A user variable like
#150 is invisible to the CNC12 status display unless something explicitly reads and shows it. Setting it doesn’t error, it just silently does nothing visible – verify with a dedicated read-back macro, not by eye.
M225‘s numeric argument is a display duration, not a value to substitute into the message text. There is no built-in string formatting for variable values in this version of CNC12 macro language; use chained IF...THEN M225... for each possible value instead.
GOTO labels can fail to resolve under specific conditions even when the label is clearly present in the file. If you hit an inexplicable “block not found” error, consider replacing the jump with M99 (or restructuring to avoid the jump) rather than debugging the label lookup itself.
- Centroid’s own tool-change (
M6) and canned cycles are documented to run as a single atomic block under Single Block mode. Single Block will pause between top-level MDI/program lines, but not between the individual steps inside a macro triggered by M6 – this is expected, not a bug.
G43 H0 and similar “offset for an empty tool” situations are worth checking directly in the Tool Geometry Offset Library rather than assumed. A stray non-zero value in an unused register is a classic and easy-to-verify source of unexpected motion.
- Not every fault in the log is a fault someone else caused. A
9033 Reset Initiated is exactly as informative as knowing whether a human pressed the Reset button – confirm before building a theory on top of it.
- Resend counts should be read as a rate, not a raw total. One resend every few minutes and thirty resends in the same second are very different situations; the log doesn’t distinguish them unless you look at the timestamps closely.
- Correlation across two logs (macro activity + communication activity) surfaced a lead that neither log would have shown alone – worth keeping a shared, timestamped log for exactly this kind of cross-referencing on any complex, multi-system debugging session.
- A plausible theory that doesn’t survive physical inspection should be said out loud, not quietly dropped. The solenoid/EMI theory looked strong on the data alone; a five-minute look at the actual wiring run weakened it. Both the data and the physical check are worth recording.
#150 is invisible to the CNC12 status display unless something explicitly reads and shows it. Setting it doesn’t error, it just silently does nothing visible – verify with a dedicated read-back macro, not by eye.M225‘s numeric argument is a display duration, not a value to substitute into the message text. There is no built-in string formatting for variable values in this version of CNC12 macro language; use chained IF...THEN M225... for each possible value instead.GOTO labels can fail to resolve under specific conditions even when the label is clearly present in the file. If you hit an inexplicable “block not found” error, consider replacing the jump with M99 (or restructuring to avoid the jump) rather than debugging the label lookup itself.M6) and canned cycles are documented to run as a single atomic block under Single Block mode. Single Block will pause between top-level MDI/program lines, but not between the individual steps inside a macro triggered by M6 – this is expected, not a bug.G43 H0 and similar “offset for an empty tool” situations are worth checking directly in the Tool Geometry Offset Library rather than assumed. A stray non-zero value in an unused register is a classic and easy-to-verify source of unexpected motion.9033 Reset Initiated is exactly as informative as knowing whether a human pressed the Reset button – confirm before building a theory on top of it.That’s where things stand. The macro-level bugs are fixed and tested; the communication reliability question is still open, and is the priority before running further production jobs on this machine.
