- circular buffer can return its count of elements (even though a better solution may be implemeted later)
- stub_motion can handle multiple planned moves
- improved load/unload filament tests
This PR brings the option to move the selector directly using
buttons of the MMU - obviously while the MMU is idle and no
filament is stuck in the selector.
Left/Right buttons move the selector Left/Right.
Middle button performs a LoadFilament (into the MMU) on the active slot.
With this PR a change of LoadFilament behavior is also introduced.
Now, LoadFilament spins the Pulley for infinite time while waiting
for either FINDA trigger and/or a button pressed.
While motion queuing is safe, code that relies on the current block
needs to run with the isr disabled.
Protect AbortPlannedMoves and CurPosition from isr' interference by
using a RAII guard.
The principle has been implemented, but the TMC is not providing
the right data for some reason - homing doesn't work at all right now.
Also, after solving the physical homing, unit tests must be updated.
This PR brings the following improvements:
- unifies the error handling of TMC and Homing/Stallguard errors on all motorized modules (Idler, Selector, Pulley)
- now we distinguish between Homing and TMC errors + we have a separate handling of these two kinds into CommandBase unified for all motorized modules
- adds unit tests to verify the function
- fixes SetFINDAStateAndDebounce (didn't obey the press parameter before)
Introduce axisUnitToTruncatedUnit to convert from an AxisUnit (now
conveniently returned from Motion::CurPosition) to a physical unit *but*
directly into a truncated integer type, avoiding conversions to long
double types at runtime.
The related function truncatedUnit perform the same truncation of a
constant unit, so that the result of axisUnitToTruncatedUnit and
truncatedUnit(unit) result in the same type for clarity.
Both functions accept a pre-multiplier, which is applied at compile
time for constant values when optimizations are enabled.
Fixes the motion stutters generally happening as more than a single
axis are active and one completes in the middle of the motion.
Do not generate a spourious interval as one axis exits the queue. This
short interval didn't account for the minimal stepping quantum,
potentially causing a timer overflow.
This solves a number of issues - if FINDA or FSensor failed,
the unload was never "complete" - filament was stuck in the selector
blocking it from normal operation.
Now, after all errors have been resolved, filament is explicitly FED
into FINDA and then RETRACTED to Pulley.
Slower loading speed is necessary for precise detection
of filament sensor trigger and starting rotation of the E-motor.
Experimentally it turned out speeds above 80mm/s tend to cause timing issues
(sometimes one can hear a crack as MMU' or the printer's drive gears
slip while pushing the filament).
Such a timing issue then causes blobs in purge towers.
On the other hand - 80mm/s for the fast part of filament load
seems not only absolutely reliable, but also very quiet.
120mm/s for unload is much louder (we may slow it down later)
Solves an interesting tiny issue introduced in the previous commits.
When we start with the filament in selector, the corresponding LED
is set to ON. However, all of the logic state machines only operated
on the LED pair of the active slot -> the starting LED may have been
left ON in some edge cases.
Now, this is resolved by clearing all other LEDs except for the active
slot where appropriate.
Both movable components now perform homing sequences transparently
whenever the logic layer invalidates the homingValid flag.
That reflects the fact, that the user may have moved the Idler or Selector
while trying to resolve a HW issue with un/loading filament.
Basic rules:
- Idler gets rehomed immediately and then moves into the target slot position
- Selector rehomes once it is possible - i.e. when filament load state
is AtPulley - then it immediately and spontanneously executes the homing
sequence and then returns to the desired state
Motivation:
- resolve startup issues (EEPROM says we have filament, but FINDA is not triggered)
- resolve accidental moves of Idler and/or Selector while
digging out stuck filament from the unit
Previously it looked like only the active block has been discarded
which worked most of the time, since we only planned single moves.
But with introduction of PlanLongMove in one of the last commits
this is not true anymore.
we may discuss when to check for the FSensor state and why,
but this approach seemed to me like the least invasive method
of just checking the fsensor state and reporting an error in case
it didn't switch off.
In AVR __builtin_abs() breaks for non-base types.
Provide a generic function and use an overload when it is safe to use
instead.
This fixes the underlying step count calculation in PlanMove, thus
removing the need for the PlanLongMove work-around.
- fix homing procedure for Idler and Selector
(homing now ends with a move to the Parking position)
- fix unit tests' startup conditions with regard to necessary
homing of Idler and Selector
TODO: still test_cut_filament fails for minor reasons
That includes:
- introduce pulley slow feedrate and fsensor-to-nozzle distance
in config necessary for slowly feeding the filament from fsensor into the nozzle.
(the constant is subject to extraction into some other config as it has to be used in the printer as well).
- update FeedToBondtech accordingly to perform a gentle push into the nozzle
after fsensor detects the filament + update its unit tests.
- slight cleanup of LoadFilament + fix its unit tests
- add FeedingToNozzle progress code, as it might be interesting
to inform the printer about this task in the future
- revert non-clean changes from RetractFromFinda - it should not disengage the idler
- revert incorrect + fix ToolChange
- clean-up UnloadFilament
Should also hopefully fix the random behavior of the DIR pins. When I was testing a really slow timing, the steppers seemed to want to not go in the right direction. That was fixed with the critical section. The 1us delay might be overkill, but I'm not the one that added a 100nF capacitor on the LATCH line (basically chip select). This might be part of the randomness that happened and why some board behaved better than others (stronger GPIO outputs)
- feed to finda engages the idler so no need to do that before
- retract from finda disengages the idler, again avoid double operation
LEDs set mostly by feed to finda and retract from finda
because of the change of semantics of LoadFilament operation.
LoadFilament pushes the filament into FINDA and then retracts it back just to keep the
filament ready to be grabbed by the idler and pulley and loaded into the printer's nozzle.
So the selector is not blocked by the filament -> filament NOT loaded
I hate when the compiler doesn't check something what it normally does:
`pgm_read_byte` is more than happy with a parameter (*str), which reads
an address at a location where *str points to - which is obviously not the intent.
Load filament performs feed to FINDA and retract:
- engage idler
- feed normal to FINDA with config::feedToFinda distance until FINDA triggers
- retract normal and as soon FINDA un-triggers move back to PTFE config::cuttingEdgeToFindaMidpoint
- disengage the idler
That implied introducing another substate machine - RetractFromFinda, which does the opposite
of FeedToFinda while also checking for the FINDA switching off while retracting filament.
Still, ToolChange and CutFilament need fixing with this change
kudos to @leptun for this original and actually a very clean idea.
For the start we report "Reset finished" which in fact corresponds with the MMU state pretty closely
and plays nicely even with the protocol implementation.
And, since the default startup command is the noCommand, which always returns "Finished"
the implementation is clean and straightforward - the response to the first Q0 messages
will look like "X0 F" until a command (T, L, U ...) has been issued.
My MM-control-board v0.3 has following ADC readings in DEBUG_BUTTONS
- none = 1023
- left = 169
- mid = 91-92
- right = 0
As the comparison was larger than 0 MY MMU2 right button wasn't detected.
This introduces a new #define UNITTEST_MOTION which is used to control
the testing scenario:
- Normal tests, we allow the stub to override the built-in definition.
- For motion tests, we stub the lower-level classes and test the
effective implementation
We also repeat the prototype of the function, which IMHO is more
readable and more flexible: we need to use inline for the real
definition, which would require even more macros otherwise.
That implies changing motor's mode from SpreadCycle into StealtMode (or vice versa)
requires a stand still MMU with no other command (i.e. motor moves) being performed.
This elegantly solves the synchronization problem of TMC2130 mode change, as it results
in severe jerking while a motor is moving.
The change in protocol is minimal - M0/M1 first return `M0 A` (accepted) and another `Q0` then
returns `M0 F` (finished). The MK4 counterpart may ignore the additional report if necessary
as the mode change is done immediately (shortly after responding with `M0 A`)
Motion::SetMode(axis, mode) was incorrectly looping through all axes,
setting the same axis three times.
Fix this and introduce Motion::SetMode(mode) which actually loops
through all axes (see PR #110)
If the queue is full and a new move is queued, panic!
Introduce a new error code QUEUE_FULL to help diagnose situations where
the queue is handled improperly: likely one of the state machines not
waiting for the previous actions to finish.
PulseGen::PlanMove returns a boolean if the queue cannot be moved.
We could extend this to Motion::PlanMove, however all moves would then
have to check for this. Having a global check such as this ensures
we never ignore such situation.
we shall think about the Pulley as well, it looks like it should get its
own class just like Idler and Selector as it behaves very similarly in terms
of stepping and error checking
Test performed by moving 500mm of filament back&forth 5 times across 5
pulleys of a MMU2 unit.
The error with this factor is centered around +/- 1.5mm depending on the
pulley and tension on the idler.
Allow to chain moves by adding one extra parameter to the PlanMove[to]
functions: ending speed.
A move will always be accelerated from the last speed towards end ending
speed. The following:
PlanMove(100._mm, 50._mm_s, 50._mm_s);
PlanMove(200._mm, 100._mm_s);
Will first move the axis 100mm, accelerating towards 50mm/s, then
accelerate again to 100mm/s. The move will for then decelerate towards a
full stop after reaching 300mm in total.
Acceleration can be changed for each segment, so that a custom
acceleration curve can be created:
SetAcceleration(10._mm_s2);
PlanMove(100._mm, 50._mm_s, 50._mm_s);
SetAcceleration(100._mm_s2);
PlanMove(100._mm, 50._mm_s, 50._mm_s);
The ending speed might not always be reached, depending on the current
acceleration settings. The new function "Rate()" will return the ending
feedrate of the last move, if necessary.
AbortPlannedMoves accepts a new "halt" parameter to control how moves
will be chanined when interrupting the current move. By default
(halt=true) the move is completely interrupted.
When halt=false is requested, a subsequent move will be chained starting
at the currently aborted velocity. This allows to chain moves in reponse
to events, for example to accelerate the pulley without stopping as soon
as the FINDA is triggered, it's sufficient to interrupt the current move
followed by a new one:
PlanMove(maximum_loading_lenght, slow_feedrate);
... wait for PINDA trigger ...
AbortPlannedMoves(true);
PlanMove(bowden_lenght, fast_feedrate);
will seamlessy continue loading and transition to the fast feedrate.
Jerk control has been simplified. It now handles only the maximal
velocity change of the last segment, which doesn't require reverse
planning.
Add a new parameter "halt" (default to true) to control the stopping
behavior:
- halt=true: no subsequent moves will be planned, motions stops abruptly
- half=false: a new move will be chained after the current one
Move motion.Step() directly inside the __AVR__ code, silencing an unused
variable warning.
Calling motion.Step() without getting or setting the timer is not useful
anyway.
Avoid calling PulseGen::Step() on idle axes by checking for a non-zero
queue size (which is more efficient to compute).
Increase stepTimerQuantum to 128us to ensure acceleration can be
computed in realtime for 3 axes at the same time.
Fix the logic of the static assertion, which was flipped: we need to
create slices larger than the maximal step frequency in order to ensure
no axis is starved while moving.
TogglePin was incorrectly using the PINx pin for toggling, causing the
current pins (in addition to the requested ones) to be toggled, causing
stepping havoc.
This is a tentative/crude implementation of an Init and ISR for the MMU
in order to check the motion API.
We remove the "extern void Isr", declaring it "static inline" instead.
We need to inline the ISR here in order to avoid the function call.
Include the missing speed_table data in the executable. This bumps the
code size to ~60% of the flash.
Implemement motion::Init to setup the ISR and timers, and replace the
call in main from tmc::Init to motion::Init. Motion will init each
driver every time the axis is enabled, so there should be no need for
a global module initialization (we need SPI, but this is initialized
earlier on by it's own module anyway).
The timer is currently setup without any HAL or proper TIMER1 wrapper.
This is to be improved later.
The real MMU unit seems to slow down quite a bit during acceleration.
At this point we need to inline some methods in PulseGen to avoid
overhead, however this breaks the stubs.
The parameter config::AxisConfig::uSteps was supposed to be
microstepping resolution, but it's instead being used as the driver's
MRES directly.
To avoid a runtime conversion, rename the field to mRes and define a new
enum listing all the possible (and valid) microstepping resolutions.
This simplifies the code and makes clear the stepsPerUnit scale.
Assign correct stepsPerUnit to all axes as a result, including working
limits.
Besides Unload Filament, which only operates on active slot, all other
top level state machines check the validity of the command's parameter.
If the parameter is out of range for available slots, they return
ErrorCode::INVALID_TOOL now.