Created
May 12, 2026 19:11
-
-
Save hakimio/28066d000c63f0e3122e42ce4bcfe14c to your computer and use it in GitHub Desktop.
Anycubic ACE 2 Pro Feed Check Analysis
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| ACE2 check_length / error_length — Complete Analysis | |
| 1. Validation limits (SET_FEED_CHECK handler, sub_800B3AC) | |
| The MCU rejects the command (returns error code 1) if: | |
| ┌──────────────┬─────┬──────────────┬─────────────────────────────────────────────────────┐ | |
| │ Parameter │ Min │ Max │ Notes │ | |
| ├──────────────┼─────┼──────────────┼─────────────────────────────────────────────────────┤ | |
| │ check_length │ 3 │ 254 │ 0–2 and 255+ → rejected │ | |
| ├──────────────┼─────┼──────────────┼─────────────────────────────────────────────────────┤ | |
| │ error_length │ 3 │ check_length │ must also be ≠ 255 (auto-excluded since max is 254) │ | |
| └──────────────┴─────┴──────────────┴─────────────────────────────────────────────────────┘ | |
| The guard logic in ARM: | |
| # check_length: SUB R3, R0, #0xFF → CMN R3, #0xFC → BCS (carry set = R0 ∈ [3, 254]) | |
| # error_length: CMP R3, #3 + CMP R3, #0xFF + CMP R3, R0 (must be ≤ check_length) | |
| 2. Firmware boot-time clamp (filament task init, 0x80157DA) | |
| When the task initialises (loop counter reaches 0x100), the MCU re-validates SRAM and corrects bad values: | |
| if check_length < 3: | |
| check_length ← 3 | |
| error_length ← 10 ← hard reset to boot defaults | |
| if error_length < 3: | |
| error_length ← 10 | |
| if error_length > check_length: | |
| error_length ← 0xFF ← sentinel that disables the deficit check | |
| 3. The encoder-deficit check algorithm (sub_8009D74) | |
| Called on every feed tick. Variables: | |
| - S0 = commanded position (running odometer, from function pointer at R4+0x10) | |
| - S16 = encoder position (running odometer, from function pointer at R4+0x14) | |
| - S20 = S0 − prev_S0 = commanded delta since last checkpoint | |
| Guard conditions — skip the check if any of these are true: | |
| S0 < 30.0 → skip (session startup, not enough data) | |
| slot == ASSISTING | |
| AND S0 < 300.0 → skip (assist warm-up zone, built in) | |
| S0 < 30.0 → skip (same, re-checked after the 300 branch) | |
| S20 ≤ check_length × 1.2342 → skip (checkpoint interval not yet reached) | |
| The actual deficit check (fires when S20 crosses the check_length threshold): | |
| encoder_delta = | S16 − prev_encoder | | |
| deficit = | S20 − encoder_delta | ← how far motor commanded vs encoder saw | |
| if deficit > error_length × 1.2342: | |
| slot_error ← 0x85 (or 0x86 if byte_20001A55 == 1) | |
| The factor 1.2342 is a fixed unit-conversion scalar hard-coded in flash at 0x8009EAC. | |
| 4. gklib hardcoded defaults (initializeDevice, 0x6292B0) | |
| SetFilamentFeedCheckParam(ace, check_length=100, error_length=90) | |
| // logged: "ACEProxyV2 device %d set feed check params: check_length=100, error_length=90" | |
| handle_set_feed_check (web API at 0x64C6B0) accepts the same params as check_len / error_len floats — allowing runtime override without reconnecting. | |
| 5. Why assist errors trigger and how to tune | |
| What the numbers actually mean: | |
| ┌─────────────────────────────────┬────────────────────────────────────────┬───────────────┐ | |
| │ Quantity │ Formula │ Default │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Check fires every… │ check_length × 1.2342 units commanded │ 123.4 units │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Deficit to trigger error │ > error_length × 1.2342 units │ > 111.1 units │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Min encoder movement per window │ (check_length − error_length) × 1.2342 │ 12.3 units │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Slip tolerance │ error_length / check_length │ 90 % │ | |
| │ Quantity │ Formula │ Default │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Check fires every… │ check_length × 1.2342 units commanded │ 123.4 units │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Deficit to trigger error │ > error_length × 1.2342 units │ > 111.1 units │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Min encoder movement per window │ (check_length − error_length) × 1.2342 │ 12.3 units │ | |
| ├─────────────────────────────────┼────────────────────────────────────────┼───────────────┤ | |
| │ Slip tolerance │ error_length / check_length │ 90 % │ | |
| └─────────────────────────────────┴────────────────────────────────────────┴───────────────┘ | |
| During buffer assist the encoder legitimately lags the motor (slack, sensor delay, filament flex). The built-in 300-unit assist warmup helps at the start, but once it expires, any consistent lag exceeding (check_length − error_length) units per | |
| check window triggers 0x85/0x86. | |
| 6. Recommended values | |
| Rule: to reduce false assist errors, (a) widen the window (check_length ↑) so transient slips average out, and (b) increase the tolerance (error_length closer to check_length). | |
| ┌──────────────────────────────────┬──────────────┬──────────────┬───────────┬───────────────────────────────────────────────────────────────────────┐ | |
| │ Goal │ check_length │ error_length │ Tolerance │ Notes │ | |
| ├──────────────────────────────────┼──────────────┼──────────────┼───────────┼───────────────────────────────────────────────────────────────────────┤ | |
| │ gklib default │ 100 │ 90 │ 90 % │ catches ~10-unit lag │ | |
| ├──────────────────────────────────┼──────────────┼──────────────┼───────────┼───────────────────────────────────────────────────────────────────────┤ | |
| │ Recommended: fewer assist errors │ 200 │ 185 │ 92.5 % │ wider window, same ratio + some extra slack │ | |
| ├──────────────────────────────────┼──────────────┼──────────────┼───────────┼───────────────────────────────────────────────────────────────────────┤ | |
| │ Aggressive: only real jams │ 200 │ 196 │ 98 % │ fires only if encoder shows < 2 % of commanded movement │ | |
| ├──────────────────────────────────┼──────────────┼──────────────┼───────────┼───────────────────────────────────────────────────────────────────────┤ | |
| │ Effectively disabled │ 254 │ 254 │ ~100 % │ error_length = check_length → deficit always ≤ threshold, never fires │ | |
| └──────────────────────────────────┴──────────────┴──────────────┴───────────┴───────────────────────────────────────────────────────────────────────┘ | |
| The 200 / 185 setting is the practical sweet spot: it doubles the measurement window (so a brief encoder dip during buffer reversals doesn't accumulate to the threshold), and raises the slip tolerance to 92.5 %, while still catching a genuinely | |
| jammed or runaway motor (encoder would have to show < 15 units out of 200 to fire). | |
| If the encoder deficit is structurally large throughout the entire assist cycle (e.g., the ACE 2 encoder is not well-coupled to the assist motor at your filament type/diameter), use 200 / 196 instead — that only fires if the encoder is almost | |
| entirely stationary while the motor runs. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A follow-up with evidence, and an apology in advance for the length.
First, a correction to my own earlier comment. I said the feed check was a slip comparator "rather
than an absolute threshold" as though that were news — but your feed-check gist already has
that exactly right (
deficit = |S20 − encoder_delta|,> error_length × 1.2342,0x85/0x86,and the
0xFFsentinel). I had been reading the older model in the main analysis and did notnotice you had already superseded it. Sorry for the noise.
What follows is only what I verified byte-by-byte myself in V1.1.31, with the disassembly quoted
so you can check it rather than take my word.
1.
SET_FEED_CHECKbounds — and the tuning example in the main analysis would be rejectedThe main analysis says "Valid range is 1–255 per field", and recommends
check_length=80, error_length=90.sub_800B3AC:So the accepted set is
check ∈ [3,254],error ∈ [3,254],error ≠ 255, anderror ≤ check.check_length=80, error_length=90fails the last test (90 > 80) and is rejected outright. Yourfeed-check gist's recommendations (200/185, 254/254) all satisfy
error ≤ check, so only the olderdocument is affected.
2. The boot clamp's second branch sets 255, not 10
feed-check gist:
The clamp at
0x080157DA:The branch at
0x801581Creuses the store at0x80157F6, butr1was reloaded with 255 twoinstructions earlier — so a bad
error_lengthbecomes 255 (the disabling sentinel), not 10.Only the first branch reaches that store with
r1 = 10.Also, the
adds #1 ; uxtb ; cmp #4idiom means both tests catch{0, 1, 2, 255}, not just< 3— so255is treated as invalid on entry as well.3. The 300 mm warm-up guard is PRELOADING, not ASSISTING
feed-check gist:
sub_8009D74:Status 5 is
preloading;assistingis 3 (per the ACEPRO driver'sACE2_SLOT_STATUS_DETAIL_BY_CODE, which matches everything I have observed live). So it is apreload warm-up. That matters practically: it means feed assist gets no warm-up allowance —
and, per the next item, no check at all.
4. A bypass flag disables the check entirely for rollback and for assist
I do not think this appears in either document, and it changes what the feature protects.
sub_8009D74begins:obj+0x3Cis written bysub_800B22Cfrom its 6th argument (0x0800B262), andFEED_OR_ROLLBACKcomputes that argument as(mode == 1)at0x0800B62C–0x0800B63A— i.e.rollback. The assist handler sets it directly at
0x0800A438. It is cleared only when theoperation ends.
So the deficit check is live for mode 0 feeds and auto-preload only. It is switched off for the
entire duration of any rollback and any feed assist. Since printing runs on assist, the check
provides no jam protection during a print — which seems worth stating explicitly, because the
tuning discussion naturally reads as though it were protecting the print.
(A side effect:
0x82 ROLLBACK_ERRORlooks unreachable, since the only thing that sets theobject's error field is the check that rollback disables.)
5.
0x85/0x86never reach the slot statusfeed-check gist:
The store is to the feed-check object, not the slot status:
The FEED handler then reads that field back through vtable+24 and substitutes its own code:
The preload path does the same with
132. So the STUCK/TANGLED distinction is computed and thendiscarded: a slot status can never read 133 or 134 in V1.1.31, and host logic branching on
those codes is dead. The
CHN_BUF_FEEDbit that distinguishes them is not exposed anywhere.Separately: protobuf descriptors
I also decoded the nanopb 0.4.x descriptors in the image, which turned up a handful of shape
differences against
ace2-pro.proto—GetTempResponsehas 7 float fields rather than 6 (so theexisting names shift by one), cmd 72's request is 2 fields not 4, cmd 78 returns 16
{u32,u32}pairs (key/linear calibration, not motor status),
GetMaterialInfoResponsefield 2 is a nestedmessage, and
SetPrinterStatusRequest.statusis a bool. I have not hand-verified every one ofthose the way I did the items above, so I offer them as "please check" rather than as corrections.
The decode is reproducible — script and full descriptor dump are in the repo, and the derived
.protocarries addresses in comments:https://github.com/Simon-CR/ace2-pro-firmware-research
Thanks again — the fact that any of this was checkable at all is down to your map.