EasyCRT
Chinese Remainder Theorem! Made easy by 6911
EasyCRT was made entirely by Team 6911!
Whitepaper
Team 4967 has made this excellent white paper describing exactly how Chinese Remainder Theorem works theoretically
Warnings
Debugging a read failure by 2181
Your defined range will affect your reading.
Check if the result is AMBIGUOUS and set your range to have a negative possibility!
For example, instead of a range from (0,2) use (-1.1,1.1) that way the range can't easily be exceeded
If your turret/mechanism is slightly out of bounds your CRT reading will be very VERY off.
There is an impact on your loop times every time you resolve the turrets angle due to the implementation. You should ONLY measure the turrets absolute encoder angle ONCE on startup to safely execute your robot program with minimal impact.
CRT is very sensitive to any latency between the two encoder reads (in practice this makes it unreliable if used with measurements taken when the mechanism is in motion).
There’s also other pitfalls with the technique (eg FRC encoders are integer measurements) that can manifest in reliability issues / large position errors in edge cases
The absolute encoder gears MUST be coprime
CRT works within a unique coverage range based off your common gearing, and absolute encoder gear teeth.
When you exceed that range CRT will resolve to different values.
Alternatives
CRT is not the only way to find the absolute angle of your turret and you should choose what is the easiest for you
A zero sensor (limit switch, magnetic limit switch) and rotating to hit it
A 10-turn pot on a reduction
An absolute encoder with a 1:1 reduction
Optionally add a slightly less than 1:1 pinon on a second absolute encoder and find how many turns there are algebraically.
EasyCRT Absolute Encoder Configuration Guide
This guide shows how to configure the EasyCRT solver (EasyCRT + EasyCRTConfig).
How the solver works
You supply two absolute enocder angles (wrapping to [0,1) is taken care of by the config) plus their rotations-per-mechanism-rotation ratios.
The solver enumerates every mechanism angle that fits encoder 1 within the allowed mechanism range, predicts what encoder 2 should read, and measures modular error.
The best match inside your tolerance becomes the mechanism angle; near ties become
AMBIGUOUS; no in-range match becomesNO_SOLUTION.The iteration count stays reasonable. Typical solves are tens of iterations. Log
getLastIterations()to view this. The gear recommender also filters gear pairs whose theoretical iterations exceed your limit.
Required inputs
Provide two
Supplier<Angle>values (encoder 1 and encoder 2). If a supplier returnsnull, it is treated asNaNand the solve will fail.All other settings are configured via
EasyCRTConfigbefore passing it toEasyCRT.
Ratio / gearing setup
Pick ONE path per encoder to define rotations per mechanism rotation.
Direct ratio:
withEncoderRatios(enc1RotPerMechRot, enc2RotPerMechRot)when you already computed the ratios. Example:Shared drive gear:
withCommonDriveGear(commonRatio, driveGearTeeth, encoder1PinionTeeth, encoder2PinionTeeth)commonRatio= mechanism rotations : shared drive gear rotations (the gear that meshes with both encoder pinions).Seeds prime tooth counts, the common scale
k, and stage 1 gear teeth + stage 2 ratio for gear recommendations.Example: turret gearbox is 12:50 -> 10:110; encoders use 30T and 31T pinions driven by the 50t gear.
commonRatiois 110.0 / 10.0 = 11.0; shared drive gear is 50T:
Gear chain (simple mesh sequence):
withAbsoluteEncoder1Gearing(teeth...)/withAbsoluteEncoder2Gearing(teeth...)Teeth listed from mechanism-side gear to encoder pinion. Example:
Explicit stages (driver, driven pairs):
withAbsoluteEncoder1GearingStages(driver1, driven1, driver2, driven2, ...)/withAbsoluteEncoder2GearingStages(...)Use for compound or same-shaft trains. Example:
Inversion: Configure both in one place (preferred). We reccomend that offsets be applied in the config to avoid confusion, but if your vendor's encoder supports on-device offsets, that is fine, however DO NOT set them in both places:
Leave device-side inversion at defaults; set inversion here as the single source of truth. The CTRE CANCoder configurator in YAMS resets device inversion to a known baseline, so you will not carry stale on-device inversion.
Sanity checks:
Ratios must be finite and non-zero; tooth counts must be positive.
If neither direct ratios nor gearing are provided, the configuration throws an error.
When using chains or stages, verify the order (mechanism -> ... -> encoder). Reversed order yields the wrong ratio.
Offsets and limits
Offsets before wrap:
withAbsoluteEncoderOffsets(enc1Offset, enc2Offset)(in rotations) shifts raw readings prior to wrapping into[0, 1). Set offsets here after mechanical zeroing so both encoders read as close to 0.0 rotations as possible at your reference pose. Keep device-side offsets at defaults; the CTRE CANCoder configurator resets device offsets, so the config holds the offsets. Similar to inversion, set this where you want but DO NOT do it twice.Motion limits:
withMechanismRange(minAngle, maxAngle)(rotations). The solver enumerates only within this window; narrower windows shrink the candidate set.Match tolerance:
withMatchTolerance(tolerance)(rotations of encoder 2). Start near the expected backlash mapped through encoder 2’s ratio; too small ->NO_SOLUTION, too large -> risk ofAMBIGUOUS. If you always preload the turret (or mechanism) to one side of its backlash before solving, effective backlash drops and you can run a smaller tolerance. MonitorgetLastErrorRotations()while tuning.
Coverage (unique range) and primes
When you provide prime tooth counts and a common scale,
getUniqueCoverage()returns the mechanism rotations you can uniquely represent.Provided automatically when you use
withCommonDriveGear(it seedsencoder1PrimeTeeth,encoder2PrimeTeeth, andcommonScaleK = commonRatio * driveGearTeeth).Formula:
coverageRot = lcm(encoder1PrimeTeeth, encoder2PrimeTeeth) / commonScaleK.coverageSatisfiesRange()checks whether the computed coverage is greater than or equal to your configuredmaxMechanismAngle. Use this as a guardrail when choosing pinion teeth.Tip: pick pinion teeth that are coprime to maximize coverage; otherwise
lcmshrinks and you may not cover your travel.
Gear recommender workflow
Goal: find the smallest coprime-ish gear pair that yields enough unique coverage with acceptable solve iterations.
Prereqs (often auto-set by
withCommonDriveGear):Stage 1 gear teeth (the shared drive gear for both encoders).
Stage 2 ratio (mechanism : drive gear).
If you do NOT use
withCommonDriveGear, you can still seed the recommender manually withwithCrtGearRecommendationInputs(stage1GearTeeth, stage2Ratio)before callingwithCrtGearRecommendationConstraints(...).Configure constraints:
withCrtGearRecommendationConstraints(coverageMargin, minTeeth, maxTeeth, maxIterations)and (if needed)withCrtGearRecommendationInputs(stage1GearTeeth, stage2Ratio).Runs only in simulation to avoid extra CPU on-robot.
coverageMarginmultiplies yourmaxMechanismAngleto enforce slack (for example, 1.2 adds 20 percent headroom).maxIterationsrejects pairs that would require too many candidate checks per solve (the real count is visible viagetLastIterations()).
Call
getRecommendedCrtGearPair()in sim; it returnsOptional<CrtGearPair>with:gearA,gearB: tooth counts.coverage: resulting unique mechanism coverage (rotations).lcm,gcd, andtheoreticalIterations.
Use the returned pinions as
encoder1PinionTeeth/encoder2PinionTeethinwithCommonDriveGear(or your own chain/stage builders) and re-checkcoverageSatisfiesRange().
Practical gearing guidance
Ratio equals the total reduction to the mechanism. Ensure backlash mapped to encoder 2 does not exceed your tolerance.
Chain or belt stages: include every stage when you compute ratios or describe the chain; missing a stage collapses your coverage and can cause ambiguity.
Prime tooth choice: pick small, coprime pinions (for example, 19T + 21T) for high coverage with minimal size. Avoid sharing factors (for example, 20T + 30T) unless coverage math still clears your travel.
Calibration and bring-up
Mechanically zero the mechanism; log both absolute readings (raw rotations in [0, 1)).
Set offsets so that the zero pose reads the desired mechanism zero after wrap (set them in the config).
Set motion range to the actual usable travel (include soft stops if you have them).
Compute ratios via one of the gearing helpers; confirm
coverageSatisfiesRange()if using prime-aware inputs.Choose an initial
matchTolerancebased on backlash mapped to encoder 2:backlash_mech_rot * ratio2. If you preload to one side, you can shrink this.Jog through the full travel; watch
getLastStatus()andgetLastErrorRotations(); ensure noAMBIGUOUSnear boundary regions.Power-cycle in multiple poses and confirm reconstructed angles stay consistent.
Seeding a motor controller with EasyCRT
Use the solved mechanism angle to initialize your motor controller's encoder so it knows its absolute position after boot:
Call this once at startup (after sensors are ready) before running closed-loop control.
Troubleshooting: Common pitfalls and how to avoid them
Wrong ratio sign: mechanism turns positive but angle decreases -> set encoder inversion
Missing a gear stage: forgetting chain or belt stages yields too-small ratios; candidates overlap ->
AMBIGUOUS.Non-coprime pinions: shared factors shrink coverage;
coverageSatisfiesRange()becomes false. Pick coprime counts or widen the coverage margin.Zeroing at a wrap edge: offsets that place the parked pose near 0 or 1 can cause wrap flips from noise. If your zero offset lands within a few hundredths of 0 or 1 rotations, physically re-phase the encoder (pull the pinion or magnet, rotate a tooth or two, and remesh) so your zero sits closer to mid-window, then reset offsets in the config.
Tolerance too tight: backlash or magnet noise bigger than tolerance ->
NO_SOLUTION. Increase tolerance or preload the mechanism to one side of backlash.Tolerance too loose: two candidates inside tolerance ->
AMBIGUOUS. Reduce tolerance or widen range margins so only one candidate survives.Double inversion or offset: applying inversion or offset both on device and in config produces mirrored or shifted angles. Keep device at defaults; do inversion and offsets in the config. The CTRE CANCoder configurator resets device settings, preventing stale on-device values.
Skipping power-cycle tests: CRT must reconstruct after reboot; always validate cold-start behavior.
Example configuration (encoders driven through the mechanism reduction)
More gearing examples
Encoders using explicit stage definitions (no CRT inputs set by gearing; seed the recommender manually):
Encoders using chain helpers (no CRT inputs; coverage helpers empty):
Validation checklist
Observe
getLastStatus()while commanding moves; it should reportOKacross travel.Ensure
getLastErrorRotations()stays below your tolerance with margin.Jog across theoretical wrap boundaries; verify no sudden +/-1 rotation jumps in mechanism space.
Run multiple power cycles in varied poses and confirm the resolved angle stays within your tolerance budget.
Last updated