Add Pulley as a Movable module

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)
pull/154/head
D.R.racer 2022-01-17 07:40:48 +01:00 committed by DRracer
parent 53d285280d
commit b36e6b99a1
32 changed files with 374 additions and 105 deletions

View File

@ -4,6 +4,7 @@
#include "../modules/finda.h"
#include "../modules/fsensor.h"
#include "../modules/idler.h"
#include "../modules/pulley.h"
#include "../modules/selector.h"
#include "../modules/motion.h"
#include "../modules/leds.h"
@ -15,7 +16,7 @@ inline ErrorCode &operator|=(ErrorCode &a, ErrorCode b) {
return a = (ErrorCode)((uint16_t)a | (uint16_t)b);
}
static ErrorCode TMC2130ToErrorCode(const hal::tmc2130::ErrorFlags &ef, uint8_t tmcIndex) {
static ErrorCode TMC2130ToErrorCode(const hal::tmc2130::ErrorFlags &ef) {
ErrorCode e = ErrorCode::RUNNING;
if (ef.reset_flag) {
@ -34,51 +35,101 @@ static ErrorCode TMC2130ToErrorCode(const hal::tmc2130::ErrorFlags &ef, uint8_t
e |= ErrorCode::TMC_OVER_TEMPERATURE_ERROR;
}
if (e != ErrorCode::RUNNING) {
return e;
}
static ErrorCode AddErrorAxisBit(ErrorCode ec, uint8_t tmcIndex) {
switch (tmcIndex) {
case config::Axis::Pulley:
e |= ErrorCode::TMC_PULLEY_BIT;
ec |= ErrorCode::TMC_PULLEY_BIT;
break;
case config::Axis::Selector:
e |= ErrorCode::TMC_SELECTOR_BIT;
ec |= ErrorCode::TMC_SELECTOR_BIT;
break;
case config::Axis::Idler:
e |= ErrorCode::TMC_IDLER_BIT;
ec |= ErrorCode::TMC_IDLER_BIT;
break;
default:
break;
}
}
return ec;
}
return e;
ErrorCode CheckMovable(mm::MovableBase &m) {
switch (m.State()) {
case mm::MovableBase::TMCFailed:
return AddErrorAxisBit(TMC2130ToErrorCode(m.TMCErrorFlags()), m.Axis());
case mm::MovableBase::HomingFailed:
return AddErrorAxisBit(ErrorCode::HOMING_FAILED, m.Axis());
}
return ErrorCode::RUNNING;
}
static inline ErrorCode WithoutAxisBits(ErrorCode ec) {
return static_cast<ErrorCode>(
static_cast<uint16_t>(ec)
& (~(static_cast<uint16_t>(ErrorCode::TMC_SELECTOR_BIT)
| static_cast<uint16_t>(ErrorCode::TMC_IDLER_BIT)
| static_cast<uint16_t>(ErrorCode::TMC_PULLEY_BIT))));
}
bool CommandBase::WaitForOneModuleErrorRecovery(ErrorCode ec, modules::motion::MovableBase &m) {
if (ec != ErrorCode::RUNNING) {
if (stateBeforeModuleFailed == ProgressCode::OK) {
// a new problem with the movable modules
// @@TODO not sure how to prevent losing the previously accumulated error ... or do I really need to do it?
// May be the TMC error word just gets updated with new flags as the motion proceeds
stateBeforeModuleFailed = state;
error = ec;
state = ProgressCode::ERRWaitingForUser; // such a situation always requires user's attention -> let the printer display an error screen
return true;
} else {
switch (state) {
case ProgressCode::ERRWaitingForUser: // waiting for a recovery - mask axis bits:
if (WithoutAxisBits(ec) == ErrorCode::HOMING_FAILED) {
// homing can be recovered
mui::Event ev = mui::userInput.ConsumeEvent();
if (ev == mui::Event::Middle) {
m.InvalidateHoming(); // @@TODO invalidate and force initiate a new homing attempt
state = ProgressCode::Homing;
}
}
// TMC errors cannot be recovered safely, waiting for power cycling the MMU
return true;
case ProgressCode::Homing:
if (m.HomingValid()) {
// managed to recover from a homing problem
state = stateBeforeModuleFailed;
stateBeforeModuleFailed = ProgressCode::OK;
return false;
}
return true;
default:
return true; // no idea what to do in other states ... set internal fw error state?
}
return true;
}
}
return false;
}
bool CommandBase::WaitForModulesErrorRecovery() {
if (WaitForOneModuleErrorRecovery(CheckMovable(mi::idler), mi::idler))
return true;
if (WaitForOneModuleErrorRecovery(CheckMovable(ms::selector), ms::selector))
return true;
if (WaitForOneModuleErrorRecovery(CheckMovable(mp::pulley), mp::pulley))
return true;
return false;
}
bool CommandBase::Step() {
ErrorCode tmcErr = ErrorCode::RUNNING;
// check the global HW errors - may be we should avoid the modules layer and check for the HAL layer errors directly
if (mi::idler.State() == mi::Idler::Failed) {
state = ProgressCode::ERRTMCFailed;
tmcErr |= TMC2130ToErrorCode(mi::idler.TMCErrorFlags(), mm::Axis::Idler);
}
if (ms::selector.State() == ms::Selector::Failed) {
state = ProgressCode::ERRTMCFailed;
tmcErr |= TMC2130ToErrorCode(ms::selector.TMCErrorFlags(), mm::Axis::Selector);
}
// may be we should model the Pulley as well...
// if (ms::selector.State() == ms::Selector::Failed) {
// state = ProgressCode::ERRTMCFailed;
// error |= TMC2130ToErrorCode(mm::motion.DriverForAxis(mm::Axis::Selector), mm::Axis::Selector);
// return true; // the HW error prevents us from continuing with the state machine - the MMU must be restarted/fixed before continuing
// }
// @@TODO not sure how to prevent losing the previously accumulated error ... or do I really need to do it?
// May be the TMC error word just gets updated with new flags as the motion proceeds
// And how about the logical errors like FINDA_DIDNT_SWITCH_ON?
if (tmcErr != ErrorCode::RUNNING) {
error |= tmcErr;
if (WaitForModulesErrorRecovery()) {
return true;
}
return StepInner();
}
@ -129,7 +180,7 @@ bool CommandBase::CheckToolIndex(uint8_t index) {
void CommandBase::ErrDisengagingIdler() {
if (!mi::idler.Engaged()) {
state = ProgressCode::ERRWaitingForUser;
mm::motion.Disable(mm::Pulley);
mp::pulley.Disable();
mui::userInput.Clear(); // remove all buffered events if any just before we wait for some input
}
}

View File

@ -4,6 +4,12 @@
#include "error_codes.h"
#include "progress_codes.h"
namespace modules {
namespace motion {
class MovableBase;
}
}
/// The logic namespace handles the application logic on top of the modules.
namespace logic {
@ -19,7 +25,8 @@ class CommandBase {
public:
inline CommandBase()
: state(ProgressCode::OK)
, error(ErrorCode::OK) {}
, error(ErrorCode::OK)
, stateBeforeModuleFailed(ProgressCode::OK) {}
// Normally, a base class should (must) have a virtual destructor to enable correct deallocation of superstructures.
// However, in our case we don't want ANY destruction of these objects and moreover - adding a destructor like this
@ -82,6 +89,15 @@ protected:
/// If not, it returns false and sets the error to ErrorCode::INVALID_TOOL
bool CheckToolIndex(uint8_t index);
/// Checks for errors of modules - that includes TMC errors, Idler and Selector errors and possibly more.
/// The idea is to check blocking errors at one spot consistently.
/// Some of the detected errors can be irrecoverable (i.e. need power cycling the MMU).
/// @returns true if waiting for a recovery, false if the state machine can continue.
bool WaitForModulesErrorRecovery();
/// @returns true when still waiting for a module to recover, false otherwise.
bool WaitForOneModuleErrorRecovery(ErrorCode iState, modules::motion::MovableBase &m);
/// Perform disengaging idler in ErrDisengagingIdler state
void ErrDisengagingIdler();
@ -93,6 +109,7 @@ protected:
ProgressCode state; ///< current progress state of the state machine
ErrorCode error; ///< current error code
ProgressCode stateBeforeModuleFailed; ///< saved state of the state machine before a common error happened
};
} // namespace logic

View File

@ -7,6 +7,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
#include "../modules/selector.h"
namespace logic {
@ -60,7 +61,7 @@ bool CutFilament::StepInner() {
} else {
// unload back to the pulley
state = ProgressCode::UnloadingToPulley;
mm::motion.PlanMove<mm::Pulley>(-config::cutLength, config::pulleyUnloadFeedrate);
mp::pulley.PlanMove(-config::cutLength, config::pulleyUnloadFeedrate);
}
}
break;
@ -74,7 +75,7 @@ bool CutFilament::StepInner() {
case ProgressCode::PreparingBlade:
if (ms::selector.Slot() == cutSlot + 1) {
state = ProgressCode::PushingFilament;
mm::motion.PlanMove<mm::Pulley>(config::cutLength, config::pulleyUnloadFeedrate); //
mp::pulley.PlanMove(config::cutLength, config::pulleyUnloadFeedrate); //
}
break;
case ProgressCode::PushingFilament:

View File

@ -7,6 +7,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
#include "../modules/selector.h"
#include "../debug.h"
@ -53,8 +54,8 @@ bool EjectFilament::StepInner() {
case ProgressCode::ParkingSelector:
if (mm::motion.QueueEmpty()) { // selector parked aside
state = ProgressCode::EjectingFilament;
mm::motion.InitAxis(mm::Pulley);
mm::motion.PlanMove<mm::Pulley>(-config::filamentMinLoadedToMMU, config::pulleySlowFeedrate);
mp::pulley.InitAxis();
mp::pulley.PlanMove(-config::filamentMinLoadedToMMU, config::pulleySlowFeedrate);
}
break;
case ProgressCode::EjectingFilament:
@ -65,7 +66,7 @@ bool EjectFilament::StepInner() {
break;
case ProgressCode::DisengagingIdler:
if (!mi::idler.Engaged()) { // idler disengaged
mm::motion.Disable(mm::Pulley);
mp::pulley.Disable();
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::NotLoaded);
state = ProgressCode::OK;
error = ErrorCode::OK;

View File

@ -41,6 +41,11 @@ enum class ErrorCode : uint_fast16_t {
QUEUE_FULL = 0x802b, ///< E32811 internal logic error - attempt to move with a full queue
HOMING_FAILED = 0x8007, ///< generic homing failed error - always reported with the corresponding axis bit set (Idler or Selector) as follows:
HOMING_SELECTOR_FAILED = HOMING_FAILED | TMC_SELECTOR_BIT, ///< E32903 the Selector was unable to home properly - that means something is blocking its movement
HOMING_IDLER_FAILED = HOMING_FAILED | TMC_IDLER_BIT, ///< E33031 the Idler was unable to home properly - that means something is blocking its movement
STALLED_PULLEY = HOMING_FAILED | TMC_PULLEY_BIT, ///< E32839 for the Pulley "homing" means just stallguard detected during Pulley's operation (Pulley doesn't home)
VERSION_MISMATCH = 0x802c, ///< E32812 internal error of the printer - incompatible version of the MMU FW
PROTOCOL_ERROR = 0x802d, ///< E32813 internal error of the printer - communication with the MMU got garbled - protocol decoder couldn't decode the incoming messages
MMU_NOT_RESPONDING = 0x802e, ///< E32814 internal error of the printer - communication with the MMU is not working

View File

@ -7,6 +7,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
#include "../debug.h"
namespace logic {
@ -23,7 +24,7 @@ void logic::FeedToBondtech::GoToPushToNozzle() {
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InFSensor);
// plan a slow move to help push filament into the nozzle
//@@TODO the speed in mm/s must correspond to printer's feeding speed!
mm::motion.PlanMove<mm::Pulley>(config::fsensorToNozzle, config::pulleySlowFeedrate);
mp::pulley.PlanMove(config::fsensorToNozzle, config::pulleySlowFeedrate);
state = PushingFilamentIntoNozzle;
}
@ -34,8 +35,8 @@ bool FeedToBondtech::Step() {
dbg_logic_P(PSTR("Feed to Bondtech --> Idler engaged"));
dbg_logic_fP(PSTR("Pulley start steps %u"), mm::motion.CurPosition(mm::Pulley));
state = PushingFilamentToFSensor;
mm::motion.InitAxis(mm::Pulley);
mm::motion.PlanMove<mm::Pulley>(config::defaultBowdenLength, config::pulleyLoadFeedrate, config::pulleySlowFeedrate);
mp::pulley.InitAxis();
mp::pulley.PlanMove(config::defaultBowdenLength, config::pulleyLoadFeedrate, config::pulleySlowFeedrate);
}
return false;
case PushingFilamentToFSensor:
@ -55,7 +56,7 @@ bool FeedToBondtech::Step() {
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InNozzle);
mi::idler.Disengage();
// while disengaging the idler, keep on moving with the pulley to avoid grinding while the printer is trying to grab the filament itself
mm::motion.PlanMove<mm::Pulley>(config::fsensorToNozzleAvoidGrind, config::pulleySlowFeedrate);
mp::pulley.PlanMove(config::fsensorToNozzleAvoidGrind, config::pulleySlowFeedrate);
state = DisengagingIdler;
}
return false;
@ -64,7 +65,7 @@ bool FeedToBondtech::Step() {
dbg_logic_P(PSTR("Feed to Bondtech --> Idler disengaged"));
dbg_logic_fP(PSTR("Pulley end steps %u"), mm::motion.CurPosition(mm::Pulley));
state = OK;
mm::motion.Disable(mm::Pulley);
mp::pulley.Disable();
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::on);
}
return false;

View File

@ -7,6 +7,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
#include "../modules/user_input.h"
#include "../debug.h"
namespace logic {
@ -29,12 +30,12 @@ bool FeedToFinda::Step() {
dbg_logic_P(PSTR("Feed to Finda --> Idler engaged"));
dbg_logic_fP(PSTR("Pulley start steps %u"), mm::motion.CurPosition(mm::Pulley));
state = PushingFilament;
mm::motion.InitAxis(mm::Pulley);
mp::pulley.InitAxis();
// @@TODO this may never happen as load filament always assumes the filament is at least at the pulley
// if (mg::globals.FilamentLoaded() == mg::FilamentLoadState::NotLoaded) { // feed slowly filament to PTFE
// mm::motion.PlanMove<mm::Pulley>(config::filamentMinLoadedToMMU, config::pulleySlowFeedrate);
// mp::pulley.PlanMove(config::filamentMinLoadedToMMU, config::pulleySlowFeedrate);
// }
mm::motion.PlanMove<mm::Pulley>(config::maximumFeedToFinda, config::pulleySlowFeedrate);
mp::pulley.PlanMove(config::maximumFeedToFinda, config::pulleySlowFeedrate);
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InSelector);
mui::userInput.Clear(); // remove all buffered events if any just before we wait for some input
}

View File

@ -6,6 +6,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
#include "../modules/selector.h"
#include "../modules/user_input.h"
#include "../debug.h"
@ -41,7 +42,7 @@ void logic::LoadFilament::FinishedCorrectly() {
state = ProgressCode::OK;
error = ErrorCode::OK;
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::off, ml::off);
mm::motion.Disable(mm::Pulley);
mp::pulley.Disable();
}
bool LoadFilament::StepInner() {
@ -117,7 +118,7 @@ bool LoadFilament::StepInner() {
case ProgressCode::ERREngagingIdler:
if (mi::idler.Engaged()) {
state = ProgressCode::ERRHelpingFilament;
mm::motion.PlanMove<mm::Pulley>(config::pulleyHelperMove, config::pulleySlowFeedrate);
mp::pulley.PlanMove(config::pulleyHelperMove, config::pulleySlowFeedrate);
}
return false;
case ProgressCode::ERRHelpingFilament:

View File

@ -5,6 +5,7 @@
#include "../modules/idler.h"
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/pulley.h"
#include "../debug.h"
namespace logic {
@ -22,7 +23,7 @@ bool RetractFromFinda::Step() {
if (mi::idler.Engaged()) {
dbg_logic_fP(PSTR("Pulley start steps %u"), mm::motion.CurPosition(mm::Pulley));
state = UnloadBackToPTFE;
mm::motion.PlanMove<mm::Pulley>(-(config::cuttingEdgeToFindaMidpoint + config::cuttingEdgeRetract), config::pulleyUnloadFeedrate);
mp::pulley.PlanMove(-(config::cuttingEdgeToFindaMidpoint + config::cuttingEdgeRetract), config::pulleyUnloadFeedrate);
}
return false;
case UnloadBackToPTFE:

View File

@ -8,6 +8,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
#include "../modules/selector.h"
#include "../modules/user_input.h"
#include "../debug.h"
@ -132,7 +133,7 @@ bool ToolChange::StepInner() {
case ProgressCode::ERREngagingIdler:
if (mi::idler.Engaged()) {
state = ProgressCode::ERRHelpingFilament;
mm::motion.PlanMove<mm::Pulley>(config::pulleyHelperMove, config::pulleySlowFeedrate);
mp::pulley.PlanMove(config::pulleyHelperMove, config::pulleySlowFeedrate);
}
return false;
case ProgressCode::ERRHelpingFilament:

View File

@ -7,6 +7,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
#include "../modules/selector.h"
#include "../modules/user_input.h"
#include "../debug.h"
@ -24,7 +25,7 @@ void UnloadFilament::Reset(uint8_t /*param*/) {
}
// unloads filament from extruder - filament is above Bondtech gears
mm::motion.InitAxis(mm::Pulley);
mp::pulley.InitAxis();
state = ProgressCode::UnloadingToFinda;
error = ErrorCode::RUNNING;
unl.Reset(maxRetries);
@ -34,7 +35,7 @@ void UnloadFilament::Reset(uint8_t /*param*/) {
void UnloadFilament::FinishedCorrectly() {
state = ProgressCode::OK;
error = ErrorCode::OK;
mm::motion.Disable(mm::Pulley);
mp::pulley.Disable();
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::AtPulley); // filament unloaded
ml::leds.SetPairButOffOthers(mg::globals.ActiveSlot(), ml::off, ml::off);
}
@ -132,7 +133,7 @@ bool UnloadFilament::StepInner() {
case ProgressCode::ERREngagingIdler:
if (mi::idler.Engaged()) {
state = ProgressCode::ERRHelpingFilament;
mm::motion.PlanMove<mm::Pulley>(-config::pulleyHelperMove, config::pulleySlowFeedrate);
mp::pulley.PlanMove(-config::pulleyHelperMove, config::pulleySlowFeedrate);
}
return false;
case ProgressCode::ERRHelpingFilament:

View File

@ -7,6 +7,7 @@
#include "../modules/leds.h"
#include "../modules/motion.h"
#include "../modules/permanent_storage.h"
#include "../modules/pulley.h"
namespace logic {
@ -32,7 +33,7 @@ bool UnloadToFinda::Step() {
case EngagingIdler:
if (mg::globals.FilamentLoaded() >= mg::FilamentLoadState::InSelector) {
state = UnloadingToFinda;
mm::motion.InitAxis(mm::Pulley);
mp::pulley.InitAxis();
ml::leds.SetMode(mg::globals.ActiveSlot(), ml::green, ml::blink0);
} else {
state = FailedFINDA;
@ -42,8 +43,8 @@ bool UnloadToFinda::Step() {
if (mi::idler.Engaged()) {
state = WaitingForFINDA;
mg::globals.SetFilamentLoaded(mg::globals.ActiveSlot(), mg::FilamentLoadState::InSelector);
unloadStart_mm = CurrentPositionPulley_mm();
mm::motion.PlanMove<mm::Pulley>(-config::defaultBowdenLength - config::feedToFinda - config::filamentMinLoadedToMMU, config::pulleyUnloadFeedrate);
unloadStart_mm = mp::pulley.CurrentPositionPulley_mm();
mp::pulley.PlanMove(-config::defaultBowdenLength - config::feedToFinda - config::filamentMinLoadedToMMU, config::pulleyUnloadFeedrate);
}
return false;
case WaitingForFINDA: {

View File

@ -11,6 +11,7 @@ target_sources(
motion.cpp
movable_base.cpp
permanent_storage.cpp
pulley.cpp
pulse_gen.cpp
selector.cpp
speed_table.cpp

View File

@ -29,7 +29,7 @@ void Idler::FinishHomingAndPlanMoveToParkPos() {
if (!plannedEngage) {
plannedSlot = IdleSlotIndex();
}
InitMovement(mm::Idler);
InitMovement();
}
void Idler::FinishMove() {
@ -48,7 +48,7 @@ Idler::OperationResult Idler::Disengage() {
// coordinates invalid, first home, then disengage
if (!homingValid) {
PerformHome(mm::Idler);
PerformHome();
return OperationResult::Accepted;
}
@ -59,7 +59,7 @@ Idler::OperationResult Idler::Disengage() {
}
// disengaging
return InitMovement(mm::Idler);
return InitMovement();
}
Idler::OperationResult Idler::Engage(uint8_t slot) {
@ -81,7 +81,7 @@ Idler::OperationResult Idler::Engage(uint8_t slot) {
// so rebooting the MMU while the printer is printing (and thus holding the filament by the moving Idler)
// should not be an issue
if (!homingValid) {
PlanHome(mm::Idler);
PlanHome();
return OperationResult::Accepted;
}
@ -92,26 +92,26 @@ Idler::OperationResult Idler::Engage(uint8_t slot) {
}
// engaging
return InitMovement(mm::Idler);
return InitMovement();
}
bool Idler::Step() {
switch (state) {
case Moving:
// dbg_logic_P(PSTR("Moving Idler"));
PerformMove(mm::Idler);
PerformMove();
return false;
case Homing:
dbg_logic_P(PSTR("Homing Idler"));
PerformHome(mm::Idler);
PerformHome();
return false;
case Ready:
if (!homingValid && mg::globals.FilamentLoaded() < mg::InFSensor) {
PlanHome(mm::Idler);
PlanHome();
return false;
}
return true;
case Failed:
case TMCFailed:
dbg_logic_P(PSTR("Idler Failed"));
default:
return true;
@ -121,7 +121,7 @@ bool Idler::Step() {
void Idler::Init() {
if (mg::globals.FilamentLoaded() < mg::InFSensor) {
// home the Idler only in case we don't have filament loaded in the printer (or at least we think we don't)
PlanHome(mm::Idler);
PlanHome();
} else {
// otherwise assume the Idler is at its idle position (that's where it usually is)
mm::motion.SetPosition(mm::Idler, SlotPosition(IdleSlotIndex()).v);

View File

@ -15,7 +15,7 @@ namespace mm = modules::motion;
class Idler : public motion::MovableBase {
public:
inline constexpr Idler()
: MovableBase()
: MovableBase(mm::Idler)
, plannedEngage(false)
, currentlyEngaged(false) {}

View File

@ -6,7 +6,7 @@
namespace modules {
namespace motion {
void MovableBase::PlanHome(config::Axis axis) {
void MovableBase::PlanHome() {
// switch to normal mode on this axis
mm::motion.InitAxis(axis);
mm::motion.SetMode(axis, mm::Normal);
@ -17,22 +17,22 @@ void MovableBase::PlanHome(config::Axis axis) {
state = Homing;
}
MovableBase::OperationResult MovableBase::InitMovement(config::Axis axis) {
MovableBase::OperationResult MovableBase::InitMovement() {
if (motion.InitAxis(axis)) {
PrepareMoveToPlannedSlot();
state = Moving;
return OperationResult::Accepted;
} else {
state = Failed;
state = TMCFailed;
return OperationResult::Failed;
}
}
void MovableBase::PerformMove(config::Axis axis) {
void MovableBase::PerformMove() {
if (!mm::motion.DriverForAxis(axis).GetErrorFlags().Good()) { // @@TODO check occasionally, i.e. not every time?
// TMC2130 entered some error state, the planned move couldn't have been finished - result of operation is Failed
tmcErrorFlags = mm::motion.DriverForAxis(axis).GetErrorFlags(); // save the failed state
state = Failed;
state = TMCFailed;
} else if (mm::motion.QueueEmpty(axis)) {
// move finished
currentSlot = plannedSlot;
@ -41,7 +41,7 @@ void MovableBase::PerformMove(config::Axis axis) {
}
}
void MovableBase::PerformHome(config::Axis axis) {
void MovableBase::PerformHome() {
if (mm::motion.StallGuard(axis)) {
// we have reached the end of the axis - homed ok
mm::motion.AbortPlannedMoves(axis, true);
@ -53,7 +53,7 @@ void MovableBase::PerformHome(config::Axis axis) {
// we ran out of planned moves but no StallGuard event has occurred - homing failed
homingValid = false;
mm::motion.SetMode(axis, mg::globals.MotorsStealth() ? mm::Stealth : mm::Normal);
state = Failed;
state = HomingFailed;
}
}

View File

@ -15,7 +15,8 @@ public:
Ready = 0, // intentionally set as zero in order to allow zeroing the Idler structure upon startup -> avoid explicit initialization code
Moving,
Homing,
Failed
TMCFailed,
HomingFailed
};
/// Operation (Engage/Disengage/MoveToSlot) return values
@ -25,11 +26,12 @@ public:
Failed ///< the operation could not been started due to HW issues
};
inline constexpr MovableBase()
inline constexpr MovableBase(config::Axis axis)
: state(Ready)
, plannedSlot(-1)
, currentSlot(-1)
, homingValid(false) {}
, homingValid(false)
, axis(axis) {}
/// virtual ~MovableBase(); intentionally disabled, see description in logic::CommandBase
@ -55,6 +57,10 @@ public:
/// (which makes homing completely transparent)
inline void InvalidateHoming() { homingValid = false; }
inline bool HomingValid() const { return homingValid; }
inline config::Axis Axis() const { return axis; }
protected:
/// internal state of the automaton
uint8_t state;
@ -71,19 +77,21 @@ protected:
/// cached TMC2130 error flags - being read only if the axis is enabled and doing something (moving)
hal::tmc2130::ErrorFlags tmcErrorFlags;
config::Axis axis;
virtual void PrepareMoveToPlannedSlot() = 0;
virtual void PlanHomingMove() = 0;
virtual void FinishHomingAndPlanMoveToParkPos() = 0;
virtual void FinishMove() = 0;
OperationResult InitMovement(config::Axis axis);
OperationResult InitMovement();
/// Prepare a homing move of the axis
void PlanHome(config::Axis axis);
void PlanHome();
void PerformMove(config::Axis axis);
void PerformMove();
void PerformHome(config::Axis axis);
void PerformHome();
};
} // namespace motion

55
src/modules/pulley.cpp Normal file
View File

@ -0,0 +1,55 @@
/// @file pulley.cpp
#include "pulley.h"
#include "buttons.h"
#include "globals.h"
#include "leds.h"
#include "motion.h"
#include "permanent_storage.h"
#include "../debug.h"
namespace modules {
namespace pulley {
Pulley pulley;
void Pulley::FinishHomingAndPlanMoveToParkPos() {
mm::motion.SetPosition(mm::Pulley, 0);
}
bool Pulley::Step() {
switch (state) {
case Moving:
PerformMove();
return false;
case Homing:
homingValid = true;
FinishHomingAndPlanMoveToParkPos();
return true;
case Ready:
return true;
case TMCFailed:
default:
return true;
}
}
void Pulley::PlanMove(unit::U_mm delta, unit::U_mm_s feed_rate, unit::U_mm_s end_rate) {
mm::motion.PlanMove<mm::Pulley>(delta, feed_rate, end_rate);
state = Moving;
}
int32_t Pulley::CurrentPositionPulley_mm() {
return mm::stepsToUnit<mm::P_pos_t>(mm::P_pos_t({ mm::motion.CurPosition(mm::Pulley) }));
}
void Pulley::InitAxis() {
mm::motion.InitAxis(mm::Pulley);
}
void Pulley::Disable() {
mm::motion.Disable(mm::Pulley);
state = Ready;
}
} // namespace pulley
} // namespace modules

50
src/modules/pulley.h Normal file
View File

@ -0,0 +1,50 @@
/// @file pulley.h
#pragma once
#include "../config/config.h"
#include "axisunit.h"
#include "../unit.h"
#include "movable_base.h"
namespace modules {
/// The pulley namespace provides all necessary facilities related to the logical model of the pulley device of the MMU unit.
namespace pulley {
namespace mm = modules::motion;
/// The Pulley model is an analogy to Idler and Selector.
/// It encapsulates the same error handling principles like the other two (motored) modules.
/// On the other hand - the Pulley is much simpler, there is no homing, engage/disengage and slots,
/// but it supports free rotation in either directions and some computation on top of it.
class Pulley : public motion::MovableBase {
public:
inline constexpr Pulley()
: MovableBase(mm::Pulley) {}
/// Performs one step of the state machine according to currently planned operation
/// @returns true if the pulley is ready to accept new commands (i.e. it has finished the last operation)
bool Step();
void PlanMove(unit::U_mm delta, unit::U_mm_s feed_rate, unit::U_mm_s end_rate = { 0 });
/// @returns rounded current position (rotation) of the Pulley
/// This exists purely to avoid expensive float (long double) computations of distance traveled by the filament
int32_t CurrentPositionPulley_mm();
void InitAxis();
void Disable();
protected:
virtual void PrepareMoveToPlannedSlot() override {}
virtual void PlanHomingMove() override {}
virtual void FinishHomingAndPlanMoveToParkPos() override;
virtual void FinishMove() override {}
};
/// The one and only instance of Pulley in the FW
extern Pulley pulley;
} // namespace pulley
} // namespace modules
namespace mp = modules::pulley;

View File

@ -31,7 +31,7 @@ void Selector::FinishHomingAndPlanMoveToParkPos() {
if (plannedSlot > config::toolCount) {
plannedSlot = IdleSlotIndex();
}
InitMovement(mm::Selector);
InitMovement();
}
void Selector::FinishMove() {
@ -52,7 +52,7 @@ Selector::OperationResult Selector::MoveToSlot(uint8_t slot) {
// coordinates invalid, first home, then engage
if (!homingValid && mg::globals.FilamentLoaded() < mg::FilamentLoadState::InSelector) {
PlanHome(mm::Selector);
PlanHome();
return OperationResult::Accepted;
}
@ -63,26 +63,26 @@ Selector::OperationResult Selector::MoveToSlot(uint8_t slot) {
}
// do the move
return InitMovement(mm::Selector);
return InitMovement();
}
bool Selector::Step() {
switch (state) {
case Moving:
PerformMove(mm::Selector);
PerformMove();
//dbg_logic_P(PSTR("Moving Selector"));
return false;
case Homing:
dbg_logic_P(PSTR("Homing Selector"));
PerformHome(mm::Selector);
PerformHome();
return false;
case Ready:
if (!homingValid && mg::globals.FilamentLoaded() < mg::InSelector) {
PlanHome(mm::Selector);
PlanHome();
return false;
}
return true;
case Failed:
case TMCFailed:
dbg_logic_P(PSTR("Selector Failed"));
default:
return true;
@ -92,7 +92,7 @@ bool Selector::Step() {
void Selector::Init() {
if (mg::globals.FilamentLoaded() < mg::FilamentLoadState::InSelector && (!mf::finda.Pressed())) {
// home the Selector only in case we don't have filament loaded (or at least we think we don't)
PlanHome(mm::Selector);
PlanHome();
} else {
// otherwise set selector's position according to know slot positions (and pretend it is correct)
mm::motion.SetPosition(mm::Selector, SlotPosition(mg::globals.ActiveSlot()).v);

View File

@ -15,7 +15,7 @@ namespace mm = modules::motion;
class Selector : public mm::MovableBase {
public:
inline constexpr Selector()
: MovableBase() {}
: MovableBase(mm::Selector) {}
/// Plan move of the selector to a specific filament slot
/// @param slot index to move to

View File

@ -16,6 +16,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -16,6 +16,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -3,6 +3,7 @@ add_executable(
failing_tmc_tests
${CMAKE_SOURCE_DIR}/src/logic/command_base.cpp
${CMAKE_SOURCE_DIR}/src/logic/feed_to_finda.cpp
${CMAKE_SOURCE_DIR}/src/logic/load_filament.cpp
${CMAKE_SOURCE_DIR}/src/logic/retract_from_finda.cpp
${CMAKE_SOURCE_DIR}/src/logic/unload_filament.cpp
${CMAKE_SOURCE_DIR}/src/logic/unload_to_finda.cpp
@ -15,6 +16,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -15,6 +15,7 @@
#include "../../../../src/modules/selector.h"
#include "../../../../src/logic/unload_filament.h"
#include "../../../../src/logic/load_filament.h"
#include "../../modules/stubs/stub_adc.h"
@ -34,7 +35,7 @@ inline ErrorCode operator|(ErrorCode a, ErrorCode b) {
return (ErrorCode)((uint16_t)a | (uint16_t)b);
}
void FailingIdler(hal::tmc2130::ErrorFlags ef, ErrorCode ec) {
void FailingMovableUnload(hal::tmc2130::ErrorFlags ef, ErrorCode ec, config::Axis axis, uint32_t failingStep) {
// prepare startup conditions
ForceReinitAllAutomata();
@ -54,15 +55,14 @@ void FailingIdler(hal::tmc2130::ErrorFlags ef, ErrorCode ec) {
REQUIRE(VerifyState(uf, mg::FilamentLoadState::InNozzle, mi::Idler::IdleSlotIndex(), 0, true, true, ml::off, ml::off, ErrorCode::RUNNING, ProgressCode::UnloadingToFinda));
uint32_t failingStep = 5;
REQUIRE(WhileCondition(
uf,
[&](uint32_t step) -> bool {
if(step == failingStep){ // on 5th step make the TMC report some error
CauseTMCError(mm::Idler, ef);
CauseTMCError(axis, ef);
}
return uf.TopLevelState() == ProgressCode::UnloadingToFinda; },
5000));
50000));
// the simulated motion may proceed, but I don't care here. In reality no one really knows what the TMC does
// The checked value is not really important here (just that it moves!), so with tuning of the constants it may break the unit test
@ -78,15 +78,75 @@ void FailingIdler(hal::tmc2130::ErrorFlags ef, ErrorCode ec) {
REQUIRE(ml::leds.Mode(0, ml::red) == ml::off);
REQUIRE(ml::leds.Mode(0, ml::green) == ml::blink0);
REQUIRE(uf.Error() == ec);
REQUIRE(uf.TopLevelState() == ProgressCode::ERRTMCFailed);
REQUIRE(uf.TopLevelState() == ProgressCode::ERRWaitingForUser);
main_loop();
uf.Step();
}
}
void FailingMovableLoad(hal::tmc2130::ErrorFlags ef, ErrorCode ec, config::Axis axis, uint32_t failingStep, uint8_t slot) {
// prepare startup conditions
ForceReinitAllAutomata();
// change the startup to what we need here
EnsureActiveSlotIndex(5, mg::FilamentLoadState::AtPulley);
// set FINDA OFF + debounce
SetFINDAStateAndDebounce(false);
logic::LoadFilament lf;
// verify startup conditions
REQUIRE(VerifyState(lf, mg::FilamentLoadState::AtPulley, mi::Idler::IdleSlotIndex(), ms::Selector::IdleSlotIndex(), false, false, ml::off, ml::off, ErrorCode::OK, ProgressCode::OK));
// UnloadFilament starts by engaging the idler (through the UnloadToFinda state machine)
lf.Reset(slot);
REQUIRE(WhileCondition(
lf,
[&](uint32_t step) -> bool {
if(step == failingStep){ // on 5th step make the TMC report some error
CauseTMCError(axis, ef);
}
return lf.TopLevelState() == ProgressCode::FeedingToFinda; },
50000));
// the simulated motion may proceed, but I don't care here. In reality no one really knows what the TMC does
// The checked value is not really important here (just that it moves!), so with tuning of the constants it may break the unit test
// Therefore it is disabled by default
// REQUIRE(mm::axes[mm::Idler].pos == failingStep * config::pulleyToCuttingEdge.v + 1);
// repeated calls to step this logic automaton shall produce no change
for (int i = 0; i < 5; ++i) {
// and this must cause the state machine to run into a TMC error state and report the error correctly
// Please note we are leaving the Idler in an intermediate position due to the TMC failure,
// so we cannot use the usual VerifyState(), but have to check the stuff manually
// REQUIRE(VerifyState(uf, true, raw_6, 0, false, ml::blink0, ml::blink0, ec, ProgressCode::ERRTMCFailed));
REQUIRE(ml::leds.Mode(0, ml::red) == ml::off);
REQUIRE(ml::leds.Mode(0, ml::green) == ml::blink0);
REQUIRE(lf.Error() == ec);
REQUIRE(lf.TopLevelState() == ProgressCode::ERRWaitingForUser);
main_loop();
lf.Step();
}
}
TEST_CASE("failing_tmc::failing_idler", "[failing_tmc]") {
hal::tmc2130::ErrorFlags ef;
ef.ot = 1; // make the TMC hot like hell
FailingIdler(ef, ErrorCode::TMC_OVER_TEMPERATURE_ERROR | ErrorCode::TMC_IDLER_BIT);
FailingMovableUnload(ef, ErrorCode::TMC_OVER_TEMPERATURE_ERROR | ErrorCode::TMC_IDLER_BIT, mm::Idler, 5);
}
TEST_CASE("failing_tmc::failing_selector", "[failing_tmc]") {
hal::tmc2130::ErrorFlags ef;
ef.ot = 1; // make the TMC hot like hell
FailingMovableLoad(ef, ErrorCode::TMC_OVER_TEMPERATURE_ERROR | ErrorCode::TMC_SELECTOR_BIT, mm::Selector, 5, 0);
}
TEST_CASE("failing_tmc::failing_pulley", "[failing_tmc]") {
hal::tmc2130::ErrorFlags ef;
ef.ot = 1; // make the TMC hot like hell
FailingMovableUnload(ef, ErrorCode::TMC_OVER_TEMPERATURE_ERROR | ErrorCode::TMC_PULLEY_BIT, mm::Pulley, 2000);
}

View File

@ -11,6 +11,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -11,6 +11,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -15,6 +15,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -12,6 +12,7 @@
#include "../../../../src/modules/leds.h"
#include "../../../../src/modules/motion.h"
#include "../../../../src/modules/permanent_storage.h"
#include "../../../../src/modules/pulley.h"
#include "../../../../src/modules/selector.h"
#include "../../../../src/modules/user_input.h"
@ -26,6 +27,7 @@ void main_loop() {
mf::finda.Step();
mfs::fsensor.Step();
mi::idler.Step();
mp::pulley.Step();
ms::selector.Step();
mm::motion.Step();
mui::userInput.Step();
@ -50,6 +52,7 @@ void ForceReinitAllAutomata() {
new (&mf::finda) mf::FINDA();
new (&mfs::fsensor) mfs::FSensor();
new (&mi::idler) mi::Idler();
new (&mp::pulley) mp::Pulley();
new (&ms::selector) ms::Selector();
new (&mm::motion) mm::Motion();
@ -137,7 +140,7 @@ void EnsureActiveSlotIndex(uint8_t slot, mg::FilamentLoadState loadState) {
}
void SetFINDAStateAndDebounce(bool press) {
hal::gpio::WritePin(FINDA_PIN, hal::gpio::Level::high);
hal::gpio::WritePin(FINDA_PIN, press ? hal::gpio::Level::high : hal::gpio::Level::low);
for (size_t i = 0; i < config::findaDebounceMs + 1; ++i)
main_loop();
}

View File

@ -18,6 +18,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -15,6 +15,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp

View File

@ -11,6 +11,7 @@ add_executable(
${CMAKE_SOURCE_DIR}/src/modules/leds.cpp
${CMAKE_SOURCE_DIR}/src/modules/movable_base.cpp
${CMAKE_SOURCE_DIR}/src/modules/permanent_storage.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulley.cpp
${CMAKE_SOURCE_DIR}/src/modules/selector.cpp
${CMAKE_SOURCE_DIR}/src/modules/user_input.cpp
${CMAKE_SOURCE_DIR}/src/modules/pulse_gen.cpp