Merge pull request #3607 from prusa3d/MK3_MMU2_fix1
MK3S/+: support for new MMU2S
This commit is contained in:
commit
d15246adde
|
|
@ -17,7 +17,6 @@ set(TEST_SOURCES
|
|||
Tests/AutoDeplete_test.cpp
|
||||
Tests/PrusaStatistics_test.cpp
|
||||
Firmware/Timer.cpp
|
||||
Firmware/AutoDeplete.cpp
|
||||
)
|
||||
add_executable(tests ${TEST_SOURCES})
|
||||
target_include_directories(tests PRIVATE Tests)
|
||||
|
|
|
|||
|
|
@ -1,79 +0,0 @@
|
|||
//! @file
|
||||
//! @author: Marek Bel
|
||||
//! @date Jan 3, 2019
|
||||
|
||||
#include "AutoDeplete.h"
|
||||
#include "assert.h"
|
||||
|
||||
//! @brief bit field marking depleted filaments
|
||||
//!
|
||||
//! binary 1 marks filament as depleted
|
||||
//! Zero initialized value means, that no filament is depleted.
|
||||
static uint8_t depleted;
|
||||
static const uint8_t filamentCount = 5;
|
||||
|
||||
//! @return binary 1 for all filaments
|
||||
//! @par fCount number of filaments
|
||||
static constexpr uint8_t allDepleted(uint8_t fCount)
|
||||
{
|
||||
return fCount == 1 ? 1 : ((1 << (fCount - 1)) | allDepleted(fCount - 1));
|
||||
}
|
||||
|
||||
//! @brief Is filament available for printing?
|
||||
//! @par filament Filament number to be checked
|
||||
//! @retval true Filament is available for printing.
|
||||
//! @retval false Filament is not available for printing.
|
||||
static bool loaded(uint8_t filament)
|
||||
{
|
||||
if (depleted & (1 << filament)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
//! @brief Mark filament as not available for printing.
|
||||
//! @par filament filament to be marked
|
||||
void ad_markDepleted(uint8_t filament)
|
||||
{
|
||||
assert(filament < filamentCount);
|
||||
if (filament < filamentCount)
|
||||
{
|
||||
depleted |= 1 << filament;
|
||||
}
|
||||
}
|
||||
|
||||
//! @brief Mark filament as available for printing.
|
||||
//! @par filament filament to be marked
|
||||
void ad_markLoaded(uint8_t filament)
|
||||
{
|
||||
assert(filament < filamentCount);
|
||||
if (filament < filamentCount)
|
||||
{
|
||||
depleted &= ~(1 << filament);
|
||||
}
|
||||
}
|
||||
|
||||
//! @brief Get alternative filament, which is not depleted
|
||||
//! @par filament filament
|
||||
//! @return Filament, if it is depleted, returns next available,
|
||||
//! if all filaments are depleted, returns filament function parameter.
|
||||
uint8_t ad_getAlternative(uint8_t filament)
|
||||
{
|
||||
assert(filament < filamentCount);
|
||||
for (uint8_t i = 0; i<filamentCount; ++i)
|
||||
{
|
||||
uint8_t nextFilament = (filament + i) % filamentCount;
|
||||
if (loaded(nextFilament)) return nextFilament;
|
||||
}
|
||||
return filament;
|
||||
}
|
||||
|
||||
//! @brief Are all filaments depleted?
|
||||
//! @retval true All filaments are depleted.
|
||||
//! @retval false All filaments are not depleted.
|
||||
bool ad_allDepleted()
|
||||
{
|
||||
if (allDepleted(filamentCount) == depleted)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
//! @file
|
||||
//! @author: Marek Bel
|
||||
//! @brief Filament auto deplete engine for multi-material prints with MMUv2 (Now marketed as SpoolJoin)
|
||||
//!
|
||||
//! Interface for marking MMUv2 filaments as depleted and getting alternative filament for printing.
|
||||
|
||||
#ifndef AUTODEPLETE_H
|
||||
#define AUTODEPLETE_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
void ad_markDepleted(uint8_t filament);
|
||||
void ad_markLoaded(uint8_t filament);
|
||||
uint8_t ad_getAlternative(uint8_t filament);
|
||||
bool ad_allDepleted();
|
||||
|
||||
#endif /* AUTODEPLETE_H */
|
||||
|
|
@ -346,6 +346,62 @@ const unsigned int dropsegments=5; //everything with less than this number of st
|
|||
// 2nd and 3rd byte (LSB first) contains a 16bit length of a command including its preceding comments.
|
||||
#define CMDHDRSIZE 3
|
||||
|
||||
/**
|
||||
* Advanced Pause for Filament Change
|
||||
* - Adds the G-code M600 Filament Change to initiate a filament change.
|
||||
* - This feature is required for the default FILAMENT_RUNOUT_SCRIPT.
|
||||
*
|
||||
* Requirements:
|
||||
* - For Filament Change parking enable and configure NOZZLE_PARK_FEATURE.
|
||||
* - For user interaction enable an LCD display, HOST_PROMPT_SUPPORT, or EMERGENCY_PARSER.
|
||||
*
|
||||
* Enable PARK_HEAD_ON_PAUSE to add the G-code M125 Pause and Park.
|
||||
*/
|
||||
|
||||
#define PAUSE_PARK_RETRACT_FEEDRATE 60 // (mm/s) Initial retract feedrate.
|
||||
#define PAUSE_PARK_RETRACT_LENGTH 2 // (mm) Initial retract.
|
||||
// This short retract is done immediately, before parking the nozzle.
|
||||
#define FILAMENT_CHANGE_UNLOAD_FEEDRATE 10 // (mm/s) Unload filament feedrate. This can be pretty fast.
|
||||
#define FILAMENT_CHANGE_UNLOAD_ACCEL 25 // (mm/s^2) Lower acceleration may allow a faster feedrate.
|
||||
#define FILAMENT_CHANGE_UNLOAD_LENGTH 100 // (mm) The length of filament for a complete unload.
|
||||
// For Bowden, the full length of the tube and nozzle.
|
||||
// For direct drive, the full length of the nozzle.
|
||||
// Set to 0 for manual unloading.
|
||||
#define FILAMENT_CHANGE_SLOW_LOAD_FEEDRATE 6 // (mm/s) Slow move when starting load.
|
||||
#define FILAMENT_CHANGE_SLOW_LOAD_LENGTH 0 // (mm) Slow length, to allow time to insert material.
|
||||
// 0 to disable start loading and skip to fast load only
|
||||
#define FILAMENT_CHANGE_FAST_LOAD_FEEDRATE 6 // (mm/s) Load filament feedrate. This can be pretty fast.
|
||||
#define FILAMENT_CHANGE_FAST_LOAD_ACCEL 25 // (mm/s^2) Lower acceleration may allow a faster feedrate.
|
||||
#define FILAMENT_CHANGE_FAST_LOAD_LENGTH 0 // (mm) Load length of filament, from extruder gear to nozzle.
|
||||
// For Bowden, the full length of the tube and nozzle.
|
||||
// For direct drive, the full length of the nozzle.
|
||||
//#define ADVANCED_PAUSE_CONTINUOUS_PURGE // Purge continuously up to the purge length until interrupted.
|
||||
#define ADVANCED_PAUSE_PURGE_FEEDRATE 3 // (mm/s) Extrude feedrate (after loading). Should be slower than load feedrate.
|
||||
#define ADVANCED_PAUSE_PURGE_LENGTH 50 // (mm) Length to extrude after loading.
|
||||
// Set to 0 for manual extrusion.
|
||||
// Filament can be extruded repeatedly from the Filament Change menu
|
||||
// until extrusion is consistent, and to purge old filament.
|
||||
#define ADVANCED_PAUSE_RESUME_PRIME 0 // (mm) Extra distance to prime nozzle after returning from park.
|
||||
//#define ADVANCED_PAUSE_FANS_PAUSE // Turn off print-cooling fans while the machine is paused.
|
||||
|
||||
// Filament Unload does a Retract, Delay, and Purge first:
|
||||
#define FILAMENT_UNLOAD_PURGE_RETRACT 13 // (mm) Unload initial retract length.
|
||||
#define FILAMENT_UNLOAD_PURGE_DELAY 5000 // (ms) Delay for the filament to cool after retract.
|
||||
#define FILAMENT_UNLOAD_PURGE_LENGTH 8 // (mm) An unretract is done, then this length is purged.
|
||||
#define FILAMENT_UNLOAD_PURGE_FEEDRATE 25 // (mm/s) feedrate to purge before unload
|
||||
|
||||
#define PAUSE_PARK_NOZZLE_TIMEOUT 45 // (seconds) Time limit before the nozzle is turned off for safety.
|
||||
#define FILAMENT_CHANGE_ALERT_BEEPS 10 // Number of alert beeps to play when a response is needed.
|
||||
#define PAUSE_PARK_NO_STEPPER_TIMEOUT // Enable for XYZ steppers to stay powered on during filament change.
|
||||
//#define FILAMENT_CHANGE_RESUME_ON_INSERT // Automatically continue / load filament when runout sensor is triggered again.
|
||||
//#define PAUSE_REHEAT_FAST_RESUME // Reduce number of waits by not prompting again post-timeout before continuing.
|
||||
|
||||
//#define PARK_HEAD_ON_PAUSE // Park the nozzle during pause and filament change.
|
||||
//#define HOME_BEFORE_FILAMENT_CHANGE // If needed, home before parking for filament change
|
||||
|
||||
//#define FILAMENT_LOAD_UNLOAD_GCODES // Add M701/M702 Load/Unload G-codes, plus Load/Unload in the LCD Prepare menu.
|
||||
//#define FILAMENT_UNLOAD_ALL_EXTRUDERS // Allow M702 to unload all extruders above a minimum target temp (as set by M302)
|
||||
|
||||
|
||||
// Firmware based and LCD controlled retract
|
||||
// M207 and M208 can be used to define parameters for the retraction.
|
||||
|
|
|
|||
|
|
@ -865,7 +865,7 @@ void dcode_2130()
|
|||
}
|
||||
#endif //TMC2130
|
||||
|
||||
#ifdef PAT9125
|
||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
/*!
|
||||
### D9125 - PAT9125 filament sensor <a href="https://reprap.org/wiki/G-code#D9:_Read.2FWrite_ADC">D9125: PAT9125 filament sensor</a>
|
||||
#### Usage
|
||||
|
|
@ -878,7 +878,6 @@ void dcode_2130()
|
|||
- `R` - Resolution. Not active in code
|
||||
- `X` - X values
|
||||
- `Y` - Y values
|
||||
- `L` - Activate filament sensor log
|
||||
*/
|
||||
void dcode_9125()
|
||||
{
|
||||
|
|
@ -912,15 +911,8 @@ void dcode_9125()
|
|||
pat9125_y = (int)code_value();
|
||||
LOG("pat9125_y=%d\n", pat9125_y);
|
||||
}
|
||||
#ifdef DEBUG_FSENSOR_LOG
|
||||
if (code_seen('L'))
|
||||
{
|
||||
fsensor_log = (int)code_value();
|
||||
LOG("fsensor_log=%d\n", fsensor_log);
|
||||
}
|
||||
#endif //DEBUG_FSENSOR_LOG
|
||||
}
|
||||
#endif //PAT9125
|
||||
#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
|
||||
#endif //DEBUG_DCODES
|
||||
|
||||
|
|
|
|||
|
|
@ -53,9 +53,9 @@ extern void dcode_81(); //D81 - Bed analysis. This command will log data to SD c
|
|||
extern void dcode_2130(); //D2130 - TMC2130
|
||||
#endif //TMC2130
|
||||
|
||||
#ifdef PAT9125
|
||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
extern void dcode_9125(); //D9125 - PAT9125
|
||||
#endif //PAT9125
|
||||
#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
|
||||
|
||||
#endif //DCODES_H
|
||||
|
|
|
|||
|
|
@ -0,0 +1,525 @@
|
|||
#include <avr/pgmspace.h>
|
||||
#include <stdio.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include "Filament_sensor.h"
|
||||
#include "Timer.h"
|
||||
#include "cardreader.h"
|
||||
#include "eeprom.h"
|
||||
#include "menu.h"
|
||||
#include "planner.h"
|
||||
#include "temperature.h"
|
||||
#include "ultralcd.h"
|
||||
|
||||
#ifdef FILAMENT_SENSOR
|
||||
FSensorBlockRunout::FSensorBlockRunout() {
|
||||
fsensor.setRunoutEnabled(false); //suppress filament runouts while loading filament.
|
||||
fsensor.setAutoLoadEnabled(false); //suppress filament autoloads while loading filament.
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
fsensor.setJamDetectionEnabled(false); //suppress filament jam detection while loading filament.
|
||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
// SERIAL_ECHOLNPGM("FSBlockRunout");
|
||||
}
|
||||
|
||||
FSensorBlockRunout::~FSensorBlockRunout() {
|
||||
fsensor.settings_init(); // restore filament runout state.
|
||||
// SERIAL_ECHOLNPGM("FSUnBlockRunout");
|
||||
}
|
||||
|
||||
# if FILAMENT_SENSOR_TYPE == FSENSOR_IR
|
||||
IR_sensor fsensor;
|
||||
# elif FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG
|
||||
IR_sensor_analog fsensor;
|
||||
# elif FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125
|
||||
PAT9125_sensor fsensor;
|
||||
# endif
|
||||
|
||||
#else // FILAMENT_SENSOR
|
||||
FSensorBlockRunout::FSensorBlockRunout() { }
|
||||
FSensorBlockRunout::~FSensorBlockRunout() { }
|
||||
#endif // FILAMENT_SENSOR
|
||||
|
||||
void Filament_sensor::setEnabled(bool enabled) {
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FSENSOR, enabled);
|
||||
if (enabled) {
|
||||
fsensor.init();
|
||||
} else {
|
||||
fsensor.deinit();
|
||||
}
|
||||
}
|
||||
|
||||
void Filament_sensor::setAutoLoadEnabled(bool state, bool updateEEPROM) {
|
||||
autoLoadEnabled = state;
|
||||
if (updateEEPROM) {
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FSENS_AUTOLOAD_ENABLED, state);
|
||||
}
|
||||
}
|
||||
|
||||
void Filament_sensor::setRunoutEnabled(bool state, bool updateEEPROM) {
|
||||
runoutEnabled = state;
|
||||
if (updateEEPROM) {
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FSENS_RUNOUT_ENABLED, state);
|
||||
}
|
||||
}
|
||||
|
||||
void Filament_sensor::setActionOnError(SensorActionOnError state, bool updateEEPROM) {
|
||||
sensorActionOnError = state;
|
||||
if (updateEEPROM) {
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_ACTION_NA, (uint8_t)state);
|
||||
}
|
||||
}
|
||||
|
||||
void Filament_sensor::settings_init_common() {
|
||||
bool enabled = eeprom_read_byte((uint8_t *)EEPROM_FSENSOR);
|
||||
if ((state != State::disabled) != enabled) {
|
||||
state = enabled ? State::initializing : State::disabled;
|
||||
}
|
||||
|
||||
autoLoadEnabled = eeprom_read_byte((uint8_t *)EEPROM_FSENS_AUTOLOAD_ENABLED);
|
||||
runoutEnabled = eeprom_read_byte((uint8_t *)EEPROM_FSENS_RUNOUT_ENABLED);
|
||||
sensorActionOnError = (SensorActionOnError)eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_ACTION_NA);
|
||||
if (sensorActionOnError == SensorActionOnError::_Undef) {
|
||||
sensorActionOnError = SensorActionOnError::_Continue;
|
||||
}
|
||||
}
|
||||
|
||||
bool Filament_sensor::checkFilamentEvents() {
|
||||
if (state != State::ready)
|
||||
return false;
|
||||
if (eventBlankingTimer.running() && !eventBlankingTimer.expired(100)) { // event blanking for 100ms
|
||||
return false;
|
||||
}
|
||||
|
||||
bool newFilamentPresent = fsensor.getFilamentPresent();
|
||||
if (oldFilamentPresent != newFilamentPresent) {
|
||||
oldFilamentPresent = newFilamentPresent;
|
||||
eventBlankingTimer.start();
|
||||
if (newFilamentPresent) { // filament insertion
|
||||
// puts_P(PSTR("filament inserted"));
|
||||
triggerFilamentInserted();
|
||||
postponedLoadEvent = true;
|
||||
} else { // filament removal
|
||||
// puts_P(PSTR("filament removed"));
|
||||
triggerFilamentRemoved();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Filament_sensor::triggerFilamentInserted() {
|
||||
if (autoLoadEnabled
|
||||
&& (eFilamentAction == FilamentAction::None)
|
||||
&& (! MMU2::mmu2.Enabled() ) // quick and dirty hack to prevent spurious runouts while the MMU is in charge
|
||||
&& !(
|
||||
moves_planned() != 0
|
||||
|| IS_SD_PRINTING
|
||||
|| usb_timer.running()
|
||||
|| (lcd_commands_type == LcdCommands::Layer1Cal)
|
||||
|| eeprom_read_byte((uint8_t *)EEPROM_WIZARD_ACTIVE)
|
||||
)
|
||||
) {
|
||||
filAutoLoad();
|
||||
}
|
||||
}
|
||||
|
||||
void Filament_sensor::triggerFilamentRemoved() {
|
||||
// SERIAL_ECHOLNPGM("triggerFilamentRemoved");
|
||||
if (runoutEnabled
|
||||
&& (! MMU2::mmu2.Enabled() ) // quick and dirty hack to prevent spurious runouts just before the toolchange
|
||||
&& (eFilamentAction == FilamentAction::None)
|
||||
&& !saved_printing
|
||||
&& (
|
||||
moves_planned() != 0
|
||||
|| IS_SD_PRINTING
|
||||
|| usb_timer.running()
|
||||
|| (lcd_commands_type == LcdCommands::Layer1Cal)
|
||||
|| eeprom_read_byte((uint8_t *)EEPROM_WIZARD_ACTIVE)
|
||||
)
|
||||
){
|
||||
// SERIAL_ECHOPGM("runoutEnabled="); SERIAL_ECHOLN((int)runoutEnabled);
|
||||
// SERIAL_ECHOPGM("eFilamentAction="); SERIAL_ECHOLN((int)eFilamentAction);
|
||||
// SERIAL_ECHOPGM("saved_printing="); SERIAL_ECHOLN((int)saved_printing);
|
||||
filRunout();
|
||||
}
|
||||
}
|
||||
|
||||
void Filament_sensor::filAutoLoad() {
|
||||
eFilamentAction = FilamentAction::AutoLoad;
|
||||
if (target_temperature[0] >= EXTRUDE_MINTEMP) {
|
||||
bFilamentPreheatState = true;
|
||||
menu_submenu(mFilamentItemForce);
|
||||
} else {
|
||||
menu_submenu(lcd_generic_preheat_menu);
|
||||
lcd_timeoutToStatus.start();
|
||||
}
|
||||
}
|
||||
|
||||
void Filament_sensor::filRunout() {
|
||||
// SERIAL_ECHOLNPGM("filRunout");
|
||||
runoutEnabled = false;
|
||||
autoLoadEnabled = false;
|
||||
stop_and_save_print_to_ram(0, 0);
|
||||
restore_print_from_ram_and_continue(0);
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FERROR_COUNT, eeprom_read_byte((uint8_t *)EEPROM_FERROR_COUNT) + 1);
|
||||
eeprom_update_word((uint16_t *)EEPROM_FERROR_COUNT_TOT, eeprom_read_word((uint16_t *)EEPROM_FERROR_COUNT_TOT) + 1);
|
||||
enquecommand_front_P((PSTR("M600")));
|
||||
}
|
||||
|
||||
void Filament_sensor::triggerError() {
|
||||
state = State::error;
|
||||
|
||||
/// some message, idk
|
||||
; //
|
||||
}
|
||||
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
void IR_sensor::init() {
|
||||
if (state == State::error) {
|
||||
fsensor.deinit(); // deinit first if there was an error.
|
||||
}
|
||||
// puts_P(PSTR("fsensor::init()"));
|
||||
SET_INPUT(IR_SENSOR_PIN); // input mode
|
||||
WRITE(IR_SENSOR_PIN, 1); // pullup
|
||||
settings_init(); // also sets the state to State::initializing
|
||||
}
|
||||
|
||||
void IR_sensor::deinit() {
|
||||
// puts_P(PSTR("fsensor::deinit()"));
|
||||
SET_INPUT(IR_SENSOR_PIN); // input mode
|
||||
WRITE(IR_SENSOR_PIN, 0); // no pullup
|
||||
state = State::disabled;
|
||||
}
|
||||
|
||||
bool IR_sensor::update() {
|
||||
switch (state) {
|
||||
case State::initializing:
|
||||
state = State::ready; // the IR sensor gets ready instantly as it's just a gpio read operation.
|
||||
// initialize the current filament state so that we don't create a switching event right after the sensor is ready.
|
||||
oldFilamentPresent = fsensor.getFilamentPresent();
|
||||
[[fallthrough]];
|
||||
case State::ready: {
|
||||
postponedLoadEvent = false;
|
||||
return checkFilamentEvents();
|
||||
} break;
|
||||
case State::disabled:
|
||||
case State::error:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef FSENSOR_PROBING
|
||||
bool IR_sensor::probeOtherType() { return pat9125_probe(); }
|
||||
#endif
|
||||
|
||||
void IR_sensor::settings_init() { Filament_sensor::settings_init_common(); }
|
||||
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
void IR_sensor_analog::init() {
|
||||
IR_sensor::init();
|
||||
IR_sensor::settings_init();
|
||||
sensorRevision = (SensorRevision)eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_PCB);
|
||||
}
|
||||
|
||||
bool IR_sensor_analog::update() {
|
||||
bool event = IR_sensor::update();
|
||||
if (state == State::ready) {
|
||||
if (getVoltReady()) {
|
||||
clearVoltReady();
|
||||
uint16_t volt = getVoltRaw();
|
||||
// printf_P(PSTR("newVoltRaw:%u\n"), volt / OVERSAMPLENR);
|
||||
|
||||
// detect min-max, some long term sliding window for filtration may be added
|
||||
// avoiding floating point operations, thus computing in raw
|
||||
if (volt > maxVolt) {
|
||||
maxVolt = volt;
|
||||
} else if (volt < minVolt) {
|
||||
minVolt = volt;
|
||||
}
|
||||
//! The trouble is, I can hold the filament in the hole in such a way, that it creates the exact voltage
|
||||
//! to be detected as the new fsensor
|
||||
//! We can either fake it by extending the detection window to a looooong time
|
||||
//! or do some other countermeasures
|
||||
|
||||
//! what we want to detect:
|
||||
//! if minvolt gets below ~0.3V, it means there is an old fsensor
|
||||
//! if maxvolt gets above 4.6V, it means we either have an old fsensor or broken cables/fsensor
|
||||
//! So I'm waiting for a situation, when minVolt gets to range <0, 1.5> and maxVolt gets into range <3.0, 5>
|
||||
//! If and only if minVolt is in range <0.3, 1.5> and maxVolt is in range <3.0, 4.6>, I'm considering a situation with the new fsensor
|
||||
if (minVolt >= IRsensor_Ldiode_TRESHOLD && minVolt <= IRsensor_Lmax_TRESHOLD && maxVolt >= IRsensor_Hmin_TRESHOLD &&
|
||||
maxVolt <= IRsensor_Hopen_TRESHOLD) {
|
||||
IR_ANALOG_Check(SensorRevision::_Old, SensorRevision::_Rev04);
|
||||
}
|
||||
//! If and only if minVolt is in range <0.0, 0.3> and maxVolt is in range <4.6, 5.0V>, I'm considering a situation with the old fsensor
|
||||
//! Note, we are not relying on one voltage here - getting just +5V can mean an old fsensor or a broken new sensor - that's why
|
||||
//! we need to have both voltages detected correctly to allow switching back to the old fsensor.
|
||||
else if (minVolt < IRsensor_Ldiode_TRESHOLD && maxVolt > IRsensor_Hopen_TRESHOLD && maxVolt <= IRsensor_VMax_TRESHOLD) {
|
||||
IR_ANALOG_Check(SensorRevision::_Rev04, SensorRevision::_Old);
|
||||
}
|
||||
|
||||
if (!checkVoltage(volt)) {
|
||||
triggerError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
; //
|
||||
|
||||
return event;
|
||||
}
|
||||
|
||||
void IR_sensor_analog::voltUpdate(uint16_t raw) { // to be called from the ADC ISR when a cycle is finished
|
||||
voltRaw = raw;
|
||||
voltReady = true;
|
||||
}
|
||||
|
||||
uint16_t IR_sensor_analog::getVoltRaw() {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { return voltRaw; }
|
||||
}
|
||||
|
||||
const char *IR_sensor_analog::getIRVersionText() {
|
||||
switch (sensorRevision) {
|
||||
case SensorRevision::_Old:
|
||||
return _T(MSG_IR_03_OR_OLDER);
|
||||
case SensorRevision::_Rev04:
|
||||
return _T(MSG_IR_04_OR_NEWER);
|
||||
default:
|
||||
return _T(MSG_IR_UNKNOWN);
|
||||
}
|
||||
}
|
||||
|
||||
void IR_sensor_analog::setSensorRevision(SensorRevision rev, bool updateEEPROM) {
|
||||
sensorRevision = rev;
|
||||
if (updateEEPROM) {
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_PCB, (uint8_t)rev);
|
||||
}
|
||||
}
|
||||
|
||||
bool IR_sensor_analog::checkVoltage(uint16_t raw) {
|
||||
if (IRsensor_Lmax_TRESHOLD <= raw && raw <= IRsensor_Hmin_TRESHOLD) {
|
||||
/// If the voltage is in forbidden range, the fsensor is ok, but the lever is mounted improperly.
|
||||
/// Or the user is so creative so that he can hold a piece of fillament in the hole in such a genius way,
|
||||
/// that the IR fsensor reading is within 1.5 and 3V ... this would have been highly unusual
|
||||
/// and would have been considered more like a sabotage than normal printer operation
|
||||
if (voltageErrorCnt++ > 4) {
|
||||
puts_P(PSTR("fsensor in forbidden range 1.5-3V - check sensor"));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
voltageErrorCnt = 0;
|
||||
}
|
||||
if (sensorRevision == SensorRevision::_Rev04) {
|
||||
/// newer IR sensor cannot normally produce 4.6-5V, this is considered a failure/bad mount
|
||||
if (IRsensor_Hopen_TRESHOLD <= raw && raw <= IRsensor_VMax_TRESHOLD) {
|
||||
puts_P(PSTR("fsensor v0.4 in fault range 4.6-5V - unconnected"));
|
||||
return false;
|
||||
}
|
||||
/// newer IR sensor cannot normally produce 0-0.3V, this is considered a failure
|
||||
#if 0 // Disabled as it has to be decided if we gonna use this or not.
|
||||
if(IRsensor_Hopen_TRESHOLD <= raw && raw <= IRsensor_VMax_TRESHOLD) {
|
||||
puts_P(PSTR("fsensor v0.4 in fault range 0.0-0.3V - wrong IR sensor"));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/// If IR sensor is "uknown state" and filament is not loaded > 1.5V return false
|
||||
#if 0
|
||||
#error "I really think this code can't be enabled anymore because we are constantly checking this voltage."
|
||||
if((sensorRevision == SensorRevision::_Undef) && (raw > IRsensor_Lmax_TRESHOLD)) {
|
||||
puts_P(PSTR("Unknown IR sensor version and no filament loaded detected."));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// otherwise the IR fsensor is considered working correctly
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IR_sensor_analog::getVoltReady() const {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ return voltReady; }
|
||||
}
|
||||
|
||||
void IR_sensor_analog::clearVoltReady(){
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE){ voltReady = false; }
|
||||
}
|
||||
|
||||
void IR_sensor_analog::IR_ANALOG_Check(SensorRevision isVersion, SensorRevision switchTo) {
|
||||
bool bTemp = (!CHECK_ALL_HEATERS);
|
||||
bTemp = bTemp && (menu_menu == lcd_status_screen);
|
||||
bTemp = bTemp && ((sensorRevision == isVersion) || (sensorRevision == SensorRevision::_Undef));
|
||||
bTemp = bTemp && (state == State::ready);
|
||||
if (bTemp) {
|
||||
nFSCheckCount++;
|
||||
if (nFSCheckCount > FS_CHECK_COUNT) {
|
||||
nFSCheckCount = 0; // not necessary
|
||||
setSensorRevision(switchTo, true);
|
||||
printf_IRSensorAnalogBoardChange();
|
||||
switch (switchTo) {
|
||||
case SensorRevision::_Old:
|
||||
lcd_setstatuspgm(_T(MSG_IR_03_OR_OLDER));
|
||||
break;
|
||||
case SensorRevision::_Rev04:
|
||||
lcd_setstatuspgm(_T(MSG_IR_04_OR_NEWER));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
nFSCheckCount = 0;
|
||||
}
|
||||
}
|
||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
void PAT9125_sensor::init() {
|
||||
if (state == State::error) {
|
||||
deinit(); // deinit first if there was an error.
|
||||
}
|
||||
// puts_P(PSTR("fsensor::init()"));
|
||||
|
||||
settings_init(); // also sets the state to State::initializing
|
||||
|
||||
calcChunkSteps(cs.axis_steps_per_unit[E_AXIS]); // for jam detection
|
||||
|
||||
if (!pat9125_init()) {
|
||||
deinit();
|
||||
triggerError();
|
||||
; //
|
||||
}
|
||||
#ifdef IR_SENSOR_PIN
|
||||
else if (!READ(IR_SENSOR_PIN)) {
|
||||
; // MK3 fw on MK3S printer
|
||||
}
|
||||
#endif // IR_SENSOR_PIN
|
||||
}
|
||||
|
||||
void PAT9125_sensor::deinit() {
|
||||
// puts_P(PSTR("fsensor::deinit()"));
|
||||
; //
|
||||
state = State::disabled;
|
||||
filter = 0;
|
||||
}
|
||||
|
||||
bool PAT9125_sensor::update() {
|
||||
switch (state) {
|
||||
case State::initializing:
|
||||
if (!updatePAT9125()) {
|
||||
break; // still not stable. Stay in the initialization state.
|
||||
}
|
||||
oldFilamentPresent =
|
||||
getFilamentPresent(); // initialize the current filament state so that we don't create a switching event right after the sensor is ready.
|
||||
oldPos = pat9125_y;
|
||||
state = State::ready;
|
||||
break;
|
||||
case State::ready: {
|
||||
updatePAT9125();
|
||||
postponedLoadEvent = false;
|
||||
bool event = checkFilamentEvents();
|
||||
|
||||
; //
|
||||
|
||||
return event;
|
||||
} break;
|
||||
case State::disabled:
|
||||
case State::error:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef FSENSOR_PROBING
|
||||
bool PAT9125_sensor::probeOtherType() {
|
||||
SET_INPUT(IR_SENSOR_PIN); // input mode
|
||||
WRITE(IR_SENSOR_PIN, 1); // pullup
|
||||
_delay_us(100); // wait for the pullup to pull the line high (might be needed, not really sure. The internal pullups are quite weak and there might be a
|
||||
// long wire attached).
|
||||
bool fsensorDetected = !READ(IR_SENSOR_PIN);
|
||||
WRITE(IR_SENSOR_PIN, 0); // no pullup
|
||||
return fsensorDetected;
|
||||
}
|
||||
#endif
|
||||
|
||||
void PAT9125_sensor::setJamDetectionEnabled(bool state, bool updateEEPROM) {
|
||||
jamDetection = state;
|
||||
oldPos = pat9125_y;
|
||||
resetStepCount();
|
||||
jamErrCnt = 0;
|
||||
if (updateEEPROM) {
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FSENSOR_JAM_DETECTION, state);
|
||||
}
|
||||
}
|
||||
|
||||
void PAT9125_sensor::settings_init() {
|
||||
// puts_P(PSTR("settings_init"));
|
||||
Filament_sensor::settings_init_common();
|
||||
setJamDetectionEnabled(eeprom_read_byte((uint8_t *)EEPROM_FSENSOR_JAM_DETECTION));
|
||||
}
|
||||
|
||||
int16_t PAT9125_sensor::getStepCount() {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { return stepCount; }
|
||||
}
|
||||
|
||||
void PAT9125_sensor::resetStepCount() {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { stepCount = 0; }
|
||||
}
|
||||
|
||||
void PAT9125_sensor::filJam() {
|
||||
runoutEnabled = false;
|
||||
autoLoadEnabled = false;
|
||||
jamDetection = false;
|
||||
stop_and_save_print_to_ram(0, 0);
|
||||
restore_print_from_ram_and_continue(0);
|
||||
eeprom_update_byte((uint8_t *)EEPROM_FERROR_COUNT, eeprom_read_byte((uint8_t *)EEPROM_FERROR_COUNT) + 1);
|
||||
eeprom_update_word((uint16_t *)EEPROM_FERROR_COUNT_TOT, eeprom_read_word((uint16_t *)EEPROM_FERROR_COUNT_TOT) + 1);
|
||||
enquecommand_front_P((PSTR("M600")));
|
||||
}
|
||||
|
||||
bool PAT9125_sensor::updatePAT9125() {
|
||||
if (jamDetection) {
|
||||
int16_t _stepCount = getStepCount();
|
||||
if (abs(_stepCount) >= chunkSteps) { // end of chunk. Check distance
|
||||
resetStepCount();
|
||||
if (!pat9125_update()) { // get up to date data. reinit on error.
|
||||
init(); // try to reinit.
|
||||
}
|
||||
bool fsDir = (pat9125_y - oldPos) > 0;
|
||||
bool stDir = _stepCount > 0;
|
||||
if (fsDir != stDir) {
|
||||
jamErrCnt++;
|
||||
} else if (jamErrCnt) {
|
||||
jamErrCnt--;
|
||||
}
|
||||
oldPos = pat9125_y;
|
||||
}
|
||||
if (jamErrCnt > 10) {
|
||||
jamErrCnt = 0;
|
||||
filJam();
|
||||
}
|
||||
}
|
||||
|
||||
if (!pollingTimer.running() || pollingTimer.expired(pollingPeriod)) {
|
||||
pollingTimer.start();
|
||||
if (!pat9125_update()) {
|
||||
init(); // try to reinit.
|
||||
}
|
||||
|
||||
bool present = (pat9125_s < 17) || (pat9125_s >= 17 && pat9125_b >= 50);
|
||||
if (present != filterFilPresent) {
|
||||
filter++;
|
||||
} else if (filter) {
|
||||
filter--;
|
||||
}
|
||||
if (filter >= filterCnt) {
|
||||
filter = 0;
|
||||
filterFilPresent = present;
|
||||
}
|
||||
}
|
||||
return (filter == 0); // return stability
|
||||
}
|
||||
#endif // #if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
|
|
@ -0,0 +1,214 @@
|
|||
#pragma once
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "cmdqueue.h"
|
||||
#include "pins.h"
|
||||
#include "fastio.h"
|
||||
#include "adc.h"
|
||||
#include "pat9125.h"
|
||||
|
||||
#define FSENSOR_IR 1
|
||||
#define FSENSOR_IR_ANALOG 2
|
||||
#define FSENSOR_PAT9125 3
|
||||
|
||||
/// Can be used to block printer's filament sensor handling - to avoid errorneous injecting of M600
|
||||
/// while doing a toolchange with the MMU
|
||||
/// In case of "no filament sensor" these methods default to an empty implementation
|
||||
class FSensorBlockRunout {
|
||||
public:
|
||||
FSensorBlockRunout();
|
||||
~FSensorBlockRunout();
|
||||
};
|
||||
|
||||
/// Base class Filament sensor
|
||||
///
|
||||
/// Ideally, there could have been a nice class hierarchy of filament sensor types with common functionality
|
||||
/// extracted into this base class.
|
||||
/// But:
|
||||
/// - virtual methods take more space
|
||||
/// - we don't need to switch among different filament sensors at runtime
|
||||
/// Therefore the class hierarchy carefully avoids using virtual methods and doesn't look too fancy.
|
||||
#ifdef FILAMENT_SENSOR
|
||||
class Filament_sensor {
|
||||
public:
|
||||
enum class State : uint8_t {
|
||||
disabled = 0,
|
||||
initializing,
|
||||
ready,
|
||||
error,
|
||||
};
|
||||
|
||||
enum class SensorActionOnError : uint8_t {
|
||||
_Continue = 0,
|
||||
_Pause = 1,
|
||||
_Undef = EEPROM_EMPTY_VALUE
|
||||
};
|
||||
|
||||
static void setEnabled(bool enabled);
|
||||
|
||||
void setAutoLoadEnabled(bool state, bool updateEEPROM = false);
|
||||
bool getAutoLoadEnabled() const { return autoLoadEnabled; }
|
||||
|
||||
void setRunoutEnabled(bool state, bool updateEEPROM = false);
|
||||
bool getRunoutEnabled() const { return runoutEnabled; }
|
||||
|
||||
void setActionOnError(SensorActionOnError state, bool updateEEPROM = false);
|
||||
SensorActionOnError getActionOnError() const { return sensorActionOnError; }
|
||||
|
||||
bool getFilamentLoadEvent() const { return postponedLoadEvent; }
|
||||
|
||||
bool isError() const { return state == State::error; }
|
||||
bool isReady() const { return state == State::ready; }
|
||||
bool isEnabled() const { return state != State::disabled; }
|
||||
|
||||
protected:
|
||||
void settings_init_common();
|
||||
|
||||
bool checkFilamentEvents();
|
||||
|
||||
void triggerFilamentInserted();
|
||||
|
||||
void triggerFilamentRemoved();
|
||||
|
||||
static void filAutoLoad();
|
||||
|
||||
void filRunout();
|
||||
|
||||
void triggerError();
|
||||
|
||||
State state;
|
||||
bool autoLoadEnabled;
|
||||
bool runoutEnabled;
|
||||
bool oldFilamentPresent; //for creating filament presence switching events.
|
||||
bool postponedLoadEvent; //this event lasts exactly one update cycle. It is long enough to be able to do polling for load event.
|
||||
ShortTimer eventBlankingTimer;
|
||||
SensorActionOnError sensorActionOnError;
|
||||
};
|
||||
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
class IR_sensor: public Filament_sensor {
|
||||
public:
|
||||
void init();
|
||||
void deinit();
|
||||
bool update();
|
||||
bool getFilamentPresent() const { return !READ(IR_SENSOR_PIN); }
|
||||
#ifdef FSENSOR_PROBING
|
||||
static bool probeOtherType(); //checks if the wrong fsensor type is detected.
|
||||
#endif
|
||||
void settings_init();
|
||||
};
|
||||
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
constexpr static uint16_t Voltage2Raw(float V) {
|
||||
return (V * 1023 * OVERSAMPLENR / VOLT_DIV_REF ) + 0.5F;
|
||||
}
|
||||
constexpr static float Raw2Voltage(uint16_t raw) {
|
||||
return VOLT_DIV_REF * (raw / (1023.F * OVERSAMPLENR));
|
||||
}
|
||||
|
||||
class IR_sensor_analog: public IR_sensor {
|
||||
public:
|
||||
void init();
|
||||
bool update();
|
||||
void voltUpdate(uint16_t raw);
|
||||
|
||||
uint16_t __attribute__((noinline)) getVoltRaw();
|
||||
|
||||
enum class SensorRevision : uint8_t {
|
||||
_Old = 0,
|
||||
_Rev04 = 1,
|
||||
_Undef = EEPROM_EMPTY_VALUE
|
||||
};
|
||||
|
||||
SensorRevision getSensorRevision() const { return sensorRevision; }
|
||||
|
||||
const char* __attribute__((noinline)) getIRVersionText();
|
||||
|
||||
void setSensorRevision(SensorRevision rev, bool updateEEPROM = false);
|
||||
|
||||
constexpr static uint16_t IRsensor_Ldiode_TRESHOLD = Voltage2Raw(0.3F); // ~0.3V, raw value=982
|
||||
constexpr static uint16_t IRsensor_Lmax_TRESHOLD = Voltage2Raw(1.5F); // ~1.5V (0.3*Vcc), raw value=4910
|
||||
constexpr static uint16_t IRsensor_Hmin_TRESHOLD = Voltage2Raw(3.0F); // ~3.0V (0.6*Vcc), raw value=9821
|
||||
constexpr static uint16_t IRsensor_Hopen_TRESHOLD = Voltage2Raw(4.6F); // ~4.6V (N.C. @ Ru~20-50k, Rd'=56k, Ru'=10k), raw value=15059
|
||||
constexpr static uint16_t IRsensor_VMax_TRESHOLD = Voltage2Raw(5.F); // ~5V, raw value=16368
|
||||
|
||||
private:
|
||||
SensorRevision sensorRevision;
|
||||
|
||||
bool voltReady; // set by the adc ISR, therefore avoid accessing the variable directly but use getVoltReady()
|
||||
bool getVoltReady()const;
|
||||
void clearVoltReady();
|
||||
|
||||
uint16_t voltRaw; // set by the adc ISR, therefore avoid accessing the variable directly but use getVoltRaw()
|
||||
bool checkVoltage(uint16_t raw);
|
||||
|
||||
uint16_t minVolt = Voltage2Raw(6.F);
|
||||
uint16_t maxVolt = 0;
|
||||
uint16_t nFSCheckCount;
|
||||
uint8_t voltageErrorCnt;
|
||||
|
||||
static constexpr uint16_t FS_CHECK_COUNT = 4;
|
||||
/// Switching mechanism of the fsensor type.
|
||||
/// Called from 2 spots which have a very similar behavior
|
||||
/// 1: SensorRevision::_Old -> SensorRevision::_Rev04 and print _i("FS v0.4 or newer")
|
||||
/// 2: SensorRevision::_Rev04 -> sensorRevision=SensorRevision::_Old and print _i("FS v0.3 or older")
|
||||
void IR_ANALOG_Check(SensorRevision isVersion, SensorRevision switchTo);
|
||||
};
|
||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_IR) || (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
class PAT9125_sensor: public Filament_sensor {
|
||||
public:
|
||||
void init();
|
||||
void deinit();
|
||||
bool update();
|
||||
bool getFilamentPresent() const { return filterFilPresent; }
|
||||
#ifdef FSENSOR_PROBING
|
||||
bool probeOtherType(); //checks if the wrong fsensor type is detected.
|
||||
#endif
|
||||
|
||||
void setJamDetectionEnabled(bool state, bool updateEEPROM = false);
|
||||
bool getJamDetectionEnabled() const { return jamDetection; }
|
||||
|
||||
void stStep(bool rev) { //from stepper isr
|
||||
stepCount += rev ? -1 : 1;
|
||||
}
|
||||
|
||||
void settings_init();
|
||||
private:
|
||||
static constexpr uint16_t pollingPeriod = 10; //[ms]
|
||||
static constexpr uint8_t filterCnt = 5; //how many checks need to be done in order to determine the filament presence precisely.
|
||||
ShortTimer pollingTimer;
|
||||
uint8_t filter;
|
||||
uint8_t filterFilPresent;
|
||||
|
||||
bool jamDetection;
|
||||
int16_t oldPos;
|
||||
int16_t stepCount;
|
||||
int16_t chunkSteps;
|
||||
uint8_t jamErrCnt;
|
||||
|
||||
constexpr void calcChunkSteps(float u) {
|
||||
chunkSteps = (int16_t)(1.25 * u); //[mm]
|
||||
}
|
||||
|
||||
int16_t getStepCount();
|
||||
|
||||
void resetStepCount();
|
||||
|
||||
void filJam();
|
||||
|
||||
bool updatePAT9125();
|
||||
};
|
||||
#endif //(FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
|
||||
#if FILAMENT_SENSOR_TYPE == FSENSOR_IR
|
||||
extern IR_sensor fsensor;
|
||||
#elif FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG
|
||||
extern IR_sensor_analog fsensor;
|
||||
#elif FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125
|
||||
extern PAT9125_sensor fsensor;
|
||||
#endif
|
||||
|
||||
#endif //FILAMENT_SENSOR
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
#include "Configuration.h"
|
||||
#include "pins.h"
|
||||
#include "Timer.h"
|
||||
#include "mmu2.h"
|
||||
extern uint8_t mbl_z_probe_nr;
|
||||
|
||||
#ifndef AT90USB
|
||||
|
|
@ -280,7 +281,6 @@ extern float max_pos[3];
|
|||
extern bool axis_known_position[3];
|
||||
extern int fanSpeed;
|
||||
extern uint8_t newFanSpeed;
|
||||
extern int8_t lcd_change_fil_state;
|
||||
extern float default_retraction;
|
||||
|
||||
void get_coordinates();
|
||||
|
|
@ -315,8 +315,9 @@ extern bool fan_state[2];
|
|||
extern int fan_edge_counter[2];
|
||||
extern int fan_speed[2];
|
||||
|
||||
// Handling multiple extruders pins
|
||||
extern uint8_t active_extruder;
|
||||
// Active extruder becomes a #define to make the whole firmware compilable.
|
||||
// We may even remove the references to it wherever possible in the future
|
||||
#define active_extruder 0
|
||||
|
||||
//Long pause
|
||||
extern unsigned long pause_time;
|
||||
|
|
@ -413,6 +414,7 @@ extern void print_physical_coordinates();
|
|||
extern void print_mesh_bed_leveling_table();
|
||||
|
||||
extern void stop_and_save_print_to_ram(float z_move, float e_move);
|
||||
void restore_extruder_temperature_from_ram();
|
||||
extern void restore_print_from_ram_and_continue(float e_move);
|
||||
extern void cancel_saved_printing();
|
||||
|
||||
|
|
@ -461,7 +463,7 @@ void gcode_M114();
|
|||
#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))))
|
||||
void gcode_M123();
|
||||
#endif //FANCHECK and TACH_0 and TACH_1
|
||||
void gcode_M701();
|
||||
void gcode_M701(float fastLoadLength, uint8_t mmuSlotIndex);
|
||||
|
||||
#define UVLO !(PINE & (1<<4))
|
||||
|
||||
|
|
@ -472,7 +474,8 @@ void M600_wait_for_user(float HotendTempBckp);
|
|||
void M600_check_state(float nozzle_temp);
|
||||
void load_filament_final_feed();
|
||||
void marlin_wait_for_click();
|
||||
void raise_z_above(float target, bool plan=true);
|
||||
float raise_z(float delta);
|
||||
void raise_z_above(float target);
|
||||
|
||||
extern "C" void softReset();
|
||||
void stack_error();
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -7,7 +7,7 @@
|
|||
#include "conv2str.h"
|
||||
#include "util.h"
|
||||
#include "ultralcd.h"
|
||||
#include "fsensor.h" //to be converted to Filament_sensor.h...
|
||||
#include "Filament_sensor.h"
|
||||
|
||||
#ifdef PRUSA_FARM
|
||||
uint8_t farm_mode = 0;
|
||||
|
|
@ -405,7 +405,7 @@ void farm_mode_init() {
|
|||
#ifdef FILAMENT_SENSOR
|
||||
//to be converted to Filament_sensor.h...
|
||||
//disabled filament autoload (PFW360)
|
||||
fsensor_autoload_set(false);
|
||||
fsensor.setAutoLoadEnabled(false);
|
||||
#endif //FILAMENT_SENSOR
|
||||
// ~ FanCheck -> on
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED, true);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@
|
|||
#define NC_TIME 10 //time in s for periodic important status messages sending which needs reponse from monitoring
|
||||
#define NC_BUTTON_LONG_PRESS 15 //time in s
|
||||
|
||||
//#define FARM_CONNECT_MESSAGE
|
||||
|
||||
#ifdef PRUSA_FARM
|
||||
extern uint8_t farm_mode;
|
||||
#else
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
#include "SpoolJoin.h"
|
||||
#include "Marlin.h"
|
||||
#include "eeprom.h"
|
||||
#include "messages.h"
|
||||
#include "language.h"
|
||||
|
||||
namespace SpoolJoin {
|
||||
|
||||
SpoolJoin spooljoin;
|
||||
|
||||
SpoolJoin::SpoolJoin()
|
||||
: status(EEPROM::Unknown)
|
||||
, currentMMUSlot(0)
|
||||
{
|
||||
}
|
||||
|
||||
void SpoolJoin::updateSpoolJoinStatus(EEPROM newStatus)
|
||||
{
|
||||
status = newStatus;
|
||||
eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)status);
|
||||
}
|
||||
|
||||
void SpoolJoin::initSpoolJoinStatus()
|
||||
{
|
||||
EEPROM currentStatus = (EEPROM)eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN);
|
||||
if( currentStatus == EEPROM::Empty)
|
||||
{
|
||||
// By default SpoolJoin is disabled
|
||||
updateSpoolJoinStatus(EEPROM::Disabled);
|
||||
} else {
|
||||
updateSpoolJoinStatus(currentStatus);
|
||||
}
|
||||
|
||||
// Useful information to see during bootup
|
||||
SERIAL_ECHOPGM("SpoolJoin is ");
|
||||
if (isSpoolJoinEnabled())
|
||||
{
|
||||
SERIAL_ECHOLNRPGM(_O(MSG_ON));
|
||||
} else {
|
||||
SERIAL_ECHOLNRPGM(_O(MSG_OFF));
|
||||
}
|
||||
}
|
||||
|
||||
void SpoolJoin::toggleSpoolJoin()
|
||||
{
|
||||
if (eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN) == (uint8_t)EEPROM::Disabled)
|
||||
{
|
||||
eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)EEPROM::Enabled);
|
||||
} else {
|
||||
eeprom_write_byte((uint8_t*)EEPROM_SPOOL_JOIN, (uint8_t)EEPROM::Disabled);
|
||||
}
|
||||
}
|
||||
|
||||
bool SpoolJoin::isSpoolJoinEnabled()
|
||||
{
|
||||
if(eeprom_read_byte((uint8_t*)EEPROM_SPOOL_JOIN) == (uint8_t)EEPROM::Enabled) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void SpoolJoin::setSlot(uint8_t slot)
|
||||
{
|
||||
currentMMUSlot = slot;
|
||||
}
|
||||
|
||||
uint8_t SpoolJoin::nextSlot()
|
||||
{
|
||||
SERIAL_ECHOPGM("SpoolJoin: ");
|
||||
SERIAL_ECHO((int)currentMMUSlot);
|
||||
|
||||
if (currentMMUSlot >= 4) currentMMUSlot = 0;
|
||||
else currentMMUSlot++;
|
||||
|
||||
SERIAL_ECHOPGM(" -> ");
|
||||
SERIAL_ECHOLN((int)currentMMUSlot);
|
||||
|
||||
return currentMMUSlot;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
/// @file
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "eeprom.h"
|
||||
|
||||
// See documentation here: https://help.prusa3d.com/article/spooljoin-mmu2s_134252
|
||||
|
||||
namespace SpoolJoin {
|
||||
|
||||
class SpoolJoin {
|
||||
public:
|
||||
SpoolJoin();
|
||||
|
||||
enum class EEPROM : uint8_t {
|
||||
Unknown, ///< SpoolJoin is unknown while printer is booting up
|
||||
Enabled, ///< SpoolJoin is enabled in EEPROM
|
||||
Disabled, ///< SpoolJoin is disabled in EEPROM
|
||||
Empty = 0xFF ///< EEPROM has not been set before and all bits are 1 (0xFF) - either a new printer or user erased the memory
|
||||
};
|
||||
|
||||
/// @brief Called when EEPROM is ready to be read
|
||||
void initSpoolJoinStatus();
|
||||
|
||||
/// @brief Enable SpoolJoin
|
||||
inline void enableSpoolJoin() { updateSpoolJoinStatus(EEPROM::Enabled); };
|
||||
|
||||
/// @brief Disable SpoolJoin
|
||||
inline void disableSpoolJoin() { updateSpoolJoinStatus(EEPROM::Disabled); };
|
||||
|
||||
/// @brief Toggle SpoolJoin
|
||||
static void toggleSpoolJoin();
|
||||
|
||||
/// @brief Check if SpoolJoin is enabled
|
||||
/// @returns true if enabled, false if disabled
|
||||
bool isSpoolJoinEnabled();
|
||||
|
||||
/// @brief Update the saved MMU slot number so SpoolJoin can determine the next slot to use
|
||||
/// @param slot number of the slot to set
|
||||
void setSlot(uint8_t slot);
|
||||
|
||||
/// @brief Fetch the next slot number should count from 0 to 4.
|
||||
/// When filament slot 4 is depleted, the next slot should be 0.
|
||||
/// @returns the next slot, ranges from 0 to 4
|
||||
uint8_t nextSlot();
|
||||
|
||||
private:
|
||||
/// @brief Update EEPROM
|
||||
/// @param newStatus Status to write into EEPROM
|
||||
void updateSpoolJoinStatus(EEPROM newStatus);
|
||||
|
||||
/// @brief SpoolJoin status
|
||||
enum EEPROM status;
|
||||
|
||||
/// @brief Currently used slot, ranges from 0 to 4
|
||||
uint8_t currentMMUSlot;
|
||||
};
|
||||
|
||||
extern SpoolJoin spooljoin;
|
||||
|
||||
} // namespace SpoolJoin
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
#include "Tcodes.h"
|
||||
#include "SpoolJoin.h"
|
||||
#include "Marlin.h"
|
||||
#include "language.h"
|
||||
#include "messages.h"
|
||||
#include "mmu2.h"
|
||||
#include "stepper.h"
|
||||
#include "ultralcd.h"
|
||||
#include <avr/pgmspace.h>
|
||||
#include <ctype.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
|
||||
static const char duplicate_Tcode_ignored[] PROGMEM = "Duplicate T-code ignored.";
|
||||
|
||||
inline bool IsInvalidTCode(char *const s, uint8_t i) {
|
||||
return ((s[i] < '0' || s[i] > '4') && s[i] != '?' && s[i] != 'x' && s[i] != 'c');
|
||||
}
|
||||
|
||||
inline void TCodeInvalid() {
|
||||
SERIAL_ECHOLNPGM("Invalid T code.");
|
||||
}
|
||||
|
||||
struct SChooseFromMenu {
|
||||
uint8_t slot:7;
|
||||
uint8_t loadToNozzle:1;
|
||||
inline constexpr SChooseFromMenu(uint8_t slot, bool loadToNozzle):slot(slot), loadToNozzle(loadToNozzle){}
|
||||
inline constexpr SChooseFromMenu():slot(0), loadToNozzle(false) { }
|
||||
};
|
||||
|
||||
SChooseFromMenu TCodeChooseFromMenu() {
|
||||
if (MMU2::mmu2.Enabled()) {
|
||||
return SChooseFromMenu( choose_menu_P(_T(MSG_SELECT_FILAMENT), _T(MSG_FILAMENT)), true );
|
||||
} else {
|
||||
return SChooseFromMenu( choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER)), false );
|
||||
}
|
||||
}
|
||||
|
||||
void TCodes(char *const strchr_pointer, uint8_t codeValue) {
|
||||
uint8_t index = 1;
|
||||
for ( /*nothing*/ ; strchr_pointer[index] == ' ' || strchr_pointer[index] == '\t'; index++)
|
||||
;
|
||||
|
||||
strchr_pointer[index] = tolower(strchr_pointer[index]);
|
||||
|
||||
if (IsInvalidTCode(strchr_pointer, index)){
|
||||
TCodeInvalid();
|
||||
} else if (strchr_pointer[index] == 'x'){
|
||||
// load to extruder gears; if mmu is not present do nothing
|
||||
if (MMU2::mmu2.Enabled()) {
|
||||
MMU2::mmu2.tool_change(strchr_pointer[index], choose_menu_P(_T(MSG_SELECT_EXTRUDER), _T(MSG_EXTRUDER)));
|
||||
}
|
||||
} else if (strchr_pointer[index] == 'c'){
|
||||
// load from extruder gears to nozzle (nozzle should be preheated)
|
||||
if (MMU2::mmu2.Enabled()) {
|
||||
MMU2::mmu2.tool_change(strchr_pointer[index], MMU2::mmu2.get_current_tool());
|
||||
}
|
||||
} else {
|
||||
SChooseFromMenu selectedSlot;
|
||||
if (strchr_pointer[index] == '?') {
|
||||
selectedSlot = TCodeChooseFromMenu();
|
||||
/*} else if (MMU2::mmu2.Enabled() && SpoolJoin::spooljoin.isSpoolJoinEnabled()) {
|
||||
// TODO: What if the next slot has no filament?
|
||||
selectedSlot.slot = SpoolJoin::spooljoin.nextSlot();*/
|
||||
} else {
|
||||
selectedSlot.slot = codeValue;
|
||||
}
|
||||
st_synchronize();
|
||||
|
||||
if (MMU2::mmu2.Enabled()) {
|
||||
if (selectedSlot.slot == MMU2::mmu2.get_current_tool()){
|
||||
// don't execute the same T-code twice in a row
|
||||
puts_P(duplicate_Tcode_ignored);
|
||||
} else {
|
||||
#if defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
|
||||
if (EEPROM_MMU_CUTTER_ENABLED_always == eeprom_read_byte((uint8_t *)EEPROM_MMU_CUTTER_ENABLED)) {
|
||||
MMU2::mmu2.cut_filament(selectedSlot.slot);
|
||||
}
|
||||
#endif // defined(MMU_HAS_CUTTER) && defined(MMU_ALWAYS_CUT)
|
||||
if (selectedSlot.loadToNozzle){ // for single material usage with mmu
|
||||
MMU2::mmu2.load_filament_to_nozzle(selectedSlot.slot);
|
||||
} else {
|
||||
MMU2::mmu2.tool_change(selectedSlot.slot);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (selectedSlot.slot >= EXTRUDERS) {
|
||||
SERIAL_ECHO_START;
|
||||
SERIAL_ECHO('T');
|
||||
SERIAL_ECHOLN(selectedSlot.slot + '0');
|
||||
SERIAL_ECHOLNRPGM(_n("Invalid extruder")); ////MSG_INVALID_EXTRUDER
|
||||
} else {
|
||||
// @@TODO if (code_seen('F')) {
|
||||
// next_feedrate = code_value();
|
||||
// if (next_feedrate > 0.0) {
|
||||
// feedrate = next_feedrate;
|
||||
// }
|
||||
// }
|
||||
SERIAL_ECHO_START;
|
||||
SERIAL_ECHORPGM(_n("Active Extruder: ")); ////MSG_ACTIVE_EXTRUDER
|
||||
SERIAL_ECHOLN(active_extruder + '0'); // this is not changed in our FW at all, can be optimized away
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
/// @file
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
void TCodes(char * const strchr_pointer, uint8_t codeValue);
|
||||
|
|
@ -15,6 +15,8 @@ http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html
|
|||
# error "ADC_CHAN_MSK oes not match ADC_CHAN_CNT"
|
||||
#endif
|
||||
|
||||
#define VOLT_DIV_REF 5 //[V]
|
||||
|
||||
extern volatile uint8_t adc_channel;
|
||||
extern volatile uint16_t adc_values[ADC_CHAN_CNT];
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
#include <util/atomic.h>
|
||||
#include "cmdqueue.h"
|
||||
#include "cardreader.h"
|
||||
#include "ultralcd.h"
|
||||
|
|
@ -152,7 +153,7 @@ static bool cmdqueue_could_enqueue_front(size_t len_asked)
|
|||
// len_asked does not contain the zero terminator size.
|
||||
// This function may update bufindw, therefore for the power panic to work, this function must be called
|
||||
// with the interrupts disabled!
|
||||
static bool cmdqueue_could_enqueue_back(size_t len_asked, bool atomic_update = false)
|
||||
static bool __attribute__((noinline)) cmdqueue_could_enqueue_back(size_t len_asked)
|
||||
{
|
||||
// MAX_CMD_SIZE has to accommodate the zero terminator.
|
||||
if (len_asked >= MAX_CMD_SIZE)
|
||||
|
|
@ -162,61 +163,29 @@ static bool cmdqueue_could_enqueue_back(size_t len_asked, bool atomic_update = f
|
|||
// Full buffer.
|
||||
return false;
|
||||
|
||||
if (serial_count > 0) {
|
||||
// If there is some data stored starting at bufindw, len_asked is certainly smaller than
|
||||
// the allocated data buffer. Try to reserve a new buffer and to move the already received
|
||||
// serial data.
|
||||
// How much memory to reserve for the commands pushed to the front?
|
||||
// End of the queue, when pushing to the end.
|
||||
size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE);
|
||||
if (bufindw < bufindr)
|
||||
// Simple case. There is a contiguous space between the write buffer and the read buffer.
|
||||
return endw + CMDBUFFER_RESERVE_FRONT <= bufindr;
|
||||
// Otherwise the free space is split between the start and end.
|
||||
if (// Could one fit to the end, including the reserve?
|
||||
endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) ||
|
||||
// Could one fit to the end, and the reserve to the start?
|
||||
(endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr))
|
||||
return true;
|
||||
// Could one fit both to the start?
|
||||
if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) {
|
||||
// Mark the rest of the buffer as used.
|
||||
memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw);
|
||||
// and point to the start.
|
||||
// Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work.
|
||||
if (atomic_update)
|
||||
cli();
|
||||
bufindw = 0;
|
||||
if (atomic_update)
|
||||
sei();
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// How much memory to reserve for the commands pushed to the front?
|
||||
// End of the queue, when pushing to the end.
|
||||
size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE);
|
||||
if (bufindw < bufindr)
|
||||
// Simple case. There is a contiguous space between the write buffer and the read buffer.
|
||||
return endw + CMDBUFFER_RESERVE_FRONT <= bufindr;
|
||||
// Otherwise the free space is split between the start and end.
|
||||
if (// Could one fit to the end, including the reserve?
|
||||
endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) ||
|
||||
// Could one fit to the end, and the reserve to the start?
|
||||
(endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr))
|
||||
return true;
|
||||
// Could one fit both to the start?
|
||||
if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) {
|
||||
// Mark the rest of the buffer as used.
|
||||
memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw);
|
||||
// and point to the start.
|
||||
// Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work.
|
||||
if (atomic_update)
|
||||
cli();
|
||||
bufindw = 0;
|
||||
if (atomic_update)
|
||||
sei();
|
||||
return true;
|
||||
}
|
||||
// If there is some data stored starting at bufindw, len_asked is certainly smaller than
|
||||
// the allocated data buffer. Try to reserve a new buffer and to move the already received
|
||||
// serial data.
|
||||
// How much memory to reserve for the commands pushed to the front?
|
||||
// End of the queue, when pushing to the end.
|
||||
size_t endw = bufindw + len_asked + (1 + CMDHDRSIZE);
|
||||
if (bufindw < bufindr)
|
||||
// Simple case. There is a contiguous space between the write buffer and the read buffer.
|
||||
return endw + CMDBUFFER_RESERVE_FRONT <= bufindr;
|
||||
// Otherwise the free space is split between the start and end.
|
||||
if (// Could one fit to the end, including the reserve?
|
||||
endw + CMDBUFFER_RESERVE_FRONT <= sizeof(cmdbuffer) ||
|
||||
// Could one fit to the end, and the reserve to the start?
|
||||
(endw <= sizeof(cmdbuffer) && CMDBUFFER_RESERVE_FRONT <= bufindr))
|
||||
return true;
|
||||
// Could one fit both to the start?
|
||||
if (len_asked + (1 + CMDHDRSIZE) + CMDBUFFER_RESERVE_FRONT <= bufindr) {
|
||||
// Mark the rest of the buffer as used.
|
||||
memset(cmdbuffer+bufindw, 0, sizeof(cmdbuffer)-bufindw);
|
||||
// and point to the start.
|
||||
// Be careful! The bufindw needs to be changed atomically for the power panic & filament panic to work.
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { bufindw = 0; }
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
@ -368,7 +337,7 @@ void repeatcommand_front()
|
|||
void get_command()
|
||||
{
|
||||
// Test and reserve space for the new command string.
|
||||
if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE - 1, true))
|
||||
if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE - 1))
|
||||
return;
|
||||
|
||||
if (MYSERIAL.available() == RX_BUFFER_SIZE - 1) { //compare number of chars buffered in rx buffer with rx buffer size
|
||||
|
|
@ -515,7 +484,7 @@ void get_command()
|
|||
serial_count = 0; //clear buffer
|
||||
// Don't call cmdqueue_could_enqueue_back if there are no characters waiting
|
||||
// in the queue, as this function will reserve the memory.
|
||||
if (MYSERIAL.available() == 0 || ! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1, true))
|
||||
if (MYSERIAL.available() == 0 || ! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1))
|
||||
return;
|
||||
} // end of "end of line" processing
|
||||
else {
|
||||
|
|
@ -571,7 +540,7 @@ void get_command()
|
|||
// This is either an empty line, or a line with just a comment.
|
||||
// Continue to the following line, and continue accumulating the number of bytes
|
||||
// read from the sdcard into sd_count,
|
||||
// so that the lenght of the already read empty lines and comments will be added
|
||||
// so that the length of the already read empty lines and comments will be added
|
||||
// to the following non-empty line.
|
||||
return; // prevent cycling indefinitely - let manage_heaters do their job
|
||||
}
|
||||
|
|
@ -613,7 +582,7 @@ void get_command()
|
|||
if(card.eof()) break;
|
||||
|
||||
// The following line will reserve buffer space if available.
|
||||
if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1, true))
|
||||
if (! cmdqueue_could_enqueue_back(MAX_CMD_SIZE-1))
|
||||
return;
|
||||
}
|
||||
else
|
||||
|
|
|
|||
|
|
@ -43,7 +43,12 @@
|
|||
//#define PAT9125_I2C_ADDR 0x73 //ID=NC
|
||||
#define PAT9125_XRES 0
|
||||
#define PAT9125_YRES 240 // maximum resolution (5*X cpi)
|
||||
#define PAT9124_YRES_MM (5*PAT9125_YRES/25.4) // counts per mm
|
||||
#define PAT9125_YRES_MM (5*PAT9125_YRES/25.4) // counts per mm
|
||||
#define PAT9125_INVERT_X 0 //1 means flipped
|
||||
#define PAT9125_INVERT_Y 1 //1 means flipped
|
||||
#define PAT9125_SWAP_XY 0 //X is Y and Y is X
|
||||
#define PAT9125_12B_RES 1 //8bit or 12bit signed motion data
|
||||
#define PAT9125_NEW_INIT 1 //set to 1 to use the magic sequence provided by pixart.
|
||||
|
||||
//SM4 configuration
|
||||
#define SM4_DEFDELAY 500 //default step delay [us]
|
||||
|
|
@ -59,7 +64,7 @@
|
|||
//#define LANG_MODE 0 // primary language only
|
||||
#define LANG_MODE 1 // sec. language support
|
||||
|
||||
#define LANG_SIZE_RESERVED 0x3000 // reserved space for secondary language (12288 bytes).
|
||||
#define LANG_SIZE_RESERVED 0x3500 // reserved space for secondary language (13568 bytes).
|
||||
// 0x3D00 Maximum 15616 bytes as it depends on xflash_layout.h
|
||||
// 16 Languages max. per group including stock
|
||||
|
||||
|
|
|
|||
|
|
@ -105,6 +105,8 @@ if (eeprom_read_byte((uint8_t*)EEPROM_PINDA_TEMP_COMPENSATION) == 0xff) eeprom_u
|
|||
eeprom_update_dword((uint32_t *)EEPROM_TOTALTIME, 0);
|
||||
eeprom_update_dword((uint32_t *)EEPROM_FILAMENTUSED, 0);
|
||||
}
|
||||
//Set Cutter OFF if 0xff
|
||||
if (eeprom_read_byte((uint8_t*)EEPROM_MMU_CUTTER_ENABLED) == 0xff) eeprom_update_byte((uint8_t *)EEPROM_MMU_CUTTER_ENABLED, 0);
|
||||
}
|
||||
|
||||
//! @brief Get default sheet name for index
|
||||
|
|
|
|||
|
|
@ -216,10 +216,10 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
|
|||
| ^ | ^ | ^ | 01h 1 | ^ | Sound mode: __once__ | ^ | ^
|
||||
| ^ | ^ | ^ | 02h 1 | ^ | Sound mode: __silent__ | ^ | ^
|
||||
| ^ | ^ | ^ | 03h 1 | ^ | Sound mode: __assist__ | ^ | ^
|
||||
| 0x0ED6 3798 | bool | EEPROM_AUTO_DEPLETE | 01h 1 | ffh 255 | MMU2/s autodeplete: __on__ | ??? | D3 Ax0ed6 C1
|
||||
| 0x0ED6 3798 | bool | EEPROM_SPOOL_JOIN | 01h 1 | ffh 255 | MMU2/s autodeplete: __on__ | ??? | D3 Ax0ed6 C1
|
||||
| ^ | ^ | ^ | 00h 0 | ^ | MMU2/s autodeplete: __off__ | ^ | ^
|
||||
| 0x0ED5 3797 | bool | EEPROM_FSENS_OQ_MEASS_ENABLED | ??? | ffh 255 | PAT1925 ??? | ??? | D3 Ax0ed5 C1
|
||||
| ^ | ^ | ^ | ??? | ^ | PAT1925 ??? | ^ | ^
|
||||
| 0x0ED5 3797 | bool | EEPROM_FSENS_RUNOUT_ENABLED | 01h 1 | ffh 255 __P__ | Filament runout: __enabled__ | LCD menu | D3 Ax0ed5 C1
|
||||
| ^ | ^ | ^ | 00h 0 | ^ | Filament runout: __disabled__ | LCD menu | ^
|
||||
| 0x0ED3 3795 | uint16 | EEPROM_MMU_FAIL_TOT | ??? | ff ffh 65535 __S/P__ | MMU2/s total failures | ??? | D3 Ax0ed3 C2
|
||||
| 0x0ED2 3794 | uint8 | EEPROM_MMU_FAIL | ??? | ffh 255 __S/P__ | MMU2/s fails during print | ??? | D3 Ax0ed2 C1
|
||||
| 0x0ED0 3792 | uint16 | EEPROM_MMU_LOAD_FAIL_TOT | ??? | ff ffh 65535 __S/P__ | MMU2/s total load failures | ??? | D3 Ax0ed0 C2
|
||||
|
|
@ -333,6 +333,8 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
|
|||
| ^ | ^ | ^ | 03h 3 | ^ | bad_isr | ^ | ^
|
||||
| ^ | ^ | ^ | 04h 4 | ^ | bad_pullup_temp_isr | ^ | ^
|
||||
| ^ | ^ | ^ | 05h 5 | ^ | bad_pullup_step_isr | ^ | ^
|
||||
| 0x0D02 3320 | uint8_t | EEPROM_FSENSOR_JAM_DETECTION | 01h 1 | ff/01 | fsensor pat9125 jam detection feature | LCD menu | D3 Ax0d02 C1
|
||||
| 0x0D01 3319 | uint8_t | EEPROM_MMU_ENABLED | 01h 1 | ff/01 | MMU enabled | LCD menu | D3 Ax0d01 C1
|
||||
|
||||
| Address begin | Bit/Type | Name | Valid values | Default/FactoryReset | Description | Gcode/Function| Debug code
|
||||
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--:
|
||||
|
|
@ -492,11 +494,11 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
|
|||
|
||||
// Sound Mode
|
||||
#define EEPROM_SOUND_MODE (EEPROM_UVLO_TARGET_HOTEND-1) // uint8
|
||||
#define EEPROM_AUTO_DEPLETE (EEPROM_SOUND_MODE-1) //bool
|
||||
#define EEPROM_SPOOL_JOIN (EEPROM_SOUND_MODE-1) //bool
|
||||
|
||||
#define EEPROM_FSENS_OQ_MEASS_ENABLED (EEPROM_AUTO_DEPLETE - 1) //bool
|
||||
#define EEPROM_FSENS_RUNOUT_ENABLED (EEPROM_SPOOL_JOIN - 1) //bool
|
||||
|
||||
#define EEPROM_MMU_FAIL_TOT (EEPROM_FSENS_OQ_MEASS_ENABLED - 2) //uint16_t
|
||||
#define EEPROM_MMU_FAIL_TOT (EEPROM_FSENS_RUNOUT_ENABLED - 2) //uint16_t
|
||||
#define EEPROM_MMU_FAIL (EEPROM_MMU_FAIL_TOT - 1) //uint8_t
|
||||
|
||||
#define EEPROM_MMU_LOAD_FAIL_TOT (EEPROM_MMU_FAIL - 2) //uint16_t
|
||||
|
|
@ -556,8 +558,10 @@ static Sheets * const EEPROM_Sheets_base = (Sheets*)(EEPROM_SHEETS_BASE);
|
|||
#define EEPROM_TEMP_MODEL_W (EEPROM_TEMP_MODEL_Ta_corr-4) // float
|
||||
#define EEPROM_TEMP_MODEL_E (EEPROM_TEMP_MODEL_W-4) // float
|
||||
|
||||
#define EEPROM_FSENSOR_JAM_DETECTION (EEPROM_TEMP_MODEL_E-1) // uint8_t
|
||||
#define EEPROM_MMU_ENABLED (EEPROM_FSENSOR_JAM_DETECTION-1) // uint8_t
|
||||
//This is supposed to point to last item to allow EEPROM overrun check. Please update when adding new items.
|
||||
#define EEPROM_LAST_ITEM EEPROM_TEMP_MODEL_E
|
||||
#define EEPROM_LAST_ITEM EEPROM_MMU_ENABLED
|
||||
// !!!!!
|
||||
// !!!!! this is end of EEPROM section ... all updates MUST BE inserted before this mark !!!!!
|
||||
// !!!!!
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
#include "language.h"
|
||||
#include "Marlin.h"
|
||||
#include "cmdqueue.h"
|
||||
#include "mmu.h"
|
||||
#include "mmu2.h"
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
//! @brief Wait for preheat
|
||||
|
|
@ -34,7 +34,7 @@ void lay1cal_wait_preheat()
|
|||
//! @param filament filament to use (applies for MMU only)
|
||||
void lay1cal_load_filament(char *cmd_buffer, uint8_t filament)
|
||||
{
|
||||
if (mmu_enabled)
|
||||
if (MMU2::mmu2.Enabled())
|
||||
{
|
||||
enquecommand_P(PSTR("M83"));
|
||||
enquecommand_P(PSTR("G1 Y-3.0 F1000.0"));
|
||||
|
|
@ -73,7 +73,7 @@ void lay1cal_intro_line()
|
|||
cmd_intro_mmu_12,
|
||||
};
|
||||
|
||||
if (mmu_enabled)
|
||||
if (MMU2::mmu2.Enabled())
|
||||
{
|
||||
for (uint8_t i = 0; i < (sizeof(intro_mmu_cmd)/sizeof(intro_mmu_cmd[0])); ++i)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,734 +0,0 @@
|
|||
//! @file
|
||||
|
||||
#include "Marlin.h"
|
||||
|
||||
#include "fsensor.h"
|
||||
#include <avr/pgmspace.h>
|
||||
#include "pat9125.h"
|
||||
#include "stepper.h"
|
||||
#include "cmdqueue.h"
|
||||
#include "ultralcd.h"
|
||||
#include "mmu.h"
|
||||
#include "cardreader.h"
|
||||
|
||||
#include "adc.h"
|
||||
#include "temperature.h"
|
||||
#include "config.h"
|
||||
|
||||
//! @name Basic parameters
|
||||
//! @{
|
||||
#define FSENSOR_CHUNK_LEN 1.25 //!< filament sensor chunk length (mm)
|
||||
#define FSENSOR_ERR_MAX 4 //!< filament sensor maximum error/chunk count for runout detection
|
||||
|
||||
#define FSENSOR_SOFTERR_CMAX 3 //!< number of contiguous soft failures before a triggering a runout
|
||||
#define FSENSOR_SOFTERR_DELTA 30000 //!< maximum interval (ms) to consider soft failures contiguous
|
||||
//! @}
|
||||
|
||||
//! @name Optical quality measurement parameters
|
||||
//! @{
|
||||
#define FSENSOR_OQ_MAX_ES 2 //!< maximum sum of error blocks during filament recheck
|
||||
#define FSENSOR_OQ_MIN_YD 2 //!< minimum yd sum during filament check (counts per inch)
|
||||
#define FSENSOR_OQ_MIN_BR 80 //!< minimum brightness value
|
||||
#define FSENSOR_OQ_MAX_SH 10 //!< maximum shutter value
|
||||
//! @}
|
||||
|
||||
const char ERRMSG_PAT9125_NOT_RESP[] PROGMEM = "PAT9125 not responding (%d)!\n";
|
||||
|
||||
// PJ7 can not be used (does not have PinChangeInterrupt possibility)
|
||||
#define FSENSOR_INT_PIN 75 //!< filament sensor interrupt pin PJ4
|
||||
#define FSENSOR_INT_PIN_MASK 0x10 //!< filament sensor interrupt pin mask (bit4)
|
||||
#define FSENSOR_INT_PIN_PIN_REG PINJ // PIN register @ PJ4
|
||||
#define FSENSOR_INT_PIN_VECT PCINT1_vect // PinChange ISR @ PJ4
|
||||
#define FSENSOR_INT_PIN_PCMSK_REG PCMSK1 // PinChangeMaskRegister @ PJ4
|
||||
#define FSENSOR_INT_PIN_PCMSK_BIT PCINT13 // PinChange Interrupt / PinChange Enable Mask @ PJ4
|
||||
#define FSENSOR_INT_PIN_PCICR_BIT PCIE1 // PinChange Interrupt Enable / Flag @ PJ4
|
||||
|
||||
//! enabled = initialized and sampled every chunk event
|
||||
bool fsensor_enabled = true;
|
||||
//! runout watching is done in fsensor_update (called from main loop)
|
||||
bool fsensor_watch_runout = true;
|
||||
//! not responding - is set if any communication error occurred during initialization or readout
|
||||
bool fsensor_not_responding = false;
|
||||
|
||||
#ifdef PAT9125
|
||||
uint8_t fsensor_int_pin_old = 0;
|
||||
//! optical checking "chunk lenght" (already in steps)
|
||||
int16_t fsensor_chunk_len = 0;
|
||||
//! enable/disable quality meassurement
|
||||
bool fsensor_oq_meassure_enabled = false;
|
||||
//! number of errors, updated in ISR
|
||||
uint8_t fsensor_err_cnt = 0;
|
||||
//! variable for accumulating step count (updated callbacks from stepper and ISR)
|
||||
int16_t fsensor_st_cnt = 0;
|
||||
//! count of total sensor "soft" failures (filament status checks)
|
||||
uint8_t fsensor_softfail = 0;
|
||||
//! timestamp of last soft failure
|
||||
unsigned long fsensor_softfail_last = 0;
|
||||
//! count of soft failures within the configured time
|
||||
uint8_t fsensor_softfail_ccnt = 0;
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG_FSENSOR_LOG
|
||||
//! log flag: 0=log disabled, 1=log enabled
|
||||
uint8_t fsensor_log = 1;
|
||||
#endif //DEBUG_FSENSOR_LOG
|
||||
|
||||
|
||||
//! @name filament autoload variables
|
||||
//! @{
|
||||
|
||||
//! autoload feature enabled
|
||||
bool fsensor_autoload_enabled = true;
|
||||
//! autoload watching enable/disable flag
|
||||
bool fsensor_watch_autoload = false;
|
||||
|
||||
#ifdef PAT9125
|
||||
//
|
||||
uint16_t fsensor_autoload_y;
|
||||
//
|
||||
uint8_t fsensor_autoload_c;
|
||||
//
|
||||
uint32_t fsensor_autoload_last_millis;
|
||||
//
|
||||
uint8_t fsensor_autoload_sum;
|
||||
//! @}
|
||||
#endif
|
||||
|
||||
|
||||
//! @name filament optical quality measurement variables
|
||||
//! @{
|
||||
|
||||
//! Measurement enable/disable flag
|
||||
bool fsensor_oq_meassure = false;
|
||||
//! skip-chunk counter, for accurate measurement is necessary to skip first chunk...
|
||||
uint8_t fsensor_oq_skipchunk;
|
||||
//! number of samples from start of measurement
|
||||
uint8_t fsensor_oq_samples;
|
||||
//! sum of steps in positive direction movements
|
||||
uint16_t fsensor_oq_st_sum;
|
||||
//! sum of deltas in positive direction movements
|
||||
uint16_t fsensor_oq_yd_sum;
|
||||
//! sum of errors during measurement
|
||||
uint16_t fsensor_oq_er_sum;
|
||||
//! max error counter value during measurement
|
||||
uint8_t fsensor_oq_er_max;
|
||||
//! minimum delta value
|
||||
int16_t fsensor_oq_yd_min;
|
||||
//! maximum delta value
|
||||
int16_t fsensor_oq_yd_max;
|
||||
//! sum of shutter value
|
||||
uint16_t fsensor_oq_sh_sum;
|
||||
//! @}
|
||||
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
ClFsensorPCB oFsensorPCB;
|
||||
ClFsensorActionNA oFsensorActionNA;
|
||||
bool bIRsensorStateFlag=false;
|
||||
ShortTimer tIRsensorCheckTimer;
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
|
||||
void fsensor_stop_and_save_print(void)
|
||||
{
|
||||
puts_P(PSTR("fsensor_stop_and_save_print"));
|
||||
stop_and_save_print_to_ram(0, 0);
|
||||
fsensor_watch_runout = false;
|
||||
}
|
||||
|
||||
#ifdef PAT9125
|
||||
// Reset all internal counters to zero, including stepper callbacks
|
||||
void fsensor_reset_err_cnt()
|
||||
{
|
||||
fsensor_err_cnt = 0;
|
||||
pat9125_y = 0;
|
||||
st_reset_fsensor();
|
||||
}
|
||||
|
||||
void fsensor_set_axis_steps_per_unit(float u)
|
||||
{
|
||||
fsensor_chunk_len = (int16_t)(FSENSOR_CHUNK_LEN * u);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
void fsensor_restore_print_and_continue(void)
|
||||
{
|
||||
puts_P(PSTR("fsensor_restore_print_and_continue"));
|
||||
fsensor_watch_runout = true;
|
||||
#ifdef PAT9125
|
||||
fsensor_reset_err_cnt();
|
||||
#endif
|
||||
restore_print_from_ram_and_continue(0);
|
||||
}
|
||||
|
||||
// fsensor_checkpoint_print cuts the current print job at the current position,
|
||||
// allowing new instructions to be inserted in the middle
|
||||
void fsensor_checkpoint_print(void)
|
||||
{
|
||||
puts_P(PSTR("fsensor_checkpoint_print"));
|
||||
stop_and_save_print_to_ram(0, 0);
|
||||
restore_print_from_ram_and_continue(0);
|
||||
}
|
||||
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
const char* FsensorIRVersionText()
|
||||
{
|
||||
switch(oFsensorPCB)
|
||||
{
|
||||
case ClFsensorPCB::_Old:
|
||||
return _T(MSG_IR_03_OR_OLDER);
|
||||
case ClFsensorPCB::_Rev04:
|
||||
return _T(MSG_IR_04_OR_NEWER);
|
||||
default:
|
||||
return _T(MSG_IR_UNKNOWN);
|
||||
}
|
||||
}
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
|
||||
void fsensor_init(void)
|
||||
{
|
||||
#ifdef PAT9125
|
||||
uint8_t pat9125 = pat9125_init();
|
||||
printf_P(PSTR("PAT9125_init:%u\n"), pat9125);
|
||||
#endif //PAT9125
|
||||
uint8_t fsensor_enabled = eeprom_read_byte((uint8_t*)EEPROM_FSENSOR);
|
||||
fsensor_autoload_enabled=eeprom_read_byte((uint8_t*)EEPROM_FSENS_AUTOLOAD_ENABLED);
|
||||
fsensor_not_responding = false;
|
||||
#ifdef PAT9125
|
||||
uint8_t oq_meassure_enabled = eeprom_read_byte((uint8_t*)EEPROM_FSENS_OQ_MEASS_ENABLED);
|
||||
fsensor_oq_meassure_enabled = (oq_meassure_enabled == 1)?true:false;
|
||||
fsensor_set_axis_steps_per_unit(cs.axis_steps_per_unit[E_AXIS]);
|
||||
|
||||
if (!pat9125){
|
||||
fsensor_enabled = 0; //disable sensor
|
||||
fsensor_not_responding = true;
|
||||
}
|
||||
#endif //PAT9125
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
bIRsensorStateFlag=false;
|
||||
oFsensorPCB = (ClFsensorPCB)eeprom_read_byte((uint8_t*)EEPROM_FSENSOR_PCB);
|
||||
oFsensorActionNA = (ClFsensorActionNA)eeprom_read_byte((uint8_t*)EEPROM_FSENSOR_ACTION_NA);
|
||||
|
||||
// If the fsensor is not responding even at the start of the printer,
|
||||
// set this flag accordingly to show N/A in Settings->Filament sensor.
|
||||
// This is even valid for both fsensor board revisions (0.3 or older and 0.4).
|
||||
// Must be done after reading what type of fsensor board we have
|
||||
fsensor_not_responding = ! fsensor_IR_check();
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
if (fsensor_enabled){
|
||||
fsensor_enable(false); // (in this case) EEPROM update is not necessary
|
||||
} else {
|
||||
fsensor_disable(false); // (in this case) EEPROM update is not necessary
|
||||
}
|
||||
printf_P(PSTR("FSensor %S"), (fsensor_enabled?PSTR("ENABLED"):PSTR("DISABLED")));
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
printf_P(PSTR(" (sensor board revision:%S)\n"), FsensorIRVersionText());
|
||||
#else //IR_SENSOR_ANALOG
|
||||
MYSERIAL.println();
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
if (check_for_ir_sensor()){
|
||||
ir_sensor_detected = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool fsensor_enable(bool bUpdateEEPROM)
|
||||
{
|
||||
#ifdef PAT9125
|
||||
(void)bUpdateEEPROM; // silence unused warning in this variant
|
||||
|
||||
if (mmu_enabled == false) { //filament sensor is pat9125, enable only if it is working
|
||||
uint8_t pat9125 = pat9125_init();
|
||||
printf_P(PSTR("PAT9125_init:%u\n"), pat9125);
|
||||
if (pat9125)
|
||||
fsensor_not_responding = false;
|
||||
else
|
||||
fsensor_not_responding = true;
|
||||
fsensor_enabled = pat9125 ? true : false;
|
||||
fsensor_watch_runout = true;
|
||||
fsensor_oq_meassure = false;
|
||||
fsensor_reset_err_cnt();
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, fsensor_enabled ? 0x01 : 0x00);
|
||||
FSensorStateMenu = fsensor_enabled ? 1 : 0;
|
||||
}
|
||||
else //filament sensor is FINDA, always enable
|
||||
{
|
||||
fsensor_enabled = true;
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, 0x01);
|
||||
FSensorStateMenu = 1;
|
||||
}
|
||||
#else // PAT9125
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
if(!fsensor_IR_check())
|
||||
{
|
||||
bUpdateEEPROM=true;
|
||||
fsensor_enabled=false;
|
||||
fsensor_not_responding=true;
|
||||
FSensorStateMenu=0;
|
||||
}
|
||||
else {
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
fsensor_enabled=true;
|
||||
fsensor_not_responding=false;
|
||||
FSensorStateMenu=1;
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
}
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
if(bUpdateEEPROM)
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, FSensorStateMenu);
|
||||
#endif //PAT9125
|
||||
return fsensor_enabled;
|
||||
}
|
||||
|
||||
void fsensor_disable(bool bUpdateEEPROM)
|
||||
{
|
||||
fsensor_enabled = false;
|
||||
FSensorStateMenu = 0;
|
||||
if(bUpdateEEPROM)
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FSENSOR, 0x00);
|
||||
}
|
||||
|
||||
void fsensor_autoload_set(bool State)
|
||||
{
|
||||
#ifdef PAT9125
|
||||
if (!State) fsensor_autoload_check_stop();
|
||||
#endif //PAT9125
|
||||
fsensor_autoload_enabled = State;
|
||||
eeprom_update_byte((unsigned char *)EEPROM_FSENS_AUTOLOAD_ENABLED, fsensor_autoload_enabled);
|
||||
}
|
||||
|
||||
void pciSetup(byte pin)
|
||||
{
|
||||
// !!! "digitalPinTo?????bit()" does not provide the correct results for some MCU pins
|
||||
*digitalPinToPCMSK(pin) |= bit (digitalPinToPCMSKbit(pin)); // enable pin
|
||||
PCIFR |= bit (digitalPinToPCICRbit(pin)); // clear any outstanding interrupt
|
||||
PCICR |= bit (digitalPinToPCICRbit(pin)); // enable interrupt for the group
|
||||
}
|
||||
|
||||
#ifdef PAT9125
|
||||
void fsensor_autoload_check_start(void)
|
||||
{
|
||||
// puts_P(_N("fsensor_autoload_check_start\n"));
|
||||
if (!fsensor_enabled) return;
|
||||
if (!fsensor_autoload_enabled) return;
|
||||
if (fsensor_watch_autoload) return;
|
||||
if (!pat9125_update()) //update sensor
|
||||
{
|
||||
fsensor_disable();
|
||||
fsensor_not_responding = true;
|
||||
fsensor_watch_autoload = false;
|
||||
printf_P(ERRMSG_PAT9125_NOT_RESP, 3);
|
||||
return;
|
||||
}
|
||||
puts_P(_N("fsensor_autoload_check_start - autoload ENABLED"));
|
||||
fsensor_autoload_y = pat9125_y; //save current y value
|
||||
fsensor_autoload_c = 0; //reset number of changes counter
|
||||
fsensor_autoload_sum = 0;
|
||||
fsensor_autoload_last_millis = _millis();
|
||||
fsensor_watch_runout = false;
|
||||
fsensor_watch_autoload = true;
|
||||
}
|
||||
|
||||
|
||||
void fsensor_autoload_check_stop(void)
|
||||
{
|
||||
// puts_P(_N("fsensor_autoload_check_stop\n"));
|
||||
if (!fsensor_enabled) return;
|
||||
// puts_P(_N("fsensor_autoload_check_stop 1\n"));
|
||||
if (!fsensor_autoload_enabled) return;
|
||||
// puts_P(_N("fsensor_autoload_check_stop 2\n"));
|
||||
if (!fsensor_watch_autoload) return;
|
||||
puts_P(_N("fsensor_autoload_check_stop - autoload DISABLED"));
|
||||
fsensor_autoload_sum = 0;
|
||||
fsensor_watch_autoload = false;
|
||||
fsensor_watch_runout = true;
|
||||
fsensor_reset_err_cnt();
|
||||
}
|
||||
#endif //PAT9125
|
||||
|
||||
bool fsensor_check_autoload(void)
|
||||
{
|
||||
if (!fsensor_enabled) return false;
|
||||
if (!fsensor_autoload_enabled) return false;
|
||||
if (ir_sensor_detected) {
|
||||
if (READ(IR_SENSOR_PIN)) {
|
||||
fsensor_watch_autoload = true;
|
||||
}
|
||||
else if (fsensor_watch_autoload == true) {
|
||||
fsensor_watch_autoload = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#ifdef PAT9125
|
||||
if (!fsensor_watch_autoload)
|
||||
{
|
||||
fsensor_autoload_check_start();
|
||||
return false;
|
||||
}
|
||||
#if 0
|
||||
uint8_t fsensor_autoload_c_old = fsensor_autoload_c;
|
||||
#endif
|
||||
if ((_millis() - fsensor_autoload_last_millis) < 25) return false;
|
||||
fsensor_autoload_last_millis = _millis();
|
||||
if (!pat9125_update_y()) //update sensor
|
||||
{
|
||||
fsensor_disable();
|
||||
fsensor_not_responding = true;
|
||||
printf_P(ERRMSG_PAT9125_NOT_RESP, 2);
|
||||
return false;
|
||||
}
|
||||
int16_t dy = pat9125_y - fsensor_autoload_y;
|
||||
if (dy) //? dy value is nonzero
|
||||
{
|
||||
if (dy > 0) //? delta-y value is positive (inserting)
|
||||
{
|
||||
fsensor_autoload_sum += dy;
|
||||
fsensor_autoload_c += 3; //increment change counter by 3
|
||||
}
|
||||
else if (fsensor_autoload_c > 1)
|
||||
fsensor_autoload_c -= 2; //decrement change counter by 2
|
||||
fsensor_autoload_y = pat9125_y; //save current value
|
||||
}
|
||||
else if (fsensor_autoload_c > 0)
|
||||
fsensor_autoload_c--;
|
||||
if (fsensor_autoload_c == 0) fsensor_autoload_sum = 0;
|
||||
#if 0
|
||||
puts_P(_N("fsensor_check_autoload\n"));
|
||||
if (fsensor_autoload_c != fsensor_autoload_c_old)
|
||||
printf_P(PSTR("fsensor_check_autoload dy=%d c=%d sum=%d\n"), dy, fsensor_autoload_c, fsensor_autoload_sum);
|
||||
#endif
|
||||
// if ((fsensor_autoload_c >= 15) && (fsensor_autoload_sum > 30))
|
||||
if ((fsensor_autoload_c >= 12) && (fsensor_autoload_sum > 20))
|
||||
{
|
||||
// puts_P(_N("fsensor_check_autoload = true !!!\n"));
|
||||
return true;
|
||||
}
|
||||
#endif //PAT9125
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef PAT9125
|
||||
void fsensor_oq_meassure_set(bool State)
|
||||
{
|
||||
fsensor_oq_meassure_enabled = State;
|
||||
eeprom_update_byte((unsigned char *)EEPROM_FSENS_OQ_MEASS_ENABLED, fsensor_oq_meassure_enabled);
|
||||
}
|
||||
|
||||
void fsensor_oq_meassure_start(uint8_t skip)
|
||||
{
|
||||
if (!fsensor_enabled) return;
|
||||
if (!fsensor_oq_meassure_enabled) return;
|
||||
puts_P(PSTR("fsensor_oq_meassure_start"));
|
||||
fsensor_oq_skipchunk = skip;
|
||||
fsensor_oq_samples = 0;
|
||||
fsensor_oq_st_sum = 0;
|
||||
fsensor_oq_yd_sum = 0;
|
||||
fsensor_oq_er_sum = 0;
|
||||
fsensor_oq_er_max = 0;
|
||||
fsensor_oq_yd_min = INT16_MAX;
|
||||
fsensor_oq_yd_max = 0;
|
||||
fsensor_oq_sh_sum = 0;
|
||||
pat9125_update();
|
||||
pat9125_y = 0;
|
||||
fsensor_oq_meassure = true;
|
||||
}
|
||||
|
||||
void fsensor_oq_meassure_stop(void)
|
||||
{
|
||||
if (!fsensor_enabled) return;
|
||||
if (!fsensor_oq_meassure_enabled) return;
|
||||
printf_P(PSTR("fsensor_oq_meassure_stop, %u samples\n"), fsensor_oq_samples);
|
||||
printf_P(_N(" st_sum=%u yd_sum=%u er_sum=%u er_max=%u\n"), fsensor_oq_st_sum, fsensor_oq_yd_sum, fsensor_oq_er_sum, fsensor_oq_er_max);
|
||||
printf_P(_N(" yd_min=%u yd_max=%u yd_avg=%u sh_avg=%u\n"), fsensor_oq_yd_min, fsensor_oq_yd_max, (uint16_t)((uint32_t)fsensor_oq_yd_sum * fsensor_chunk_len / fsensor_oq_st_sum), (uint16_t)(fsensor_oq_sh_sum / fsensor_oq_samples));
|
||||
fsensor_oq_meassure = false;
|
||||
}
|
||||
|
||||
#ifdef FSENSOR_QUALITY
|
||||
const char _OK[] PROGMEM = "OK";
|
||||
const char _NG[] PROGMEM = "NG!";
|
||||
|
||||
bool fsensor_oq_result(void)
|
||||
{
|
||||
if (!fsensor_enabled) return true;
|
||||
if (!fsensor_oq_meassure_enabled) return true;
|
||||
puts_P(_N("fsensor_oq_result"));
|
||||
bool res_er_sum = (fsensor_oq_er_sum <= FSENSOR_OQ_MAX_ES);
|
||||
printf_P(_N(" er_sum = %u %S\n"), fsensor_oq_er_sum, (res_er_sum?_OK:_NG));
|
||||
bool res_er_max = (fsensor_oq_er_max <= FSENSOR_OQ_MAX_EM);
|
||||
printf_P(_N(" er_max = %u %S\n"), fsensor_oq_er_max, (res_er_max?_OK:_NG));
|
||||
uint8_t yd_avg = ((uint32_t)fsensor_oq_yd_sum * fsensor_chunk_len / fsensor_oq_st_sum);
|
||||
bool res_yd_avg = (yd_avg >= FSENSOR_OQ_MIN_YD) && (yd_avg <= FSENSOR_OQ_MAX_YD);
|
||||
printf_P(_N(" yd_avg = %u %S\n"), yd_avg, (res_yd_avg?_OK:_NG));
|
||||
bool res_yd_max = (fsensor_oq_yd_max <= (yd_avg * FSENSOR_OQ_MAX_PD));
|
||||
printf_P(_N(" yd_max = %u %S\n"), fsensor_oq_yd_max, (res_yd_max?_OK:_NG));
|
||||
bool res_yd_min = (fsensor_oq_yd_min >= (yd_avg / FSENSOR_OQ_MAX_ND));
|
||||
printf_P(_N(" yd_min = %u %S\n"), fsensor_oq_yd_min, (res_yd_min?_OK:_NG));
|
||||
|
||||
uint16_t yd_dev = (fsensor_oq_yd_max - yd_avg) + (yd_avg - fsensor_oq_yd_min);
|
||||
printf_P(_N(" yd_dev = %u\n"), yd_dev);
|
||||
|
||||
uint16_t yd_qua = 10 * yd_avg / (yd_dev + 1);
|
||||
printf_P(_N(" yd_qua = %u %S\n"), yd_qua, ((yd_qua >= 8)?_OK:_NG));
|
||||
|
||||
uint8_t sh_avg = (fsensor_oq_sh_sum / fsensor_oq_samples);
|
||||
bool res_sh_avg = (sh_avg <= FSENSOR_OQ_MAX_SH);
|
||||
if (yd_qua >= 8) res_sh_avg = true;
|
||||
|
||||
printf_P(_N(" sh_avg = %u %S\n"), sh_avg, (res_sh_avg?_OK:_NG));
|
||||
bool res = res_er_sum && res_er_max && res_yd_avg && res_yd_max && res_yd_min && res_sh_avg;
|
||||
printf_P(_N("fsensor_oq_result %S\n"), (res?_OK:_NG));
|
||||
return res;
|
||||
}
|
||||
#endif //FSENSOR_QUALITY
|
||||
|
||||
FORCE_INLINE static void fsensor_isr(int st_cnt)
|
||||
{
|
||||
uint8_t old_err_cnt = fsensor_err_cnt;
|
||||
uint8_t pat9125_res = fsensor_oq_meassure?pat9125_update():pat9125_update_y();
|
||||
if (!pat9125_res)
|
||||
{
|
||||
fsensor_disable();
|
||||
fsensor_not_responding = true;
|
||||
printf_P(ERRMSG_PAT9125_NOT_RESP, 1);
|
||||
}
|
||||
|
||||
if (st_cnt != 0)
|
||||
{
|
||||
// movement was planned, check for sensor movement
|
||||
int8_t st_dir = st_cnt >= 0;
|
||||
int8_t pat9125_dir = pat9125_y >= 0;
|
||||
|
||||
if (pat9125_y == 0)
|
||||
{
|
||||
if (st_dir)
|
||||
{
|
||||
// no movement detected: we might be within a blind sensor range,
|
||||
// update the frame and shutter parameters we didn't earlier
|
||||
if (!fsensor_oq_meassure)
|
||||
pat9125_update_bs();
|
||||
|
||||
// increment the error count only if underexposed: filament likely missing
|
||||
if ((pat9125_b < FSENSOR_OQ_MIN_BR) && (pat9125_s > FSENSOR_OQ_MAX_SH))
|
||||
{
|
||||
// check for a dark frame (<30% avg brightness) with long exposure
|
||||
++fsensor_err_cnt;
|
||||
}
|
||||
else
|
||||
{
|
||||
// good frame, filament likely present
|
||||
if(fsensor_err_cnt) --fsensor_err_cnt;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (pat9125_dir != st_dir)
|
||||
{
|
||||
// detected direction opposite of motor movement
|
||||
if (st_dir) ++fsensor_err_cnt;
|
||||
}
|
||||
else if (pat9125_dir == st_dir)
|
||||
{
|
||||
// direction agreeing with planned movement
|
||||
if (fsensor_err_cnt) --fsensor_err_cnt;
|
||||
}
|
||||
|
||||
if (st_dir && fsensor_oq_meassure)
|
||||
{
|
||||
// extruding with quality assessment
|
||||
if (fsensor_oq_skipchunk)
|
||||
{
|
||||
fsensor_oq_skipchunk--;
|
||||
fsensor_err_cnt = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (st_cnt == fsensor_chunk_len)
|
||||
{
|
||||
if (pat9125_y > 0) if (fsensor_oq_yd_min > pat9125_y) fsensor_oq_yd_min = (fsensor_oq_yd_min + pat9125_y) / 2;
|
||||
if (pat9125_y >= 0) if (fsensor_oq_yd_max < pat9125_y) fsensor_oq_yd_max = (fsensor_oq_yd_max + pat9125_y) / 2;
|
||||
}
|
||||
fsensor_oq_samples++;
|
||||
fsensor_oq_st_sum += st_cnt;
|
||||
if (pat9125_y > 0) fsensor_oq_yd_sum += pat9125_y;
|
||||
if (fsensor_err_cnt > old_err_cnt)
|
||||
fsensor_oq_er_sum += (fsensor_err_cnt - old_err_cnt);
|
||||
if (fsensor_oq_er_max < fsensor_err_cnt)
|
||||
fsensor_oq_er_max = fsensor_err_cnt;
|
||||
fsensor_oq_sh_sum += pat9125_s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_FSENSOR_LOG
|
||||
if (fsensor_log)
|
||||
{
|
||||
printf_P(_N("FSENSOR cnt=%d dy=%d err=%u %S\n"), st_cnt, pat9125_y, fsensor_err_cnt, (fsensor_err_cnt > old_err_cnt)?_N("NG!"):_N("OK"));
|
||||
if (fsensor_oq_meassure) printf_P(_N("FSENSOR st_sum=%u yd_sum=%u er_sum=%u er_max=%u yd_max=%u\n"), fsensor_oq_st_sum, fsensor_oq_yd_sum, fsensor_oq_er_sum, fsensor_oq_er_max, fsensor_oq_yd_max);
|
||||
}
|
||||
#endif //DEBUG_FSENSOR_LOG
|
||||
|
||||
pat9125_y = 0;
|
||||
}
|
||||
|
||||
ISR(FSENSOR_INT_PIN_VECT)
|
||||
{
|
||||
if (mmu_enabled || ir_sensor_detected) return;
|
||||
if (!((fsensor_int_pin_old ^ FSENSOR_INT_PIN_PIN_REG) & FSENSOR_INT_PIN_MASK)) return;
|
||||
fsensor_int_pin_old = FSENSOR_INT_PIN_PIN_REG;
|
||||
|
||||
// prevent isr re-entry
|
||||
static bool _lock = false;
|
||||
if (!_lock)
|
||||
{
|
||||
// fetch fsensor_st_cnt atomically
|
||||
int st_cnt = fsensor_st_cnt;
|
||||
fsensor_st_cnt = 0;
|
||||
|
||||
_lock = true;
|
||||
sei();
|
||||
fsensor_isr(st_cnt);
|
||||
cli();
|
||||
_lock = false;
|
||||
}
|
||||
}
|
||||
|
||||
void fsensor_setup_interrupt(void)
|
||||
{
|
||||
WRITE(FSENSOR_INT_PIN, 0);
|
||||
SET_OUTPUT(FSENSOR_INT_PIN);
|
||||
fsensor_int_pin_old = 0;
|
||||
|
||||
//pciSetup(FSENSOR_INT_PIN);
|
||||
// !!! "pciSetup()" does not provide the correct results for some MCU pins
|
||||
// so interrupt registers settings:
|
||||
FSENSOR_INT_PIN_PCMSK_REG |= bit(FSENSOR_INT_PIN_PCMSK_BIT); // enable corresponding PinChangeInterrupt (individual pin)
|
||||
PCIFR |= bit(FSENSOR_INT_PIN_PCICR_BIT); // clear previous occasional interrupt (set of pins)
|
||||
PCICR |= bit(FSENSOR_INT_PIN_PCICR_BIT); // enable corresponding PinChangeInterrupt (set of pins)
|
||||
}
|
||||
|
||||
void fsensor_st_block_chunk(int cnt)
|
||||
{
|
||||
if (!fsensor_enabled) return;
|
||||
fsensor_st_cnt += cnt;
|
||||
|
||||
// !!! bit toggling (PINxn <- 1) (for PinChangeInterrupt) does not work for some MCU pins
|
||||
WRITE(FSENSOR_INT_PIN, !READ(FSENSOR_INT_PIN));
|
||||
}
|
||||
#endif //PAT9125
|
||||
|
||||
|
||||
//! Common code for enqueing M600 and supplemental codes into the command queue.
|
||||
//! Used both for the IR sensor and the PAT9125
|
||||
void fsensor_enque_M600(){
|
||||
puts_P(PSTR("fsensor_update - M600"));
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FERROR_COUNT, eeprom_read_byte((uint8_t*)EEPROM_FERROR_COUNT) + 1);
|
||||
eeprom_update_word((uint16_t*)EEPROM_FERROR_COUNT_TOT, eeprom_read_word((uint16_t*)EEPROM_FERROR_COUNT_TOT) + 1);
|
||||
enquecommand_front_P((PSTR("M600")));
|
||||
}
|
||||
|
||||
//! @brief filament sensor update (perform M600 on filament runout)
|
||||
//!
|
||||
//! Works only if filament sensor is enabled.
|
||||
//! When the filament sensor error count is larger then FSENSOR_ERR_MAX, pauses print, tries to move filament back and forth.
|
||||
//! If there is still no plausible signal from filament sensor plans M600 (Filament change).
|
||||
void fsensor_update(void)
|
||||
{
|
||||
#ifdef PAT9125
|
||||
if (fsensor_watch_runout && (fsensor_err_cnt > FSENSOR_ERR_MAX))
|
||||
{
|
||||
fsensor_stop_and_save_print();
|
||||
KEEPALIVE_STATE(IN_HANDLER);
|
||||
|
||||
bool autoload_enabled_tmp = fsensor_autoload_enabled;
|
||||
fsensor_autoload_enabled = false;
|
||||
bool oq_meassure_enabled_tmp = fsensor_oq_meassure_enabled;
|
||||
fsensor_oq_meassure_enabled = true;
|
||||
|
||||
// move the nozzle away while checking the filament
|
||||
current_position[Z_AXIS] += 0.8;
|
||||
if(current_position[Z_AXIS] > Z_MAX_POS) current_position[Z_AXIS] = Z_MAX_POS;
|
||||
plan_buffer_line_curposXYZE(max_feedrate[Z_AXIS]);
|
||||
st_synchronize();
|
||||
|
||||
// check the filament in isolation
|
||||
fsensor_reset_err_cnt();
|
||||
fsensor_oq_meassure_start(0);
|
||||
float e_tmp = current_position[E_AXIS];
|
||||
current_position[E_AXIS] -= 3;
|
||||
plan_buffer_line_curposXYZE(250/60);
|
||||
current_position[E_AXIS] = e_tmp;
|
||||
plan_buffer_line_curposXYZE(200/60);
|
||||
st_synchronize();
|
||||
fsensor_oq_meassure_stop();
|
||||
|
||||
bool err = false;
|
||||
err |= (fsensor_err_cnt > 0); // final error count is non-zero
|
||||
err |= (fsensor_oq_er_sum > FSENSOR_OQ_MAX_ES); // total error count is above limit
|
||||
err |= (fsensor_oq_yd_sum < FSENSOR_OQ_MIN_YD); // total measured distance is below limit
|
||||
|
||||
fsensor_restore_print_and_continue();
|
||||
fsensor_autoload_enabled = autoload_enabled_tmp;
|
||||
fsensor_oq_meassure_enabled = oq_meassure_enabled_tmp;
|
||||
unsigned long now = _millis();
|
||||
if (!err && (now - fsensor_softfail_last) > FSENSOR_SOFTERR_DELTA)
|
||||
fsensor_softfail_ccnt = 0;
|
||||
if (!err && fsensor_softfail_ccnt <= FSENSOR_SOFTERR_CMAX)
|
||||
{
|
||||
puts_P(PSTR("fsensor_err_cnt = 0"));
|
||||
++fsensor_softfail;
|
||||
++fsensor_softfail_ccnt;
|
||||
fsensor_softfail_last = now;
|
||||
}
|
||||
else
|
||||
{
|
||||
fsensor_softfail_ccnt = 0;
|
||||
fsensor_softfail_last = 0;
|
||||
fsensor_enque_M600();
|
||||
}
|
||||
}
|
||||
#else //PAT9125
|
||||
if (CHECK_FSENSOR && ir_sensor_detected)
|
||||
{
|
||||
if (READ(IR_SENSOR_PIN))
|
||||
{ // IR_SENSOR_PIN ~ H
|
||||
fsensor_checkpoint_print();
|
||||
fsensor_enque_M600();
|
||||
}
|
||||
}
|
||||
#endif //PAT9125
|
||||
}
|
||||
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
/// This is called only upon start of the printer or when switching the fsensor ON in the menu
|
||||
/// We cannot do temporal window checks here (aka the voltage has been in some range for a period of time)
|
||||
bool fsensor_IR_check(){
|
||||
if( IRsensor_Lmax_TRESHOLD <= current_voltage_raw_IR && current_voltage_raw_IR <= IRsensor_Hmin_TRESHOLD ){
|
||||
/// If the voltage is in forbidden range, the fsensor is ok, but the lever is mounted improperly.
|
||||
/// Or the user is so creative so that he can hold a piece of fillament in the hole in such a genius way,
|
||||
/// that the IR fsensor reading is within 1.5 and 3V ... this would have been highly unusual
|
||||
/// and would have been considered more like a sabotage than normal printer operation
|
||||
puts_P(PSTR("fsensor in forbidden range 1.5-3V - check sensor"));
|
||||
return false;
|
||||
}
|
||||
if( oFsensorPCB == ClFsensorPCB::_Rev04 ){
|
||||
/// newer IR sensor cannot normally produce 4.6-5V, this is considered a failure/bad mount
|
||||
if( IRsensor_Hopen_TRESHOLD <= current_voltage_raw_IR && current_voltage_raw_IR <= IRsensor_VMax_TRESHOLD ){
|
||||
puts_P(PSTR("fsensor v0.4 in fault range 4.6-5V - unconnected"));
|
||||
return false;
|
||||
}
|
||||
/// newer IR sensor cannot normally produce 0-0.3V, this is considered a failure
|
||||
#if 0 //Disabled as it has to be decided if we gonna use this or not.
|
||||
if( IRsensor_Hopen_TRESHOLD <= current_voltage_raw_IR && current_voltage_raw_IR <= IRsensor_VMax_TRESHOLD ){
|
||||
puts_P(PSTR("fsensor v0.4 in fault range 0.0-0.3V - wrong IR sensor"));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
/// If IR sensor is "uknown state" and filament is not loaded > 1.5V return false
|
||||
#if 0
|
||||
if( (oFsensorPCB == ClFsensorPCB::_Undef) && ( current_voltage_raw_IR > IRsensor_Lmax_TRESHOLD ) ){
|
||||
puts_P(PSTR("Unknown IR sensor version and no filament loaded detected."));
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
// otherwise the IR fsensor is considered working correctly
|
||||
return true;
|
||||
}
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
//! @file
|
||||
#ifndef FSENSOR_H
|
||||
#define FSENSOR_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "config.h"
|
||||
|
||||
|
||||
// enable/disable flag
|
||||
extern bool fsensor_enabled;
|
||||
// not responding flag
|
||||
extern bool fsensor_not_responding;
|
||||
#ifdef PAT9125
|
||||
// optical checking "chunk lenght" (already in steps)
|
||||
extern int16_t fsensor_chunk_len;
|
||||
// count of soft failures
|
||||
extern uint8_t fsensor_softfail;
|
||||
#endif
|
||||
|
||||
//! @name save restore printing
|
||||
//! @{
|
||||
extern void fsensor_stop_and_save_print(void);
|
||||
//! restore print - restore position and heatup to original temperature
|
||||
extern void fsensor_restore_print_and_continue(void);
|
||||
//! split the current gcode stream to insert new instructions
|
||||
extern void fsensor_checkpoint_print(void);
|
||||
//! @}
|
||||
|
||||
//! initialize
|
||||
extern void fsensor_init(void);
|
||||
|
||||
#ifdef PAT9125
|
||||
//! update axis resolution
|
||||
extern void fsensor_set_axis_steps_per_unit(float u);
|
||||
#endif
|
||||
|
||||
//! @name enable/disable
|
||||
//! @{
|
||||
extern bool fsensor_enable(bool bUpdateEEPROM=true);
|
||||
extern void fsensor_disable(bool bUpdateEEPROM=true);
|
||||
//! @}
|
||||
|
||||
//autoload feature enabled
|
||||
extern bool fsensor_autoload_enabled;
|
||||
extern void fsensor_autoload_set(bool State);
|
||||
|
||||
extern void fsensor_update(void);
|
||||
#ifdef PAT9125
|
||||
//! setup pin-change interrupt
|
||||
extern void fsensor_setup_interrupt(void);
|
||||
|
||||
//! @name autoload support
|
||||
//! @{
|
||||
|
||||
extern void fsensor_autoload_check_start(void);
|
||||
extern void fsensor_autoload_check_stop(void);
|
||||
#endif //PAT9125
|
||||
extern bool fsensor_check_autoload(void);
|
||||
//! @}
|
||||
|
||||
#ifdef PAT9125
|
||||
//! @name optical quality measurement support
|
||||
//! @{
|
||||
extern bool fsensor_oq_meassure_enabled;
|
||||
extern void fsensor_oq_meassure_set(bool State);
|
||||
extern void fsensor_oq_meassure_start(uint8_t skip);
|
||||
extern void fsensor_oq_meassure_stop(void);
|
||||
extern bool fsensor_oq_result(void);
|
||||
//! @}
|
||||
|
||||
//! @name callbacks from stepper
|
||||
//! @{
|
||||
extern void fsensor_st_block_chunk(int cnt);
|
||||
|
||||
// debugging
|
||||
extern uint8_t fsensor_log;
|
||||
|
||||
// There's really nothing to do in block_begin: the stepper ISR likely has
|
||||
// called us already at the end of the last block, making this integration
|
||||
// redundant. LA1.5 might not always do that during a coasting move, so attempt
|
||||
// to drain fsensor_st_cnt anyway at the beginning of the new block.
|
||||
#define fsensor_st_block_begin(rev) fsensor_st_block_chunk(0)
|
||||
//! @}
|
||||
#endif //PAT9125
|
||||
|
||||
#define VOLT_DIV_REF 5
|
||||
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
#define IR_SENSOR_STEADY 10 // [ms]
|
||||
|
||||
enum class ClFsensorPCB:uint_least8_t
|
||||
{
|
||||
_Old=0,
|
||||
_Rev04=1,
|
||||
_Undef=EEPROM_EMPTY_VALUE
|
||||
};
|
||||
|
||||
enum class ClFsensorActionNA:uint_least8_t
|
||||
{
|
||||
_Continue=0,
|
||||
_Pause=1,
|
||||
_Undef=EEPROM_EMPTY_VALUE
|
||||
};
|
||||
|
||||
extern ClFsensorPCB oFsensorPCB;
|
||||
extern ClFsensorActionNA oFsensorActionNA;
|
||||
extern const char* FsensorIRVersionText();
|
||||
|
||||
extern bool fsensor_IR_check();
|
||||
constexpr uint16_t Voltage2Raw(float V){
|
||||
return ( V * 1023 * OVERSAMPLENR / VOLT_DIV_REF ) + 0.5F;
|
||||
}
|
||||
constexpr float Raw2Voltage(uint16_t raw){
|
||||
return VOLT_DIV_REF*(raw / (1023.F * OVERSAMPLENR) );
|
||||
}
|
||||
constexpr uint16_t IRsensor_Ldiode_TRESHOLD = Voltage2Raw(0.3F); // ~0.3V, raw value=982
|
||||
constexpr uint16_t IRsensor_Lmax_TRESHOLD = Voltage2Raw(1.5F); // ~1.5V (0.3*Vcc), raw value=4910
|
||||
constexpr uint16_t IRsensor_Hmin_TRESHOLD = Voltage2Raw(3.0F); // ~3.0V (0.6*Vcc), raw value=9821
|
||||
constexpr uint16_t IRsensor_Hopen_TRESHOLD = Voltage2Raw(4.6F); // ~4.6V (N.C. @ Ru~20-50k, Rd'=56k, Ru'=10k), raw value=15059
|
||||
constexpr uint16_t IRsensor_VMax_TRESHOLD = Voltage2Raw(5.F); // ~5V, raw value=16368
|
||||
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
|
||||
#endif //FSENSOR_H
|
||||
|
|
@ -190,14 +190,14 @@ private:
|
|||
//Custom characters defined in the first 8 characters of the LCD
|
||||
#define LCD_STR_BEDTEMP "\x00"
|
||||
#define LCD_STR_DEGREE "\x01"
|
||||
#define LCD_STR_ARROW_2_DOWN "\x01"
|
||||
#define LCD_STR_THERMOMETER "\x02"
|
||||
#define LCD_STR_CONFIRM "\x02"
|
||||
#define LCD_STR_UPLEVEL "\x03"
|
||||
#define LCD_STR_REFRESH "\x04"
|
||||
#define LCD_STR_FOLDER "\x05"
|
||||
#define LCD_STR_FEEDRATE "\x06"
|
||||
#define LCD_STR_ARROW_2_DOWN "\x06"
|
||||
#define LCD_STR_CLOCK "\x07"
|
||||
#define LCD_STR_CONFIRM "\x07"
|
||||
#define LCD_STR_ARROW_RIGHT "\x7E" //from the default character set
|
||||
#define LCD_STR_SOLID_BLOCK "\xFF" //from the default character set
|
||||
|
||||
|
|
|
|||
|
|
@ -995,8 +995,7 @@ bool find_bed_induction_sensor_point_z(float minimum_z, uint8_t n_iter, int
|
|||
//printf_P(PSTR("Zs: %f, Z: %f, delta Z: %f"), z_bckp, current_position[Z_AXIS], (z_bckp - current_position[Z_AXIS]));
|
||||
if (fabs(current_position[Z_AXIS] - z_bckp) < 0.025) {
|
||||
//printf_P(PSTR("PINDA triggered immediately, move Z higher and repeat measurement\n"));
|
||||
current_position[Z_AXIS] += 0.5;
|
||||
go_to_current(homing_feedrate[Z_AXIS]/60);
|
||||
raise_z(0.5);
|
||||
current_position[Z_AXIS] = minimum_z;
|
||||
go_to_current(homing_feedrate[Z_AXIS]/(4*60));
|
||||
// we have to let the planner know where we are right now as it is not where we said to go.
|
||||
|
|
@ -2792,10 +2791,7 @@ canceled:
|
|||
bool sample_z() {
|
||||
bool sampled = true;
|
||||
//make space
|
||||
current_position[Z_AXIS] += 150;
|
||||
go_to_current(homing_feedrate[Z_AXIS] / 60);
|
||||
//plan_buffer_line_curposXYZE(feedrate, active_extruder););
|
||||
|
||||
raise_z(150);
|
||||
lcd_show_fullscreen_message_and_wait_P(_T(MSG_PLACE_STEEL_SHEET));
|
||||
|
||||
// Sample Z heights for the mesh bed leveling.
|
||||
|
|
|
|||
|
|
@ -41,7 +41,9 @@ const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1[] PROGMEM_I1 = ISTR("Searching bed
|
|||
const char MSG_FINISHING_MOVEMENTS[] PROGMEM_I1 = ISTR("Finishing movements"); ////MSG_FINISHING_MOVEMENTS c=20
|
||||
const char MSG_FOLLOW_CALIBRATION_FLOW[] PROGMEM_I1 = ISTR("Printer has not been calibrated yet. Please follow the manual, chapter First steps, section Calibration flow."); ////MSG_FOLLOW_CALIBRATION_FLOW c=20 r=8
|
||||
const char MSG_FOLLOW_Z_CALIBRATION_FLOW[] PROGMEM_I1 = ISTR("There is still a need to make Z calibration. Please follow the manual, chapter First steps, section Calibration flow."); ////MSG_FOLLOW_Z_CALIBRATION_FLOW c=20 r=9
|
||||
const char MSG_FSENSOR_RUNOUT[] PROGMEM_I1 = ISTR("F. runout"); ////MSG_FSENSOR_RUNOUT c=13
|
||||
const char MSG_FSENSOR_AUTOLOAD[] PROGMEM_I1 = ISTR("F. autoload"); ////MSG_FSENSOR_AUTOLOAD c=13
|
||||
const char MSG_FSENSOR_JAM_DETECTION[] PROGMEM_I1 = ISTR("F. jam detect"); ////MSG_FSENSOR_JAM_DETECTION c=13
|
||||
const char MSG_FSENSOR[] PROGMEM_I1 = ISTR("Fil. sensor"); ////MSG_FSENSOR c=12
|
||||
const char MSG_HEATING[] PROGMEM_I1 = ISTR("Heating"); ////MSG_HEATING c=20
|
||||
const char MSG_HEATING_COMPLETE[] PROGMEM_I1 = ISTR("Heating done."); ////MSG_HEATING_COMPLETE c=20
|
||||
|
|
@ -52,7 +54,9 @@ const char MSG_SELECT_FILAMENT[] PROGMEM_I1 = ISTR("Select filament:"); ////MSG_
|
|||
const char MSG_LAST_PRINT[] PROGMEM_I1 = ISTR("Last print"); ////MSG_LAST_PRINT c=18
|
||||
const char MSG_LAST_PRINT_FAILURES[] PROGMEM_I1 = ISTR("Last print failures"); ////MSG_LAST_PRINT_FAILURES c=20
|
||||
const char MSG_LOAD_FILAMENT[] PROGMEM_I1 = ISTR("Load filament"); ////MSG_LOAD_FILAMENT c=17
|
||||
const char MSG_LOAD_TO_EXTRUDER[] PROGMEM_I1 = ISTR("Load to extruder"); ////MSG_LOAD_TO_EXTRUDER c=18
|
||||
const char MSG_LOADING_FILAMENT[] PROGMEM_I1 = ISTR("Loading filament"); ////MSG_LOADING_FILAMENT c=20
|
||||
const char MSG_TESTING_FILAMENT[] PROGMEM_I1 = ISTR("Testing filament"); ////MSG_TESTING_FILAMENT c=20
|
||||
const char MSG_EJECT_FILAMENT[] PROGMEM_I1 = ISTR("Eject filament"); ////MSG_EJECT_FILAMENT c=17
|
||||
const char MSG_CUT_FILAMENT[] PROGMEM_I1 = ISTR("Cut filament"); ////MSG_CUT_FILAMENT c=17
|
||||
const char MSG_MAIN[] PROGMEM_I1 = ISTR("Main"); ////MSG_MAIN c=18
|
||||
|
|
@ -108,7 +112,7 @@ const char MSG_STOP_PRINT[] PROGMEM_I1 = ISTR("Stop print"); ////MSG_STOP_PRINT
|
|||
const char MSG_STOPPED[] PROGMEM_I1 = ISTR("STOPPED."); ////MSG_STOPPED c=20
|
||||
const char MSG_PINDA_CALIBRATION[] PROGMEM_I1 = ISTR("PINDA cal."); ////MSG_PINDA_CALIBRATION c=13
|
||||
const char MSG_PINDA_CALIBRATION_DONE[] PROGMEM_I1 = ISTR("PINDA calibration is finished and active. It can be disabled in menu Settings->PINDA cal."); ////MSG_PINDA_CALIBRATION_DONE c=20 r=8
|
||||
const char MSG_UNLOAD_FILAMENT[] PROGMEM_I1 = ISTR("Unload filament"); ////MSG_UNLOAD_FILAMENT c=18
|
||||
const char MSG_UNLOAD_FILAMENT[] PROGMEM_I1 = ISTR("Unload filament"); ////MSG_UNLOAD_FILAMENT c=16
|
||||
const char MSG_UNLOADING_FILAMENT[] PROGMEM_I1 = ISTR("Unloading filament"); ////MSG_UNLOADING_FILAMENT c=20
|
||||
const char MSG_INFO_SCREEN[] PROGMEM_I1 = ISTR("Info screen"); ////MSG_INFO_SCREEN c=18
|
||||
const char MSG_WIZARD_CALIBRATION_FAILED[] PROGMEM_I1 = ISTR("Please check our handbook and fix the problem. Then resume the Wizard by rebooting the printer."); ////MSG_WIZARD_CALIBRATION_FAILED c=20 r=8
|
||||
|
|
@ -116,7 +120,7 @@ const char MSG_WIZARD_DONE[] PROGMEM_I1 = ISTR("All is done. Happy printing!");
|
|||
const char MSG_WIZARD_HEATING[] PROGMEM_I1 = ISTR("Preheating nozzle. Please wait."); ////MSG_WIZARD_HEATING c=20 r=3
|
||||
const char MSG_WIZARD_QUIT[] PROGMEM_I1 = ISTR("You can always resume the Wizard from Calibration -> Wizard."); ////MSG_WIZARD_QUIT c=20 r=8
|
||||
const char MSG_WIZARD_WELCOME[] PROGMEM_I1 = ISTR("Hi, I am your Original Prusa i3 printer. Would you like me to guide you through the setup process?"); ////MSG_WIZARD_WELCOME c=20 r=7
|
||||
const char MSG_WIZARD_WELCOME_SHIPPING[] PROGMEM_I1 = ISTR("Hi, I am your Original Prusa i3 printer. I will guide you through a short setup process, in which the Z-axis will be calibrated. Then, you will be ready to print."); ////MSG_WIZARD_WELCOME_SHIPPING c=20 r=16
|
||||
const char MSG_WIZARD_WELCOME_SHIPPING[] PROGMEM_I1 = ISTR("Hi, I am your Original Prusa i3 printer. I will guide you through a short setup process, in which the Z-axis will be calibrated. Then, you will be ready to print."); ////MSG_WIZARD_WELCOME_SHIPPING c=20 r=12
|
||||
const char MSG_YES[] PROGMEM_I1 = ISTR("Yes"); ////MSG_YES c=4
|
||||
const char MSG_V2_CALIBRATION[] PROGMEM_I1 = ISTR("First layer cal."); ////MSG_V2_CALIBRATION c=18
|
||||
const char MSG_OFF[] PROGMEM_I1 = ISTR("Off"); ////MSG_OFF c=3
|
||||
|
|
@ -155,7 +159,7 @@ const char MSG_TIMEOUT[] PROGMEM_I1 = ISTR("Timeout"); ////MSG_TIMEOUT c=12
|
|||
const char MSG_BRIGHT[] PROGMEM_I1 = ISTR("Bright"); ////MSG_BRIGHT c=6
|
||||
const char MSG_DIM[] PROGMEM_I1 = ISTR("Dim"); ////MSG_DIM c=6
|
||||
const char MSG_AUTO[] PROGMEM_I1 = ISTR("Auto"); ////MSG_AUTO c=6
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
// Beware - the space at the beginning is necessary since it is reused in LCD menu items which are to be with a space
|
||||
const char MSG_IR_04_OR_NEWER[] PROGMEM_I1 = ISTR(" 0.4 or newer");////MSG_IR_04_OR_NEWER c=18
|
||||
const char MSG_IR_03_OR_OLDER[] PROGMEM_I1 = ISTR(" 0.3 or older");////MSG_IR_03_OR_OLDER c=18
|
||||
|
|
@ -165,9 +169,10 @@ extern const char MSG_PAUSED_THERMAL_ERROR[] PROGMEM_I1 = ISTR("PAUSED THERMAL E
|
|||
#ifdef TEMP_MODEL
|
||||
extern const char MSG_THERMAL_ANOMALY[] PROGMEM_I1 = ISTR("THERMAL ANOMALY");////MSG_THERMAL_ANOMALY c=20
|
||||
#endif
|
||||
extern const char MSG_LOAD_ALL[] PROGMEM_I1 = ISTR("Load All"); ////MSG_LOAD_ALL c=18
|
||||
|
||||
//not internationalized messages
|
||||
const char MSG_AUTO_DEPLETE[] PROGMEM_N1 = "SpoolJoin"; ////MSG_AUTO_DEPLETE c=13
|
||||
const char MSG_SPOOL_JOIN[] PROGMEM_N1 = "SpoolJoin"; ////MSG_SPOOL_JOIN c=13
|
||||
const char MSG_FIRMWARE[] PROGMEM_N1 = "Firmware"; ////MSG_FIRMWARE c=8
|
||||
const char MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY[] PROGMEM_N1 = "FlashAir"; ////MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY c=8
|
||||
const char MSG_PINDA[] PROGMEM_N1 = "PINDA"; ////MSG_PINDA c=5
|
||||
|
|
|
|||
|
|
@ -47,7 +47,9 @@ extern const char MSG_FIND_BED_OFFSET_AND_SKEW_LINE1[];
|
|||
extern const char MSG_FINISHING_MOVEMENTS[];
|
||||
extern const char MSG_FOLLOW_CALIBRATION_FLOW[];
|
||||
extern const char MSG_FOLLOW_Z_CALIBRATION_FLOW[];
|
||||
extern const char MSG_FSENSOR_RUNOUT[];
|
||||
extern const char MSG_FSENSOR_AUTOLOAD[];
|
||||
extern const char MSG_FSENSOR_JAM_DETECTION[];
|
||||
extern const char MSG_FSENSOR[];
|
||||
extern const char MSG_HEATING[];
|
||||
extern const char MSG_HEATING_COMPLETE[];
|
||||
|
|
@ -58,7 +60,9 @@ extern const char MSG_SELECT_FILAMENT[];
|
|||
extern const char MSG_LAST_PRINT[];
|
||||
extern const char MSG_LAST_PRINT_FAILURES[];
|
||||
extern const char MSG_LOAD_FILAMENT[];
|
||||
extern const char MSG_LOAD_TO_EXTRUDER[];
|
||||
extern const char MSG_LOADING_FILAMENT[];
|
||||
extern const char MSG_TESTING_FILAMENT[];
|
||||
extern const char MSG_M117_V2_CALIBRATION[];
|
||||
extern const char MSG_MAIN[];
|
||||
extern const char MSG_BACK[];
|
||||
|
|
@ -130,7 +134,7 @@ extern const char MSG_WELCOME[];
|
|||
extern const char MSG_OFF[];
|
||||
extern const char MSG_ON[];
|
||||
extern const char MSG_NA[];
|
||||
extern const char MSG_AUTO_DEPLETE[];
|
||||
extern const char MSG_SPOOL_JOIN[];
|
||||
extern const char MSG_CUTTER[];
|
||||
extern const char MSG_NONE[];
|
||||
extern const char MSG_WARN[];
|
||||
|
|
@ -166,7 +170,7 @@ extern const char MSG_TIMEOUT[];
|
|||
extern const char MSG_BRIGHT[];
|
||||
extern const char MSG_DIM[];
|
||||
extern const char MSG_AUTO[];
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
#if (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
extern const char MSG_IR_04_OR_NEWER[];
|
||||
extern const char MSG_IR_03_OR_OLDER[];
|
||||
extern const char MSG_IR_UNKNOWN[];
|
||||
|
|
@ -175,6 +179,7 @@ extern const char MSG_PAUSED_THERMAL_ERROR[];
|
|||
#ifdef TEMP_MODEL
|
||||
extern const char MSG_THERMAL_ANOMALY[];
|
||||
#endif
|
||||
extern const char MSG_LOAD_ALL[];
|
||||
|
||||
//not internationalized messages
|
||||
extern const char MSG_BROWNOUT_RESET[];
|
||||
|
|
|
|||
1267
Firmware/mmu.cpp
1267
Firmware/mmu.cpp
File diff suppressed because it is too large
Load Diff
120
Firmware/mmu.h
120
Firmware/mmu.h
|
|
@ -1,120 +0,0 @@
|
|||
//! @file
|
||||
|
||||
#ifndef MMU_H
|
||||
#define MMU_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "Timer.h"
|
||||
|
||||
|
||||
extern bool mmu_enabled;
|
||||
extern bool mmu_fil_loaded;
|
||||
|
||||
extern uint8_t mmu_extruder;
|
||||
|
||||
extern uint8_t tmp_extruder;
|
||||
|
||||
extern int8_t mmu_finda;
|
||||
extern LongTimer mmu_last_finda_response;
|
||||
extern bool ir_sensor_detected;
|
||||
|
||||
extern int16_t mmu_version;
|
||||
extern int16_t mmu_buildnr;
|
||||
|
||||
extern uint16_t mmu_power_failures;
|
||||
|
||||
#define MMU_FILAMENT_UNKNOWN 255
|
||||
|
||||
#define MMU_NO_MOVE 0
|
||||
#define MMU_UNLOAD_MOVE 1
|
||||
#define MMU_LOAD_MOVE 2
|
||||
#define MMU_TCODE_MOVE 3
|
||||
|
||||
#define MMU_LOAD_FEEDRATE 19.02f //mm/s
|
||||
#define MMU_LOAD_TIME_MS 2000 //should be fine tuned to load time for shortest allowed PTFE tubing and maximum loading speed
|
||||
|
||||
enum class MmuCmd : uint_least8_t
|
||||
{
|
||||
None,
|
||||
T0,
|
||||
T1,
|
||||
T2,
|
||||
T3,
|
||||
T4,
|
||||
L0,
|
||||
L1,
|
||||
L2,
|
||||
L3,
|
||||
L4,
|
||||
C0,
|
||||
U0,
|
||||
E0,
|
||||
E1,
|
||||
E2,
|
||||
E3,
|
||||
E4,
|
||||
K0,
|
||||
K1,
|
||||
K2,
|
||||
K3,
|
||||
K4,
|
||||
R0,
|
||||
S3,
|
||||
W0, //!< Wait and signal load error
|
||||
};
|
||||
|
||||
inline MmuCmd operator+ (MmuCmd cmd, uint8_t filament)
|
||||
{
|
||||
return static_cast<MmuCmd>(static_cast<uint8_t>(cmd) + filament );
|
||||
}
|
||||
|
||||
inline uint8_t operator- (MmuCmd cmda, MmuCmd cmdb)
|
||||
{
|
||||
return (static_cast<uint8_t>(cmda) - static_cast<uint8_t>(cmdb));
|
||||
}
|
||||
|
||||
extern int mmu_puts_P(const char* str);
|
||||
|
||||
extern int mmu_printf_P(const char* format, ...);
|
||||
|
||||
extern int8_t mmu_rx_ok(void);
|
||||
|
||||
extern bool check_for_ir_sensor();
|
||||
|
||||
extern void mmu_init(void);
|
||||
|
||||
extern void mmu_loop(void);
|
||||
|
||||
|
||||
extern void mmu_reset(void);
|
||||
|
||||
extern int8_t mmu_set_filament_type(uint8_t extruder, uint8_t filament);
|
||||
|
||||
extern void mmu_command(MmuCmd cmd);
|
||||
|
||||
extern bool mmu_get_response(uint8_t move = 0);
|
||||
|
||||
extern void manage_response(bool move_axes, bool turn_off_nozzle, uint8_t move = MMU_NO_MOVE);
|
||||
|
||||
extern void mmu_load_to_nozzle();
|
||||
|
||||
extern void mmu_M600_load_filament(bool automatic, float nozzle_temp);
|
||||
extern void mmu_M600_wait_and_beep();
|
||||
|
||||
extern void extr_adj(uint8_t extruder);
|
||||
extern void extr_unload();
|
||||
extern void load_all();
|
||||
|
||||
extern bool mmu_check_version();
|
||||
extern void mmu_show_warning();
|
||||
extern void lcd_mmu_load_to_nozzle(uint8_t filament_nr);
|
||||
extern void mmu_eject_filament(uint8_t filament, bool recover);
|
||||
#ifdef MMU_HAS_CUTTER
|
||||
extern void mmu_cut_filament(uint8_t filament_nr);
|
||||
#endif //MMU_HAS_CUTTER
|
||||
extern void mmu_continue_loading(bool blocking);
|
||||
extern void mmu_filament_ramming();
|
||||
extern void mmu_wait_for_heater_blocking();
|
||||
extern void mmu_load_step(bool synchronize = true);
|
||||
|
||||
#endif //MMU_H
|
||||
|
|
@ -0,0 +1,926 @@
|
|||
#include "mmu2.h"
|
||||
#include "mmu2_error_converter.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
#include "mmu2_log.h"
|
||||
#include "mmu2_power.h"
|
||||
#include "mmu2_progress_converter.h"
|
||||
#include "mmu2_reporting.h"
|
||||
|
||||
#include "Marlin.h"
|
||||
#include "language.h"
|
||||
#include "messages.h"
|
||||
#include "sound.h"
|
||||
#include "stepper.h"
|
||||
#include "strlen_cx.h"
|
||||
#include "temperature.h"
|
||||
#include "ultralcd.h"
|
||||
#include "cardreader.h" // for IS_SD_PRINTING
|
||||
#include "SpoolJoin.h"
|
||||
|
||||
// As of FW 3.12 we only support building the FW with only one extruder, all the multi-extruder infrastructure will be removed.
|
||||
// Saves at least 800B of code size
|
||||
static_assert(EXTRUDERS==1);
|
||||
|
||||
// Settings for filament load / unload from the LCD menu.
|
||||
// This is for Prusa MK3-style extruders. Customize for your hardware.
|
||||
#define MMU2_FILAMENTCHANGE_EJECT_FEED 80.0
|
||||
|
||||
#define NOZZLE_PARK_XY_FEEDRATE 50
|
||||
#define NOZZLE_PARK_Z_FEEDRATE 15
|
||||
|
||||
// Nominal distance from the extruder gear to the nozzle tip is 87mm
|
||||
// However, some slipping may occur and we need separate distances for
|
||||
// LoadToNozzle and ToolChange.
|
||||
// - +5mm seemed good for LoadToNozzle,
|
||||
// - but too much (made blobs) for a ToolChange
|
||||
static constexpr float MMU2_LOAD_TO_NOZZLE_LENGTH = 87.0F + 5.0F;
|
||||
|
||||
// As discussed with our PrusaSlicer profile specialist
|
||||
// - ToolChange shall not try to push filament into the very tip of the nozzle
|
||||
// to have some space for additional G-code to tune the extruded filament length
|
||||
// in the profile
|
||||
static constexpr float MMU2_TOOL_CHANGE_LOAD_LENGTH = 30.0F;
|
||||
|
||||
static constexpr float MMU2_LOAD_TO_NOZZLE_FEED_RATE = 20.0F; // mm/s
|
||||
static constexpr float MMU2_UNLOAD_TO_FINDA_FEED_RATE = 120.0F; // mm/s
|
||||
|
||||
// The first the MMU does is initialise its axis. Meanwhile the E-motor will unload 20mm of filament in approx. 1 second.
|
||||
static constexpr float MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH = 20.0f; // mm
|
||||
static constexpr float MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE = 20.0f; // mm/s
|
||||
|
||||
static constexpr uint8_t MMU2_NO_TOOL = 99;
|
||||
static constexpr uint32_t MMU_BAUD = 115200;
|
||||
|
||||
struct E_Step {
|
||||
float extrude; ///< extrude distance in mm
|
||||
float feedRate; ///< feed rate in mm/s
|
||||
};
|
||||
|
||||
static constexpr E_Step ramming_sequence[] PROGMEM = {
|
||||
{ 0.2816F, 1339.0F / 60.F},
|
||||
{ 0.3051F, 1451.0F / 60.F},
|
||||
{ 0.3453F, 1642.0F / 60.F},
|
||||
{ 0.3990F, 1897.0F / 60.F},
|
||||
{ 0.4761F, 2264.0F / 60.F},
|
||||
{ 0.5767F, 2742.0F / 60.F},
|
||||
{ 0.5691F, 3220.0F / 60.F},
|
||||
{ 0.1081F, 3220.0F / 60.F},
|
||||
{ 0.7644F, 3635.0F / 60.F},
|
||||
{ 0.8248F, 3921.0F / 60.F},
|
||||
{ 0.8483F, 4033.0F / 60.F},
|
||||
{ -15.0F, 6000.0F / 60.F},
|
||||
{ -24.5F, 1200.0F / 60.F},
|
||||
{ -7.0F, 600.0F / 60.F},
|
||||
{ -3.5F, 360.0F / 60.F},
|
||||
{ 20.0F, 454.0F / 60.F},
|
||||
{ -20.0F, 303.0F / 60.F},
|
||||
{ -35.0F, 2000.0F / 60.F},
|
||||
};
|
||||
|
||||
static constexpr E_Step load_to_nozzle_sequence[] PROGMEM = {
|
||||
{ 10.0F, 810.0F / 60.F}, // feed rate = 13.5mm/s - Load fast until filament reach end of nozzle
|
||||
{ 25.0F, 198.0F / 60.F}, // feed rate = 3.3mm/s - Load slower once filament is out of the nozzle
|
||||
};
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
void execute_extruder_sequence(const E_Step *sequence, int steps);
|
||||
|
||||
template<typename F>
|
||||
void waitForHotendTargetTemp(uint16_t delay, F f){
|
||||
while (((degTargetHotend(active_extruder) - degHotend(active_extruder)) > 5)) {
|
||||
f();
|
||||
delay_keep_alive(delay);
|
||||
}
|
||||
}
|
||||
|
||||
void WaitForHotendTargetTempBeep(){
|
||||
waitForHotendTargetTemp(3000, []{ Sound_MakeSound(e_SOUND_TYPE_StandardPrompt); } );
|
||||
}
|
||||
|
||||
MMU2 mmu2;
|
||||
|
||||
MMU2::MMU2()
|
||||
: is_mmu_error_monitor_active(false)
|
||||
, logic(&mmu2Serial)
|
||||
, extruder(MMU2_NO_TOOL)
|
||||
, tool_change_extruder(MMU2_NO_TOOL)
|
||||
, resume_position()
|
||||
, resume_hotend_temp(0)
|
||||
, logicStepLastStatus(StepStatus::Finished)
|
||||
, state(xState::Stopped)
|
||||
, mmu_print_saved(SavedState::None)
|
||||
, loadFilamentStarted(false)
|
||||
, unloadFilamentStarted(false)
|
||||
, loadingToNozzle(false)
|
||||
, inAutoRetry(false)
|
||||
, retryAttempts(MAX_RETRIES)
|
||||
{
|
||||
}
|
||||
|
||||
void MMU2::Start() {
|
||||
#ifdef MMU_HWRESET
|
||||
WRITE(MMU_RST_PIN, 1);
|
||||
SET_OUTPUT(MMU_RST_PIN); // setup reset pin
|
||||
#endif //MMU_HWRESET
|
||||
|
||||
mmu2Serial.begin(MMU_BAUD);
|
||||
|
||||
PowerOn(); // I repurposed this to serve as our EEPROM disable toggle.
|
||||
Reset(ResetForm::ResetPin);
|
||||
|
||||
mmu2Serial.flush(); // make sure the UART buffer is clear before starting communication
|
||||
|
||||
extruder = MMU2_NO_TOOL;
|
||||
state = xState::Connecting;
|
||||
|
||||
// start the communication
|
||||
logic.Start();
|
||||
|
||||
ResetRetryAttempts();
|
||||
}
|
||||
|
||||
void MMU2::Stop() {
|
||||
StopKeepPowered();
|
||||
PowerOff(); // This also disables the MMU in the EEPROM.
|
||||
}
|
||||
|
||||
void MMU2::StopKeepPowered(){
|
||||
state = xState::Stopped;
|
||||
logic.Stop();
|
||||
mmu2Serial.close();
|
||||
}
|
||||
|
||||
void MMU2::Reset(ResetForm level){
|
||||
switch (level) {
|
||||
case Software: ResetX0(); break;
|
||||
case ResetPin: TriggerResetPin(); break;
|
||||
case CutThePower: PowerCycle(); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
|
||||
void MMU2::ResetX0() {
|
||||
logic.ResetMMU(); // Send soft reset
|
||||
}
|
||||
|
||||
void MMU2::TriggerResetPin(){
|
||||
reset();
|
||||
}
|
||||
|
||||
void MMU2::PowerCycle(){
|
||||
// cut the power to the MMU and after a while restore it
|
||||
// Sadly, MK3/S/+ cannot do this
|
||||
// NOTE: the below will toggle the EEPROM var. Should we
|
||||
// assert this function is never called in the MK3 FW? Do we even care?
|
||||
PowerOff();
|
||||
delay_keep_alive(1000);
|
||||
PowerOn();
|
||||
}
|
||||
|
||||
void MMU2::PowerOff(){
|
||||
power_off();
|
||||
}
|
||||
|
||||
void MMU2::PowerOn(){
|
||||
power_on();
|
||||
}
|
||||
|
||||
bool MMU2::ReadRegister(uint8_t address){
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
logic.ReadRegister(address); // we may signal the accepted/rejected status of the response as return value of this function
|
||||
manage_response(false, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMU2::WriteRegister(uint8_t address, uint16_t data){
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
logic.WriteRegister(address, data); // we may signal the accepted/rejected status of the response as return value of this function
|
||||
manage_response(false, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
void MMU2::mmu_loop() {
|
||||
// We only leave this method if the current command was successfully completed - that's the Marlin's way of blocking operation
|
||||
// Atomic compare_exchange would have been the most appropriate solution here, but this gets called only in Marlin's task,
|
||||
// so thread safety should be kept
|
||||
static bool avoidRecursion = false;
|
||||
if (avoidRecursion)
|
||||
return;
|
||||
avoidRecursion = true;
|
||||
|
||||
logicStepLastStatus = LogicStep(); // it looks like the mmu_loop doesn't need to be a blocking call
|
||||
|
||||
if (is_mmu_error_monitor_active){
|
||||
// Call this every iteration to keep the knob rotation responsive
|
||||
// This includes when mmu_loop is called within manage_response
|
||||
ReportErrorHook((uint16_t)lastErrorCode, mmu2.MMUCurrentErrorCode() == ErrorCode::OK ? ErrorSourcePrinter : ErrorSourceMMU);
|
||||
}
|
||||
|
||||
avoidRecursion = false;
|
||||
}
|
||||
|
||||
void MMU2::CheckFINDARunout()
|
||||
{
|
||||
// Check for FINDA filament runout
|
||||
if (!FindaDetectsFilament() && CHECK_FSENSOR) {
|
||||
SERIAL_ECHOLNPGM("FINDA filament runout!");
|
||||
stop_and_save_print_to_ram(0, 0);
|
||||
restore_print_from_ram_and_continue(0);
|
||||
if (SpoolJoin::spooljoin.isSpoolJoinEnabled() && get_current_tool() != (uint8_t)FILAMENT_UNKNOWN) // Can't auto if F=?
|
||||
{
|
||||
enquecommand_front_P(PSTR("M600 AUTO")); //save print and run M600 command
|
||||
}
|
||||
else
|
||||
{
|
||||
enquecommand_front_P(PSTR("M600")); //save print and run M600 command
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ReportingRAII {
|
||||
CommandInProgress cip;
|
||||
inline ReportingRAII(CommandInProgress cip):cip(cip){
|
||||
BeginReport(cip, (uint16_t)ProgressCode::EngagingIdler);
|
||||
}
|
||||
inline ~ReportingRAII(){
|
||||
EndReport(cip, (uint16_t)ProgressCode::OK);
|
||||
}
|
||||
};
|
||||
|
||||
bool MMU2::WaitForMMUReady(){
|
||||
switch(State()){
|
||||
case xState::Stopped:
|
||||
return false;
|
||||
case xState::Connecting:
|
||||
// shall we wait until the MMU reconnects?
|
||||
// fire-up a fsm_dlg and show "MMU not responding"?
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool MMU2::RetryIfPossible(uint16_t ec){
|
||||
if( retryAttempts ){
|
||||
SERIAL_ECHOPGM("retryAttempts=");SERIAL_ECHOLN((uint16_t)retryAttempts);
|
||||
SetButtonResponse(ButtonOperations::Retry);
|
||||
// check, that Retry is actually allowed on that operation
|
||||
if( ButtonAvailable(ec) != NoButton ){
|
||||
inAutoRetry = true;
|
||||
SERIAL_ECHOLNPGM("RetryButtonPressed");
|
||||
// We don't decrement until the button is acknowledged by the MMU.
|
||||
//--retryAttempts; // "used" one retry attempt
|
||||
return true;
|
||||
}
|
||||
}
|
||||
inAutoRetry = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
void MMU2::ResetRetryAttempts(){
|
||||
SERIAL_ECHOLNPGM("ResetRetryAttempts");
|
||||
retryAttempts = MAX_RETRIES;
|
||||
}
|
||||
|
||||
void MMU2::DecrementRetryAttempts(){
|
||||
if (inAutoRetry && retryAttempts)
|
||||
{
|
||||
SERIAL_ECHOLNPGM("DecrementRetryAttempts");
|
||||
retryAttempts--;
|
||||
}
|
||||
}
|
||||
|
||||
bool MMU2::tool_change(uint8_t index) {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
if (index != extruder) {
|
||||
if (!IS_SD_PRINTING && !usb_timer.running())
|
||||
{
|
||||
// If Tcodes are used manually through the serial
|
||||
// we need to unload manually as well
|
||||
unload();
|
||||
}
|
||||
|
||||
ReportingRAII rep(CommandInProgress::ToolChange);
|
||||
FSensorBlockRunout blockRunout;
|
||||
|
||||
st_synchronize();
|
||||
|
||||
tool_change_extruder = index;
|
||||
logic.ToolChange(index); // let the MMU pull the filament out and push a new one in
|
||||
manage_response(true, true);
|
||||
|
||||
// reset current position to whatever the planner thinks it is
|
||||
plan_set_e_position(current_position[E_AXIS]);
|
||||
|
||||
extruder = index; //filament change is finished
|
||||
SpoolJoin::spooljoin.setSlot(index);
|
||||
|
||||
// @@TODO really report onto the serial? May be for the Octoprint? Not important now
|
||||
// SERIAL_ECHO_START();
|
||||
// SERIAL_ECHOLNPAIR(MSG_ACTIVE_EXTRUDER, int(extruder));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Handle special T?/Tx/Tc commands
|
||||
///
|
||||
///- T? Gcode to extrude shouldn't have to follow, load to extruder wheels is done automatically
|
||||
///- Tx Same as T?, except nozzle doesn't have to be preheated. Tc must be placed after extruder nozzle is preheated to finish filament load.
|
||||
///- Tc Load to nozzle after filament was prepared by Tx and extruder nozzle is already heated.
|
||||
bool MMU2::tool_change(char code, uint8_t slot) {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
FSensorBlockRunout blockRunout;
|
||||
|
||||
switch (code) {
|
||||
case '?': {
|
||||
waitForHotendTargetTemp(100, []{});
|
||||
load_filament_to_nozzle(slot);
|
||||
} break;
|
||||
|
||||
case 'x': {
|
||||
set_extrude_min_temp(0); // Allow cold extrusion since Tx only loads to the gears not nozzle
|
||||
st_synchronize();
|
||||
tool_change_extruder = slot;
|
||||
logic.ToolChange(slot);
|
||||
manage_response(false, false);
|
||||
extruder = slot;
|
||||
SpoolJoin::spooljoin.setSlot(slot);
|
||||
set_extrude_min_temp(EXTRUDE_MINTEMP);
|
||||
} break;
|
||||
|
||||
case 'c': {
|
||||
waitForHotendTargetTemp(100, []{});
|
||||
execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0]));
|
||||
} break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MMU2::get_statistics() {
|
||||
logic.Statistics();
|
||||
}
|
||||
|
||||
uint8_t MMU2::get_current_tool() const {
|
||||
return extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : extruder;
|
||||
}
|
||||
|
||||
uint8_t MMU2::get_tool_change_tool() const {
|
||||
return tool_change_extruder == MMU2_NO_TOOL ? (uint8_t)FILAMENT_UNKNOWN : tool_change_extruder;
|
||||
}
|
||||
|
||||
bool MMU2::set_filament_type(uint8_t index, uint8_t type) {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
// @@TODO - this is not supported in the new MMU yet
|
||||
// cmd_arg = filamentType;
|
||||
// command(MMU_CMD_F0 + index);
|
||||
|
||||
manage_response(false, false); // true, true); -- Comment: how is it possible for a filament type set to fail?
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMU2::unload() {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
WaitForHotendTargetTempBeep();
|
||||
|
||||
{
|
||||
FSensorBlockRunout blockRunout;
|
||||
ReportingRAII rep(CommandInProgress::UnloadFilament);
|
||||
filament_ramming();
|
||||
|
||||
logic.UnloadFilament();
|
||||
manage_response(false, true);
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
|
||||
// no active tool
|
||||
extruder = MMU2_NO_TOOL;
|
||||
tool_change_extruder = MMU2_NO_TOOL;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMU2::cut_filament(uint8_t index){
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
ReportingRAII rep(CommandInProgress::CutFilament);
|
||||
logic.CutFilament(index);
|
||||
manage_response(false, true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void FullScreenMsg(const char *pgmS, uint8_t slot){
|
||||
lcd_update_enable(false);
|
||||
lcd_clear();
|
||||
lcd_puts_at_P(0, 1, pgmS);
|
||||
lcd_print(' ');
|
||||
lcd_print(slot + 1);
|
||||
}
|
||||
|
||||
bool MMU2::load_to_extruder(uint8_t index){
|
||||
FullScreenMsg(_T(MSG_TESTING_FILAMENT), index);
|
||||
tool_change(index);
|
||||
st_synchronize();
|
||||
unload();
|
||||
lcd_update_enable(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMU2::load_filament(uint8_t index) {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
FullScreenMsg(_T(MSG_LOADING_FILAMENT), index);
|
||||
|
||||
ReportingRAII rep(CommandInProgress::LoadFilament);
|
||||
logic.LoadFilament(index);
|
||||
manage_response(false, false);
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
|
||||
lcd_update_enable(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
struct LoadingToNozzleRAII {
|
||||
MMU2 &mmu2;
|
||||
explicit inline LoadingToNozzleRAII(MMU2 &mmu2):mmu2(mmu2){
|
||||
mmu2.loadingToNozzle = true;
|
||||
}
|
||||
inline ~LoadingToNozzleRAII(){
|
||||
mmu2.loadingToNozzle = false;
|
||||
}
|
||||
};
|
||||
|
||||
bool MMU2::load_filament_to_nozzle(uint8_t index) {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
LoadingToNozzleRAII ln(*this);
|
||||
|
||||
WaitForHotendTargetTempBeep();
|
||||
|
||||
FullScreenMsg(_T(MSG_LOADING_FILAMENT), index);
|
||||
{
|
||||
// used for MMU-menu operation "Load to Nozzle"
|
||||
ReportingRAII rep(CommandInProgress::ToolChange);
|
||||
FSensorBlockRunout blockRunout;
|
||||
|
||||
if( extruder != MMU2_NO_TOOL ){ // we already have some filament loaded - free it + shape its tip properly
|
||||
filament_ramming();
|
||||
}
|
||||
|
||||
tool_change_extruder = index;
|
||||
logic.ToolChange(index);
|
||||
manage_response(true, true);
|
||||
|
||||
// The MMU's idler is disengaged at this point
|
||||
// That means the MK3/S now has fully control
|
||||
|
||||
// reset current position to whatever the planner thinks it is
|
||||
st_synchronize();
|
||||
plan_set_e_position(current_position[E_AXIS]);
|
||||
|
||||
// Finish loading to the nozzle with finely tuned steps.
|
||||
execute_extruder_sequence((const E_Step *)load_to_nozzle_sequence, sizeof(load_to_nozzle_sequence) / sizeof (load_to_nozzle_sequence[0]));
|
||||
|
||||
extruder = index;
|
||||
SpoolJoin::spooljoin.setSlot(index);
|
||||
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
}
|
||||
lcd_update_enable(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool MMU2::eject_filament(uint8_t index, bool recover) {
|
||||
if( ! WaitForMMUReady())
|
||||
return false;
|
||||
|
||||
ReportingRAII rep(CommandInProgress::EjectFilament);
|
||||
current_position[E_AXIS] -= MMU2_FILAMENTCHANGE_EJECT_FEED;
|
||||
plan_buffer_line_curposXYZE(2500.F / 60.F);
|
||||
st_synchronize();
|
||||
logic.EjectFilament(index);
|
||||
manage_response(false, false);
|
||||
|
||||
if (recover) {
|
||||
// LCD_MESSAGEPGM(MSG_MMU2_EJECT_RECOVER);
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardPrompt);
|
||||
//@@TODO wait_for_user = true;
|
||||
|
||||
//#if ENABLED(HOST_PROMPT_SUPPORT)
|
||||
// host_prompt_do(PROMPT_USER_CONTINUE, PSTR("MMU2 Eject Recover"), PSTR("Continue"));
|
||||
//#endif
|
||||
//#if ENABLED(EXTENSIBLE_UI)
|
||||
// ExtUI::onUserConfirmRequired_P(PSTR("MMU2 Eject Recover"));
|
||||
//#endif
|
||||
|
||||
//@@TODO while (wait_for_user) idle(true);
|
||||
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
// logic.Command(); //@@TODO command(MMU_CMD_R0);
|
||||
manage_response(false, false);
|
||||
}
|
||||
|
||||
// no active tool
|
||||
extruder = MMU2_NO_TOOL;
|
||||
tool_change_extruder = MMU2_NO_TOOL;
|
||||
Sound_MakeSound(e_SOUND_TYPE_StandardConfirm);
|
||||
// disable_E0();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MMU2::Button(uint8_t index){
|
||||
LogEchoEvent_P(PSTR("Button"));
|
||||
logic.Button(index);
|
||||
}
|
||||
|
||||
void MMU2::Home(uint8_t mode){
|
||||
logic.Home(mode);
|
||||
}
|
||||
|
||||
void MMU2::SaveAndPark(bool move_axes, bool turn_off_nozzle) {
|
||||
if (mmu_print_saved == SavedState::None) { // First occurrence. Save current position, park print head, disable nozzle heater.
|
||||
LogEchoEvent_P(PSTR("Saving and parking"));
|
||||
st_synchronize();
|
||||
|
||||
resume_hotend_temp = degTargetHotend(active_extruder);
|
||||
|
||||
if (move_axes){
|
||||
mmu_print_saved |= SavedState::ParkExtruder;
|
||||
// save current pos
|
||||
for(uint8_t i = 0; i < 3; ++i){
|
||||
resume_position.xyz[i] = current_position[i];
|
||||
}
|
||||
|
||||
// lift Z
|
||||
raise_z(MMU_ERR_Z_PAUSE_LIFT);
|
||||
|
||||
// move XY aside
|
||||
current_position[X_AXIS] = MMU_ERR_X_PAUSE_POS;
|
||||
current_position[Y_AXIS] = MMU_ERR_Y_PAUSE_POS;
|
||||
plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE);
|
||||
st_synchronize();
|
||||
}
|
||||
|
||||
if (turn_off_nozzle){
|
||||
mmu_print_saved |= SavedState::CooldownPending;
|
||||
LogEchoEvent_P(PSTR("Heater cooldown pending"));
|
||||
// This just sets the flag that we should timeout and shut off the nozzle in 30 minutes...
|
||||
//setAllTargetHotends(0);
|
||||
}
|
||||
}
|
||||
// keep the motors powered forever (until some other strategy is chosen)
|
||||
// @@TODO do we need that in 8bit?
|
||||
// gcode.reset_stepper_timeout();
|
||||
}
|
||||
|
||||
void MMU2::ResumeHotendTemp() {
|
||||
if ((mmu_print_saved & SavedState::CooldownPending))
|
||||
{
|
||||
// Clear the "pending" flag if we haven't cooled yet.
|
||||
mmu_print_saved &= ~(SavedState::CooldownPending);
|
||||
LogEchoEvent_P(PSTR("Cooldown flag cleared"));
|
||||
}
|
||||
if ((mmu_print_saved & SavedState::Cooldown) && resume_hotend_temp) {
|
||||
LogEchoEvent_P(PSTR("Resuming Temp"));
|
||||
MMU2_ECHO_MSGRPGM(PSTR("Restoring hotend temperature "));
|
||||
SERIAL_ECHOLN(resume_hotend_temp);
|
||||
mmu_print_saved &= ~(SavedState::Cooldown);
|
||||
setTargetHotend(resume_hotend_temp, active_extruder);
|
||||
lcd_display_message_fullscreen_P(_i("MMU Retry: Restoring temperature...")); ////MSG_MMU_RESTORE_TEMP c=20 r=4
|
||||
//@todo better report the event and let the GUI do its work somewhere else
|
||||
ReportErrorHookSensorLineRender();
|
||||
waitForHotendTargetTemp(1000, []{
|
||||
ReportErrorHookDynamicRender();
|
||||
manage_inactivity(true);
|
||||
});
|
||||
lcd_update_enable(true); // temporary hack to stop this locking the printer...
|
||||
LogEchoEvent_P(PSTR("Hotend temperature reached"));
|
||||
lcd_clear();
|
||||
}
|
||||
}
|
||||
|
||||
void MMU2::ResumeUnpark(){
|
||||
if (mmu_print_saved & SavedState::ParkExtruder) {
|
||||
LogEchoEvent_P(PSTR("Resuming XYZ"));
|
||||
|
||||
current_position[X_AXIS] = resume_position.xyz[X_AXIS];
|
||||
current_position[Y_AXIS] = resume_position.xyz[Y_AXIS];
|
||||
plan_buffer_line_curposXYZE(NOZZLE_PARK_XY_FEEDRATE);
|
||||
st_synchronize();
|
||||
|
||||
current_position[Z_AXIS] = resume_position.xyz[Z_AXIS];
|
||||
plan_buffer_line_curposXYZE(NOZZLE_PARK_Z_FEEDRATE);
|
||||
st_synchronize();
|
||||
mmu_print_saved &= ~(SavedState::ParkExtruder);
|
||||
}
|
||||
}
|
||||
|
||||
void MMU2::CheckUserInput(){
|
||||
auto btn = ButtonPressed((uint16_t)lastErrorCode);
|
||||
|
||||
// Was a button pressed on the MMU itself instead of the LCD?
|
||||
if (btn == Buttons::NoButton && lastButton != Buttons::NoButton){
|
||||
btn = lastButton;
|
||||
lastButton = Buttons::NoButton; // Clear it.
|
||||
}
|
||||
|
||||
switch (btn) {
|
||||
case Left:
|
||||
case Middle:
|
||||
case Right:
|
||||
SERIAL_ECHOPGM("CheckUserInput-btnLMR ");
|
||||
SERIAL_ECHOLN(btn);
|
||||
ResumeHotendTemp(); // Recover the hotend temp before we attempt to do anything else...
|
||||
Button(btn);
|
||||
break;
|
||||
case RestartMMU:
|
||||
Reset(ResetPin); // we cannot do power cycle on the MK3
|
||||
// ... but mmu2_power.cpp knows this and triggers a soft-reset instead.
|
||||
break;
|
||||
case DisableMMU:
|
||||
Stop(); // Poweroff handles updating the EEPROM shutoff.
|
||||
break;
|
||||
case StopPrint:
|
||||
// @@TODO not sure if we shall handle this high level operation at this spot
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// Originally, this was used to wait for response and deal with timeout if necessary.
|
||||
/// The new protocol implementation enables much nicer and intense reporting, so this method will boil down
|
||||
/// just to verify the result of an issued command (which was basically the original idea)
|
||||
///
|
||||
/// It is closely related to mmu_loop() (which corresponds to our ProtocolLogic::Step()), which does NOT perform any blocking wait for a command to finish.
|
||||
/// But - in case of an error, the command is not yet finished, but we must react accordingly - move the printhead elsewhere, stop heating, eat a cat or so.
|
||||
/// That's what's being done here...
|
||||
void MMU2::manage_response(const bool move_axes, const bool turn_off_nozzle) {
|
||||
mmu_print_saved = SavedState::None;
|
||||
|
||||
KEEPALIVE_STATE(IN_PROCESS);
|
||||
|
||||
LongTimer nozzleTimeout;
|
||||
|
||||
for (;;) {
|
||||
// in our new implementation, we know the exact state of the MMU at any moment, we do not have to wait for a timeout
|
||||
// So in this case we shall decide if the operation is:
|
||||
// - still running -> wait normally in idle()
|
||||
// - failed -> then do the safety moves on the printer like before
|
||||
// - finished ok -> proceed with reading other commands
|
||||
manage_heater();
|
||||
manage_inactivity(true); // calls LogicStep() and remembers its return status
|
||||
lcd_update(0);
|
||||
|
||||
if (mmu_print_saved & SavedState::CooldownPending){
|
||||
if (!nozzleTimeout.running()){
|
||||
nozzleTimeout.start();
|
||||
LogEchoEvent_P(PSTR("Cooling Timeout started"));
|
||||
} else if (nozzleTimeout.expired(DEFAULT_SAFETYTIMER_TIME_MINS*60*1000ul)){ // mins->msec. TODO: do we use the global or have our own independent timeout
|
||||
mmu_print_saved &= ~(SavedState::CooldownPending);
|
||||
mmu_print_saved |= SavedState::Cooldown;
|
||||
setAllTargetHotends(0);
|
||||
LogEchoEvent_P(PSTR("Heater cooldown"));
|
||||
}
|
||||
} else if (nozzleTimeout.running()) {
|
||||
nozzleTimeout.stop();
|
||||
LogEchoEvent_P(PSTR("Cooling timer stopped"));
|
||||
}
|
||||
|
||||
switch (logicStepLastStatus) {
|
||||
case Finished:
|
||||
// command/operation completed, let Marlin continue its work
|
||||
// the E may have some more moves to finish - wait for them
|
||||
ResumeUnpark(); // We can now travel back to the tower or wherever we were when we saved.
|
||||
ResetRetryAttempts(); // Reset the retry counter.
|
||||
st_synchronize();
|
||||
return;
|
||||
case VersionMismatch: // this basically means the MMU will be disabled until reconnected
|
||||
CheckUserInput();
|
||||
return;
|
||||
case CommandError:
|
||||
// Don't proceed to the park/save if we are doing an autoretry.
|
||||
if (inAutoRetry){
|
||||
continue;
|
||||
}
|
||||
[[fallthrough]];
|
||||
case CommunicationTimeout:
|
||||
case ProtocolError:
|
||||
SaveAndPark(move_axes, turn_off_nozzle); // and wait for the user to resolve the problem
|
||||
CheckUserInput();
|
||||
break;
|
||||
case CommunicationRecovered: // @@TODO communication recovered and may be an error recovered as well
|
||||
// may be the logic layer can detect the change of state a respond with one "Recovered" to be handled here
|
||||
ResumeHotendTemp();
|
||||
ResumeUnpark();
|
||||
break;
|
||||
case Processing: // wait for the MMU to respond
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus MMU2::LogicStep() {
|
||||
CheckUserInput(); // Process any buttons before proceeding with another MMU Query
|
||||
StepStatus ss = logic.Step();
|
||||
switch (ss) {
|
||||
case Finished:
|
||||
// At this point it is safe to trigger a runout and not interrupt the MMU protocol
|
||||
CheckFINDARunout();
|
||||
break;
|
||||
case Processing:
|
||||
OnMMUProgressMsg(logic.Progress());
|
||||
break;
|
||||
case CommandError:
|
||||
ReportError(logic.Error(), ErrorSourceMMU);
|
||||
break;
|
||||
case CommunicationTimeout:
|
||||
state = xState::Connecting;
|
||||
ReportError(ErrorCode::MMU_NOT_RESPONDING, ErrorSourcePrinter);
|
||||
break;
|
||||
case ProtocolError:
|
||||
state = xState::Connecting;
|
||||
ReportError(ErrorCode::PROTOCOL_ERROR, ErrorSourcePrinter);
|
||||
break;
|
||||
case VersionMismatch:
|
||||
StopKeepPowered();
|
||||
ReportError(ErrorCode::VERSION_MISMATCH, ErrorSourcePrinter);
|
||||
break;
|
||||
case ButtonPushed:
|
||||
lastButton = logic.Button();
|
||||
LogEchoEvent_P(PSTR("MMU Button pushed"));
|
||||
CheckUserInput(); // Process the button immediately
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if( logic.Running() ){
|
||||
state = xState::Active;
|
||||
}
|
||||
return ss;
|
||||
}
|
||||
|
||||
void MMU2::filament_ramming() {
|
||||
execute_extruder_sequence((const E_Step *)ramming_sequence, sizeof(ramming_sequence) / sizeof(E_Step));
|
||||
}
|
||||
|
||||
void MMU2::execute_extruder_sequence(const E_Step *sequence, uint8_t steps) {
|
||||
st_synchronize();
|
||||
const E_Step *step = sequence;
|
||||
for (uint8_t i = 0; i < steps; i++) {
|
||||
current_position[E_AXIS] += pgm_read_float(&(step->extrude));
|
||||
plan_buffer_line_curposXYZE(pgm_read_float(&(step->feedRate)));
|
||||
st_synchronize();
|
||||
step++;
|
||||
}
|
||||
}
|
||||
|
||||
void MMU2::ReportError(ErrorCode ec, uint8_t res) {
|
||||
// Due to a potential lossy error reporting layers linked to this hook
|
||||
// we'd better report everything to make sure especially the error states
|
||||
// do not get lost.
|
||||
// - The good news here is the fact, that the MMU reports the errors repeatedly until resolved.
|
||||
// - The bad news is, that MMU not responding may repeatedly occur on printers not having the MMU at all.
|
||||
//
|
||||
// Not sure how to properly handle this situation, options:
|
||||
// - skip reporting "MMU not responding" (at least for now)
|
||||
// - report only changes of states (we can miss an error message)
|
||||
// - may be some combination of MMUAvailable + UseMMU flags and decide based on their state
|
||||
// Right now the filtering of MMU_NOT_RESPONDING is done in ReportErrorHook() as it is not a problem if mmu2.cpp
|
||||
|
||||
// Depending on the Progress code, we may want to do some action when an error occurs
|
||||
switch (logic.Progress()){
|
||||
case ProgressCode::UnloadingToFinda:
|
||||
unloadFilamentStarted = false;
|
||||
break;
|
||||
case ProgressCode::FeedingToFSensor:
|
||||
// FSENSOR error during load. Make sure E-motor stops moving.
|
||||
loadFilamentStarted = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
ReportErrorHook((uint16_t)ec, res);
|
||||
|
||||
if( ec != lastErrorCode ){ // deduplicate: only report changes in error codes into the log
|
||||
lastErrorCode = ec;
|
||||
LogErrorEvent_P( _O(PrusaErrorTitle(PrusaErrorCodeIndex((uint16_t)ec))) );
|
||||
}
|
||||
|
||||
static_assert(mmu2Magic[0] == 'M'
|
||||
&& mmu2Magic[1] == 'M'
|
||||
&& mmu2Magic[2] == 'U'
|
||||
&& mmu2Magic[3] == '2'
|
||||
&& mmu2Magic[4] == ':'
|
||||
&& strlen_constexpr(mmu2Magic) == 5,
|
||||
"MMU2 logging prefix mismatch, must be updated at various spots"
|
||||
);
|
||||
}
|
||||
|
||||
void MMU2::ReportProgress(ProgressCode pc) {
|
||||
ReportProgressHook((CommandInProgress)logic.CommandInProgress(), (uint16_t)pc);
|
||||
LogEchoEvent_P( _O(ProgressCodeToText((uint16_t)pc)) );
|
||||
}
|
||||
|
||||
void MMU2::OnMMUProgressMsg(ProgressCode pc){
|
||||
if (pc != lastProgressCode) {
|
||||
OnMMUProgressMsgChanged(pc);
|
||||
} else {
|
||||
OnMMUProgressMsgSame(pc);
|
||||
}
|
||||
}
|
||||
|
||||
void MMU2::OnMMUProgressMsgChanged(ProgressCode pc){
|
||||
ReportProgress(pc);
|
||||
lastProgressCode = pc;
|
||||
switch (pc) {
|
||||
case ProgressCode::UnloadingToFinda:
|
||||
if ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::UnloadFilament
|
||||
|| ((CommandInProgress)logic.CommandInProgress() == CommandInProgress::ToolChange))
|
||||
{
|
||||
// If MK3S sent U0 command, ramming sequence takes care of releasing the filament.
|
||||
// If Toolchange is done while printing, PrusaSlicer takes care of releasing the filament
|
||||
// If printing is not in progress, ToolChange will issue a U0 command.
|
||||
break;
|
||||
} else {
|
||||
// We're likely recovering from an MMU error
|
||||
st_synchronize();
|
||||
unloadFilamentStarted = true;
|
||||
current_position[E_AXIS] -= MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH;
|
||||
plan_buffer_line_curposXYZE(MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE);
|
||||
}
|
||||
break;
|
||||
case ProgressCode::FeedingToFSensor:
|
||||
// prepare for the movement of the E-motor
|
||||
st_synchronize();
|
||||
loadFilamentStarted = true;
|
||||
break;
|
||||
default:
|
||||
// do nothing yet
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void MMU2::OnMMUProgressMsgSame(ProgressCode pc){
|
||||
switch (pc) {
|
||||
case ProgressCode::UnloadingToFinda:
|
||||
if (unloadFilamentStarted && !blocks_queued()) { // Only plan a move if there is no move ongoing
|
||||
if (fsensor.getFilamentPresent()) {
|
||||
current_position[E_AXIS] -= MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH;
|
||||
plan_buffer_line_curposXYZE(MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE);
|
||||
} else {
|
||||
unloadFilamentStarted = false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case ProgressCode::FeedingToFSensor:
|
||||
if (loadFilamentStarted) {
|
||||
switch (WhereIsFilament()) {
|
||||
case FilamentState::AT_FSENSOR:
|
||||
// fsensor triggered, finish FeedingToExtruder state
|
||||
loadFilamentStarted = false;
|
||||
// After the MMU knows the FSENSOR is triggered it will:
|
||||
// 1. Push the filament by additional 30mm (see fsensorToNozzle)
|
||||
// 2. Disengage the idler and push another 5mm.
|
||||
current_position[E_AXIS] += 30.0f + 2.0f;
|
||||
plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
break;
|
||||
case FilamentState::NOT_PRESENT:
|
||||
// fsensor not triggered, continue moving extruder
|
||||
if (!blocks_queued()) { // Only plan a move if there is no move ongoing
|
||||
current_position[E_AXIS] += 2.0f;
|
||||
plan_buffer_line_curposXYZE(MMU2_LOAD_TO_NOZZLE_FEED_RATE);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// Abort here?
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// do nothing yet
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
/// @file
|
||||
#pragma once
|
||||
#include "mmu2_protocol_logic.h"
|
||||
|
||||
struct E_Step;
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
static constexpr uint8_t MAX_RETRIES = 3U;
|
||||
|
||||
/// @@TODO hmmm, 12 bytes... may be we can reduce that
|
||||
struct xyz_pos_t {
|
||||
float xyz[3];
|
||||
xyz_pos_t()=default;
|
||||
};
|
||||
|
||||
// general MMU setup for MK3
|
||||
enum : uint8_t {
|
||||
FILAMENT_UNKNOWN = 0xffU
|
||||
};
|
||||
|
||||
struct Version {
|
||||
uint8_t major, minor, build;
|
||||
};
|
||||
|
||||
/// Top-level interface between Logic and Marlin.
|
||||
/// Intentionally named MMU2 to be (almost) a drop-in replacement for the previous implementation.
|
||||
/// Most of the public methods share the original naming convention as well.
|
||||
class MMU2 {
|
||||
public:
|
||||
MMU2();
|
||||
|
||||
/// Powers ON the MMU, then initializes the UART and protocol logic
|
||||
void Start();
|
||||
|
||||
/// Stops the protocol logic, closes the UART, powers OFF the MMU
|
||||
void Stop();
|
||||
|
||||
/// States of a printer with the MMU:
|
||||
/// - Active
|
||||
/// - Connecting
|
||||
/// - Stopped
|
||||
///
|
||||
/// When the printer's FW starts, the MMU2 mode is either Stopped or NotResponding (based on user's preference).
|
||||
/// When the MMU successfully establishes communication, the state changes to Active.
|
||||
enum class xState : uint_fast8_t {
|
||||
Active, ///< MMU has been detected, connected, communicates and is ready to be worked with.
|
||||
Connecting, ///< MMU is connected but it doesn't communicate (yet). The user wants the MMU, but it is not ready to be worked with.
|
||||
Stopped ///< The user doesn't want the printer to work with the MMU. The MMU itself is not powered and does not work at all.
|
||||
};
|
||||
|
||||
inline xState State() const { return state; }
|
||||
|
||||
// @@TODO temporary wrappers to make old gcc survive the code
|
||||
inline bool Enabled()const { return State() == xState::Active; }
|
||||
|
||||
/// Different levels of resetting the MMU
|
||||
enum ResetForm : uint8_t {
|
||||
Software = 0, ///< sends a X0 command into the MMU, the MMU will watchdog-reset itself
|
||||
ResetPin = 1, ///< trigger the reset pin of the MMU
|
||||
CutThePower = 2 ///< power off and power on (that includes +5V and +24V power lines)
|
||||
};
|
||||
|
||||
/// Saved print state on error.
|
||||
enum SavedState: uint8_t {
|
||||
None = 0, // No state saved.
|
||||
ParkExtruder = 1, // The extruder was parked.
|
||||
Cooldown = 2, // The extruder was allowed to cool.
|
||||
CooldownPending = 4,
|
||||
};
|
||||
|
||||
/// Source of operation error
|
||||
enum ReportErrorSource: uint8_t {
|
||||
ErrorSourcePrinter = 0,
|
||||
ErrorSourceMMU = 1,
|
||||
};
|
||||
|
||||
/// Perform a reset of the MMU
|
||||
/// @param level physical form of the reset
|
||||
void Reset(ResetForm level);
|
||||
|
||||
/// Power off the MMU (cut the power)
|
||||
void PowerOff();
|
||||
|
||||
/// Power on the MMU
|
||||
void PowerOn();
|
||||
|
||||
/// Read from a MMU register (See gcode M707)
|
||||
/// @param address Address of register in hexidecimal
|
||||
/// @returns true upon success
|
||||
bool ReadRegister(uint8_t address);
|
||||
|
||||
/// Write from a MMU register (See gcode M708)
|
||||
/// @param address Address of register in hexidecimal
|
||||
/// @param data Data to write to register
|
||||
/// @returns true upon success
|
||||
bool WriteRegister(uint8_t address, uint16_t data);
|
||||
|
||||
|
||||
/// The main loop of MMU processing.
|
||||
/// Doesn't loop (block) inside, performs just one step of logic state machines.
|
||||
/// Also, internally it prevents recursive entries.
|
||||
void mmu_loop();
|
||||
|
||||
/// The main MMU command - select a different slot
|
||||
/// @param index of the slot to be selected
|
||||
/// @returns false if the operation cannot be performed (Stopped)
|
||||
bool tool_change(uint8_t index);
|
||||
|
||||
/// Handling of special Tx, Tc, T? commands
|
||||
bool tool_change(char code, uint8_t slot);
|
||||
|
||||
/// Unload of filament in collaboration with the MMU.
|
||||
/// That includes rotating the printer's extruder in order to release filament.
|
||||
/// @returns false if the operation cannot be performed (Stopped or cold extruder)
|
||||
bool unload();
|
||||
|
||||
/// Load (insert) filament just into the MMU (not into printer's nozzle)
|
||||
/// @returns false if the operation cannot be performed (Stopped)
|
||||
bool load_filament(uint8_t index);
|
||||
|
||||
/// Load (push) filament from the MMU into the printer's nozzle
|
||||
/// @returns false if the operation cannot be performed (Stopped or cold extruder)
|
||||
bool load_filament_to_nozzle(uint8_t index);
|
||||
|
||||
/// Move MMU's selector aside and push the selected filament forward.
|
||||
/// Usable for improving filament's tip or pulling the remaining piece of filament out completely.
|
||||
bool eject_filament(uint8_t index, bool recover);
|
||||
|
||||
/// Issue a Cut command into the MMU
|
||||
/// Requires unloaded filament from the printer (obviously)
|
||||
/// @returns false if the operation cannot be performed (Stopped)
|
||||
bool cut_filament(uint8_t index);
|
||||
|
||||
/// Issue a planned request for statistics data from MMU
|
||||
void get_statistics();
|
||||
|
||||
/// Issue a Try-Load command
|
||||
/// It behaves very similarly like a ToolChange, but it doesn't load the filament
|
||||
/// all the way down to the nozzle. The sole purpose of this operation
|
||||
/// is to check, that the filament will be ready for printing.
|
||||
bool load_to_extruder(uint8_t index);
|
||||
|
||||
/// @returns the active filament slot index (0-4) or 0xff in case of no active tool
|
||||
uint8_t get_current_tool() const;
|
||||
|
||||
/// @returns The filament slot index (0 to 4) that will be loaded next, 0xff in case of no active tool change
|
||||
uint8_t get_tool_change_tool() const;
|
||||
|
||||
bool set_filament_type(uint8_t index, uint8_t type);
|
||||
|
||||
/// Issue a "button" click into the MMU - to be used from Error screens of the MMU
|
||||
/// to select one of the 3 possible options to resolve the issue
|
||||
void Button(uint8_t index);
|
||||
|
||||
/// Issue an explicit "homing" command into the MMU
|
||||
void Home(uint8_t mode);
|
||||
|
||||
/// @returns current state of FINDA (true=filament present, false=filament not present)
|
||||
inline bool FindaDetectsFilament()const { return logic.FindaPressed(); }
|
||||
|
||||
inline uint16_t TotalFailStatistics()const { return logic.FailStatistics(); }
|
||||
|
||||
/// @returns Current error code
|
||||
inline ErrorCode MMUCurrentErrorCode() const { return logic.Error(); }
|
||||
|
||||
/// @returns the version of the connected MMU FW.
|
||||
/// In the future we'll return the trully detected FW version
|
||||
Version GetMMUFWVersion()const {
|
||||
if( State() == xState::Active ){
|
||||
return { logic.MmuFwVersionMajor(), logic.MmuFwVersionMinor(), logic.MmuFwVersionRevision() };
|
||||
} else {
|
||||
return { 0, 0, 0};
|
||||
}
|
||||
}
|
||||
|
||||
// Helper variable to monitor knob in MMU error screen in blocking functions e.g. manage_response
|
||||
bool is_mmu_error_monitor_active;
|
||||
|
||||
/// Method to read-only mmu_print_saved
|
||||
bool MMU_PRINT_SAVED() const { return mmu_print_saved != SavedState::None; }
|
||||
|
||||
/// Automagically "press" a Retry button if we have any retry attempts left
|
||||
bool RetryIfPossible(uint16_t ec);
|
||||
|
||||
/// Decrement the retry attempts, if in a retry.
|
||||
// Called by the MMU protocol when a sent button is acknowledged.
|
||||
void DecrementRetryAttempts();
|
||||
|
||||
private:
|
||||
/// Reset the retryAttempts back to the default value
|
||||
void ResetRetryAttempts();
|
||||
/// Perform software self-reset of the MMU (sends an X0 command)
|
||||
void ResetX0();
|
||||
|
||||
/// Trigger reset pin of the MMU
|
||||
void TriggerResetPin();
|
||||
|
||||
/// Perform power cycle of the MMU (cold boot)
|
||||
/// Please note this is a blocking operation (sleeps for some time inside while doing the power cycle)
|
||||
void PowerCycle();
|
||||
|
||||
/// Stop the communication, but keep the MMU powered on (for scenarios with incorrect FW version)
|
||||
void StopKeepPowered();
|
||||
|
||||
/// Along with the mmu_loop method, this loops until a response from the MMU is received and acts upon.
|
||||
/// In case of an error, it parks the print head and turns off nozzle heating
|
||||
void manage_response(const bool move_axes, const bool turn_off_nozzle);
|
||||
|
||||
/// Performs one step of the protocol logic state machine
|
||||
/// and reports progress and errors if needed to attached ExtUIs.
|
||||
/// Updates the global state of MMU (Active/Connecting/Stopped) at runtime, see @ref State
|
||||
StepStatus LogicStep();
|
||||
|
||||
void filament_ramming();
|
||||
void execute_extruder_sequence(const E_Step *sequence, uint8_t steps);
|
||||
|
||||
/// Reports an error into attached ExtUIs
|
||||
/// @param ec error code, see ErrorCode
|
||||
/// @param res reporter error source, is either Printer (0) or MMU (1)
|
||||
void ReportError(ErrorCode ec, uint8_t res);
|
||||
|
||||
/// Reports progress of operations into attached ExtUIs
|
||||
/// @param pc progress code, see ProgressCode
|
||||
void ReportProgress(ProgressCode pc);
|
||||
|
||||
/// Responds to a change of MMU's progress
|
||||
/// - plans additional steps, e.g. starts the E-motor after fsensor trigger
|
||||
void OnMMUProgressMsg(ProgressCode pc);
|
||||
/// Progress code changed - act accordingly
|
||||
void OnMMUProgressMsgChanged(ProgressCode pc);
|
||||
/// Repeated calls when progress code remains the same
|
||||
void OnMMUProgressMsgSame(ProgressCode pc);
|
||||
|
||||
/// Save print and park the print head
|
||||
void SaveAndPark(bool move_axes, bool turn_off_nozzle);
|
||||
|
||||
/// Resume hotend temperature, if it was cooled. Safe to call if we aren't saved.
|
||||
void ResumeHotendTemp();
|
||||
|
||||
/// Resume position, if the extruder was parked. Safe to all if state was not saved.
|
||||
void ResumeUnpark();
|
||||
|
||||
/// Check for any button/user input coming from the printer's UI
|
||||
void CheckUserInput();
|
||||
|
||||
/// @brief Check whether to trigger a FINDA runout. If triggered this function will call M600 AUTO
|
||||
/// if SpoolJoin is enabled, otherwise M600 is called without AUTO which will prompt the user
|
||||
/// for the next filament slot to use
|
||||
void CheckFINDARunout();
|
||||
|
||||
/// Entry check of all external commands.
|
||||
/// It can wait until the MMU becomes ready.
|
||||
/// Optionally, it can also emit/display an error screen and the user can decide what to do next.
|
||||
/// @returns false if the MMU is not ready to perform the command (for whatever reason)
|
||||
bool WaitForMMUReady();
|
||||
|
||||
ProtocolLogic logic; ///< implementation of the protocol logic layer
|
||||
uint8_t extruder; ///< currently active slot in the MMU ... somewhat... not sure where to get it from yet
|
||||
uint8_t tool_change_extruder; ///< only used for UI purposes
|
||||
|
||||
xyz_pos_t resume_position;
|
||||
int16_t resume_hotend_temp;
|
||||
|
||||
ProgressCode lastProgressCode = ProgressCode::OK;
|
||||
ErrorCode lastErrorCode = ErrorCode::MMU_NOT_RESPONDING;
|
||||
Buttons lastButton = Buttons::NoButton;
|
||||
|
||||
StepStatus logicStepLastStatus;
|
||||
|
||||
enum xState state;
|
||||
|
||||
uint8_t mmu_print_saved;
|
||||
bool loadFilamentStarted;
|
||||
bool unloadFilamentStarted;
|
||||
|
||||
friend struct LoadingToNozzleRAII;
|
||||
/// true in case we are doing the LoadToNozzle operation - that means the filament shall be loaded all the way down to the nozzle
|
||||
/// unlike the mid-print ToolChange commands, which only load the first ~30mm and then the G-code takes over.
|
||||
bool loadingToNozzle;
|
||||
|
||||
uint8_t retryAttempts;
|
||||
|
||||
bool inAutoRetry;
|
||||
};
|
||||
|
||||
/// following Marlin's way of doing stuff - one and only instance of MMU implementation in the code base
|
||||
/// + avoiding buggy singletons on the AVR platform
|
||||
extern MMU2 mmu2;
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
// Helper macros to parse the operations from Btns()
|
||||
#define BUTTON_OP_RIGHT(X) ( ( X & 0xF0 ) >> 4 )
|
||||
#define BUTTON_OP_MIDDLE(X) ( X & 0x0F )
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
/// Will be mapped onto dialog button responses in the FW
|
||||
/// Those responses have their unique+translated texts as well
|
||||
enum class ButtonOperations : uint8_t {
|
||||
NoOperation = 0,
|
||||
Retry = 1,
|
||||
Continue = 2,
|
||||
RestartMMU = 3,
|
||||
Unload = 4,
|
||||
StopPrint = 5,
|
||||
DisableMMU = 6,
|
||||
};
|
||||
|
||||
/// Button codes + extended actions performed on the printer's side
|
||||
enum Buttons : uint8_t {
|
||||
Right = 0,
|
||||
Middle,
|
||||
Left,
|
||||
|
||||
// performed on the printer's side
|
||||
RestartMMU,
|
||||
StopPrint,
|
||||
DisableMMU,
|
||||
|
||||
NoButton = 0xff // shall be kept last
|
||||
};
|
||||
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/// @file error_codes.h
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
/// A complete set of error codes which may be a result of a high-level command/operation.
|
||||
/// This header file shall be included in the printer's firmware as well as a reference,
|
||||
/// therefore the error codes have been extracted to one place.
|
||||
///
|
||||
/// Please note the errors are intentionally coded as "negative" values (highest bit set),
|
||||
/// becase they are a complement to reporting the state of the high-level state machines -
|
||||
/// positive values are considered as normal progress, negative values are errors.
|
||||
///
|
||||
/// Please note, that multiple TMC errors can occur at once, thus they are defined as a bitmask of the higher byte.
|
||||
/// Also, as there are 3 TMC drivers on the board, each error is added a bit for the corresponding TMC -
|
||||
/// TMC_PULLEY_BIT, TMC_SELECTOR_BIT, TMC_IDLER_BIT,
|
||||
/// The resulting error is a bitwise OR over 3 TMC drivers and their status, which should cover most of the situations correctly.
|
||||
enum class ErrorCode : uint_fast16_t {
|
||||
RUNNING = 0x0000, ///< the operation is still running - keep this value as ZERO as it is used for initialization of error codes as well
|
||||
OK = 0x0001, ///< the operation finished OK
|
||||
|
||||
// TMC bit masks
|
||||
TMC_PULLEY_BIT = 0x0040, ///< +64 TMC Pulley bit
|
||||
TMC_SELECTOR_BIT = 0x0080, ///< +128 TMC Pulley bit
|
||||
TMC_IDLER_BIT = 0x0100, ///< +256 TMC Pulley bit
|
||||
|
||||
/// Unload Filament related error codes
|
||||
FINDA_DIDNT_SWITCH_ON = 0x8001, ///< E32769 FINDA didn't switch on while loading filament - either there is something blocking the metal ball or a cable is broken/disconnected
|
||||
FINDA_DIDNT_SWITCH_OFF = 0x8002, ///< E32770 FINDA didn't switch off while unloading filament
|
||||
|
||||
FSENSOR_DIDNT_SWITCH_ON = 0x8003, ///< E32771 Filament sensor didn't switch on while performing LoadFilament
|
||||
FSENSOR_DIDNT_SWITCH_OFF = 0x8004, ///< E32772 Filament sensor didn't switch off while performing UnloadFilament
|
||||
|
||||
FILAMENT_ALREADY_LOADED = 0x8005, ///< E32773 cannot perform operation LoadFilament or move the selector as the filament is already loaded
|
||||
|
||||
INVALID_TOOL = 0x8006, ///< E32774 tool/slot index out of range (typically issuing T5 into an MMU with just 5 slots - valid range 0-4)
|
||||
|
||||
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)
|
||||
|
||||
FINDA_VS_EEPROM_DISREPANCY = 0x8008, ///< E32776 FINDA is pressed but we have no such record in EEPROM - this can only happen at the start of the MMU and can be resolved by issuing an Unload command
|
||||
|
||||
FSENSOR_TOO_EARLY = 0x8009, ///< E32777 FSensor triggered while doing FastFeedToExtruder - that means either:
|
||||
///< - the PTFE is too short
|
||||
///< - a piece of filament was left inside - pushed in front of the loaded filament causing the fsensor trigger too early
|
||||
///< - fsensor is faulty producing bogus triggers
|
||||
|
||||
MOVE_FAILED = 0x800a, ///< generic move failed error - always reported with the corresponding axis bit set (Idler or Selector) as follows:
|
||||
MOVE_SELECTOR_FAILED = MOVE_FAILED | TMC_SELECTOR_BIT, ///< E32905 the Selector was unable to move to desired position properly - that means something is blocking its movement, e.g. a piece of filament got out of pulley body
|
||||
MOVE_IDLER_FAILED = MOVE_FAILED | TMC_IDLER_BIT, ///< E33033 the Idler was unable to move - unused at the time of creation, but added for completeness
|
||||
MOVE_PULLEY_FAILED = MOVE_FAILED | TMC_PULLEY_BIT, ///< E32841 the Pulley was unable to move - unused at the time of creation, but added for completeness
|
||||
|
||||
QUEUE_FULL = 0x802b, ///< E32811 internal logic error - attempt to move with a full queue
|
||||
|
||||
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
|
||||
INTERNAL = 0x802f, ///< E32815 internal runtime error (software)
|
||||
|
||||
/// TMC driver init error - TMC dead or bad communication
|
||||
/// - E33344 Pulley TMC driver
|
||||
/// - E33404 Selector TMC driver
|
||||
/// - E33536 Idler TMC driver
|
||||
/// - E33728 All 3 TMC driver
|
||||
TMC_IOIN_MISMATCH = 0x8200,
|
||||
|
||||
/// TMC driver reset - recoverable, we just need to rehome the axis
|
||||
/// Idler: can be rehomed any time
|
||||
/// Selector: if there is a filament, remove it and rehome, if there is no filament, just rehome
|
||||
/// Pulley: do nothing - for the loading sequence - just restart and move slowly, for the unload sequence just restart
|
||||
/// - E33856 Pulley TMC driver
|
||||
/// - E33920 Selector TMC driver
|
||||
/// - E34048 Idler TMC driver
|
||||
/// - E34240 All 3 TMC driver
|
||||
TMC_RESET = 0x8400,
|
||||
|
||||
/// not enough current for the TMC, NOT RECOVERABLE
|
||||
/// - E34880 Pulley TMC driver
|
||||
/// - E34944 Selector TMC driver
|
||||
/// - E35072 Idler TMC driver
|
||||
/// - E35264 All 3 TMC driver
|
||||
TMC_UNDERVOLTAGE_ON_CHARGE_PUMP = 0x8800,
|
||||
|
||||
/// TMC driver serious error - short to ground on coil A or coil B - dangerous to recover
|
||||
/// - E36928 Pulley TMC driver
|
||||
/// - E36992 Selector TMC driver
|
||||
/// - E37120 Idler TMC driver
|
||||
/// - E37312 All 3 TMC driver
|
||||
TMC_SHORT_TO_GROUND = 0x9000,
|
||||
|
||||
/// TMC driver over temperature warning - can be recovered by restarting the driver.
|
||||
/// If this error happens, we should probably go into the error state as soon as the current command is finished.
|
||||
/// The driver technically still works at this point.
|
||||
/// - E41024 Pulley TMC driver
|
||||
/// - E41088 Selector TMC driver
|
||||
/// - E41216 Idler TMC driver
|
||||
/// - E41408 All 3 TMC driver
|
||||
TMC_OVER_TEMPERATURE_WARN = 0xA000,
|
||||
|
||||
/// TMC driver over temperature error - we really shouldn't ever reach this error.
|
||||
/// It can still be recovered if the driver cools down below 120C.
|
||||
/// The driver needs to be disabled and enabled again for operation to resume after this error is cleared.
|
||||
/// - E49216 Pulley TMC driver
|
||||
/// - E49280 Selector TMC driver
|
||||
/// - E49408 Idler TMC driver
|
||||
/// - E49600 All 3 TMC driver
|
||||
TMC_OVER_TEMPERATURE_ERROR = 0xC000
|
||||
};
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
// Extracted from Prusa-Error-Codes repo
|
||||
// Subject to automation and optimization
|
||||
// BEWARE - this file shall be included only into mmu2_error_converter.cpp, not anywhere else!
|
||||
#pragma once
|
||||
#include "inttypes.h"
|
||||
#include "../language.h"
|
||||
#include <avr/pgmspace.h>
|
||||
#include "buttons.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
static constexpr uint8_t ERR_MMU_CODE = 4;
|
||||
|
||||
typedef enum : uint16_t {
|
||||
ERR_UNDEF = 0,
|
||||
|
||||
ERR_MECHANICAL = 100,
|
||||
ERR_MECHANICAL_FINDA_DIDNT_TRIGGER = 101,
|
||||
ERR_MECHANICAL_FINDA_DIDNT_GO_OFF = 102,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER = 103,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF = 104,
|
||||
|
||||
ERR_MECHANICAL_PULLEY_CANNOT_MOVE = 105,
|
||||
ERR_MECHANICAL_FSENSOR_TOO_EARLY = 106,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_HOME = 115,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_MOVE = 116,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_HOME = 125,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_MOVE = 126,
|
||||
|
||||
ERR_TEMPERATURE = 200,
|
||||
ERR_TEMPERATURE_PULLEY_WARNING_TMC_TOO_HOT = 201,
|
||||
ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT = 211,
|
||||
ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT = 221,
|
||||
|
||||
ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR = 202,
|
||||
ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR = 212,
|
||||
ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR = 222,
|
||||
|
||||
|
||||
ERR_ELECTRICAL = 300,
|
||||
ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR = 301,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR = 311,
|
||||
ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR = 321,
|
||||
|
||||
ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET = 302,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET = 312,
|
||||
ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET = 322,
|
||||
|
||||
ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR = 303,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR = 313,
|
||||
ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR = 323,
|
||||
|
||||
ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED = 304,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED = 314,
|
||||
ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED = 324,
|
||||
|
||||
|
||||
ERR_CONNECT = 400,
|
||||
ERR_CONNECT_MMU_NOT_RESPONDING = 401,
|
||||
ERR_CONNECT_COMMUNICATION_ERROR = 402,
|
||||
|
||||
|
||||
ERR_SYSTEM = 500,
|
||||
ERR_SYSTEM_FILAMENT_ALREADY_LOADED = 501,
|
||||
ERR_SYSTEM_INVALID_TOOL = 502,
|
||||
ERR_SYSTEM_QUEUE_FULL = 503,
|
||||
ERR_SYSTEM_FW_UPDATE_NEEDED = 504,
|
||||
ERR_SYSTEM_FW_RUNTIME_ERROR = 505,
|
||||
ERR_SYSTEM_UNLOAD_MANUALLY = 506,
|
||||
|
||||
ERR_OTHER = 900
|
||||
} err_num_t;
|
||||
|
||||
// Avr gcc has serious trouble understanding static data structures in PROGMEM
|
||||
// and inadvertedly falls back to copying the whole structure into RAM (which is obviously unwanted).
|
||||
// But since this file ought to be generated in the future from yaml prescription,
|
||||
// it really makes no difference if there are "nice" data structures or plain arrays.
|
||||
static const constexpr uint16_t errorCodes[] PROGMEM = {
|
||||
ERR_MECHANICAL_FINDA_DIDNT_TRIGGER,
|
||||
ERR_MECHANICAL_FINDA_DIDNT_GO_OFF,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF,
|
||||
ERR_MECHANICAL_PULLEY_CANNOT_MOVE,
|
||||
ERR_MECHANICAL_FSENSOR_TOO_EARLY,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_HOME,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_MOVE,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_HOME,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_MOVE,
|
||||
ERR_TEMPERATURE_PULLEY_WARNING_TMC_TOO_HOT,
|
||||
ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT,
|
||||
ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT,
|
||||
ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR,
|
||||
ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR,
|
||||
ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR,
|
||||
ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED,
|
||||
ERR_CONNECT_MMU_NOT_RESPONDING,
|
||||
ERR_CONNECT_COMMUNICATION_ERROR,
|
||||
ERR_SYSTEM_FILAMENT_ALREADY_LOADED,
|
||||
ERR_SYSTEM_INVALID_TOOL,
|
||||
ERR_SYSTEM_QUEUE_FULL,
|
||||
ERR_SYSTEM_FW_UPDATE_NEEDED,
|
||||
ERR_SYSTEM_FW_RUNTIME_ERROR,
|
||||
ERR_SYSTEM_UNLOAD_MANUALLY
|
||||
};
|
||||
|
||||
// @@TODO some of the strings are duplicates, can be merged into one 01234567890123456789
|
||||
static const char MSG_TITLE_FINDA_DIDNT_TRIGGER[] PROGMEM_I1 = ISTR("FINDA DIDNT TRIGGER"); ////MSG_TITLE_FINDA_DIDNT_TRIGGER c=20
|
||||
static const char MSG_TITLE_FINDA_DIDNT_GO_OFF[] PROGMEM_I1 = ISTR("FINDA: FILAM. STUCK"); ////MSG_TITLE_FINDA_DIDNT_GO_OFF c=20
|
||||
static const char MSG_TITLE_FSENSOR_DIDNT_TRIGGER[] PROGMEM_I1 = ISTR("FSENSOR DIDNT TRIGG."); ////MSG_TITLE_FSENSOR_DIDNT_TRIGGER c=20
|
||||
static const char MSG_TITLE_FSENSOR_DIDNT_GO_OFF[] PROGMEM_I1 = ISTR("FSENSOR: FIL. STUCK"); ////MSG_TITLE_FSENSOR_DIDNT_GO_OFF c=20
|
||||
static const char MSG_TITLE_PULLEY_CANNOT_MOVE[] PROGMEM_I1 = ISTR("PULLEY CANNOT MOVE"); ////MSG_TITLE_PULLEY_CANNOT_MOVE c=20
|
||||
static const char MSG_TITLE_FSENSOR_TOO_EARLY[] PROGMEM_I1 = ISTR("FSENSOR TOO EARLY"); ////MSG_TITLE_FSENSOR_TOO_EARLY c=20
|
||||
static const char MSG_TITLE_SELECTOR_CANNOT_MOVE[] PROGMEM_I1 = ISTR("SELECTOR CANNOT MOVE"); ////MSG_TITLE_SELECTOR_CANNOT_MOVE c=20
|
||||
static const char MSG_TITLE_SELECTOR_CANNOT_HOME[] PROGMEM_I1 = ISTR("SELECTOR CANNOT HOME"); ////MSG_TITLE_SELECTOR_CANNOT_HOME c=20
|
||||
static const char MSG_TITLE_IDLER_CANNOT_MOVE[] PROGMEM_I1 = ISTR("IDLER CANNOT MOVE"); ////MSG_TITLE_IDLER_CANNOT_MOVE c=20
|
||||
static const char MSG_TITLE_IDLER_CANNOT_HOME[] PROGMEM_I1 = ISTR("IDLER CANNOT HOME"); ////MSG_TITLE_IDLER_CANNOT_HOME c=20
|
||||
static const char MSG_TITLE_TMC_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("WARNING TMC TOO HOT"); ////MSG_TITLE_TMC_WARNING_TMC_TOO_HOT c=20
|
||||
//static const char MSG_TITLE_TMC_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("WARNING TMC TOO HOT"); ////MSG_TITLE_TMC_WARNING_TMC_TOO_HOT c=20
|
||||
//static const char MSG_TITLE_TMC_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("WARNING TMC TOO HOT");
|
||||
static const char MSG_TITLE_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC OVERHEAT ERROR"); ////MSG_TITLE_TMC_OVERHEAT_ERROR c=20
|
||||
//static const char MSG_TITLE_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC OVERHEAT ERROR");
|
||||
//static const char MSG_TITLE_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC OVERHEAT ERROR");
|
||||
static const char MSG_TITLE_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC DRIVER ERROR"); ////MSG_TITLE_TMC_DRIVER_ERROR c=20
|
||||
//static const char MSG_TITLE_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC DRIVER ERROR");
|
||||
//static const char MSG_TITLE_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC DRIVER ERROR");
|
||||
static const char MSG_TITLE_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC DRIVER RESET"); ////MSG_TITLE_TMC_DRIVER_RESET c=20
|
||||
//static const char MSG_TITLE_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC DRIVER RESET");
|
||||
//static const char MSG_TITLE_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC DRIVER RESET");
|
||||
static const char MSG_TITLE_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("TMC UNDERVOLTAGE ERR"); ////MSG_TITLE_TMC_UNDERVOLTAGE_ERROR c=20
|
||||
//static const char MSG_TITLE_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("TMC UNDERVOLTAGE ERR");
|
||||
//static const char MSG_TITLE_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("TMC UNDERVOLTAGE ERR");
|
||||
static const char MSG_TITLE_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("TMC DRIVER SHORTED"); ////MSG_TITLE_TMC_DRIVER_SHORTED c=20
|
||||
//static const char MSG_TITLE_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("TMC DRIVER SHORTED");
|
||||
//static const char MSG_TITLE_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("TMC DRIVER SHORTED");
|
||||
static const char MSG_TITLE_MMU_NOT_RESPONDING[] PROGMEM_I1 = ISTR("MMU NOT RESPONDING"); ////MSG_TITLE_MMU_NOT_RESPONDING c=20
|
||||
static const char MSG_TITLE_COMMUNICATION_ERROR[] PROGMEM_I1 = ISTR("COMMUNICATION ERROR"); ////MSG_TITLE_COMMUNICATION_ERROR c=20
|
||||
static const char MSG_TITLE_FIL_ALREADY_LOADED[] PROGMEM_I1 = ISTR("FILAMENT ALREADY LOA"); ////MSG_TITLE_FIL_ALREADY_LOADED c=20
|
||||
static const char MSG_TITLE_INVALID_TOOL[] PROGMEM_I1 = ISTR("INVALID TOOL"); ////MSG_TITLE_INVALID_TOOL c=20
|
||||
static const char MSG_TITLE_QUEUE_FULL[] PROGMEM_I1 = ISTR("QUEUE FULL"); ////MSG_TITLE_QUEUE_FULL c=20
|
||||
static const char MSG_TITLE_FW_UPDATE_NEEDED[] PROGMEM_I1 = ISTR("MMU FW UPDATE NEEDED"); ////MSG_TITLE_FW_UPDATE_NEEDED c=20
|
||||
static const char MSG_TITLE_FW_RUNTIME_ERROR[] PROGMEM_I1 = ISTR("FW RUNTIME ERROR"); ////MSG_TITLE_FW_RUNTIME_ERROR c=20
|
||||
static const char MSG_TITLE_UNLOAD_MANUALLY[] PROGMEM_I1 = ISTR("UNLOAD MANUALLY"); ////MSG_TITLE_UNLOAD_MANUALLY c=20
|
||||
|
||||
static const char * const errorTitles [] PROGMEM = {
|
||||
_R(MSG_TITLE_FINDA_DIDNT_TRIGGER),
|
||||
_R(MSG_TITLE_FINDA_DIDNT_GO_OFF),
|
||||
_R(MSG_TITLE_FSENSOR_DIDNT_TRIGGER),
|
||||
_R(MSG_TITLE_FSENSOR_DIDNT_GO_OFF),
|
||||
_R(MSG_TITLE_PULLEY_CANNOT_MOVE),
|
||||
_R(MSG_TITLE_FSENSOR_TOO_EARLY),
|
||||
_R(MSG_TITLE_SELECTOR_CANNOT_HOME),
|
||||
_R(MSG_TITLE_SELECTOR_CANNOT_MOVE),
|
||||
_R(MSG_TITLE_IDLER_CANNOT_HOME),
|
||||
_R(MSG_TITLE_IDLER_CANNOT_MOVE),
|
||||
_R(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
_R(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
_R(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
_R(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
_R(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
_R(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
_R(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
_R(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
_R(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
_R(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
_R(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
_R(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
_R(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
_R(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
_R(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
_R(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
_R(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
_R(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
_R(MSG_TITLE_MMU_NOT_RESPONDING),
|
||||
_R(MSG_TITLE_COMMUNICATION_ERROR),
|
||||
_R(MSG_TITLE_FIL_ALREADY_LOADED),
|
||||
_R(MSG_TITLE_INVALID_TOOL),
|
||||
_R(MSG_TITLE_QUEUE_FULL),
|
||||
_R(MSG_TITLE_FW_UPDATE_NEEDED),
|
||||
_R(MSG_TITLE_FW_RUNTIME_ERROR),
|
||||
_R(MSG_TITLE_UNLOAD_MANUALLY)
|
||||
};
|
||||
|
||||
// @@TODO looking at the texts, they can be composed of several parts and/or parametrized (could save a lot of space ;) )
|
||||
// Moreover, some of them have been disabled in favour of saving some more code size.
|
||||
static const char MSG_DESC_FINDA_DIDNT_TRIGGER[] PROGMEM_I1 = ISTR("FINDA didn't trigger while loading the filament. Ensure the filament can move and FINDA works."); ////MSG_DESC_FINDA_DIDNT_TRIGGER c=20 r=8
|
||||
static const char MSG_DESC_FINDA_DIDNT_GO_OFF[] PROGMEM_I1 = ISTR("FINDA didn't switch off while unloading filament. Try unloading manually. Ensure filament can move and FINDA works."); ////MSG_DESC_FINDA_DIDNT_GO_OFF c=20 r=8
|
||||
static const char MSG_DESC_FSENSOR_DIDNT_TRIGGER[] PROGMEM_I1 = ISTR("Filament sensor didn't trigger while loading the filament. Ensure the filament reached the fsensor and the sensor works."); ////MSG_DESC_FSENSOR_DIDNT_TRIGGER c=20 r=8
|
||||
static const char MSG_DESC_FSENSOR_DIDNT_GO_OFF[] PROGMEM_I1 = ISTR("Filament sensor didn't switch off while unloading filament. Ensure filament can move and the sensor works."); ////MSG_DESC_FSENSOR_DIDNT_GO_OFF c=20 r=8
|
||||
static const char MSG_DESC_PULLEY_STALLED[] PROGMEM_I1 = ISTR("Pulley motor stalled. Ensure the pulley can move and check the wiring."); ////MSG_DESC_PULLEY_STALLED c=20 r=8
|
||||
static const char MSG_DESC_FSENSOR_TOO_EARLY[] PROGMEM_I1 = ISTR("Filament sensor triggered too early while loading to extruder. Check there isn't anything stuck in PTFE tube. Check that sensor reads properly."); ////MSG_DESC_FSENSOR_TOO_EARLY c=20 r=8
|
||||
static const char MSG_DESC_SELECTOR_CANNOT_HOME[] PROGMEM_I1 = ISTR("The Selector cannot home properly. Check for anything blocking its movement."); ////MSG_DESC_SELECTOR_CANNOT_HOME c=20 r=8
|
||||
static const char MSG_DESC_CANNOT_MOVE[] PROGMEM_I1 = ISTR("Can't move Selector or Idler."); /////MSG_DESC_CANNOT_MOVE c=20 r=4
|
||||
//static const char MSG_DESC_SELECTOR_CANNOT_MOVE[] PROGMEM_I1 = ISTR("The Selector cannot move. Check for anything blocking its movement. Check the wiring is correct.");
|
||||
static const char MSG_DESC_IDLER_CANNOT_HOME[] PROGMEM_I1 = ISTR("The Idler cannot home properly. Check for anything blocking its movement."); ////MSG_DESC_IDLER_CANNOT_HOME c=20 r=8
|
||||
//static const char MSG_DESC_IDLER_CANNOT_MOVE[] PROGMEM_I1 = ISTR("The Idler cannot move properly. Check for anything blocking its movement. Check the wiring is correct.");
|
||||
static const char MSG_DESC_TMC[] PROGMEM_I1 = ISTR("More details online."); ////MSG_DESC_TMC c=20 r=8
|
||||
//static const char MSG_DESC_PULLEY_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor is almost overheating. Make sure there is sufficient airflow near the MMU board.");
|
||||
//static const char MSG_DESC_SELECTOR_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor is almost overheating. Make sure there is sufficient airflow near the MMU board.");
|
||||
//static const char MSG_DESC_IDLER_WARNING_TMC_TOO_HOT[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor is almost overheating. Make sure there is sufficient airflow near the MMU board.");
|
||||
//static const char MSG_DESC_PULLEY_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor is overheated. Cool down the MMU board and reset MMU.");
|
||||
//static const char MSG_DESC_SELECTOR_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor is overheated. Cool down the MMU board and reset MMU.");
|
||||
//static const char MSG_DESC_IDLER_TMC_OVERHEAT_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor is overheated. Cool down the MMU board and reset MMU.");
|
||||
//static const char MSG_DESC_PULLEY_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor is not responding. Try resetting the MMU. If the issue persists contact support.");
|
||||
//static const char MSG_DESC_SELECTOR_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor is not responding. Try resetting the MMU. If the issue persists contact support.");
|
||||
//static const char MSG_DESC_IDLER_TMC_DRIVER_ERROR[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor is not responding. Try resetting the MMU. If the issue persists contact support.");
|
||||
//static const char MSG_DESC_PULLEY_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC driver for the Pulley motor was restarted. There is probably an issue with the electronics. Check the wiring and connectors.");
|
||||
//static const char MSG_DESC_SELECTOR_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC driver for the Selector motor was restarted. There is probably an issue with the electronics. Check the wiring and connectors.");
|
||||
//static const char MSG_DESC_IDLER_TMC_DRIVER_RESET[] PROGMEM_I1 = ISTR("TMC driver for the Idler motor was restarted. There is probably an issue with the electronics. Check the wiring and connectors.");
|
||||
//static const char MSG_DESC_PULLEY_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("Not enough current for the Pulley TMC driver. There is probably an issue with the electronics. Check the wiring and connectors.");
|
||||
//static const char MSG_DESC_SELECTOR_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("Not enough current for the Selector TMC driver. There is probably an issue with the electronics. Check the wiring and connectors.");
|
||||
//static const char MSG_DESC_IDLER_TMC_UNDERVOLTAGE_ERROR[] PROGMEM_I1 = ISTR("Not enough current for the Idler TMC driver. There is probably an issue with the electronics. Check the wiring and connectors.");
|
||||
//static const char MSG_DESC_PULLEY_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("Short circuit on the Pulley TMC driver. Check the wiring and connectors. If the issue persists contact support.");
|
||||
//static const char MSG_DESC_SELECTOR_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("Short circuit on the Selector TMC driver. Check the wiring and connectors. If the issue persists contact support.");
|
||||
//static const char MSG_DESC_IDLER_TMC_DRIVER_SHORTED[] PROGMEM_I1 = ISTR("Short circuit on the Idler TMC driver. Check the wiring and connectors. If the issue persists contact support.");
|
||||
static const char MSG_DESC_MMU_NOT_RESPONDING[] PROGMEM_I1 = ISTR("MMU unit not responding. Check the wiring and connectors. If the issue persists, contact support."); ////MSG_DESC_MMU_NOT_RESPONDING c=20 r=8
|
||||
static const char MSG_DESC_COMMUNICATION_ERROR[] PROGMEM_I1 = ISTR("MMU unit not responding correctly. Check the wiring and connectors. If the issue persists, contact support."); ////MSG_DESC_COMMUNICATION_ERROR c=20 r=9
|
||||
static const char MSG_DESC_FILAMENT_ALREADY_LOADED[] PROGMEM_I1 = ISTR("Cannot perform the action, filament is already loaded. Unload it first."); ////MSG_DESC_FILAMENT_ALREADY_LOADED c=20 r=8
|
||||
static const char MSG_DESC_INVALID_TOOL[] PROGMEM_I1 = ISTR("Requested filament tool is not available on this hardware. Check the G-code for tool index out of range (T0-T4)."); ////MSG_DESC_INVALID_TOOL c=20 r=8
|
||||
static const char MSG_DESC_QUEUE_FULL[] PROGMEM_I1 = ISTR("MMU Firmware internal error, please reset the MMU."); ////MSG_DESC_QUEUE_FULL c=20 r=8
|
||||
static const char MSG_DESC_FW_UPDATE_NEEDED[] PROGMEM_I1 = ISTR("The MMU unit reports its FW version incompatible with the printer's firmware. Make sure the MMU firmware is up to date."); ////MSG_DESC_FW_UPDATE_NEEDED c=20 r=9
|
||||
static const char MSG_DESC_FW_RUNTIME_ERROR[] PROGMEM_I1 = ISTR("Internal runtime error. Try resetting the MMU unit or updating the firmware. If the issue persists, contact support."); ////MSG_DESC_FW_RUNTIME_ERROR c=20 r=11
|
||||
static const char MSG_DESC_UNLOAD_MANUALLY[] PROGMEM_I1 = ISTR("Unexpected FINDA reading. Ensure no filament is under FINDA and the selector is free. Check FINDA connection."); ////MSG_DESC_UNLOAD_MANUALLY c=20 r=8
|
||||
|
||||
static const char * const errorDescs[] PROGMEM = {
|
||||
_R(MSG_DESC_FINDA_DIDNT_TRIGGER),
|
||||
_R(MSG_DESC_FINDA_DIDNT_GO_OFF),
|
||||
_R(MSG_DESC_FSENSOR_DIDNT_TRIGGER),
|
||||
_R(MSG_DESC_FSENSOR_DIDNT_GO_OFF),
|
||||
_R(MSG_DESC_PULLEY_STALLED),
|
||||
_R(MSG_DESC_FSENSOR_TOO_EARLY),
|
||||
_R(MSG_DESC_SELECTOR_CANNOT_HOME),
|
||||
_R(MSG_DESC_CANNOT_MOVE),
|
||||
_R(MSG_DESC_IDLER_CANNOT_HOME),
|
||||
_R(MSG_DESC_CANNOT_MOVE),
|
||||
_R(MSG_DESC_TMC), // descPULLEY_WARNING_TMC_TOO_HOT
|
||||
_R(MSG_DESC_TMC), // descSELECTOR_WARNING_TMC_TOO_HOT
|
||||
_R(MSG_DESC_TMC), // descIDLER_WARNING_TMC_TOO_HOT
|
||||
_R(MSG_DESC_TMC), // descPULLEY_TMC_OVERHEAT_ERROR
|
||||
_R(MSG_DESC_TMC), // descSELECTOR_TMC_OVERHEAT_ERROR
|
||||
_R(MSG_DESC_TMC), // descIDLER_TMC_OVERHEAT_ERROR
|
||||
_R(MSG_DESC_TMC), // descPULLEY_TMC_DRIVER_ERROR
|
||||
_R(MSG_DESC_TMC), // descSELECTOR_TMC_DRIVER_ERROR
|
||||
_R(MSG_DESC_TMC), // descIDLER_TMC_DRIVER_ERROR
|
||||
_R(MSG_DESC_TMC), // descPULLEY_TMC_DRIVER_RESET
|
||||
_R(MSG_DESC_TMC), // descSELECTOR_TMC_DRIVER_RESET
|
||||
_R(MSG_DESC_TMC), // descIDLER_TMC_DRIVER_RESET
|
||||
_R(MSG_DESC_TMC), // descPULLEY_TMC_UNDERVOLTAGE_ERROR
|
||||
_R(MSG_DESC_TMC), // descSELECTOR_TMC_UNDERVOLTAGE_ERROR
|
||||
_R(MSG_DESC_TMC), // descIDLER_TMC_UNDERVOLTAGE_ERROR
|
||||
_R(MSG_DESC_TMC), // descPULLEY_TMC_DRIVER_SHORTED
|
||||
_R(MSG_DESC_TMC), // descSELECTOR_TMC_DRIVER_SHORTED
|
||||
_R(MSG_DESC_TMC), // descIDLER_TMC_DRIVER_SHORTED
|
||||
_R(MSG_DESC_MMU_NOT_RESPONDING),
|
||||
_R(MSG_DESC_COMMUNICATION_ERROR),
|
||||
_R(MSG_DESC_FILAMENT_ALREADY_LOADED),
|
||||
_R(MSG_DESC_INVALID_TOOL),
|
||||
_R(MSG_DESC_QUEUE_FULL),
|
||||
_R(MSG_DESC_FW_UPDATE_NEEDED),
|
||||
_R(MSG_DESC_FW_RUNTIME_ERROR),
|
||||
_R(MSG_DESC_UNLOAD_MANUALLY)
|
||||
};
|
||||
|
||||
// we have max 3 buttons/operations to select from
|
||||
// one of them is "More" to show the explanation text normally hidden in the next screens.
|
||||
// 01234567890123456789
|
||||
// >bttxt >bttxt>MoreW
|
||||
// Therefore at least some of the buttons, which can occur on the screen together, need to be 5-chars long max @@TODO.
|
||||
// Beware - we only have space for 2 buttons on the LCD while the MMU has 3 buttons
|
||||
// -> the left button on the MMU is not used/rendered on the LCD (it is also almost unused on the MMU side)
|
||||
static const char MSG_BTN_RETRY[] PROGMEM_I1 = ISTR("Retry"); ////MSG_BTN_RETRY c=5
|
||||
static const char MSG_BTN_CONTINUE[] PROGMEM_I1 = ISTR("Done"); ////MSG_BTN_CONTINUE c=5
|
||||
static const char MSG_BTN_RESTART_MMU[] PROGMEM_I1 = ISTR("Reset MMU"); ////MSG_BTN_RESTART_MMU c=9
|
||||
static const char MSG_BTN_UNLOAD[] PROGMEM_I1 = ISTR("Unload"); ////MSG_BTN_UNLOAD c=6
|
||||
static const char MSG_BTN_STOP[] PROGMEM_I1 = ISTR("Stop"); ////MSG_BTN_STOP c=5
|
||||
static const char MSG_BTN_DISABLE_MMU[] PROGMEM_I1 = ISTR("Disable"); ////MSG_BTN_DISABLE_MMU c=9
|
||||
static const char MSG_BTN_MORE[] PROGMEM_I1 = ISTR("More\x06"); ////MSG_BTN_MORE c=5
|
||||
|
||||
// Used to parse the buttons from Btns().
|
||||
static const char * const btnOperation[] PROGMEM = {
|
||||
_R(MSG_BTN_RETRY),
|
||||
_R(MSG_BTN_CONTINUE),
|
||||
_R(MSG_BTN_RESTART_MMU),
|
||||
_R(MSG_BTN_UNLOAD),
|
||||
_R(MSG_BTN_STOP),
|
||||
_R(MSG_BTN_DISABLE_MMU),
|
||||
};
|
||||
|
||||
// We have 8 different operations/buttons at this time, so we need at least 4 bits to encode each.
|
||||
// Since one of the buttons is always "More", we can skip that one.
|
||||
// Therefore we need just 1 byte to describe the necessary buttons for each screen.
|
||||
uint8_t constexpr Btns(ButtonOperations bMiddle, ButtonOperations bRight){
|
||||
return ((uint8_t)bRight) << 4 | ((uint8_t)bMiddle);
|
||||
}
|
||||
|
||||
static const uint8_t errorButtons[] PROGMEM = {
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::Continue),//FINDA_DIDNT_TRIGGER
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::Continue),//FINDA_DIDNT_GO_OFF
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//FSENSOR_DIDNT_TRIGGER
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//FSENSOR_DIDNT_GO_OFF
|
||||
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//PULLEY_STALLED
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//FSENSOR_TOO_EARLY
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//SELECTOR_CANNOT_HOME
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//SELECTOR_CANNOT_MOVE
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//IDLER_CANNOT_HOME
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//IDLER_CANNOT_MOVE
|
||||
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::RestartMMU),//PULLEY_WARNING_TMC_TOO_HOT
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::RestartMMU),//SELECTOR_WARNING_TMC_TOO_HOT
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::RestartMMU),//IDLER_WARNING_TMC_TOO_HOT
|
||||
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_DRIVER_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_DRIVER_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_DRIVER_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_DRIVER_RESET
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_DRIVER_RESET
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_DRIVER_RESET
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//PULLEY_TMC_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//SELECTOR_TMC_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//IDLER_TMC_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//MMU_NOT_RESPONDING
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//COMMUNICATION_ERROR
|
||||
|
||||
Btns(ButtonOperations::Unload, ButtonOperations::Continue),//FILAMENT_ALREADY_LOADED
|
||||
Btns(ButtonOperations::StopPrint, ButtonOperations::RestartMMU),//INVALID_TOOL
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//QUEUE_FULL
|
||||
Btns(ButtonOperations::DisableMMU, ButtonOperations::NoOperation),//FW_UPDATE_NEEDED
|
||||
Btns(ButtonOperations::RestartMMU, ButtonOperations::NoOperation),//FW_RUNTIME_ERROR
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation),//UNLOAD_MANUALLY
|
||||
};
|
||||
|
||||
static_assert( sizeof(errorCodes) / sizeof(errorCodes[0]) == sizeof(errorDescs) / sizeof (errorDescs[0]));
|
||||
static_assert( sizeof(errorCodes) / sizeof(errorCodes[0]) == sizeof(errorTitles) / sizeof (errorTitles[0]));
|
||||
static_assert( sizeof(errorCodes) / sizeof(errorCodes[0]) == sizeof(errorButtons) / sizeof (errorButtons[0]));
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
/// @file progress_codes.h
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
/// A complete set of progress codes which may be reported while running a high-level command/operation
|
||||
/// This header file shall be included in the printer's firmware as well as a reference,
|
||||
/// therefore the progress codes have been extracted to one place
|
||||
enum class ProgressCode : uint_fast8_t {
|
||||
OK = 0, ///< finished ok
|
||||
|
||||
EngagingIdler, // P1
|
||||
DisengagingIdler, // P2
|
||||
UnloadingToFinda, // P3
|
||||
UnloadingToPulley, //P4
|
||||
FeedingToFinda, // P5
|
||||
FeedingToExtruder, // P6
|
||||
FeedingToNozzle, // P7
|
||||
AvoidingGrind, // P8
|
||||
FinishingMoves, // P9
|
||||
|
||||
ERRDisengagingIdler, // P10
|
||||
ERREngagingIdler, // P11
|
||||
ERRWaitingForUser, // P12
|
||||
ERRInternal, // P13
|
||||
ERRHelpingFilament, // P14
|
||||
ERRTMCFailed, // P15
|
||||
|
||||
UnloadingFilament, // P16
|
||||
LoadingFilament, // P17
|
||||
SelectingFilamentSlot, // P18
|
||||
PreparingBlade, // P19
|
||||
PushingFilament, // P20
|
||||
PerformingCut, // P21
|
||||
ReturningSelector, // P22
|
||||
ParkingSelector, // P23
|
||||
EjectingFilament, // P24
|
||||
RetractingFromFinda, // P25
|
||||
|
||||
Homing, // P26
|
||||
MovingSelector, // P27
|
||||
|
||||
FeedingToFSensor, // P28
|
||||
|
||||
Empty = 0xff // dummy empty state
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
/// @file
|
||||
#include "mmu2_crc.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <util/crc16.h>
|
||||
#endif
|
||||
|
||||
namespace modules {
|
||||
namespace crc {
|
||||
|
||||
#ifdef __AVR__
|
||||
uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
|
||||
return _crc8_ccitt_update(crc, b);
|
||||
}
|
||||
#else
|
||||
uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
|
||||
return CCITT_updateCX(crc, b);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace crc
|
||||
} // namespace modules
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
/// @file
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace modules {
|
||||
|
||||
/// Contains all the necessary functions for computation of CRC
|
||||
namespace crc {
|
||||
|
||||
class CRC8 {
|
||||
public:
|
||||
/// Compute/update CRC8 CCIIT from 8bits.
|
||||
/// Details: https://www.nongnu.org/avr-libc/user-manual/group__util__crc.html
|
||||
static uint8_t CCITT_update(uint8_t crc, uint8_t b);
|
||||
|
||||
static constexpr uint8_t CCITT_updateCX(uint8_t crc, uint8_t b) {
|
||||
uint8_t data = crc ^ b;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
if ((data & 0x80U) != 0) {
|
||||
data <<= 1U;
|
||||
data ^= 0x07U;
|
||||
} else {
|
||||
data <<= 1U;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Compute/update CRC8 CCIIT from 16bits (convenience wrapper)
|
||||
static constexpr uint8_t CCITT_updateW(uint8_t crc, uint16_t w) {
|
||||
union U {
|
||||
uint8_t b[2];
|
||||
uint16_t w;
|
||||
explicit constexpr inline U(uint16_t w)
|
||||
: w(w) {}
|
||||
} u(w);
|
||||
return CCITT_updateCX(CCITT_updateCX(crc, u.b[0]), u.b[1]);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace crc
|
||||
|
||||
} // namespace modules
|
||||
|
|
@ -0,0 +1,294 @@
|
|||
#include "mmu2_error_converter.h"
|
||||
#include "mmu2/error_codes.h"
|
||||
#include "mmu2/errors_list.h"
|
||||
#include "language.h"
|
||||
#include <stdio.h>
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
static ButtonOperations buttonSelectedOperation = ButtonOperations::NoOperation;
|
||||
|
||||
// we don't have a constexpr find_if in C++17/STL yet
|
||||
template <class InputIt, class UnaryPredicate>
|
||||
constexpr InputIt find_if_cx(InputIt first, InputIt last, UnaryPredicate p) {
|
||||
for (; first != last; ++first) {
|
||||
if (p(*first)) {
|
||||
return first;
|
||||
}
|
||||
}
|
||||
return last;
|
||||
}
|
||||
|
||||
// Making a constexpr FindError should instruct the compiler to optimize the ConvertMMUErrorCode
|
||||
// in such a way that no searching will ever be done at runtime.
|
||||
// A call to FindError then compiles to a single instruction even on the AVR.
|
||||
static constexpr uint8_t FindErrorIndex(uint16_t pec) {
|
||||
constexpr uint16_t errorCodesSize = sizeof(errorCodes) / sizeof(errorCodes[0]);
|
||||
constexpr const auto *errorCodesEnd = errorCodes + errorCodesSize;
|
||||
const auto *i = find_if_cx(errorCodes, errorCodesEnd, [pec](uint16_t ed){ return ed == pec; });
|
||||
return (i != errorCodesEnd) ? (i-errorCodes) : (errorCodesSize - 1);
|
||||
}
|
||||
|
||||
// check that the searching algoritm works
|
||||
static_assert( FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER) == 0);
|
||||
static_assert( FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_GO_OFF) == 1);
|
||||
static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER) == 2);
|
||||
static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF) == 3);
|
||||
|
||||
uint8_t PrusaErrorCodeIndex(uint16_t ec) {
|
||||
switch (ec) {
|
||||
case (uint16_t)ErrorCode::FINDA_DIDNT_SWITCH_ON:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER);
|
||||
case (uint16_t)ErrorCode::FINDA_DIDNT_SWITCH_OFF:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_GO_OFF);
|
||||
case (uint16_t)ErrorCode::FSENSOR_DIDNT_SWITCH_ON:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER);
|
||||
case (uint16_t)ErrorCode::FSENSOR_DIDNT_SWITCH_OFF:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF);
|
||||
case (uint16_t)ErrorCode::FSENSOR_TOO_EARLY:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_TOO_EARLY);
|
||||
|
||||
case (uint16_t)ErrorCode::STALLED_PULLEY:
|
||||
case (uint16_t)ErrorCode::MOVE_PULLEY_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_PULLEY_CANNOT_MOVE);
|
||||
|
||||
case (uint16_t)ErrorCode::HOMING_SELECTOR_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_HOME);
|
||||
case (uint16_t)ErrorCode::MOVE_SELECTOR_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_MOVE);
|
||||
|
||||
case (uint16_t)ErrorCode::HOMING_IDLER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_HOME);
|
||||
case (uint16_t)ErrorCode::MOVE_IDLER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_MOVE);
|
||||
|
||||
case (uint16_t)ErrorCode::MMU_NOT_RESPONDING:
|
||||
return FindErrorIndex(ERR_CONNECT_MMU_NOT_RESPONDING);
|
||||
case (uint16_t)ErrorCode::PROTOCOL_ERROR:
|
||||
return FindErrorIndex(ERR_CONNECT_COMMUNICATION_ERROR);
|
||||
case (uint16_t)ErrorCode::FILAMENT_ALREADY_LOADED:
|
||||
return FindErrorIndex(ERR_SYSTEM_FILAMENT_ALREADY_LOADED);
|
||||
case (uint16_t)ErrorCode::INVALID_TOOL:
|
||||
return FindErrorIndex(ERR_SYSTEM_INVALID_TOOL);
|
||||
case (uint16_t)ErrorCode::QUEUE_FULL:
|
||||
return FindErrorIndex(ERR_SYSTEM_QUEUE_FULL);
|
||||
case (uint16_t)ErrorCode::VERSION_MISMATCH:
|
||||
return FindErrorIndex(ERR_SYSTEM_FW_UPDATE_NEEDED);
|
||||
case (uint16_t)ErrorCode::INTERNAL:
|
||||
return FindErrorIndex(ERR_SYSTEM_FW_RUNTIME_ERROR);
|
||||
case (uint16_t)ErrorCode::FINDA_VS_EEPROM_DISREPANCY:
|
||||
return FindErrorIndex(ERR_SYSTEM_UNLOAD_MANUALLY);
|
||||
}
|
||||
|
||||
// TMC-related errors - multiple of these can occur at once
|
||||
// - in such a case we report the first which gets found/converted into Prusa-Error-Codes (usually the fact, that one TMC has an issue is serious enough)
|
||||
// By carefully ordering the checks here we can prioritize the errors being reported to the user.
|
||||
if (ec & (uint16_t)ErrorCode::TMC_PULLEY_BIT) {
|
||||
if (ec & (uint16_t)ErrorCode::TMC_IOIN_MISMATCH)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_RESET)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_SHORT_TO_GROUND)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_WARN)
|
||||
return FindErrorIndex(ERR_TEMPERATURE_PULLEY_WARNING_TMC_TOO_HOT);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_ERROR)
|
||||
return FindErrorIndex(ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR);
|
||||
} else if (ec & (uint16_t)ErrorCode::TMC_SELECTOR_BIT) {
|
||||
if (ec & (uint16_t)ErrorCode::TMC_IOIN_MISMATCH)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_RESET)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_SHORT_TO_GROUND)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_WARN)
|
||||
return FindErrorIndex(ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_ERROR)
|
||||
return FindErrorIndex(ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR);
|
||||
} else if (ec & (uint16_t)ErrorCode::TMC_IDLER_BIT) {
|
||||
if (ec & (uint16_t)ErrorCode::TMC_IOIN_MISMATCH)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_RESET)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_SHORT_TO_GROUND)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_WARN)
|
||||
return FindErrorIndex(ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT);
|
||||
if (ec & (uint16_t)ErrorCode::TMC_OVER_TEMPERATURE_ERROR)
|
||||
return FindErrorIndex(ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR);
|
||||
}
|
||||
|
||||
// if nothing got caught, return a generic runtime error
|
||||
return FindErrorIndex(ERR_SYSTEM_FW_RUNTIME_ERROR);
|
||||
}
|
||||
|
||||
uint16_t PrusaErrorCode(uint8_t i){
|
||||
return pgm_read_word(errorCodes + i);
|
||||
}
|
||||
|
||||
const char * const PrusaErrorTitle(uint8_t i){
|
||||
return (const char * const)pgm_read_ptr(errorTitles + i);
|
||||
}
|
||||
|
||||
const char * const PrusaErrorDesc(uint8_t i){
|
||||
return (const char * const)pgm_read_ptr(errorDescs + i);
|
||||
}
|
||||
|
||||
uint8_t PrusaErrorButtons(uint8_t i){
|
||||
return pgm_read_byte(errorButtons + i);
|
||||
}
|
||||
|
||||
const char * const PrusaErrorButtonTitle(uint8_t bi){
|
||||
// -1 represents the hidden NoOperation button which is not drawn in any way
|
||||
return (const char * const)pgm_read_ptr(btnOperation + bi - 1);
|
||||
}
|
||||
|
||||
const char * const PrusaErrorButtonMore(){
|
||||
return _R(MSG_BTN_MORE);
|
||||
}
|
||||
|
||||
struct ResetOnExit {
|
||||
ResetOnExit() = default;
|
||||
~ResetOnExit(){
|
||||
buttonSelectedOperation = ButtonOperations::NoOperation;
|
||||
}
|
||||
};
|
||||
|
||||
Buttons ButtonPressed(uint16_t ec) {
|
||||
if (buttonSelectedOperation == ButtonOperations::NoOperation) {
|
||||
return NoButton; // no button
|
||||
}
|
||||
|
||||
ResetOnExit ros; // clear buttonSelectedOperation on exit from this call
|
||||
return ButtonAvailable(ec);
|
||||
}
|
||||
|
||||
Buttons ButtonAvailable(uint16_t ec) {
|
||||
uint8_t ei = PrusaErrorCodeIndex(ec);
|
||||
|
||||
// The list of responses which occur in mmu error dialogs
|
||||
// Return button index or perform some action on the MK3 by itself (like restart MMU)
|
||||
// Based on Prusa-Error-Codes errors_list.h
|
||||
// So far hardcoded, but shall be generated in the future
|
||||
switch ( PrusaErrorCode(ei) ) {
|
||||
case ERR_MECHANICAL_FINDA_DIDNT_TRIGGER:
|
||||
case ERR_MECHANICAL_FINDA_DIDNT_GO_OFF:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Retry: // "Repeat action"
|
||||
return Middle;
|
||||
case ButtonOperations::Continue: // "Continue"
|
||||
return Right;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER:
|
||||
case ERR_MECHANICAL_FSENSOR_DIDNT_GO_OFF:
|
||||
case ERR_MECHANICAL_FSENSOR_TOO_EARLY:
|
||||
case ERR_MECHANICAL_SELECTOR_CANNOT_HOME:
|
||||
case ERR_MECHANICAL_SELECTOR_CANNOT_MOVE:
|
||||
case ERR_MECHANICAL_IDLER_CANNOT_HOME:
|
||||
case ERR_MECHANICAL_IDLER_CANNOT_MOVE:
|
||||
case ERR_MECHANICAL_PULLEY_CANNOT_MOVE:
|
||||
case ERR_SYSTEM_UNLOAD_MANUALLY:
|
||||
switch (buttonSelectedOperation) {
|
||||
// may be allow move selector right and left in the future
|
||||
case ButtonOperations::Retry: // "Repeat action"
|
||||
return Middle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_TEMPERATURE_PULLEY_WARNING_TMC_TOO_HOT:
|
||||
case ERR_TEMPERATURE_SELECTOR_WARNING_TMC_TOO_HOT:
|
||||
case ERR_TEMPERATURE_IDLER_WARNING_TMC_TOO_HOT:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Continue: // "Continue"
|
||||
return Left;
|
||||
case ButtonOperations::RestartMMU: // "Restart MMU"
|
||||
return RestartMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_TEMPERATURE_PULLEY_TMC_OVERHEAT_ERROR:
|
||||
case ERR_TEMPERATURE_SELECTOR_TMC_OVERHEAT_ERROR:
|
||||
case ERR_TEMPERATURE_IDLER_TMC_OVERHEAT_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_PULLEY_TMC_DRIVER_ERROR:
|
||||
case ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_ERROR:
|
||||
case ERR_ELECTRICAL_IDLER_TMC_DRIVER_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_PULLEY_TMC_DRIVER_RESET:
|
||||
case ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_RESET:
|
||||
case ERR_ELECTRICAL_IDLER_TMC_DRIVER_RESET:
|
||||
|
||||
case ERR_ELECTRICAL_PULLEY_TMC_UNDERVOLTAGE_ERROR:
|
||||
case ERR_ELECTRICAL_SELECTOR_TMC_UNDERVOLTAGE_ERROR:
|
||||
case ERR_ELECTRICAL_IDLER_TMC_UNDERVOLTAGE_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_PULLEY_TMC_DRIVER_SHORTED:
|
||||
case ERR_ELECTRICAL_SELECTOR_TMC_DRIVER_SHORTED:
|
||||
case ERR_ELECTRICAL_IDLER_TMC_DRIVER_SHORTED:
|
||||
|
||||
case ERR_CONNECT_MMU_NOT_RESPONDING:
|
||||
case ERR_CONNECT_COMMUNICATION_ERROR:
|
||||
|
||||
case ERR_SYSTEM_QUEUE_FULL:
|
||||
case ERR_SYSTEM_FW_RUNTIME_ERROR:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::RestartMMU: // "Restart MMU"
|
||||
return RestartMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_SYSTEM_FW_UPDATE_NEEDED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::DisableMMU: // "Disable"
|
||||
return DisableMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_SYSTEM_FILAMENT_ALREADY_LOADED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Unload: // "Unload"
|
||||
return Left;
|
||||
case ButtonOperations::Continue: // "Proceed/Continue"
|
||||
return Right;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_SYSTEM_INVALID_TOOL:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::StopPrint: // "Stop print"
|
||||
return StopPrint;
|
||||
case ButtonOperations::RestartMMU: // "Restart MMU"
|
||||
return RestartMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return NoButton;
|
||||
}
|
||||
|
||||
void SetButtonResponse(ButtonOperations rsp){
|
||||
buttonSelectedOperation = rsp;
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "mmu2/buttons.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
/// Translates MMU2::ErrorCode into an index of Prusa-Error-Codes
|
||||
/// Basically this is the way to obtain an index into all other functions in this API
|
||||
uint8_t PrusaErrorCodeIndex(uint16_t ec);
|
||||
|
||||
/// @returns pointer to a PROGMEM string representing the Title of the Prusa-Error-Codes error
|
||||
/// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
const char * const PrusaErrorTitle(uint8_t i);
|
||||
|
||||
/// @returns pointer to a PROGMEM string representing the multi-page Description of the Prusa-Error-Codes error
|
||||
/// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
const char * const PrusaErrorDesc(uint8_t i);
|
||||
|
||||
/// @returns the actual numerical value of the Prusa-Error-Codes error
|
||||
/// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
uint16_t PrusaErrorCode(uint8_t i);
|
||||
|
||||
/// @returns Btns pair of buttons for a particular Prusa-Error-Codes error
|
||||
/// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
uint8_t PrusaErrorButtons(uint8_t i);
|
||||
|
||||
/// @returns pointer to a PROGMEM string representing the Title of a button
|
||||
/// @param i index of the error - obtained by calling PrusaErrorButtons + extracting low or high nibble from the Btns pair
|
||||
const char * const PrusaErrorButtonTitle(uint8_t bi);
|
||||
|
||||
/// @returns pointer to a PROGMEM string representing the "More" button
|
||||
const char * const PrusaErrorButtonMore();
|
||||
|
||||
/// Sets the selected button for later pick-up by the MMU state machine.
|
||||
/// Used to save the GUI selection/decoupling
|
||||
void SetButtonResponse(ButtonOperations rsp);
|
||||
|
||||
/// @returns button index/code based on currently processed error/screen
|
||||
/// Clears the "pressed" button upon exit
|
||||
Buttons ButtonPressed(uint16_t ec);
|
||||
|
||||
/// @returns button index/code based on currently processed error/screen
|
||||
/// Used as a subfunction of ButtonPressed.
|
||||
/// Does not clear the "pressed" button upon exit
|
||||
Buttons ButtonAvailable(uint16_t ec);
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
#include "mmu2_fsensor.h"
|
||||
#include "Filament_sensor.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
FilamentState WhereIsFilament(){
|
||||
return fsensor.getFilamentPresent() ? FilamentState::AT_FSENSOR : FilamentState::NOT_PRESENT;
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "Filament_sensor.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
/// Possible states of filament from the perspective of presence in various parts of the printer
|
||||
/// Beware, the numeric codes are important and sent into the MMU
|
||||
enum class FilamentState : uint_fast8_t {
|
||||
NOT_PRESENT = 0, ///< filament sensor doesn't see the filament
|
||||
AT_FSENSOR = 1, ///< filament detected by the filament sensor, but the nozzle has not detected the filament yet
|
||||
IN_NOZZLE = 2 ///< filament detected by the filament sensor and also loaded in the nozzle
|
||||
};
|
||||
|
||||
FilamentState WhereIsFilament();
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#include "mmu2_log.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
void LogErrorEvent_P(const char *msg){
|
||||
SERIAL_ERROR_START;
|
||||
SERIAL_MMU2();
|
||||
SERIAL_ECHOLNRPGM(msg);
|
||||
}
|
||||
|
||||
void LogEchoEvent_P(const char *msg){
|
||||
SERIAL_ECHO_START;
|
||||
SERIAL_MMU2();
|
||||
SERIAL_ECHOLNRPGM(msg);
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
#pragma once
|
||||
|
||||
#ifndef UNITTEST
|
||||
#include "Marlin.h"
|
||||
|
||||
// Beware - before changing this prefix, think twice
|
||||
// you'd need to change appmain.cpp app_marlin_serial_output_write_hook
|
||||
// and MMU2::ReportError + MMU2::ReportProgress
|
||||
static constexpr char mmu2Magic[] PROGMEM = "MMU2:";
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
/// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
/// @param msg pointer to a string in PROGMEM
|
||||
void LogErrorEvent_P(const char *msg);
|
||||
|
||||
/// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
/// @param msg pointer to a string in PROGMEM
|
||||
void LogEchoEvent_P(const char *msg);
|
||||
|
||||
} // namespace
|
||||
|
||||
#define SERIAL_MMU2() { serialprintPGM(mmu2Magic); }
|
||||
|
||||
#define MMU2_ECHO_MSG(S) do{ SERIAL_ECHO_START; SERIAL_MMU2(); SERIAL_ECHO(S); }while(0)
|
||||
#define MMU2_ERROR_MSG(S) do{ SERIAL_ERROR_START; SERIAL_MMU2(); SERIAL_ECHO(S); }while(0)
|
||||
#define MMU2_ECHO_MSGRPGM(S) do{ SERIAL_ECHO_START; SERIAL_MMU2(); SERIAL_ECHORPGM(S); }while(0)
|
||||
#define MMU2_ERROR_MSGRPGM(S) do{ SERIAL_ERROR_START; SERIAL_MMU2(); SERIAL_ECHORPGM(S); }while(0)
|
||||
|
||||
#else // #ifndef UNITTEST
|
||||
|
||||
#define MMU2_ECHO_MSG(S) /* */
|
||||
#define MMU2_ERROR_MSG(S) /* */
|
||||
#define SERIAL_ECHO(S) /* */
|
||||
#define SERIAL_ECHOLN(S) /* */
|
||||
#define MMU2_ECHO_MSGRPGM(S) /* */
|
||||
#define MMU2_ERROR_MSGRPGM(S) /* */
|
||||
|
||||
#endif // #ifndef UNITTEST
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
#include "mmu2_power.h"
|
||||
#include "Configuration_prusa.h"
|
||||
#include "pins.h"
|
||||
#include "fastio.h"
|
||||
#include <util/delay.h>
|
||||
#include "mmu2.h"
|
||||
#include "eeprom.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
// sadly, on MK3 we cannot do actual power cycle on HW...
|
||||
// so we just block the MMU via EEPROM var instead.
|
||||
void power_on()
|
||||
{
|
||||
eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, true);
|
||||
}
|
||||
|
||||
void power_off()
|
||||
{
|
||||
eeprom_update_byte((uint8_t *)EEPROM_MMU_ENABLED, false);
|
||||
}
|
||||
|
||||
void reset() {
|
||||
#ifdef MMU_HWRESET // HW - pulse reset pin
|
||||
WRITE(MMU_RST_PIN, 0);
|
||||
_delay_us(100);
|
||||
WRITE(MMU_RST_PIN, 1);
|
||||
#else
|
||||
mmu2.Reset(MMU2::Software); // @@TODO needs to be redesigned, this power implementation shall not know anything about the MMU itself
|
||||
#endif
|
||||
// otherwise HW reset is not available
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
void power_on();
|
||||
|
||||
void power_off();
|
||||
|
||||
void reset();
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
#include "mmu2_progress_converter.h"
|
||||
#include "language.h"
|
||||
#include "mmu2/progress_codes.h"
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
namespace MMU2 {
|
||||
//01234567890123456789
|
||||
static const char MSG_PROGRESS_OK[] PROGMEM_I1 = ISTR("OK"); ////MSG_PROGRESS_OK c=4
|
||||
static const char MSG_PROGRESS_ENGAGE_IDLER[] PROGMEM_I1 = ISTR("Engaging idler"); ////MSG_PROGRESS_ENGAGE_IDLER c=20
|
||||
static const char MSG_PROGRESS_DISENGAGE_IDLER[] PROGMEM_I1 = ISTR("Disengaging idler"); ////MSG_PROGRESS_DISENGAGE_IDLER c=20
|
||||
static const char MSG_PROGRESS_UNLOAD_FINDA[] PROGMEM_I1 = ISTR("Unloading to FINDA"); ////MSG_PROGRESS_UNLOAD_FINDA c=20
|
||||
static const char MSG_PROGRESS_UNLOAD_PULLEY[] PROGMEM_I1 = ISTR("Unloading to pulley"); ////MSG_PROGRESS_UNLOAD_PULLEY c=20
|
||||
static const char MSG_PROGRESS_FEED_FINDA[] PROGMEM_I1 = ISTR("Feeding to FINDA"); ////MSG_PROGRESS_FEED_FINDA c=20
|
||||
static const char MSG_PROGRESS_FEED_EXTRUDER[] PROGMEM_I1 = ISTR("Feeding to extruder"); ////MSG_PROGRESS_FEED_EXTRUDER c=20
|
||||
static const char MSG_PROGRESS_FEED_NOZZLE[] PROGMEM_I1 = ISTR("Feeding to nozzle"); ////MSG_PROGRESS_FEED_NOZZLE c=20
|
||||
static const char MSG_PROGRESS_AVOID_GRIND[] PROGMEM_I1 = ISTR("Avoiding grind"); ////MSG_PROGRESS_AVOID_GRIND c=20
|
||||
static const char MSG_PROGRESS_WAIT_USER[] PROGMEM_I1 = ISTR("ERR Wait for User"); ////MSG_PROGRESS_WAIT_USER c=20
|
||||
static const char MSG_PROGRESS_ERR_INTERNAL[] PROGMEM_I1 = ISTR("ERR Internal"); ////MSG_PROGRESS_ERR_INTERNAL c=20
|
||||
static const char MSG_PROGRESS_ERR_HELP_FIL[] PROGMEM_I1 = ISTR("ERR Help filament"); ////MSG_PROGRESS_ERR_HELP_FIL c=20
|
||||
static const char MSG_PROGRESS_ERR_TMC[] PROGMEM_I1 = ISTR("ERR TMC failed"); ////MSG_PROGRESS_ERR_TMC c=20
|
||||
static const char MSG_PROGRESS_SELECT_SLOT[] PROGMEM_I1 = ISTR("Selecting fil. slot"); ////MSG_PROGRESS_SELECT_SLOT c=20
|
||||
static const char MSG_PROGRESS_PREPARE_BLADE[] PROGMEM_I1 = ISTR("Preparing blade"); ////MSG_PROGRESS_PREPARE_BLADE c=20
|
||||
static const char MSG_PROGRESS_PUSH_FILAMENT[] PROGMEM_I1 = ISTR("Pushing filament"); ////MSG_PROGRESS_PUSH_FILAMENT c=20
|
||||
static const char MSG_PROGRESS_PERFORM_CUT[] PROGMEM_I1 = ISTR("Performing cut"); ////MSG_PROGRESS_PERFORM_CUT c=20
|
||||
static const char MSG_PROGRESS_RETURN_SELECTOR[] PROGMEM_I1 = ISTR("Returning selector"); ////MSG_PROGRESS_RETURN_SELECTOR c=20
|
||||
static const char MSG_PROGRESS_PARK_SELECTOR[] PROGMEM_I1 = ISTR("Parking selector"); ////MSG_PROGRESS_PARK_SELECTOR c=20
|
||||
static const char MSG_PROGRESS_EJECT_FILAMENT[] PROGMEM_I1 = ISTR("Ejecting filament"); ////MSG_PROGRESS_EJECT_FILAMENT c=20 //@@todo duplicate
|
||||
static const char MSG_PROGRESS_RETRACT_FINDA[] PROGMEM_I1 = ISTR("Retract from FINDA"); ////MSG_PROGRESS_RETRACT_FINDA c=20
|
||||
static const char MSG_PROGRESS_HOMING[] PROGMEM_I1 = ISTR("Homing"); ////MSG_PROGRESS_HOMING c=20
|
||||
static const char MSG_PROGRESS_MOVING_SELECTOR[] PROGMEM_I1 = ISTR("Moving selector"); ////MSG_PROGRESS_MOVING_SELECTOR c=20
|
||||
static const char MSG_PROGRESS_FEED_FSENSOR[] PROGMEM_I1 = ISTR("Feeding to FSensor"); ////MSG_PROGRESS_FEED_FSENSOR c=20
|
||||
|
||||
static const char * const progressTexts[] PROGMEM = {
|
||||
_R(MSG_PROGRESS_OK),
|
||||
_R(MSG_PROGRESS_ENGAGE_IDLER),
|
||||
_R(MSG_PROGRESS_DISENGAGE_IDLER),
|
||||
_R(MSG_PROGRESS_UNLOAD_FINDA),
|
||||
_R(MSG_PROGRESS_UNLOAD_PULLEY),
|
||||
_R(MSG_PROGRESS_FEED_FINDA),
|
||||
_R(MSG_PROGRESS_FEED_EXTRUDER),
|
||||
_R(MSG_PROGRESS_FEED_NOZZLE),
|
||||
_R(MSG_PROGRESS_AVOID_GRIND),
|
||||
_R(MSG_FINISHING_MOVEMENTS), //reuse from messages.cpp
|
||||
_R(MSG_PROGRESS_DISENGAGE_IDLER), // err disengaging idler is the same text
|
||||
_R(MSG_PROGRESS_ENGAGE_IDLER), // engage dtto.
|
||||
_R(MSG_PROGRESS_WAIT_USER),
|
||||
_R(MSG_PROGRESS_ERR_INTERNAL),
|
||||
_R(MSG_PROGRESS_ERR_HELP_FIL),
|
||||
_R(MSG_PROGRESS_ERR_TMC),
|
||||
_R(MSG_UNLOADING_FILAMENT), //reuse from messages.cpp
|
||||
_R(MSG_LOADING_FILAMENT), //reuse from messages.cpp
|
||||
_R(MSG_PROGRESS_SELECT_SLOT),
|
||||
_R(MSG_PROGRESS_PREPARE_BLADE),
|
||||
_R(MSG_PROGRESS_PUSH_FILAMENT),
|
||||
_R(MSG_PROGRESS_PERFORM_CUT),
|
||||
_R(MSG_PROGRESS_RETURN_SELECTOR),
|
||||
_R(MSG_PROGRESS_PARK_SELECTOR),
|
||||
_R(MSG_PROGRESS_EJECT_FILAMENT),
|
||||
_R(MSG_PROGRESS_RETRACT_FINDA),
|
||||
_R(MSG_PROGRESS_HOMING),
|
||||
_R(MSG_PROGRESS_MOVING_SELECTOR),
|
||||
_R(MSG_PROGRESS_FEED_FSENSOR)
|
||||
};
|
||||
|
||||
const char * const ProgressCodeToText(uint16_t pc){
|
||||
// @@TODO ?? a better fallback option?
|
||||
return ( pc <= (sizeof(progressTexts) / sizeof(progressTexts[0])) )
|
||||
? static_cast<const char * const>(pgm_read_ptr(&progressTexts[pc]))
|
||||
: static_cast<const char * const>(pgm_read_ptr(&progressTexts[0]));
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
const char * const ProgressCodeToText(uint16_t pc);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,366 @@
|
|||
/// @file
|
||||
#include "mmu2_protocol.h"
|
||||
|
||||
// protocol definition
|
||||
// command: Q0
|
||||
// meaning: query operation status
|
||||
// Query/command: query
|
||||
// Expected reply from the MMU:
|
||||
// any of the running operation statuses: OID: [T|L|U|E|C|W|K][0-4]
|
||||
// <OID> P[0-9] : command being processed i.e. operation running, may contain a state number
|
||||
// <OID> E[0-9][0-9] : error 1-9 while doing a tool change
|
||||
// <OID> F[0-9] : operation finished - will be repeated to "Q" messages until a new command is issued
|
||||
|
||||
namespace modules {
|
||||
namespace protocol {
|
||||
|
||||
// decoding automaton
|
||||
// states: input -> transition into state
|
||||
// Code QTLMUXPSBEWK -> msgcode
|
||||
// \n ->start
|
||||
// * ->error
|
||||
// error \n ->start
|
||||
// * ->error
|
||||
// msgcode 0-9 ->msgvalue
|
||||
// * ->error
|
||||
// msgvalue 0-9 ->msgvalue
|
||||
// \n ->start successfully accepted command
|
||||
|
||||
DecodeStatus Protocol::DecodeRequest(uint8_t c) {
|
||||
switch (rqState) {
|
||||
case RequestStates::Code:
|
||||
switch (c) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
case 'L':
|
||||
case 'M':
|
||||
case 'U':
|
||||
case 'X':
|
||||
case 'P':
|
||||
case 'S':
|
||||
case 'B':
|
||||
case 'E':
|
||||
case 'W': // write is gonna be a special one
|
||||
case 'K':
|
||||
case 'F':
|
||||
case 'f':
|
||||
case 'H':
|
||||
case 'R':
|
||||
requestMsg.code = (RequestMsgCodes)c;
|
||||
requestMsg.value = 0;
|
||||
requestMsg.value2 = 0;
|
||||
requestMsg.crc8 = 0;
|
||||
rqState = (c == 'W') ? RequestStates::Address : RequestStates::Value; // prepare special automaton path for Write commands
|
||||
return DecodeStatus::NeedMoreData;
|
||||
default:
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::Value:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.value <<= 4U;
|
||||
requestMsg.value |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else if (IsCRCSeparator(c)) {
|
||||
rqState = RequestStates::CRC;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::Address:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.value <<= 4U;
|
||||
requestMsg.value |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else if (c == ' ') { // end of address, value coming
|
||||
rqState = RequestStates::WriteValue;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::WriteValue:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.value2 <<= 4U;
|
||||
requestMsg.value2 |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else if (IsCRCSeparator(c)) {
|
||||
rqState = RequestStates::CRC;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case RequestStates::CRC:
|
||||
if (IsHexDigit(c)) {
|
||||
requestMsg.crc8 <<= 4U;
|
||||
requestMsg.crc8 |= Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else if (IsNewLine(c)) {
|
||||
// check CRC at this spot
|
||||
if (requestMsg.crc8 != requestMsg.ComputeCRC8()) {
|
||||
// CRC mismatch
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
} else {
|
||||
rqState = RequestStates::Code;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
}
|
||||
}
|
||||
default: //case error:
|
||||
if (IsNewLine(c)) {
|
||||
rqState = RequestStates::Code;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
} else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeRequest(const RequestMsg &msg, uint8_t *txbuff) {
|
||||
txbuff[0] = (uint8_t)msg.code;
|
||||
uint8_t i = 1 + UInt8ToHex(msg.value, txbuff + 1);
|
||||
|
||||
i += AppendCRC(msg.CRC(), txbuff + i);
|
||||
|
||||
txbuff[i] = '\n';
|
||||
++i;
|
||||
return i;
|
||||
static_assert(7 <= MaxRequestSize(), "Request message length exceeded the maximum size, increase the magic constant in MaxRequestSize()");
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff) {
|
||||
const RequestMsg msg(RequestMsgCodes::Write, address, value);
|
||||
uint8_t i = BeginEncodeRequest(msg, txbuff);
|
||||
// dump the value
|
||||
i += UInt16ToHex(value, txbuff + i);
|
||||
|
||||
i += AppendCRC(msg.CRC(), txbuff + i);
|
||||
|
||||
txbuff[i] = '\n';
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
DecodeStatus Protocol::DecodeResponse(uint8_t c) {
|
||||
switch (rspState) {
|
||||
case ResponseStates::RequestCode:
|
||||
switch (c) {
|
||||
case 'Q':
|
||||
case 'T':
|
||||
case 'L':
|
||||
case 'M':
|
||||
case 'U':
|
||||
case 'X':
|
||||
case 'P':
|
||||
case 'S':
|
||||
case 'B':
|
||||
case 'E':
|
||||
case 'W':
|
||||
case 'K':
|
||||
case 'F':
|
||||
case 'f':
|
||||
case 'H':
|
||||
case 'R':
|
||||
responseMsg.request.code = (RequestMsgCodes)c;
|
||||
responseMsg.request.value = 0;
|
||||
responseMsg.request.value2 = 0;
|
||||
responseMsg.request.crc8 = 0;
|
||||
rspState = ResponseStates::RequestValue;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
case 0x0a:
|
||||
case 0x0d:
|
||||
// skip leading whitespace if any (makes integration with other SW easier/tolerant)
|
||||
return DecodeStatus::NeedMoreData;
|
||||
default:
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::RequestValue:
|
||||
if (IsHexDigit(c)) {
|
||||
responseMsg.request.value <<= 4U;
|
||||
responseMsg.request.value += Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else if (c == ' ') {
|
||||
rspState = ResponseStates::ParamCode;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else {
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::ParamCode:
|
||||
switch (c) {
|
||||
case 'P':
|
||||
case 'E':
|
||||
case 'F':
|
||||
case 'A':
|
||||
case 'R':
|
||||
case 'B':
|
||||
rspState = ResponseStates::ParamValue;
|
||||
responseMsg.paramCode = (ResponseMsgParamCodes)c;
|
||||
responseMsg.paramValue = 0;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
default:
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::ParamValue:
|
||||
if (IsHexDigit(c)) {
|
||||
responseMsg.paramValue <<= 4U;
|
||||
responseMsg.paramValue += Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else if (IsCRCSeparator(c)) {
|
||||
rspState = ResponseStates::CRC;
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else {
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
case ResponseStates::CRC:
|
||||
if (IsHexDigit(c)) {
|
||||
responseMsg.request.crc8 <<= 4U;
|
||||
responseMsg.request.crc8 += Char2Nibble(c);
|
||||
return DecodeStatus::NeedMoreData;
|
||||
} else if (IsNewLine(c)) {
|
||||
// check CRC at this spot
|
||||
if (responseMsg.request.crc8 != responseMsg.ComputeCRC8()) {
|
||||
// CRC mismatch
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
} else {
|
||||
rspState = ResponseStates::RequestCode;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
}
|
||||
} else {
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
rspState = ResponseStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
default: //case error:
|
||||
if (IsNewLine(c)) {
|
||||
rspState = ResponseStates::RequestCode;
|
||||
return DecodeStatus::MessageCompleted;
|
||||
} else {
|
||||
responseMsg.paramCode = ResponseMsgParamCodes::unknown;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff) {
|
||||
// BEWARE:
|
||||
// ResponseMsg rsp(RequestMsg(msg.code, msg.value), ar, 0);
|
||||
// ... is NOT the same as:
|
||||
// ResponseMsg rsp(msg, ar, 0);
|
||||
// ... because of the usually unused parameter value2 (which only comes non-zero in write requests).
|
||||
// It took me a few hours to find out why the CRC from the MMU never matched all the other sides (unit tests and the MK3S)
|
||||
// It is because this was the only place where the original request kept its value2 non-zero.
|
||||
// In the response, we must make sure value2 is actually zero unless being sent along with it (which is not right now)
|
||||
const ResponseMsg rsp(RequestMsg(msg.code, msg.value), ar, 0); // this needs some cleanup @@TODO - check assembly how bad is it
|
||||
uint8_t i = BeginEncodeRequest(rsp.request, txbuff);
|
||||
txbuff[i] = (uint8_t)ar;
|
||||
++i;
|
||||
i += AppendCRC(rsp.CRC(), txbuff + i);
|
||||
txbuff[i] = '\n';
|
||||
++i;
|
||||
return i;
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff) {
|
||||
return EncodeResponseRead(msg, true, findaValue, txbuff);
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseQueryOperation(const RequestMsg &msg, ResponseCommandStatus rcs, uint8_t *txbuff) {
|
||||
const ResponseMsg rsp(msg, rcs.code, rcs.value);
|
||||
uint8_t i = BeginEncodeRequest(msg, txbuff);
|
||||
txbuff[i] = (uint8_t)rsp.paramCode;
|
||||
++i;
|
||||
i += UInt16ToHex(rsp.paramValue, txbuff + i);
|
||||
i += AppendCRC(rsp.CRC(), txbuff + i);
|
||||
txbuff[i] = '\n';
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
uint8_t Protocol::EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff) {
|
||||
const ResponseMsg rsp(msg,
|
||||
accepted ? ResponseMsgParamCodes::Accepted : ResponseMsgParamCodes::Rejected,
|
||||
accepted ? value2 : 0 // be careful about this value for CRC computation - rejected status doesn't have any meaningful value which could be reconstructed from the textual form of the message
|
||||
);
|
||||
uint8_t i = BeginEncodeRequest(msg, txbuff);
|
||||
txbuff[i] = (uint8_t)rsp.paramCode;
|
||||
++i;
|
||||
if (accepted) {
|
||||
// dump the value
|
||||
i += UInt16ToHex(value2, txbuff + i);
|
||||
}
|
||||
i += AppendCRC(rsp.CRC(), txbuff + i);
|
||||
txbuff[i] = '\n';
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
uint8_t Protocol::UInt8ToHex(uint8_t value, uint8_t *dst) {
|
||||
if (value == 0) {
|
||||
*dst = '0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint8_t v = value >> 4U;
|
||||
uint8_t charsOut = 1;
|
||||
if (v != 0) { // skip the first '0' if any
|
||||
*dst = Nibble2Char(v);
|
||||
++dst;
|
||||
charsOut = 2;
|
||||
}
|
||||
v = value & 0xfU;
|
||||
*dst = Nibble2Char(v);
|
||||
return charsOut;
|
||||
}
|
||||
|
||||
uint8_t Protocol::UInt16ToHex(uint16_t value, uint8_t *dst) {
|
||||
constexpr uint16_t topNibbleMask = 0xf000;
|
||||
if (value == 0) {
|
||||
*dst = '0';
|
||||
return 1;
|
||||
}
|
||||
// skip initial zeros
|
||||
uint8_t charsOut = 4;
|
||||
while ((value & topNibbleMask) == 0) {
|
||||
value <<= 4U;
|
||||
--charsOut;
|
||||
}
|
||||
for (uint8_t i = 0; i < charsOut; ++i) {
|
||||
uint8_t n = (value & topNibbleMask) >> (8U + 4U);
|
||||
value <<= 4U;
|
||||
*dst = Nibble2Char(n);
|
||||
++dst;
|
||||
}
|
||||
return charsOut;
|
||||
}
|
||||
|
||||
uint8_t Protocol::BeginEncodeRequest(const RequestMsg &msg, uint8_t *dst) {
|
||||
dst[0] = (uint8_t)msg.code;
|
||||
|
||||
uint8_t i = 1 + UInt8ToHex(msg.value, dst + 1);
|
||||
|
||||
dst[i] = ' ';
|
||||
return i + 1;
|
||||
}
|
||||
|
||||
uint8_t Protocol::AppendCRC(uint8_t crc, uint8_t *dst) {
|
||||
dst[0] = '*'; // reprap-style separator of CRC
|
||||
return 1 + UInt8ToHex(crc, dst + 1);
|
||||
}
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace modules
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
/// @file protocol.h
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include "mmu2_crc.h"
|
||||
|
||||
namespace modules {
|
||||
|
||||
/// @brief The MMU communication protocol implementation and related stuff.
|
||||
///
|
||||
/// See description of the new protocol in the MMU 2021 doc
|
||||
namespace protocol {
|
||||
|
||||
/// Definition of request message codes
|
||||
enum class RequestMsgCodes : uint8_t {
|
||||
unknown = 0,
|
||||
Query = 'Q',
|
||||
Tool = 'T',
|
||||
Load = 'L',
|
||||
Mode = 'M',
|
||||
Unload = 'U',
|
||||
Reset = 'X',
|
||||
Finda = 'P',
|
||||
Version = 'S',
|
||||
Button = 'B',
|
||||
Eject = 'E',
|
||||
Write = 'W',
|
||||
Cut = 'K',
|
||||
FilamentType = 'F',
|
||||
FilamentSensor = 'f',
|
||||
Home = 'H',
|
||||
Read = 'R'
|
||||
};
|
||||
|
||||
/// Definition of response message parameter codes
|
||||
enum class ResponseMsgParamCodes : uint8_t {
|
||||
unknown = 0,
|
||||
Processing = 'P',
|
||||
Error = 'E',
|
||||
Finished = 'F',
|
||||
Accepted = 'A',
|
||||
Rejected = 'R',
|
||||
Button = 'B' // the MMU registered a button press and is sending it to the printer for processing
|
||||
};
|
||||
|
||||
/// A request message - requests are being sent by the printer into the MMU.
|
||||
struct RequestMsg {
|
||||
RequestMsgCodes code; ///< code of the request message
|
||||
uint8_t value; ///< value of the request message or address of variable to read/write
|
||||
uint16_t value2; ///< in case or write messages - value to be written into the register
|
||||
|
||||
/// CRC8 check - please note we abuse this byte for CRC of ResponseMsgs as well.
|
||||
/// The crc8 byte itself is not added into the CRC computation (obviously ;) )
|
||||
/// Beware - adding any members of this data structure may need changing the way CRC is being computed!
|
||||
uint8_t crc8;
|
||||
|
||||
constexpr uint8_t ComputeCRC8() const {
|
||||
uint8_t crc = 0;
|
||||
crc = modules::crc::CRC8::CCITT_updateCX(0, (uint8_t)code);
|
||||
crc = modules::crc::CRC8::CCITT_updateCX(crc, value);
|
||||
crc = modules::crc::CRC8::CCITT_updateW(crc, value2);
|
||||
return crc;
|
||||
}
|
||||
|
||||
/// @param code of the request message
|
||||
/// @param value of the request message
|
||||
inline constexpr RequestMsg(RequestMsgCodes code, uint8_t value)
|
||||
: code(code)
|
||||
, value(value)
|
||||
, value2(0)
|
||||
, crc8(ComputeCRC8()) {
|
||||
}
|
||||
|
||||
/// Intended for write requests
|
||||
/// @param code of the request message ('W')
|
||||
/// @param address of the register
|
||||
/// @param value to write into the register
|
||||
inline constexpr RequestMsg(RequestMsgCodes code, uint8_t address, uint16_t value)
|
||||
: code(code)
|
||||
, value(address)
|
||||
, value2(value)
|
||||
, crc8(ComputeCRC8()) {
|
||||
}
|
||||
|
||||
constexpr uint8_t CRC() const { return crc8; }
|
||||
};
|
||||
|
||||
/// A response message - responses are being sent from the MMU into the printer as a response to a request message.
|
||||
struct ResponseMsg {
|
||||
RequestMsg request; ///< response is always preceeded by the request message
|
||||
ResponseMsgParamCodes paramCode; ///< code of the parameter
|
||||
uint16_t paramValue; ///< value of the parameter
|
||||
|
||||
constexpr uint8_t ComputeCRC8() const {
|
||||
uint8_t crc = request.ComputeCRC8();
|
||||
crc = modules::crc::CRC8::CCITT_updateCX(crc, (uint8_t)paramCode);
|
||||
crc = modules::crc::CRC8::CCITT_updateW(crc, paramValue);
|
||||
return crc;
|
||||
}
|
||||
|
||||
/// @param request the source request message this response is a reply to
|
||||
/// @param paramCode code of the parameter
|
||||
/// @param paramValue value of the parameter
|
||||
inline constexpr ResponseMsg(RequestMsg request, ResponseMsgParamCodes paramCode, uint16_t paramValue)
|
||||
: request(request)
|
||||
, paramCode(paramCode)
|
||||
, paramValue(paramValue) {
|
||||
this->request.crc8 = ComputeCRC8();
|
||||
}
|
||||
|
||||
constexpr uint8_t CRC() const { return request.crc8; }
|
||||
};
|
||||
|
||||
/// Combined commandStatus and its value into one data structure (optimization purposes)
|
||||
struct ResponseCommandStatus {
|
||||
ResponseMsgParamCodes code;
|
||||
uint16_t value;
|
||||
inline constexpr ResponseCommandStatus(ResponseMsgParamCodes code, uint16_t value)
|
||||
: code(code)
|
||||
, value(value) {}
|
||||
};
|
||||
|
||||
/// Message decoding return values
|
||||
enum class DecodeStatus : uint_fast8_t {
|
||||
MessageCompleted, ///< message completed and successfully lexed
|
||||
NeedMoreData, ///< message incomplete yet, waiting for another byte to come
|
||||
Error, ///< input character broke message decoding
|
||||
};
|
||||
|
||||
/// Protocol class is responsible for creating/decoding messages in Rx/Tx buffer
|
||||
///
|
||||
/// Beware - in the decoding more, it is meant to be a statefull instance which works through public methods
|
||||
/// processing one input byte per call.
|
||||
class Protocol {
|
||||
public:
|
||||
inline Protocol()
|
||||
: rqState(RequestStates::Code)
|
||||
, requestMsg(RequestMsgCodes::unknown, 0)
|
||||
, rspState(ResponseStates::RequestCode)
|
||||
, responseMsg(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0) {
|
||||
}
|
||||
|
||||
/// Takes the input byte c and steps one step through the state machine
|
||||
/// @returns state of the message being decoded
|
||||
DecodeStatus DecodeRequest(uint8_t c);
|
||||
|
||||
/// Decodes response message in rxbuff
|
||||
/// @returns decoded response message structure
|
||||
DecodeStatus DecodeResponse(uint8_t c);
|
||||
|
||||
/// Encodes request message msg into txbuff memory
|
||||
/// It is expected the txbuff is large enough to fit the message
|
||||
/// @returns number of bytes written into txbuff
|
||||
static uint8_t EncodeRequest(const RequestMsg &msg, uint8_t *txbuff);
|
||||
|
||||
/// Encodes Write request message msg into txbuff memory
|
||||
/// It is expected the txbuff is large enough to fit the message
|
||||
/// @returns number of bytes written into txbuff
|
||||
static uint8_t EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff);
|
||||
|
||||
/// @returns the maximum byte length necessary to encode a request message
|
||||
/// Beneficial in case of pre-allocating a buffer for enconding a RequestMsg.
|
||||
static constexpr uint8_t MaxRequestSize() { return 13; }
|
||||
|
||||
/// @returns the maximum byte length necessary to encode a response message
|
||||
/// Beneficial in case of pre-allocating a buffer for enconding a ResponseMsg.
|
||||
static constexpr uint8_t MaxResponseSize() { return 14; }
|
||||
|
||||
/// Encode generic response Command Accepted or Rejected
|
||||
/// @param msg source request message for this response
|
||||
/// @param ar code of response parameter
|
||||
/// @param txbuff where to format the message
|
||||
/// @returns number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseCmdAR(const RequestMsg &msg, ResponseMsgParamCodes ar, uint8_t *txbuff);
|
||||
|
||||
/// Encode response to Read FINDA query
|
||||
/// @param msg source request message for this response
|
||||
/// @param findaValue 1/0 (on/off) status of FINDA
|
||||
/// @param txbuff where to format the message
|
||||
/// @returns number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// Encode response to Query operation status
|
||||
/// @param msg source request message for this response
|
||||
/// @param code status of operation (Processing, Error, Finished)
|
||||
/// @param value related to status of operation(e.g. error code or progress)
|
||||
/// @param txbuff where to format the message
|
||||
/// @returns number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseQueryOperation(const RequestMsg &msg, ResponseCommandStatus rcs, uint8_t *txbuff);
|
||||
|
||||
/// Encode response to Read query
|
||||
/// @param msg source request message for this response
|
||||
/// @param accepted true if the read query was accepted
|
||||
/// @param value2 variable value
|
||||
/// @param txbuff where to format the message
|
||||
/// @returns number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff);
|
||||
|
||||
/// @returns the most recently lexed request message
|
||||
inline const RequestMsg GetRequestMsg() const { return requestMsg; }
|
||||
|
||||
/// @returns the most recently lexed response message
|
||||
inline const ResponseMsg GetResponseMsg() const { return responseMsg; }
|
||||
|
||||
/// resets the internal request decoding state (typically after an error)
|
||||
void ResetRequestDecoder() {
|
||||
rqState = RequestStates::Code;
|
||||
}
|
||||
|
||||
/// resets the internal response decoding state (typically after an error)
|
||||
void ResetResponseDecoder() {
|
||||
rspState = ResponseStates::RequestCode;
|
||||
}
|
||||
|
||||
#ifndef UNITTEST
|
||||
private:
|
||||
#endif
|
||||
enum class RequestStates : uint8_t {
|
||||
Code, ///< starting state - expects message code
|
||||
Value, ///< expecting code value
|
||||
Address, ///< expecting address for Write command
|
||||
WriteValue, ///< value to be written (Write command)
|
||||
CRC, ///< CRC
|
||||
Error ///< automaton in error state
|
||||
};
|
||||
|
||||
RequestStates rqState;
|
||||
RequestMsg requestMsg;
|
||||
|
||||
enum class ResponseStates : uint8_t {
|
||||
RequestCode, ///< starting state - expects message code
|
||||
RequestValue, ///< expecting code value
|
||||
ParamCode, ///< expecting param code
|
||||
ParamValue, ///< expecting param value
|
||||
CRC, ///< expecting CRC value
|
||||
Error ///< automaton in error state
|
||||
};
|
||||
|
||||
ResponseStates rspState;
|
||||
ResponseMsg responseMsg;
|
||||
|
||||
static constexpr bool IsNewLine(uint8_t c) {
|
||||
return c == '\n' || c == '\r';
|
||||
}
|
||||
static constexpr bool IsDigit(uint8_t c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
static constexpr bool IsCRCSeparator(uint8_t c) {
|
||||
return c == '*';
|
||||
}
|
||||
static constexpr bool IsHexDigit(uint8_t c) {
|
||||
return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f');
|
||||
}
|
||||
static constexpr uint8_t Char2Nibble(uint8_t c) {
|
||||
switch (c) {
|
||||
case '0':
|
||||
case '1':
|
||||
case '2':
|
||||
case '3':
|
||||
case '4':
|
||||
case '5':
|
||||
case '6':
|
||||
case '7':
|
||||
case '8':
|
||||
case '9':
|
||||
return c - '0';
|
||||
case 'a':
|
||||
case 'b':
|
||||
case 'c':
|
||||
case 'd':
|
||||
case 'e':
|
||||
case 'f':
|
||||
return c - 'a' + 10;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t Nibble2Char(uint8_t n) {
|
||||
switch (n) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
return n + '0';
|
||||
case 0xa:
|
||||
case 0xb:
|
||||
case 0xc:
|
||||
case 0xd:
|
||||
case 0xe:
|
||||
case 0xf:
|
||||
return n - 10 + 'a';
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// @returns number of characters written
|
||||
static uint8_t UInt8ToHex(uint8_t value, uint8_t *dst);
|
||||
|
||||
/// @returns number of characters written
|
||||
static uint8_t UInt16ToHex(uint16_t value, uint8_t *dst);
|
||||
|
||||
static uint8_t BeginEncodeRequest(const RequestMsg &msg, uint8_t *dst);
|
||||
|
||||
static uint8_t AppendCRC(uint8_t crc, uint8_t *dst);
|
||||
};
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace modules
|
||||
|
||||
namespace mp = modules::protocol;
|
||||
|
|
@ -0,0 +1,756 @@
|
|||
#include "mmu2_protocol_logic.h"
|
||||
#include "mmu2_log.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
#include "system_timer.h"
|
||||
#include <string.h>
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
static const uint8_t supportedMmuFWVersion[3] PROGMEM = { 2, 1, 3 };
|
||||
|
||||
void ProtocolLogic::CheckAndReportAsyncEvents() {
|
||||
// even when waiting for a query period, we need to report a change in filament sensor's state
|
||||
// - it is vital for a precise synchronization of moves of the printer and the MMU
|
||||
uint8_t fs = (uint8_t)WhereIsFilament();
|
||||
if (fs != lastFSensor) {
|
||||
SendAndUpdateFilamentSensor();
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendQuery() {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Query, 0));
|
||||
scopeState = ScopeState::QuerySent;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendFINDAQuery() {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Finda, 0));
|
||||
scopeState = ScopeState::FINDAReqSent;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendAndUpdateFilamentSensor() {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::FilamentSensor, lastFSensor = (uint8_t)WhereIsFilament()));
|
||||
scopeState = ScopeState::FilamentSensorStateSent;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendButton(uint8_t btn) {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Button, btn));
|
||||
scopeState = ScopeState::ButtonSent;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendVersion(uint8_t stage) {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Version, stage));
|
||||
scopeState = (ScopeState)((uint_fast8_t)ScopeState::S0Sent + stage);
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendReadRegister(uint8_t index, ScopeState nextState) {
|
||||
SendMsg(RequestMsg(RequestMsgCodes::Read, index));
|
||||
scopeState = nextState;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendWriteRegister(uint8_t index, uint16_t value, ScopeState nextState){
|
||||
SendWriteMsg(RequestMsg(RequestMsgCodes::Write, index, value));
|
||||
scopeState = nextState;
|
||||
}
|
||||
|
||||
// searches for "ok\n" in the incoming serial data (that's the usual response of the old MMU FW)
|
||||
struct OldMMUFWDetector {
|
||||
uint8_t ok;
|
||||
inline constexpr OldMMUFWDetector():ok(0) { }
|
||||
|
||||
enum class State : uint8_t { MatchingPart, SomethingElse, Matched };
|
||||
|
||||
/// @returns true when "ok\n" gets detected
|
||||
State Detect(uint8_t c){
|
||||
// consume old MMU FW's data if any -> avoid confusion of protocol decoder
|
||||
if(ok == 0 && c == 'o'){
|
||||
++ok;
|
||||
return State::MatchingPart;
|
||||
} else if(ok == 1 && c == 'k'){
|
||||
++ok;
|
||||
return State::MatchingPart;
|
||||
} else if(ok == 2 && c == '\n'){
|
||||
return State::Matched;
|
||||
}
|
||||
return State::SomethingElse;
|
||||
}
|
||||
};
|
||||
|
||||
StepStatus ProtocolLogic::ExpectingMessage() {
|
||||
int bytesConsumed = 0;
|
||||
int c = -1;
|
||||
|
||||
OldMMUFWDetector oldMMUh4x0r; // old MMU FW hacker ;)
|
||||
|
||||
// try to consume as many rx bytes as possible (until a message has been completed)
|
||||
while ((c = uart->read()) >= 0) {
|
||||
++bytesConsumed;
|
||||
RecordReceivedByte(c);
|
||||
switch (protocol.DecodeResponse(c)) {
|
||||
case DecodeStatus::MessageCompleted:
|
||||
rsp = protocol.GetResponseMsg();
|
||||
LogResponse();
|
||||
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||
return MessageReady;
|
||||
case DecodeStatus::NeedMoreData:
|
||||
break;
|
||||
case DecodeStatus::Error:{
|
||||
// consume old MMU FW's data if any -> avoid confusion of protocol decoder
|
||||
auto old = oldMMUh4x0r.Detect(c);
|
||||
if( old == OldMMUFWDetector::State::Matched ){
|
||||
// hack bad FW version - BEWARE - we silently assume that the first query is an "S0"
|
||||
// The old MMU FW responds with "ok\n" and we fake the response to a bad FW version at this spot
|
||||
rsp = ResponseMsg(RequestMsg(RequestMsgCodes::Version, 0), ResponseMsgParamCodes::Accepted, 0);
|
||||
return MessageReady;
|
||||
} else if( old == OldMMUFWDetector::State::MatchingPart ){
|
||||
break;
|
||||
}
|
||||
}
|
||||
[[fallthrough]]; // otherwise
|
||||
default:
|
||||
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||
return ProtocolError;
|
||||
}
|
||||
}
|
||||
if (bytesConsumed != 0) {
|
||||
RecordUARTActivity(); // something has happened on the UART, update the timeout record
|
||||
return Processing; // consumed some bytes, but message still not ready
|
||||
} else if (Elapsed(linkLayerTimeout)) {
|
||||
return CommunicationTimeout;
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendMsg(RequestMsg rq) {
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()];
|
||||
uint8_t len = Protocol::EncodeRequest(rq, txbuff);
|
||||
uart->write(txbuff, len);
|
||||
LogRequestMsg(txbuff, len);
|
||||
RecordUARTActivity();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendWriteMsg(RequestMsg rq){
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()];
|
||||
uint8_t len = Protocol::EncodeWriteRequest(rq.value, rq.value2, txbuff);
|
||||
uart->write(txbuff, len);
|
||||
LogRequestMsg(txbuff, len);
|
||||
RecordUARTActivity();
|
||||
}
|
||||
|
||||
void ProtocolLogic::StartSeqRestart() {
|
||||
retries = maxRetries;
|
||||
SendVersion(0);
|
||||
}
|
||||
|
||||
void ProtocolLogic::DelayedRestartRestart() {
|
||||
scopeState = ScopeState::RecoveringProtocolError;
|
||||
}
|
||||
|
||||
void ProtocolLogic::CommandRestart() {
|
||||
scopeState = ScopeState::CommandSent;
|
||||
SendMsg(rq);
|
||||
}
|
||||
|
||||
void ProtocolLogic::IdleRestart() {
|
||||
scopeState = ScopeState::Ready;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::ProcessVersionResponse(uint8_t stage) {
|
||||
if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != stage) {
|
||||
// got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
|
||||
SendVersion(stage);
|
||||
} else {
|
||||
mmuFwVersion[stage] = rsp.paramValue;
|
||||
if (mmuFwVersion[stage] != pgm_read_byte(supportedMmuFWVersion + stage)) {
|
||||
if (--retries == 0) {
|
||||
return VersionMismatch;
|
||||
} else {
|
||||
SendVersion(stage);
|
||||
}
|
||||
} else {
|
||||
dataTO.Reset(); // got a meaningful response from the MMU, stop data layer timeout tracking
|
||||
SendVersion(stage + 1);
|
||||
}
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::ScopeStep() {
|
||||
if ( ! ExpectsResponse() ) {
|
||||
// we are waiting for something
|
||||
switch (currentScope) {
|
||||
case Scope::DelayedRestart:
|
||||
return DelayedRestartWait();
|
||||
case Scope::Idle:
|
||||
return IdleWait();
|
||||
case Scope::Command:
|
||||
return CommandWait();
|
||||
case Scope::Stopped:
|
||||
return StoppedStep();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// we are expecting a message
|
||||
if (auto expmsg = ExpectingMessage(); expmsg != MessageReady) // this whole statement takes 12B
|
||||
return expmsg;
|
||||
|
||||
// process message
|
||||
switch (currentScope) {
|
||||
case Scope::StartSeq:
|
||||
return StartSeqStep(); // ~270B
|
||||
case Scope::Idle:
|
||||
return IdleStep(); // ~300B
|
||||
case Scope::Command:
|
||||
return CommandStep(); // ~430B
|
||||
case Scope::Stopped:
|
||||
return StoppedStep();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
return Finished;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::StartSeqStep() {
|
||||
// solve initial handshake
|
||||
switch (scopeState) {
|
||||
case ScopeState::S0Sent: // received response to S0 - major
|
||||
case ScopeState::S1Sent: // received response to S1 - minor
|
||||
case ScopeState::S2Sent: // received response to S2 - minor
|
||||
return ProcessVersionResponse((uint8_t)scopeState - (uint8_t)ScopeState::S0Sent);
|
||||
case ScopeState::S3Sent: // received response to S3 - revision
|
||||
if (rsp.request.code != RequestMsgCodes::Version || rsp.request.value != 3) {
|
||||
// got a response to something else - protocol corruption probably, repeat the query OR restart the comm by issuing S0?
|
||||
SendVersion(3);
|
||||
} else {
|
||||
mmuFwVersionBuild = rsp.paramValue; // just register the build number
|
||||
// Start General Interrogation after line up.
|
||||
// For now we just send the state of the filament sensor, but we may request
|
||||
// data point states from the MMU as well. TBD in the future, especially with another protocol
|
||||
SendAndUpdateFilamentSensor();
|
||||
}
|
||||
return Processing;
|
||||
case ScopeState::FilamentSensorStateSent:
|
||||
SwitchFromStartToIdle();
|
||||
return Processing; // Returning Finished is not a good idea in case of a fast error recovery
|
||||
// - it tells the printer, that the command which experienced a protocol error and recovered successfully actually terminated.
|
||||
// In such a case we must return "Processing" in order to keep the MMU state machine running and prevent the printer from executing next G-codes.
|
||||
break;
|
||||
default:
|
||||
return VersionMismatch;
|
||||
}
|
||||
return Finished;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::DelayedRestartWait() {
|
||||
if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
|
||||
while (uart->read() != -1)
|
||||
; // clear the input buffer
|
||||
// switch to StartSeq
|
||||
Start();
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::CommandWait() {
|
||||
if (Elapsed(heartBeatPeriod)) {
|
||||
SendQuery();
|
||||
} else {
|
||||
// even when waiting for a query period, we need to report a change in filament sensor's state
|
||||
// - it is vital for a precise synchronization of moves of the printer and the MMU
|
||||
CheckAndReportAsyncEvents();
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::ProcessCommandQueryResponse() {
|
||||
switch (rsp.paramCode) {
|
||||
case ResponseMsgParamCodes::Processing:
|
||||
progressCode = static_cast<ProgressCode>(rsp.paramValue);
|
||||
errorCode = ErrorCode::OK;
|
||||
SendAndUpdateFilamentSensor(); // keep on reporting the state of fsensor regularly
|
||||
return Processing;
|
||||
case ResponseMsgParamCodes::Error:
|
||||
// in case of an error the progress code remains as it has been before
|
||||
errorCode = static_cast<ErrorCode>(rsp.paramValue);
|
||||
// keep on reporting the state of fsensor regularly even in command error state
|
||||
// - the MMU checks FINDA and fsensor even while recovering from errors
|
||||
SendAndUpdateFilamentSensor();
|
||||
return CommandError;
|
||||
case ResponseMsgParamCodes::Button:
|
||||
// The user pushed a button on the MMU. Save it, do what we need to do
|
||||
// to prepare, then pass it back to the MMU so it can work its magic.
|
||||
buttonCode = static_cast<Buttons>(rsp.paramValue);
|
||||
SendAndUpdateFilamentSensor();
|
||||
return ButtonPushed;
|
||||
case ResponseMsgParamCodes::Finished:
|
||||
progressCode = ProgressCode::OK;
|
||||
scopeState = ScopeState::Ready;
|
||||
return Finished;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::CommandStep() {
|
||||
switch (scopeState) {
|
||||
case ScopeState::CommandSent: {
|
||||
switch (rsp.paramCode) { // the response should be either accepted or rejected
|
||||
case ResponseMsgParamCodes::Accepted:
|
||||
progressCode = ProgressCode::OK;
|
||||
errorCode = ErrorCode::RUNNING;
|
||||
scopeState = ScopeState::Wait;
|
||||
break;
|
||||
case ResponseMsgParamCodes::Rejected:
|
||||
// rejected - should normally not happen, but report the error up
|
||||
progressCode = ProgressCode::OK;
|
||||
errorCode = ErrorCode::PROTOCOL_ERROR;
|
||||
return CommandRejected;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
} break;
|
||||
case ScopeState::QuerySent:
|
||||
return ProcessCommandQueryResponse();
|
||||
case ScopeState::FilamentSensorStateSent:
|
||||
SendFINDAQuery();
|
||||
return Processing;
|
||||
case ScopeState::FINDAReqSent:
|
||||
findaPressed = rsp.paramValue;
|
||||
SendReadRegister(4, ScopeState::StatisticsSent);
|
||||
return Processing;
|
||||
case ScopeState::StatisticsSent:
|
||||
scopeState = ScopeState::Wait;
|
||||
return Processing;
|
||||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// Button was accepted, decrement the retry.
|
||||
mmu2.DecrementRetryAttempts();
|
||||
}
|
||||
SendAndUpdateFilamentSensor();
|
||||
break;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::IdleWait() {
|
||||
if (scopeState == ScopeState::Ready) { // check timeout
|
||||
if (Elapsed(heartBeatPeriod)) {
|
||||
SendQuery();
|
||||
return Processing;
|
||||
}
|
||||
}
|
||||
return Finished;
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::IdleStep() {
|
||||
switch (scopeState) {
|
||||
case ScopeState::QuerySent: // check UART
|
||||
// If we are accidentally in Idle and we receive something like "T0 P1" - that means the communication dropped out while a command was in progress.
|
||||
// That causes no issues here, we just need to switch to Command processing and continue there from now on.
|
||||
// The usual response in this case should be some command and "F" - finished - that confirms we are in an Idle state even on the MMU side.
|
||||
switch (rsp.request.code) {
|
||||
case RequestMsgCodes::Cut:
|
||||
case RequestMsgCodes::Eject:
|
||||
case RequestMsgCodes::Load:
|
||||
case RequestMsgCodes::Mode:
|
||||
case RequestMsgCodes::Tool:
|
||||
case RequestMsgCodes::Unload:
|
||||
if (rsp.paramCode != ResponseMsgParamCodes::Finished) {
|
||||
return SwitchFromIdleToCommand();
|
||||
}
|
||||
break;
|
||||
case RequestMsgCodes::Reset:
|
||||
// this one is kind of special
|
||||
// we do not transfer to any "running" command (i.e. we stay in Idle),
|
||||
// but in case there is an error reported we must make sure it gets propagated
|
||||
switch (rsp.paramCode) {
|
||||
case ResponseMsgParamCodes::Button:
|
||||
// The user pushed a button on the MMU. Save it, do what we need to do
|
||||
// to prepare, then pass it back to the MMU so it can work its magic.
|
||||
buttonCode = static_cast<Buttons>(rsp.paramValue);
|
||||
SendFINDAQuery();
|
||||
return ButtonPushed;
|
||||
case ResponseMsgParamCodes::Processing:
|
||||
// @@TODO we may actually use this branch to report progress of manual operation on the MMU
|
||||
// The MMU sends e.g. X0 P27 after its restart when the user presses an MMU button to move the Selector
|
||||
// For now let's behave just like "finished"
|
||||
case ResponseMsgParamCodes::Finished:
|
||||
errorCode = ErrorCode::OK;
|
||||
break;
|
||||
default:
|
||||
errorCode = static_cast<ErrorCode>(rsp.paramValue);
|
||||
SendFINDAQuery(); // continue Idle state without restarting the communication
|
||||
return CommandError;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
SendFINDAQuery();
|
||||
return Processing;
|
||||
case ScopeState::FINDAReqSent:
|
||||
findaPressed = rsp.paramValue;
|
||||
SendReadRegister(4, ScopeState::StatisticsSent);
|
||||
return Processing;
|
||||
case ScopeState::StatisticsSent:
|
||||
failStatistics = rsp.paramValue;
|
||||
scopeState = ScopeState::Ready;
|
||||
return Finished;
|
||||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// Button was accepted, decrement the retry.
|
||||
mmu2.DecrementRetryAttempts();
|
||||
}
|
||||
SendFINDAQuery();
|
||||
return Processing;
|
||||
case ScopeState::ReadRegisterSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// @@TODO just dump the value onto the serial
|
||||
}
|
||||
return Finished;
|
||||
case ScopeState::WriteRegisterSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted) {
|
||||
// @@TODO do something? Retry if not accepted?
|
||||
}
|
||||
return Finished;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
|
||||
// The "return Finished" in this state machine requires a bit of explanation:
|
||||
// The Idle state either did nothing (still waiting for the heartbeat timeout)
|
||||
// or just successfully received the answer to Q0, whatever that was.
|
||||
// In both cases, it is ready to hand over work to a command or something else,
|
||||
// therefore we are returning Finished (also to exit mmu_loop() and unblock Marlin's loop!).
|
||||
// If there is no work, we'll end up in the Idle state again
|
||||
// and we'll send the heartbeat message after the specified timeout.
|
||||
return Finished;
|
||||
}
|
||||
|
||||
ProtocolLogic::ProtocolLogic(MMU2Serial *uart)
|
||||
: currentScope(Scope::Stopped)
|
||||
, scopeState(ScopeState::Ready)
|
||||
, plannedRq(RequestMsgCodes::unknown, 0)
|
||||
, lastUARTActivityMs(0)
|
||||
, dataTO()
|
||||
, rsp(RequestMsg(RequestMsgCodes::unknown, 0), ResponseMsgParamCodes::unknown, 0)
|
||||
, state(State::Stopped)
|
||||
, lrb(0)
|
||||
, uart(uart)
|
||||
, errorCode(ErrorCode::OK)
|
||||
, progressCode(ProgressCode::OK)
|
||||
, buttonCode(NoButton)
|
||||
, lastFSensor((uint8_t)WhereIsFilament())
|
||||
, findaPressed(false)
|
||||
, failStatistics(0)
|
||||
, mmuFwVersion { 0, 0, 0 }
|
||||
{}
|
||||
|
||||
void ProtocolLogic::Start() {
|
||||
state = State::InitSequence;
|
||||
currentScope = Scope::StartSeq;
|
||||
protocol.ResetResponseDecoder(); // important - finished delayed restart relies on this
|
||||
StartSeqRestart();
|
||||
}
|
||||
|
||||
void ProtocolLogic::Stop() {
|
||||
state = State::Stopped;
|
||||
currentScope = Scope::Stopped;
|
||||
}
|
||||
|
||||
void ProtocolLogic::ToolChange(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Tool, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::Statistics() {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Version, 3));
|
||||
}
|
||||
|
||||
void ProtocolLogic::UnloadFilament() {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Unload, 0));
|
||||
}
|
||||
|
||||
void ProtocolLogic::LoadFilament(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Load, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::EjectFilament(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Eject, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::CutFilament(uint8_t slot) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Cut, slot));
|
||||
}
|
||||
|
||||
void ProtocolLogic::ResetMMU() {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, 0));
|
||||
}
|
||||
|
||||
void ProtocolLogic::Button(uint8_t index) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Button, index));
|
||||
}
|
||||
|
||||
void ProtocolLogic::Home(uint8_t mode) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Home, mode));
|
||||
}
|
||||
|
||||
void ProtocolLogic::ReadRegister(uint8_t address){
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Read, address));
|
||||
}
|
||||
|
||||
void ProtocolLogic::WriteRegister(uint8_t address, uint16_t data){
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Write, address, data));
|
||||
}
|
||||
|
||||
void ProtocolLogic::PlanGenericRequest(RequestMsg rq) {
|
||||
plannedRq = rq;
|
||||
if (!ExpectsResponse()) {
|
||||
ActivatePlannedRequest();
|
||||
} // otherwise wait for an empty window to activate the request
|
||||
}
|
||||
|
||||
bool ProtocolLogic::ActivatePlannedRequest() {
|
||||
switch(plannedRq.code){
|
||||
case RequestMsgCodes::Button:
|
||||
// only issue the button to the MMU and do not restart the state machines
|
||||
SendButton(plannedRq.value);
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
return true;
|
||||
case RequestMsgCodes::Read:
|
||||
SendReadRegister(plannedRq.value, ScopeState::ReadRegisterSent );
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
return true;
|
||||
case RequestMsgCodes::Write:
|
||||
SendWriteRegister(plannedRq.value, plannedRq.value2, ScopeState::WriteRegisterSent );
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
return true;
|
||||
case RequestMsgCodes::unknown:
|
||||
return false;
|
||||
default:// commands
|
||||
currentScope = Scope::Command;
|
||||
SetRequestMsg(plannedRq);
|
||||
plannedRq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
CommandRestart();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::SwitchFromIdleToCommand() {
|
||||
currentScope = Scope::Command;
|
||||
SetRequestMsg(rsp.request);
|
||||
// we are recovering from a communication drop out, the command is already running
|
||||
// and we have just received a response to a Q0 message about a command progress
|
||||
return ProcessCommandQueryResponse();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SwitchToIdle() {
|
||||
state = State::Running;
|
||||
currentScope = Scope::Idle;
|
||||
IdleRestart();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SwitchFromStartToIdle() {
|
||||
state = State::Running;
|
||||
currentScope = Scope::Idle;
|
||||
IdleRestart();
|
||||
SendQuery(); // force sending Q0 immediately
|
||||
}
|
||||
|
||||
bool ProtocolLogic::Elapsed(uint32_t timeout) const {
|
||||
return _millis() >= (lastUARTActivityMs + timeout);
|
||||
}
|
||||
|
||||
void ProtocolLogic::RecordUARTActivity() {
|
||||
lastUARTActivityMs = _millis();
|
||||
}
|
||||
|
||||
void ProtocolLogic::RecordReceivedByte(uint8_t c) {
|
||||
lastReceivedBytes[lrb] = c;
|
||||
lrb = (lrb + 1) % lastReceivedBytes.size();
|
||||
}
|
||||
|
||||
constexpr char NibbleToChar(uint8_t c) {
|
||||
switch (c) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
case 3:
|
||||
case 4:
|
||||
case 5:
|
||||
case 6:
|
||||
case 7:
|
||||
case 8:
|
||||
case 9:
|
||||
return c + '0';
|
||||
case 10:
|
||||
case 11:
|
||||
case 12:
|
||||
case 13:
|
||||
case 14:
|
||||
case 15:
|
||||
return (c - 10) + 'a';
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolLogic::FormatLastReceivedBytes(char *dst) {
|
||||
for (uint8_t i = 0; i < lastReceivedBytes.size(); ++i) {
|
||||
uint8_t b = lastReceivedBytes[(lrb - i - 1) % lastReceivedBytes.size()];
|
||||
dst[i * 3] = NibbleToChar(b >> 4);
|
||||
dst[i * 3 + 1] = NibbleToChar(b & 0xf);
|
||||
dst[i * 3 + 2] = ' ';
|
||||
}
|
||||
dst[(lastReceivedBytes.size() - 1) * 3 + 2] = 0; // terminate properly
|
||||
}
|
||||
|
||||
void ProtocolLogic::FormatLastResponseMsgAndClearLRB(char *dst) {
|
||||
*dst++ = '<';
|
||||
for (uint8_t i = 0; i < lrb; ++i) {
|
||||
uint8_t b = lastReceivedBytes[i];
|
||||
if (b < 32)
|
||||
b = '.';
|
||||
if (b > 127)
|
||||
b = '.';
|
||||
*dst++ = b;
|
||||
}
|
||||
*dst = 0; // terminate properly
|
||||
lrb = 0; // reset the input buffer index in case of a clean message
|
||||
}
|
||||
|
||||
void ProtocolLogic::LogRequestMsg(const uint8_t *txbuff, uint8_t size) {
|
||||
constexpr uint_fast8_t rqs = modules::protocol::Protocol::MaxRequestSize() + 2;
|
||||
char tmp[rqs] = ">";
|
||||
static char lastMsg[rqs] = "";
|
||||
for (uint8_t i = 0; i < size; ++i) {
|
||||
uint8_t b = txbuff[i];
|
||||
if (b < 32)
|
||||
b = '.';
|
||||
if (b > 127)
|
||||
b = '.';
|
||||
tmp[i + 1] = b;
|
||||
}
|
||||
tmp[size + 1] = '\n';
|
||||
tmp[size + 2] = 0;
|
||||
if (!strncmp_P(tmp, PSTR(">S0*99.\n"), rqs) && !strncmp(lastMsg, tmp, rqs)) {
|
||||
// @@TODO we skip the repeated request msgs for now
|
||||
// to avoid spoiling the whole log just with ">S0" messages
|
||||
// especially when the MMU is not connected.
|
||||
// We'll lose the ability to see if the printer is actually
|
||||
// trying to find the MMU, but since it has been reliable in the past
|
||||
// we can live without it for now.
|
||||
} else {
|
||||
MMU2_ECHO_MSG(tmp);
|
||||
}
|
||||
memcpy(lastMsg, tmp, rqs);
|
||||
}
|
||||
|
||||
void ProtocolLogic::LogError(const char *reason_P) {
|
||||
char lrb[lastReceivedBytes.size() * 3];
|
||||
FormatLastReceivedBytes(lrb);
|
||||
|
||||
MMU2_ERROR_MSGRPGM(reason_P);
|
||||
SERIAL_ECHOPGM(", last bytes: ");
|
||||
SERIAL_ECHOLN(lrb);
|
||||
}
|
||||
|
||||
void ProtocolLogic::LogResponse() {
|
||||
char lrb[lastReceivedBytes.size()];
|
||||
FormatLastResponseMsgAndClearLRB(lrb);
|
||||
MMU2_ECHO_MSG(lrb);
|
||||
SERIAL_ECHOLN();
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg_P, StepStatus ss) {
|
||||
if (dataTO.Record(ss)) {
|
||||
LogError(msg_P);
|
||||
return dataTO.InitialCause();
|
||||
} else {
|
||||
return Processing; // suppress short drop outs of communication
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::HandleCommunicationTimeout() {
|
||||
uart->flush(); // clear the output buffer
|
||||
protocol.ResetResponseDecoder();
|
||||
Start();
|
||||
return SuppressShortDropOuts(PSTR("Communication timeout"), CommunicationTimeout);
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::HandleProtocolError() {
|
||||
uart->flush(); // clear the output buffer
|
||||
state = State::InitSequence;
|
||||
currentScope = Scope::DelayedRestart;
|
||||
DelayedRestartRestart();
|
||||
return SuppressShortDropOuts(PSTR("Protocol Error"), ProtocolError);
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::Step() {
|
||||
if (!ExpectsResponse()) { // if not waiting for a response, activate a planned request immediately
|
||||
ActivatePlannedRequest();
|
||||
}
|
||||
auto currentStatus = ScopeStep();
|
||||
switch (currentStatus) {
|
||||
case Processing:
|
||||
// we are ok, the state machine continues correctly
|
||||
break;
|
||||
case Finished: {
|
||||
// We are ok, switching to Idle if there is no potential next request planned.
|
||||
// But the trouble is we must report a finished command if the previous command has just been finished
|
||||
// i.e. only try to find some planned command if we just finished the Idle cycle
|
||||
bool previousCommandFinished = currentScope == Scope::Command; // @@TODO this is a nasty hack :(
|
||||
if (!ActivatePlannedRequest()) { // if nothing is planned, switch to Idle
|
||||
SwitchToIdle();
|
||||
} else {
|
||||
// if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
|
||||
if (!previousCommandFinished && currentScope == Scope::Command) {
|
||||
currentStatus = Processing;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
case CommandRejected:
|
||||
// we have to repeat it - that's the only thing we can do
|
||||
// no change in state
|
||||
// @@TODO wait until Q0 returns command in progress finished, then we can send this one
|
||||
LogError(PSTR("Command rejected"));
|
||||
CommandRestart();
|
||||
break;
|
||||
case CommandError:
|
||||
LogError(PSTR("Command Error"));
|
||||
// we shall probably transfer into the Idle state and await further instructions from the upper layer
|
||||
// Idle state may solve the problem of keeping up the heart beat running
|
||||
break;
|
||||
case VersionMismatch:
|
||||
LogError(PSTR("Version mismatch"));
|
||||
Stop(); // cannot continue
|
||||
break;
|
||||
case ProtocolError:
|
||||
currentStatus = HandleProtocolError();
|
||||
break;
|
||||
case CommunicationTimeout:
|
||||
currentStatus = HandleCommunicationTimeout();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return currentStatus;
|
||||
}
|
||||
|
||||
uint8_t ProtocolLogic::CommandInProgress() const {
|
||||
if (currentScope != Scope::Command)
|
||||
return 0;
|
||||
return (uint8_t)ReqMsg().code;
|
||||
}
|
||||
|
||||
bool DropOutFilter::Record(StepStatus ss) {
|
||||
if (occurrences == maxOccurrences) {
|
||||
cause = ss;
|
||||
}
|
||||
--occurrences;
|
||||
return occurrences == 0;
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
// #include <array> //@@TODO Don't we have STL for AVR somewhere?
|
||||
template<typename T, uint8_t N>
|
||||
class array {
|
||||
T data[N];
|
||||
public:
|
||||
array() = default;
|
||||
inline constexpr T* begin()const { return data; }
|
||||
inline constexpr T* end()const { return data + N; }
|
||||
constexpr uint8_t size()const { return N; }
|
||||
inline T &operator[](uint8_t i){
|
||||
return data[i];
|
||||
}
|
||||
};
|
||||
|
||||
#include "mmu2/error_codes.h"
|
||||
#include "mmu2/progress_codes.h"
|
||||
#include "mmu2/buttons.h"
|
||||
#include "mmu2_protocol.h"
|
||||
|
||||
#include "mmu2_serial.h"
|
||||
|
||||
/// New MMU2 protocol logic
|
||||
namespace MMU2 {
|
||||
|
||||
using namespace modules::protocol;
|
||||
|
||||
class ProtocolLogic;
|
||||
|
||||
/// ProtocolLogic stepping statuses
|
||||
enum StepStatus : uint_fast8_t {
|
||||
Processing = 0,
|
||||
MessageReady, ///< a message has been successfully decoded from the received bytes
|
||||
Finished,
|
||||
CommunicationTimeout, ///< the MMU failed to respond to a request within a specified time frame
|
||||
ProtocolError, ///< bytes read from the MMU didn't form a valid response
|
||||
CommandRejected, ///< the MMU rejected the command due to some other command in progress, may be the user is operating the MMU locally (button commands)
|
||||
CommandError, ///< the command in progress stopped due to unrecoverable error, user interaction required
|
||||
VersionMismatch, ///< the MMU reports its firmware version incompatible with our implementation
|
||||
CommunicationRecovered,
|
||||
ButtonPushed, ///< The MMU reported the user pushed one of its three buttons.
|
||||
};
|
||||
|
||||
static constexpr uint32_t linkLayerTimeout = 2000; ///< default link layer communication timeout
|
||||
static constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; ///< data layer communication timeout
|
||||
static constexpr uint32_t heartBeatPeriod = linkLayerTimeout / 2; ///< period of heart beat messages (Q0)
|
||||
|
||||
static_assert(heartBeatPeriod < linkLayerTimeout && linkLayerTimeout < dataLayerTimeout, "Incorrect ordering of timeouts");
|
||||
|
||||
///< Filter of short consecutive drop outs which are recovered instantly
|
||||
class DropOutFilter {
|
||||
StepStatus cause;
|
||||
uint8_t occurrences;
|
||||
public:
|
||||
static constexpr uint8_t maxOccurrences = 10; // ideally set this to >8 seconds -> 12x heartBeatPeriod
|
||||
static_assert(maxOccurrences > 1, "we should really silently ignore at least 1 comm drop out if recovered immediately afterwards");
|
||||
DropOutFilter() = default;
|
||||
|
||||
/// @returns true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
|
||||
bool Record(StepStatus ss);
|
||||
|
||||
/// @returns the initial cause which started this drop out event
|
||||
inline StepStatus InitialCause() const { return cause; }
|
||||
|
||||
/// Rearms the object for further processing - basically call this once the MMU responds with something meaningful (e.g. S0 A2)
|
||||
inline void Reset() { occurrences = maxOccurrences; }
|
||||
};
|
||||
|
||||
/// Logic layer of the MMU vs. printer communication protocol
|
||||
class ProtocolLogic {
|
||||
public:
|
||||
ProtocolLogic(MMU2Serial *uart);
|
||||
|
||||
/// Start/Enable communication with the MMU
|
||||
void Start();
|
||||
|
||||
/// Stop/Disable communication with the MMU
|
||||
void Stop();
|
||||
|
||||
// Issue commands to the MMU
|
||||
void ToolChange(uint8_t slot);
|
||||
void Statistics();
|
||||
void UnloadFilament();
|
||||
void LoadFilament(uint8_t slot);
|
||||
void EjectFilament(uint8_t slot);
|
||||
void CutFilament(uint8_t slot);
|
||||
void ResetMMU();
|
||||
void Button(uint8_t index);
|
||||
void Home(uint8_t mode);
|
||||
void ReadRegister(uint8_t address);
|
||||
void WriteRegister(uint8_t address, uint16_t data);
|
||||
|
||||
/// Step the state machine
|
||||
StepStatus Step();
|
||||
|
||||
/// @returns the current/latest error code as reported by the MMU
|
||||
ErrorCode Error() const { return errorCode; }
|
||||
|
||||
/// @returns the current/latest process code as reported by the MMU
|
||||
ProgressCode Progress() const { return progressCode; }
|
||||
|
||||
/// @returns the current/latest button code as reported by the MMU
|
||||
Buttons Button() const { return buttonCode; }
|
||||
|
||||
uint8_t CommandInProgress() const;
|
||||
|
||||
inline bool Running() const {
|
||||
return state == State::Running;
|
||||
}
|
||||
|
||||
inline bool FindaPressed() const {
|
||||
return findaPressed;
|
||||
}
|
||||
|
||||
inline uint16_t FailStatistics() const {
|
||||
return failStatistics;
|
||||
}
|
||||
|
||||
inline uint8_t MmuFwVersionMajor() const {
|
||||
return mmuFwVersion[0];
|
||||
}
|
||||
|
||||
inline uint8_t MmuFwVersionMinor() const {
|
||||
return mmuFwVersion[1];
|
||||
}
|
||||
|
||||
inline uint8_t MmuFwVersionRevision() const {
|
||||
return mmuFwVersion[2];
|
||||
}
|
||||
#ifndef UNITTEST
|
||||
private:
|
||||
#endif
|
||||
StepStatus ExpectingMessage();
|
||||
void SendMsg(RequestMsg rq);
|
||||
void SendWriteMsg(RequestMsg rq);
|
||||
void SwitchToIdle();
|
||||
StepStatus SuppressShortDropOuts(const char *msg_P, StepStatus ss);
|
||||
StepStatus HandleCommunicationTimeout();
|
||||
StepStatus HandleProtocolError();
|
||||
bool Elapsed(uint32_t timeout) const;
|
||||
void RecordUARTActivity();
|
||||
void RecordReceivedByte(uint8_t c);
|
||||
void FormatLastReceivedBytes(char *dst);
|
||||
void FormatLastResponseMsgAndClearLRB(char *dst);
|
||||
void LogRequestMsg(const uint8_t *txbuff, uint8_t size);
|
||||
void LogError(const char *reason_P);
|
||||
void LogResponse();
|
||||
StepStatus SwitchFromIdleToCommand();
|
||||
void SwitchFromStartToIdle();
|
||||
|
||||
enum class State : uint_fast8_t {
|
||||
Stopped, ///< stopped for whatever reason
|
||||
InitSequence, ///< initial sequence running
|
||||
Running ///< normal operation - Idle + Command processing
|
||||
};
|
||||
|
||||
// individual sub-state machines - may be they can be combined into a union since only one is active at once
|
||||
// or we can blend them into ProtocolLogic at the cost of a less nice code (but hopefully shorter)
|
||||
// Stopped stopped;
|
||||
// StartSeq startSeq;
|
||||
// DelayedRestart delayedRestart;
|
||||
// Idle idle;
|
||||
// Command command;
|
||||
// ProtocolLogicPartBase *currentState; ///< command currently being processed
|
||||
|
||||
enum class Scope : uint_fast8_t {
|
||||
Stopped,
|
||||
StartSeq,
|
||||
DelayedRestart,
|
||||
Idle,
|
||||
Command
|
||||
};
|
||||
Scope currentScope;
|
||||
|
||||
// basic scope members
|
||||
/// @returns true if the state machine is waiting for a response from the MMU
|
||||
bool ExpectsResponse() const { return ((uint8_t)scopeState & (uint8_t)ScopeState::NotExpectsResponse) == 0; }
|
||||
|
||||
/// Common internal states of the derived sub-automata
|
||||
/// General rule of thumb: *Sent states are waiting for a response from the MMU
|
||||
enum class ScopeState : uint_fast8_t {
|
||||
S0Sent, // beware - due to optimization reasons these SxSent must be kept one after another
|
||||
S1Sent,
|
||||
S2Sent,
|
||||
S3Sent,
|
||||
QuerySent,
|
||||
CommandSent,
|
||||
FilamentSensorStateSent,
|
||||
FINDAReqSent,
|
||||
StatisticsSent,
|
||||
ButtonSent,
|
||||
ReadRegisterSent,
|
||||
WriteRegisterSent,
|
||||
|
||||
// States which do not expect a message - MSb set
|
||||
NotExpectsResponse = 0x80,
|
||||
Wait = NotExpectsResponse + 1,
|
||||
Ready = NotExpectsResponse + 2,
|
||||
RecoveringProtocolError = NotExpectsResponse + 3,
|
||||
};
|
||||
|
||||
ScopeState scopeState; ///< internal state of the sub-automaton
|
||||
|
||||
/// @returns the status of processing of the FINDA query response
|
||||
/// @param finishedRV returned value in case the message was successfully received and processed
|
||||
/// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
|
||||
// StepStatus ProcessFINDAReqSent(StepStatus finishedRV, State nextState);
|
||||
|
||||
/// @returns the status of processing of the statistics query response
|
||||
/// @param finishedRV returned value in case the message was successfully received and processed
|
||||
/// @param nextState is a state where the state machine should transfer to after the message was successfully received and processed
|
||||
// StepStatus ProcessStatisticsReqSent(StepStatus finishedRV, State nextState);
|
||||
|
||||
/// Called repeatedly while waiting for a query (Q0) period.
|
||||
/// All event checks to report immediately from the printer to the MMU shall be done in this method.
|
||||
/// So far, the only such a case is the filament sensor, but there can be more like this in the future.
|
||||
void CheckAndReportAsyncEvents();
|
||||
void SendQuery();
|
||||
void SendFINDAQuery();
|
||||
void SendAndUpdateFilamentSensor();
|
||||
void SendButton(uint8_t btn);
|
||||
void SendVersion(uint8_t stage);
|
||||
void SendReadRegister(uint8_t index, ScopeState nextState);
|
||||
void SendWriteRegister(uint8_t index, uint16_t value, ScopeState nextState);
|
||||
|
||||
StepStatus ProcessVersionResponse(uint8_t stage);
|
||||
|
||||
/// Top level split - calls the appropriate step based on current scope
|
||||
StepStatus ScopeStep();
|
||||
|
||||
static constexpr uint8_t maxRetries = 6;
|
||||
uint8_t retries;
|
||||
|
||||
void StartSeqRestart();
|
||||
void DelayedRestartRestart();
|
||||
void IdleRestart();
|
||||
void CommandRestart();
|
||||
|
||||
StepStatus StartSeqStep();
|
||||
StepStatus DelayedRestartWait();
|
||||
StepStatus IdleStep();
|
||||
StepStatus IdleWait();
|
||||
StepStatus CommandStep();
|
||||
StepStatus CommandWait();
|
||||
StepStatus StoppedStep() { return Processing; }
|
||||
|
||||
StepStatus ProcessCommandQueryResponse();
|
||||
|
||||
inline void SetRequestMsg(RequestMsg msg) {
|
||||
rq = msg;
|
||||
}
|
||||
inline const RequestMsg &ReqMsg() const { return rq; }
|
||||
RequestMsg rq = RequestMsg(RequestMsgCodes::unknown, 0);
|
||||
|
||||
/// Records the next planned state, "unknown" msg code if no command is planned.
|
||||
/// This is not intended to be a queue of commands to process, protocol_logic must not queue commands.
|
||||
/// It exists solely to prevent breaking the Request-Response protocol handshake -
|
||||
/// - during tests it turned out, that the commands from Marlin are coming in such an asynchronnous way, that
|
||||
/// we could accidentally send T2 immediately after Q0 without waiting for reception of response to Q0.
|
||||
///
|
||||
/// Beware, if Marlin manages to call PlanGenericCommand multiple times before a response comes,
|
||||
/// these variables will get overwritten by the last call.
|
||||
/// However, that should not happen under normal circumstances as Marlin should wait for the Command to finish,
|
||||
/// which includes all responses (and error recovery if any).
|
||||
RequestMsg plannedRq;
|
||||
|
||||
/// Plan a command to be processed once the immediate response to a sent request arrives
|
||||
void PlanGenericRequest(RequestMsg rq);
|
||||
/// Activate the planned state once the immediate response to a sent request arrived
|
||||
bool ActivatePlannedRequest();
|
||||
|
||||
uint32_t lastUARTActivityMs; ///< timestamp - last ms when something occurred on the UART
|
||||
DropOutFilter dataTO; ///< Filter of short consecutive drop outs which are recovered instantly
|
||||
|
||||
ResponseMsg rsp; ///< decoded response message from the MMU protocol
|
||||
|
||||
State state; ///< internal state of ProtocolLogic
|
||||
|
||||
Protocol protocol; ///< protocol codec
|
||||
|
||||
array<uint8_t, 16> lastReceivedBytes; ///< remembers the last few bytes of incoming communication for diagnostic purposes
|
||||
uint8_t lrb;
|
||||
|
||||
MMU2Serial *uart; ///< UART interface
|
||||
|
||||
ErrorCode errorCode; ///< last received error code from the MMU
|
||||
ProgressCode progressCode; ///< last received progress code from the MMU
|
||||
Buttons buttonCode; ///< Last received button from the MMU.
|
||||
|
||||
uint8_t lastFSensor; ///< last state of filament sensor
|
||||
|
||||
bool findaPressed;
|
||||
uint16_t failStatistics;
|
||||
|
||||
uint8_t mmuFwVersion[3];
|
||||
uint16_t mmuFwVersionBuild;
|
||||
|
||||
friend class ProtocolLogicPartBase;
|
||||
friend class Stopped;
|
||||
friend class Command;
|
||||
friend class Idle;
|
||||
friend class StartSeq;
|
||||
friend class DelayedRestart;
|
||||
|
||||
friend class MMU2;
|
||||
};
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
#include "mmu2.h"
|
||||
#include "mmu2_reporting.h"
|
||||
#include "mmu2_error_converter.h"
|
||||
#include "mmu2/error_codes.h"
|
||||
#include "mmu2/buttons.h"
|
||||
#include "ultralcd.h"
|
||||
#include "Filament_sensor.h"
|
||||
#include "language.h"
|
||||
#include "temperature.h"
|
||||
#include "sound.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
const char * const ProgressCodeToText(uint16_t pc); // we may join progress convertor and reporter together
|
||||
|
||||
void BeginReport(CommandInProgress cip, uint16_t ec) {
|
||||
custom_message_type = CustomMsg::MMUProgress;
|
||||
lcd_setstatuspgm( _T(ProgressCodeToText(ec)) );
|
||||
}
|
||||
|
||||
void EndReport(CommandInProgress cip, uint16_t ec) {
|
||||
// clear the status msg line - let the printed filename get visible again
|
||||
custom_message_type = CustomMsg::Status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Renders any characters that will be updated live on the MMU error screen.
|
||||
*Currently, this is FINDA and Filament Sensor status and Extruder temperature.
|
||||
*/
|
||||
extern void ReportErrorHookDynamicRender(void){
|
||||
// beware - this optimization abuses the fact, that FindaDetectsFilament returns 0 or 1 and '0' is followed by '1' in the ASCII table
|
||||
lcd_putc_at(3, 2, mmu2.FindaDetectsFilament() + '0');
|
||||
lcd_putc_at(8, 2, fsensor.getFilamentPresent() + '0');
|
||||
|
||||
// print active/changing filament slot
|
||||
lcd_set_cursor(10, 2);
|
||||
lcdui_print_extruder();
|
||||
|
||||
// Print active extruder temperature
|
||||
lcd_set_cursor(16, 2);
|
||||
lcd_printf_P(PSTR("%3d"), (int)(degHotend(0) + 0.5));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Renders any characters that are static on the MMU error screen i.e. they don't change.
|
||||
* @param[in] ei Error code index
|
||||
*/
|
||||
static void ReportErrorHookStaticRender(uint8_t ei) {
|
||||
//! Show an error screen
|
||||
//! When an MMU error occurs, the LCD content will look like this:
|
||||
//! |01234567890123456789|
|
||||
//! |MMU FW update needed| <- title/header of the error: max 20 characters
|
||||
//! |prusa3d.com/ERR04504| <- URL 20 characters
|
||||
//! |FI:1 FS:1 5>3 t201°| <- status line, t is thermometer symbol
|
||||
//! |>Retry >Done >MoreW | <- buttons
|
||||
bool two_choices = false;
|
||||
|
||||
// Read and determine what operations should be shown on the menu
|
||||
const uint8_t button_operation = PrusaErrorButtons(ei);
|
||||
const uint8_t button_op_right = BUTTON_OP_RIGHT(button_operation);
|
||||
const uint8_t button_op_middle = BUTTON_OP_MIDDLE(button_operation);
|
||||
|
||||
// Check if the menu should have three or two choices
|
||||
if (button_op_right == (uint8_t)ButtonOperations::NoOperation){
|
||||
// Two operations not specified, the error menu should only show two choices
|
||||
two_choices = true;
|
||||
}
|
||||
|
||||
lcd_set_custom_characters_nextpage();
|
||||
lcd_update_enable(false);
|
||||
lcd_clear();
|
||||
|
||||
// Print title and header
|
||||
lcd_printf_P(PSTR("%.20S\nprusa3d.com/ERR04%hu"), _T(PrusaErrorTitle(ei)), PrusaErrorCode(ei) );
|
||||
|
||||
ReportErrorHookSensorLineRender();
|
||||
|
||||
// Render the choices
|
||||
lcd_show_choices_prompt_P(two_choices ? LCD_LEFT_BUTTON_CHOICE : LCD_MIDDLE_BUTTON_CHOICE, _T(PrusaErrorButtonTitle(button_op_middle)), _T(two_choices ? PrusaErrorButtonMore() : PrusaErrorButtonTitle(button_op_right)), two_choices ? 10 : 7, two_choices ? nullptr : _T(PrusaErrorButtonMore()));
|
||||
}
|
||||
|
||||
extern void ReportErrorHookSensorLineRender()
|
||||
{
|
||||
// Render static characters in third line
|
||||
lcd_set_cursor(0, 2);
|
||||
lcd_printf_P(PSTR("FI: FS: > %c %c"), LCD_STR_THERMOMETER[0], LCD_STR_DEGREE[0]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @brief Monitors the LCD button selection without blocking MMU communication
|
||||
* @param[in] ei Error code index
|
||||
* @return 0 if there is no knob click --
|
||||
* 1 if user clicked 'More' and firmware should render
|
||||
* the error screen when ReportErrorHook is called next --
|
||||
* 2 if the user selects an operation and we would like
|
||||
* to exit the error screen. The MMU will raise the menu
|
||||
* again if the error is not solved.
|
||||
*/
|
||||
static uint8_t ReportErrorHookMonitor(uint8_t ei) {
|
||||
uint8_t ret = 0;
|
||||
bool two_choices = false;
|
||||
static int8_t enc_dif = lcd_encoder_diff;
|
||||
|
||||
if (lcd_encoder_diff == 0)
|
||||
{
|
||||
// lcd_update_enable(true) was called outside ReportErrorHookMonitor
|
||||
// It will set lcd_encoder_diff to 0, sync enc_dif
|
||||
enc_dif = 0;
|
||||
}
|
||||
|
||||
// Read and determine what operations should be shown on the menu
|
||||
const uint8_t button_operation = PrusaErrorButtons(ei);
|
||||
const uint8_t button_op_right = BUTTON_OP_RIGHT(button_operation);
|
||||
const uint8_t button_op_middle = BUTTON_OP_MIDDLE(button_operation);
|
||||
|
||||
// Check if the menu should have three or two choices
|
||||
if (button_op_right == (uint8_t)ButtonOperations::NoOperation){
|
||||
// Two operations not specified, the error menu should only show two choices
|
||||
two_choices = true;
|
||||
}
|
||||
|
||||
static int8_t current_selection = two_choices ? LCD_LEFT_BUTTON_CHOICE : LCD_MIDDLE_BUTTON_CHOICE;
|
||||
static int8_t choice_selected = -1;
|
||||
|
||||
// Check if knob was rotated
|
||||
if (abs(enc_dif - lcd_encoder_diff) >= ENCODER_PULSES_PER_STEP) {
|
||||
if (two_choices == false) { // third_choice is not nullptr, safe to dereference
|
||||
if (enc_dif > lcd_encoder_diff && current_selection != LCD_LEFT_BUTTON_CHOICE) {
|
||||
// Rotating knob counter clockwise
|
||||
current_selection--;
|
||||
} else if (enc_dif < lcd_encoder_diff && current_selection != LCD_RIGHT_BUTTON_CHOICE) {
|
||||
// Rotating knob clockwise
|
||||
current_selection++;
|
||||
}
|
||||
} else {
|
||||
if (enc_dif > lcd_encoder_diff && current_selection != LCD_LEFT_BUTTON_CHOICE) {
|
||||
// Rotating knob counter clockwise
|
||||
current_selection = LCD_LEFT_BUTTON_CHOICE;
|
||||
} else if (enc_dif < lcd_encoder_diff && current_selection != LCD_MIDDLE_BUTTON_CHOICE) {
|
||||
// Rotating knob clockwise
|
||||
current_selection = LCD_MIDDLE_BUTTON_CHOICE;
|
||||
}
|
||||
}
|
||||
|
||||
// Update '>' render only
|
||||
//! @brief Button menu
|
||||
//!
|
||||
//! @code{.unparsed}
|
||||
//! |01234567890123456789|
|
||||
//! | |
|
||||
//! | |
|
||||
//! | |
|
||||
//! |>(left) |
|
||||
//! ----------------------
|
||||
//! Three choices
|
||||
//! |>(left)>(mid)>(righ)|
|
||||
//! ----------------------
|
||||
//! Two choices
|
||||
//! ----------------------
|
||||
//! |>(left) >(mid) |
|
||||
//! ----------------------
|
||||
//! @endcode
|
||||
//
|
||||
lcd_set_cursor(0, 3);
|
||||
lcd_print(current_selection == LCD_LEFT_BUTTON_CHOICE ? '>': ' ');
|
||||
if (two_choices == false)
|
||||
{
|
||||
lcd_set_cursor(7, 3);
|
||||
lcd_print(current_selection == LCD_MIDDLE_BUTTON_CHOICE ? '>': ' ');
|
||||
lcd_set_cursor(13, 3);
|
||||
lcd_print(current_selection == LCD_RIGHT_BUTTON_CHOICE ? '>': ' ');
|
||||
} else {
|
||||
lcd_set_cursor(10, 3);
|
||||
lcd_print(current_selection == LCD_MIDDLE_BUTTON_CHOICE ? '>': ' ');
|
||||
}
|
||||
// Consume rotation event and make feedback sound
|
||||
enc_dif = lcd_encoder_diff;
|
||||
Sound_MakeSound(e_SOUND_TYPE_EncoderMove);
|
||||
}
|
||||
|
||||
// Check if knob was clicked and consume the event
|
||||
if (lcd_clicked()) {
|
||||
Sound_MakeSound(e_SOUND_TYPE_ButtonEcho);
|
||||
choice_selected = current_selection;
|
||||
} else {
|
||||
// continue monitoring
|
||||
return ret;
|
||||
}
|
||||
|
||||
if ((two_choices && choice_selected == LCD_MIDDLE_BUTTON_CHOICE) // Two choices and middle button selected
|
||||
|| (!two_choices && choice_selected == LCD_RIGHT_BUTTON_CHOICE)) // Three choices and right most button selected
|
||||
{
|
||||
// 'More' show error description
|
||||
lcd_show_fullscreen_message_and_wait_P(_T(PrusaErrorDesc(ei)));
|
||||
ret = 1;
|
||||
} else if(choice_selected == LCD_MIDDLE_BUTTON_CHOICE) {
|
||||
SetButtonResponse((ButtonOperations)button_op_right);
|
||||
ret = 2;
|
||||
} else {
|
||||
SetButtonResponse((ButtonOperations)button_op_middle);
|
||||
ret = 2;
|
||||
}
|
||||
|
||||
// Reset static variables to their default value
|
||||
current_selection = two_choices ? LCD_LEFT_BUTTON_CHOICE : LCD_MIDDLE_BUTTON_CHOICE;
|
||||
choice_selected = -1;
|
||||
return ret;
|
||||
}
|
||||
|
||||
enum class ReportErrorHookStates : uint8_t {
|
||||
RENDER_ERROR_SCREEN = 0,
|
||||
MONITOR_SELECTION = 1,
|
||||
DISMISS_ERROR_SCREEN = 2,
|
||||
};
|
||||
|
||||
enum ReportErrorHookStates ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
|
||||
void ReportErrorHook(uint16_t ec, uint8_t res) {
|
||||
if (mmu2.MMUCurrentErrorCode() == ErrorCode::OK && res == MMU2::ErrorSourceMMU)
|
||||
{
|
||||
// If the error code suddenly changes to OK, that means
|
||||
// a button was pushed on the MMU and the LCD should
|
||||
// dismiss the error screen until MMU raises a new error
|
||||
ReportErrorHookState = ReportErrorHookStates::DISMISS_ERROR_SCREEN;
|
||||
} else {
|
||||
// attempt an automatic Retry button
|
||||
if( ReportErrorHookState == ReportErrorHookStates::MONITOR_SELECTION ){
|
||||
if( mmu2.RetryIfPossible(ec) ){
|
||||
ReportErrorHookState = ReportErrorHookStates::DISMISS_ERROR_SCREEN;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t ei = PrusaErrorCodeIndex(ec);
|
||||
|
||||
switch ((uint8_t)ReportErrorHookState)
|
||||
{
|
||||
case (uint8_t)ReportErrorHookStates::RENDER_ERROR_SCREEN:
|
||||
ReportErrorHookStaticRender(ei);
|
||||
ReportErrorHookState = ReportErrorHookStates::MONITOR_SELECTION;
|
||||
// Fall through
|
||||
case (uint8_t)ReportErrorHookStates::MONITOR_SELECTION:
|
||||
mmu2.is_mmu_error_monitor_active = true;
|
||||
ReportErrorHookDynamicRender(); // Render dynamic characters
|
||||
switch (ReportErrorHookMonitor(ei))
|
||||
{
|
||||
case 0:
|
||||
// No choice selected, return to loop()
|
||||
break;
|
||||
case 1:
|
||||
// More button selected, change state
|
||||
ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
break;
|
||||
case 2:
|
||||
// Exit error screen and enable lcd updates
|
||||
lcd_set_custom_characters();
|
||||
lcd_update_enable(true);
|
||||
lcd_return_to_status();
|
||||
// Reset the state in case a new error is reported
|
||||
mmu2.is_mmu_error_monitor_active = false;
|
||||
ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return; // Always return to loop() to let MMU trigger a call to ReportErrorHook again
|
||||
break;
|
||||
case (uint8_t)ReportErrorHookStates::DISMISS_ERROR_SCREEN:
|
||||
lcd_set_custom_characters();
|
||||
lcd_update_enable(true);
|
||||
lcd_return_to_status();
|
||||
// Reset the state in case a new error is reported
|
||||
mmu2.is_mmu_error_monitor_active = false;
|
||||
ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ReportProgressHook(CommandInProgress cip, uint16_t ec) {
|
||||
if (cip != CommandInProgress::NoCommand)
|
||||
{
|
||||
custom_message_type = CustomMsg::MMUProgress;
|
||||
lcd_setstatuspgm( _T(ProgressCodeToText(ec)) );
|
||||
} else {
|
||||
// If there is no command in progress we can display other
|
||||
// useful information such as the name of the SD file
|
||||
// being printed
|
||||
custom_message_type = CustomMsg::Status;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/// @file mmu2_reporting.h
|
||||
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
enum CommandInProgress : uint8_t {
|
||||
NoCommand = 0,
|
||||
CutFilament = 'C',
|
||||
EjectFilament = 'E',
|
||||
Homing = 'H',
|
||||
LoadFilament = 'L',
|
||||
Reset = 'X',
|
||||
ToolChange = 'T',
|
||||
UnloadFilament = 'U',
|
||||
};
|
||||
|
||||
/// Called at the begin of every MMU operation
|
||||
void BeginReport(CommandInProgress cip, uint16_t ec);
|
||||
|
||||
/// Called at the end of every MMU operation
|
||||
void EndReport(CommandInProgress cip, uint16_t ec);
|
||||
|
||||
/**
|
||||
* @brief Called when the MMU or MK3S sends operation error (even repeatedly).
|
||||
* Render MMU error screen on the LCD. This must be non-blocking
|
||||
* and allow the MMU and printer to communicate with each other.
|
||||
* @param[in] ec error code
|
||||
* @param[in] res reporter error source, is either Printer (0) or MMU (1)
|
||||
*/
|
||||
void ReportErrorHook(uint16_t ec, uint8_t res);
|
||||
|
||||
/// Called when the MMU sends operation progress update
|
||||
void ReportProgressHook(CommandInProgress cip, uint16_t ec);
|
||||
|
||||
/// Remders the sensor status line. Also used by the "resume temperature" screen.
|
||||
void ReportErrorHookDynamicRender();
|
||||
|
||||
/// Renders the static part of the sensor state line. Also used by "resuming temperature screen"
|
||||
void ReportErrorHookSensorLineRender();
|
||||
|
||||
/// @returns true if the MMU is communicating and available
|
||||
/// can change at runtime
|
||||
bool MMUAvailable();
|
||||
|
||||
/// Global Enable/Disable use MMU (to be stored in EEPROM)
|
||||
bool UseMMU();
|
||||
|
||||
} // namespace
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
#include "mmu2_serial.h"
|
||||
#include "uart2.h"
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
void MMU2Serial::begin(uint32_t baud){
|
||||
uart2_init(baud); // @@TODO we may skip the baud rate setting in case of 8bit FW ... could save some bytes...
|
||||
}
|
||||
|
||||
void MMU2Serial::close() {
|
||||
// @@TODO - probably turn off the UART
|
||||
}
|
||||
|
||||
int MMU2Serial::read() {
|
||||
return fgetc(uart2io);
|
||||
}
|
||||
|
||||
void MMU2Serial::flush() {
|
||||
// @@TODO - clear the output buffer
|
||||
}
|
||||
|
||||
size_t MMU2Serial::write(const uint8_t *buffer, size_t size) {
|
||||
while(size--){
|
||||
fputc(*buffer, uart2io);
|
||||
++buffer;
|
||||
}
|
||||
}
|
||||
|
||||
MMU2Serial mmu2Serial;
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
namespace MMU2 {
|
||||
|
||||
/// A minimal serial interface for the MMU
|
||||
class MMU2Serial {
|
||||
public:
|
||||
MMU2Serial() = default;
|
||||
void begin(uint32_t baud);
|
||||
void close();
|
||||
int read();
|
||||
void flush();
|
||||
size_t write(const uint8_t *buffer, size_t size);
|
||||
};
|
||||
|
||||
extern MMU2Serial mmu2Serial;
|
||||
|
||||
} // namespace MMU2
|
||||
|
|
@ -4,7 +4,9 @@
|
|||
#include <avr/pgmspace.h>
|
||||
#include "config.h"
|
||||
#include <stdio.h>
|
||||
#include "Configuration_prusa.h"
|
||||
|
||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
|
||||
//PAT9125 registers
|
||||
#define PAT9125_PID1 0x00
|
||||
|
|
@ -46,27 +48,26 @@ uint8_t pat9125_s = 0;
|
|||
|
||||
|
||||
// Init sequence, address & value.
|
||||
const PROGMEM uint8_t pat9125_init_seq1[] = {
|
||||
const PROGMEM uint8_t pat9125_init_bank0[] = {
|
||||
// Disable write protect.
|
||||
PAT9125_WP, 0x5a,
|
||||
// Set the X resolution to zero to let the sensor know that it could safely ignore movement in the X axis.
|
||||
PAT9125_RES_X, PAT9125_XRES,
|
||||
// Set the Y resolution to a maximum (or nearly a maximum).
|
||||
PAT9125_RES_Y, PAT9125_YRES,
|
||||
// Set 12-bit X/Y data format.
|
||||
PAT9125_ORIENTATION, 0x04,
|
||||
// PAT9125_ORIENTATION, 0x04 | (xinv?0x08:0) | (yinv?0x10:0), //!? direction switching does not work
|
||||
// Now continues the magic sequence from the PAT912EL Application Note: Firmware Guides for Tracking Optimization.
|
||||
0x5e, 0x08,
|
||||
0x20, 0x64,
|
||||
0x2b, 0x6d,
|
||||
0x32, 0x2f,
|
||||
// stopper
|
||||
0x0ff
|
||||
PAT9125_RES_X, PAT9125_XRES,
|
||||
// Set the Y resolution to a maximum (or nearly a maximum).
|
||||
PAT9125_RES_Y, PAT9125_YRES,
|
||||
// Set data format and sensor orientation.
|
||||
PAT9125_ORIENTATION, ((PAT9125_12B_RES?0x04:0) | (PAT9125_INVERT_X?0x08:0) | (PAT9125_INVERT_Y?0x10:0) | (PAT9125_SWAP_XY?0x20:0)),
|
||||
|
||||
// Now continues the magic sequence from the PAT912EL Application Note: Firmware Guides for Tracking Optimization.
|
||||
0x5e, 0x08,
|
||||
0x20, 0x64,
|
||||
0x2b, 0x6d,
|
||||
0x32, 0x2f,
|
||||
0xff //end of sequence
|
||||
};
|
||||
|
||||
// Init sequence, address & value.
|
||||
const PROGMEM uint8_t pat9125_init_seq2[] = {
|
||||
const PROGMEM uint8_t pat9125_init_bank1[] = {
|
||||
// Magic sequence to enforce full frame rate of the sensor.
|
||||
0x06, 0x028,
|
||||
0x33, 0x0d0,
|
||||
|
|
@ -93,14 +94,14 @@ const PROGMEM uint8_t pat9125_init_seq2[] = {
|
|||
0x6e, 0x022,
|
||||
0x71, 0x007,
|
||||
0x72, 0x008,
|
||||
// stopper
|
||||
0x0ff
|
||||
0xff //end of sequence
|
||||
};
|
||||
|
||||
|
||||
uint8_t pat9125_rd_reg(uint8_t addr);
|
||||
void pat9125_wr_reg(uint8_t addr, uint8_t data);
|
||||
uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data);
|
||||
static uint8_t pat9125_rd_reg(uint8_t addr);
|
||||
static void pat9125_wr_reg(uint8_t addr, uint8_t data);
|
||||
static uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data);
|
||||
static uint8_t pat9125_wr_seq(const uint8_t* seq);
|
||||
|
||||
extern FILE _uartout;
|
||||
#define uartout (&_uartout)
|
||||
|
|
@ -113,26 +114,23 @@ uint8_t pat9125_probe()
|
|||
#error not implemented
|
||||
#elif defined(PAT9125_SWI2C)
|
||||
swi2c_init();
|
||||
return swi2c_readByte_A8(PAT9125_I2C_ADDR,0x00,NULL);
|
||||
return swi2c_check(PAT9125_I2C_ADDR) == 0;
|
||||
#elif defined(PAT9125_I2C)
|
||||
twi_init();
|
||||
#ifdef IR_SENSOR
|
||||
// NOTE: this is called from the MK3S variant, so it should be kept minimal
|
||||
uint8_t data;
|
||||
return (twi_r8(PAT9125_I2C_ADDR,PAT9125_PID1,&data) == 0);
|
||||
#else
|
||||
return (pat9125_rd_reg(PAT9125_PID1) != 0);
|
||||
#endif
|
||||
return twi_check(PAT9125_I2C_ADDR) == 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint8_t pat9125_init(void)
|
||||
{
|
||||
if (!pat9125_probe())
|
||||
return 0;
|
||||
if (!pat9125_probe())
|
||||
return 0;
|
||||
|
||||
// Verify that the sensor responds with its correct product ID.
|
||||
pat9125_PID1 = pat9125_rd_reg(PAT9125_PID1);
|
||||
// Switch to bank0, not allowed to perform pat9125_wr_reg_verify on this register.
|
||||
pat9125_wr_reg(PAT9125_BANK_SELECTION, 0);
|
||||
|
||||
// Verify that the sensor responds with its correct product ID.
|
||||
pat9125_PID1 = pat9125_rd_reg(PAT9125_PID1);
|
||||
pat9125_PID2 = pat9125_rd_reg(PAT9125_PID2);
|
||||
if ((pat9125_PID1 != 0x31) || (pat9125_PID2 != 0x91))
|
||||
{
|
||||
|
|
@ -142,54 +140,49 @@ uint8_t pat9125_init(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef PAT9125_NEW_INIT
|
||||
// Switch to bank0, not allowed to perform OTS_RegWriteRead.
|
||||
pat9125_wr_reg(PAT9125_BANK_SELECTION, 0);
|
||||
#if PAT9125_NEW_INIT
|
||||
// Software reset (i.e. set bit7 to 1). It will reset to 0 automatically.
|
||||
// After the reset, OTS_RegWriteRead is not allowed.
|
||||
// pat9125_wr_reg_verify is not allowed because the register contents will change as soon as they are written. No point in verifying those.
|
||||
pat9125_wr_reg(PAT9125_CONFIG, 0x97);
|
||||
// Wait until the sensor reboots.
|
||||
// Delay 1ms.
|
||||
_delay_us(1000);
|
||||
{
|
||||
const uint8_t *ptr = pat9125_init_seq1;
|
||||
for (;;) {
|
||||
const uint8_t addr = pgm_read_byte_near(ptr ++);
|
||||
if (addr == 0x0ff)
|
||||
break;
|
||||
if (! pat9125_wr_reg_verify(addr, pgm_read_byte_near(ptr ++)))
|
||||
// Verification of the register write failed.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Delay 10ms.
|
||||
_delay_ms(10);
|
||||
// Switch to bank1, not allowed to perform OTS_RegWrite.
|
||||
_delay_ms(1);
|
||||
|
||||
//Write init sequence in bank0. MUST ALREADY BE IN bank0.
|
||||
if (!pat9125_wr_seq(pat9125_init_bank0))
|
||||
return 0;
|
||||
|
||||
_delay_ms(10); // not sure why this is here. But I'll allow it.
|
||||
|
||||
// Switch to bank1, not allowed to perform pat9125_wr_reg_verify on this register.
|
||||
pat9125_wr_reg(PAT9125_BANK_SELECTION, 0x01);
|
||||
{
|
||||
const uint8_t *ptr = pat9125_init_seq2;
|
||||
for (;;) {
|
||||
const uint8_t addr = pgm_read_byte_near(ptr ++);
|
||||
if (addr == 0x0ff)
|
||||
break;
|
||||
if (! pat9125_wr_reg_verify(addr, pgm_read_byte_near(ptr ++)))
|
||||
// Verification of the register write failed.
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
// Switch to bank0, not allowed to perform OTS_RegWriteRead.
|
||||
//Write init sequence in bank1. MUST ALREADY BE IN bank1.
|
||||
if (!pat9125_wr_seq(pat9125_init_bank1))
|
||||
return 0;
|
||||
|
||||
// Switch to bank0, not allowed to perform pat9125_wr_reg_verify on this register.
|
||||
pat9125_wr_reg(PAT9125_BANK_SELECTION, 0x00);
|
||||
|
||||
// Enable write protect.
|
||||
pat9125_wr_reg(PAT9125_WP, 0x00);
|
||||
pat9125_wr_reg(PAT9125_WP, 0x00); //prevents writing to registers over 0x09
|
||||
|
||||
pat9125_PID1 = pat9125_rd_reg(PAT9125_PID1);
|
||||
pat9125_PID2 = pat9125_rd_reg(PAT9125_PID2);
|
||||
#endif //PAT9125_NEW_INIT
|
||||
|
||||
#else //PAT9125_NEW_INIT
|
||||
// Disable write protect.
|
||||
pat9125_wr_reg(PAT9125_WP, 0x5a); //allows writing to all registers
|
||||
|
||||
pat9125_wr_reg(PAT9125_RES_X, PAT9125_XRES);
|
||||
pat9125_wr_reg(PAT9125_RES_Y, PAT9125_YRES);
|
||||
fprintf_P(uartout, PSTR("PAT9125_RES_X=%u\n"), pat9125_rd_reg(PAT9125_RES_X));
|
||||
fprintf_P(uartout, PSTR("PAT9125_RES_Y=%u\n"), pat9125_rd_reg(PAT9125_RES_Y));
|
||||
printf_P(PSTR("PAT9125_RES_X=%u\n"), pat9125_rd_reg(PAT9125_RES_X));
|
||||
printf_P(PSTR("PAT9125_RES_Y=%u\n"), pat9125_rd_reg(PAT9125_RES_Y));
|
||||
|
||||
pat9125_wr_reg(PAT9125_ORIENTATION, ((PAT9125_12B_RES?0x04:0) | (PAT9125_INVERT_X?0x08:0) | (PAT9125_INVERT_Y?0x10:0) | (PAT9125_SWAP_XY?0x20:0)));
|
||||
|
||||
// Enable write protect.
|
||||
pat9125_wr_reg(PAT9125_WP, 0x00); //prevents writing to registers over 0x09
|
||||
#endif //PAT9125_NEW_INIT
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -212,7 +205,7 @@ uint8_t pat9125_update(void)
|
|||
if (iDX & 0x800) iDX -= 4096;
|
||||
if (iDY & 0x800) iDY -= 4096;
|
||||
pat9125_x += iDX;
|
||||
pat9125_y -= iDY; //negative number, because direction switching does not work
|
||||
pat9125_y += iDY;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -232,7 +225,7 @@ uint8_t pat9125_update_y(void)
|
|||
if (pat9125_PID1 == 0xff) return 0;
|
||||
int16_t iDY = ucYL | ((ucXYH << 8) & 0xf00);
|
||||
if (iDY & 0x800) iDY -= 4096;
|
||||
pat9125_y -= iDY; //negative number, because direction switching does not work
|
||||
pat9125_y += iDY;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -251,7 +244,7 @@ uint8_t pat9125_update_bs(void)
|
|||
return 0;
|
||||
}
|
||||
|
||||
uint8_t pat9125_rd_reg(uint8_t addr)
|
||||
static uint8_t pat9125_rd_reg(uint8_t addr)
|
||||
{
|
||||
uint8_t data = 0;
|
||||
#if defined(PAT9125_SWSPI)
|
||||
|
|
@ -274,7 +267,7 @@ uint8_t pat9125_rd_reg(uint8_t addr)
|
|||
return 0;
|
||||
}
|
||||
|
||||
void pat9125_wr_reg(uint8_t addr, uint8_t data)
|
||||
static void pat9125_wr_reg(uint8_t addr, uint8_t data)
|
||||
{
|
||||
#if defined(PAT9125_SWSPI)
|
||||
swspi_start();
|
||||
|
|
@ -296,8 +289,23 @@ void pat9125_wr_reg(uint8_t addr, uint8_t data)
|
|||
return;
|
||||
}
|
||||
|
||||
uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data)
|
||||
static uint8_t pat9125_wr_reg_verify(uint8_t addr, uint8_t data)
|
||||
{
|
||||
pat9125_wr_reg(addr, data);
|
||||
return pat9125_rd_reg(addr) == data;
|
||||
}
|
||||
|
||||
static uint8_t pat9125_wr_seq(const uint8_t* seq)
|
||||
{
|
||||
for (;;) {
|
||||
const uint8_t addr = pgm_read_byte(seq++);
|
||||
if (addr == 0xff)
|
||||
break;
|
||||
if (!pat9125_wr_reg_verify(addr, pgm_read_byte(seq++)))
|
||||
// Verification of the register write failed.
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
//pat9125.h
|
||||
#ifndef PAT9125_H
|
||||
#define PAT9125_H
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
extern uint8_t pat9125_PID1;
|
||||
extern uint8_t pat9125_PID2;
|
||||
|
||||
extern int16_t pat9125_x;
|
||||
extern int16_t pat9125_y;
|
||||
extern uint8_t pat9125_b;
|
||||
|
|
@ -17,5 +12,3 @@ extern uint8_t pat9125_init(void);
|
|||
extern uint8_t pat9125_update(void); // update all sensor data
|
||||
extern uint8_t pat9125_update_y(void); // update _y only
|
||||
extern uint8_t pat9125_update_bs(void); // update _b/_s only
|
||||
|
||||
#endif //PAT9125_H
|
||||
|
|
|
|||
|
|
@ -911,8 +911,6 @@ block->steps_y.wide = labs((target[X_AXIS]-position[X_AXIS]) - (target[Y_AXIS]-p
|
|||
block->direction_bits |= (1<<E_AXIS);
|
||||
}
|
||||
|
||||
block->active_extruder = extruder;
|
||||
|
||||
//enable active axes
|
||||
#ifdef COREXY
|
||||
if((block->steps_x.wide != 0) || (block->steps_y.wide != 0))
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ typedef struct {
|
|||
dda_usteps_t step_event_count; // The number of step events required to complete this block
|
||||
uint32_t acceleration_rate; // The acceleration rate used for acceleration calculation
|
||||
unsigned char direction_bits; // The direction bit set for this block (refers to *_DIRECTION_BIT in config.h)
|
||||
unsigned char active_extruder; // Selects the active extruder
|
||||
// accelerate_until and decelerate_after are set by calculate_trapezoid_for_block() and they need to be synchronized with the stepper interrupt controller.
|
||||
uint32_t accelerate_until; // The index of the step event on which to stop acceleration
|
||||
uint32_t decelerate_after; // The index of the step event on which to start decelerating
|
||||
|
|
|
|||
|
|
@ -1,18 +1,34 @@
|
|||
#pragma once
|
||||
#include <stdint.h>
|
||||
#ifndef SOUND_H
|
||||
#define SOUND_H
|
||||
|
||||
|
||||
#define e_SOUND_MODE_NULL 0xFF
|
||||
typedef enum : uint8_t
|
||||
{e_SOUND_MODE_LOUD,e_SOUND_MODE_ONCE,e_SOUND_MODE_SILENT,e_SOUND_MODE_BLIND} eSOUND_MODE;
|
||||
typedef enum : uint8_t {
|
||||
e_SOUND_MODE_LOUD,
|
||||
e_SOUND_MODE_ONCE,
|
||||
e_SOUND_MODE_SILENT,
|
||||
e_SOUND_MODE_BLIND
|
||||
} eSOUND_MODE;
|
||||
|
||||
#define e_SOUND_MODE_DEFAULT e_SOUND_MODE_LOUD
|
||||
|
||||
typedef enum : uint8_t
|
||||
{e_SOUND_TYPE_ButtonEcho,e_SOUND_TYPE_EncoderEcho,e_SOUND_TYPE_StandardPrompt,e_SOUND_TYPE_StandardConfirm,e_SOUND_TYPE_StandardWarning,e_SOUND_TYPE_StandardAlert,e_SOUND_TYPE_EncoderMove,e_SOUND_TYPE_BlindAlert} eSOUND_TYPE;
|
||||
typedef enum : uint8_t
|
||||
{e_SOUND_CLASS_Echo,e_SOUND_CLASS_Prompt,e_SOUND_CLASS_Confirm,e_SOUND_CLASS_Warning,e_SOUND_CLASS_Alert} eSOUND_CLASS;
|
||||
typedef enum : uint8_t {
|
||||
e_SOUND_TYPE_ButtonEcho,
|
||||
e_SOUND_TYPE_EncoderEcho,
|
||||
e_SOUND_TYPE_StandardPrompt,
|
||||
e_SOUND_TYPE_StandardConfirm,
|
||||
e_SOUND_TYPE_StandardWarning,
|
||||
e_SOUND_TYPE_StandardAlert,
|
||||
e_SOUND_TYPE_EncoderMove,
|
||||
e_SOUND_TYPE_BlindAlert
|
||||
} eSOUND_TYPE;
|
||||
|
||||
typedef enum : uint8_t {
|
||||
e_SOUND_CLASS_Echo,
|
||||
e_SOUND_CLASS_Prompt,
|
||||
e_SOUND_CLASS_Confirm,
|
||||
e_SOUND_CLASS_Warning,
|
||||
e_SOUND_CLASS_Alert
|
||||
} eSOUND_CLASS;
|
||||
|
||||
extern eSOUND_MODE eSoundMode;
|
||||
|
||||
|
|
@ -25,5 +41,3 @@ extern void Sound_MakeCustom(uint16_t ms,uint16_t tone_ ,bool critical);
|
|||
|
||||
//static void Sound_DoSound_Echo(void);
|
||||
//static void Sound_DoSound_Prompt(void);
|
||||
|
||||
#endif // SOUND_H
|
||||
|
|
|
|||
|
|
@ -36,12 +36,9 @@
|
|||
#include "tmc2130.h"
|
||||
#endif //TMC2130
|
||||
|
||||
#if defined(FILAMENT_SENSOR) && defined(PAT9125)
|
||||
#include "fsensor.h"
|
||||
int fsensor_counter; //counter for e-steps
|
||||
#endif //FILAMENT_SENSOR
|
||||
#include "Filament_sensor.h"
|
||||
|
||||
#include "mmu.h"
|
||||
#include "mmu2.h"
|
||||
#include "ConfigurationStore.h"
|
||||
|
||||
#include "Prusa_farm.h"
|
||||
|
|
@ -457,9 +454,6 @@ FORCE_INLINE void stepper_next_block()
|
|||
#endif /* LIN_ADVANCE */
|
||||
count_direction[E_AXIS] = 1;
|
||||
}
|
||||
#if defined(FILAMENT_SENSOR) && defined(PAT9125)
|
||||
fsensor_st_block_begin(count_direction[E_AXIS] < 0);
|
||||
#endif //FILAMENT_SENSOR
|
||||
}
|
||||
else {
|
||||
_NEXT_ISR(2000); // 1kHz.
|
||||
|
|
@ -704,9 +698,9 @@ FORCE_INLINE void stepper_tick_lowres()
|
|||
#ifdef LIN_ADVANCE
|
||||
e_steps += count_direction[E_AXIS];
|
||||
#else
|
||||
#ifdef FILAMENT_SENSOR
|
||||
fsensor_counter += count_direction[E_AXIS];
|
||||
#endif //FILAMENT_SENSOR
|
||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
fsensor.stStep(count_direction[E_AXIS] < 0);
|
||||
#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
STEP_NC_LO(E_AXIS);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -766,9 +760,9 @@ FORCE_INLINE void stepper_tick_highres()
|
|||
#ifdef LIN_ADVANCE
|
||||
e_steps += count_direction[E_AXIS];
|
||||
#else
|
||||
#ifdef FILAMENT_SENSOR
|
||||
fsensor_counter += count_direction[E_AXIS];
|
||||
#endif //FILAMENT_SENSOR
|
||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
fsensor.stStep(count_direction[E_AXIS] < 0);
|
||||
#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
STEP_NC_LO(E_AXIS);
|
||||
#endif
|
||||
}
|
||||
|
|
@ -963,21 +957,9 @@ FORCE_INLINE void isr() {
|
|||
|
||||
// If current block is finished, reset pointer
|
||||
if (step_events_completed.wide >= current_block->step_event_count.wide) {
|
||||
#if !defined(LIN_ADVANCE) && defined(FILAMENT_SENSOR)
|
||||
fsensor_st_block_chunk(fsensor_counter);
|
||||
fsensor_counter = 0;
|
||||
#endif //FILAMENT_SENSOR
|
||||
|
||||
current_block = NULL;
|
||||
plan_discard_current_block();
|
||||
}
|
||||
#if !defined(LIN_ADVANCE) && defined(FILAMENT_SENSOR)
|
||||
else if ((abs(fsensor_counter) >= fsensor_chunk_len))
|
||||
{
|
||||
fsensor_st_block_chunk(fsensor_counter);
|
||||
fsensor_counter = 0;
|
||||
}
|
||||
#endif //FILAMENT_SENSOR
|
||||
}
|
||||
|
||||
#ifdef TMC2130
|
||||
|
|
@ -1073,19 +1055,11 @@ FORCE_INLINE void advance_isr_scheduler() {
|
|||
STEP_NC_HI(E_AXIS);
|
||||
e_steps += (rev? 1: -1);
|
||||
STEP_NC_LO(E_AXIS);
|
||||
#if defined(FILAMENT_SENSOR) && defined(PAT9125)
|
||||
fsensor_counter += (rev? -1: 1);
|
||||
#endif
|
||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
fsensor.stStep(rev);
|
||||
#endif //defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_PAT9125)
|
||||
}
|
||||
while(--max_ticks);
|
||||
|
||||
#if defined(FILAMENT_SENSOR) && defined(PAT9125)
|
||||
if (abs(fsensor_counter) >= fsensor_chunk_len)
|
||||
{
|
||||
fsensor_st_block_chunk(fsensor_counter);
|
||||
fsensor_counter = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// Schedule the next closest tick, ignoring advance if scheduled too
|
||||
|
|
@ -1668,13 +1642,3 @@ void microstep_readings()
|
|||
#endif
|
||||
}
|
||||
#endif //TMC2130
|
||||
|
||||
|
||||
#if defined(FILAMENT_SENSOR) && defined(PAT9125)
|
||||
void st_reset_fsensor()
|
||||
{
|
||||
CRITICAL_SECTION_START;
|
||||
fsensor_counter = 0;
|
||||
CRITICAL_SECTION_END;
|
||||
}
|
||||
#endif //FILAMENT_SENSOR
|
||||
|
|
|
|||
|
|
@ -87,9 +87,4 @@ void microstep_readings();
|
|||
void babystep(const uint8_t axis,const bool direction); // perform a short step with a single stepper motor, outside of any convention
|
||||
#endif
|
||||
|
||||
#if defined(FILAMENT_SENSOR) && defined(PAT9125)
|
||||
// reset the internal filament sensor state
|
||||
void st_reset_fsensor();
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
constexpr inline int strlen_constexpr(const char* str){
|
||||
return *str ? 1 + strlen_constexpr(str + 1) : 0;
|
||||
}
|
||||
|
|
@ -15,23 +15,49 @@
|
|||
#define SWI2C_ASHF 0x01 //address shift (<< 1)
|
||||
#define SWI2C_DMSK 0x7f //device address mask
|
||||
|
||||
static void __delay(void);
|
||||
static void swi2c_start(void);
|
||||
static void swi2c_stop(void);
|
||||
// static void swi2c_ack(void);
|
||||
static void swi2c_nack(void);
|
||||
static uint8_t swi2c_wait_ack();
|
||||
static uint8_t swi2c_read(void);
|
||||
static void swi2c_write(uint8_t data);
|
||||
|
||||
void __delay(void)
|
||||
|
||||
void swi2c_init(void)
|
||||
{
|
||||
SET_INPUT(SWI2C_SDA);
|
||||
WRITE(SWI2C_SDA, 1); //SDA must be input with pullups while we are not sure if the slave is outputing or not
|
||||
|
||||
WRITE(SWI2C_SCL, 0);
|
||||
SET_OUTPUT(SWI2C_SCL); //SCL can be an output at all times. The bus is not in a multi-master configuration.
|
||||
|
||||
for (uint8_t i = 0; i < 100; i++) //wait. Not sure what for, but wait anyway.
|
||||
__delay();
|
||||
|
||||
for (uint8_t i = 0; i < 10; i++) { //send nack 10 times. This makes sure that the slave gets a nack regardless of it's state when we init the bus.
|
||||
swi2c_nack();
|
||||
}
|
||||
swi2c_stop(); //"release" the bus by sending a stop condition.
|
||||
|
||||
SET_OUTPUT(SWI2C_SDA); //finally make the SDA line an output since the bus is idle for sure.
|
||||
}
|
||||
|
||||
void swi2c_disable(void)
|
||||
{
|
||||
SET_INPUT(SWI2C_SDA);
|
||||
WRITE(SWI2C_SDA, 0);
|
||||
SET_INPUT(SWI2C_SCL);
|
||||
WRITE(SWI2C_SCL, 0);
|
||||
}
|
||||
|
||||
static void __delay(void)
|
||||
{
|
||||
_delay_us(1.5);
|
||||
}
|
||||
|
||||
void swi2c_init(void)
|
||||
{
|
||||
WRITE(SWI2C_SDA, 1);
|
||||
WRITE(SWI2C_SCL, 1);
|
||||
SET_OUTPUT(SWI2C_SDA);
|
||||
SET_OUTPUT(SWI2C_SCL);
|
||||
uint8_t i; for (i = 0; i < 100; i++)
|
||||
__delay();
|
||||
}
|
||||
|
||||
void swi2c_start(void)
|
||||
static void swi2c_start(void)
|
||||
{
|
||||
WRITE(SWI2C_SDA, 0);
|
||||
__delay();
|
||||
|
|
@ -39,7 +65,7 @@ void swi2c_start(void)
|
|||
__delay();
|
||||
}
|
||||
|
||||
void swi2c_stop(void)
|
||||
static void swi2c_stop(void)
|
||||
{
|
||||
WRITE(SWI2C_SCL, 1);
|
||||
__delay();
|
||||
|
|
@ -47,7 +73,8 @@ void swi2c_stop(void)
|
|||
__delay();
|
||||
}
|
||||
|
||||
void swi2c_ack(void)
|
||||
/*
|
||||
static void swi2c_ack(void)
|
||||
{
|
||||
WRITE(SWI2C_SDA, 0);
|
||||
__delay();
|
||||
|
|
@ -56,8 +83,19 @@ void swi2c_ack(void)
|
|||
WRITE(SWI2C_SCL, 0);
|
||||
__delay();
|
||||
}
|
||||
*/
|
||||
|
||||
uint8_t swi2c_wait_ack()
|
||||
static void swi2c_nack(void)
|
||||
{
|
||||
WRITE(SWI2C_SDA, 1);
|
||||
__delay();
|
||||
WRITE(SWI2C_SCL, 1);
|
||||
__delay();
|
||||
WRITE(SWI2C_SCL, 0);
|
||||
__delay();
|
||||
}
|
||||
|
||||
static uint8_t swi2c_wait_ack()
|
||||
{
|
||||
SET_INPUT(SWI2C_SDA);
|
||||
__delay();
|
||||
|
|
@ -77,13 +115,13 @@ uint8_t swi2c_wait_ack()
|
|||
return ack;
|
||||
}
|
||||
|
||||
uint8_t swi2c_read(void)
|
||||
static uint8_t swi2c_read(void)
|
||||
{
|
||||
WRITE(SWI2C_SDA, 1);
|
||||
__delay();
|
||||
SET_INPUT(SWI2C_SDA);
|
||||
uint8_t data = 0;
|
||||
int8_t bit; for (bit = 7; bit >= 0; bit--)
|
||||
for (uint8_t bit = 8; bit-- > 0;)
|
||||
{
|
||||
WRITE(SWI2C_SCL, 1);
|
||||
__delay();
|
||||
|
|
@ -95,9 +133,9 @@ uint8_t swi2c_read(void)
|
|||
return data;
|
||||
}
|
||||
|
||||
void swi2c_write(uint8_t data)
|
||||
static void swi2c_write(uint8_t data)
|
||||
{
|
||||
int8_t bit; for (bit = 7; bit >= 0; bit--)
|
||||
for (uint8_t bit = 8; bit-- > 0;)
|
||||
{
|
||||
WRITE(SWI2C_SDA, data & _BV(bit));
|
||||
__delay();
|
||||
|
|
@ -112,9 +150,9 @@ uint8_t swi2c_check(uint8_t dev_addr)
|
|||
{
|
||||
swi2c_start();
|
||||
swi2c_write((dev_addr & SWI2C_DMSK) << SWI2C_ASHF);
|
||||
if (!swi2c_wait_ack()) { swi2c_stop(); return 0; }
|
||||
if (!swi2c_wait_ack()) { swi2c_stop(); return 1; }
|
||||
swi2c_stop();
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef SWI2C_A8 //8bit address
|
||||
|
|
|
|||
|
|
@ -13,6 +13,9 @@ extern "C" {
|
|||
//initialize
|
||||
extern void swi2c_init(void);
|
||||
|
||||
//deinit pins
|
||||
extern void swi2c_disable(void);
|
||||
|
||||
//check device address acknowledge
|
||||
extern uint8_t swi2c_check(uint8_t dev_addr);
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
#include "planner.h"
|
||||
|
||||
constexpr uint8_t TEMP_MODEL_CAL_S = 60; // Maximum recording lenght during calibration (s)
|
||||
constexpr uint8_t TEMP_MODEL_CAL_S = 60; // Maximum recording length during calibration (s)
|
||||
constexpr uint8_t TEMP_MODEL_CAL_R_STEP = 4; // Fan interpolation steps during calibration
|
||||
constexpr float TEMP_MODEL_fS = 0.065; // simulation filter (1st-order IIR factor)
|
||||
constexpr float TEMP_MODEL_fE = 0.05; // error filter (1st-order IIR factor)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@
|
|||
#include "temp_model.h"
|
||||
#endif
|
||||
|
||||
#include "Filament_sensor.h"
|
||||
|
||||
//===========================================================================
|
||||
//=============================public variables============================
|
||||
//===========================================================================
|
||||
|
|
@ -123,10 +125,6 @@ int current_voltage_raw_pwr = 0;
|
|||
int current_voltage_raw_bed = 0;
|
||||
#endif
|
||||
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
uint16_t current_voltage_raw_IR = 0;
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
|
||||
int current_temperature_bed_raw = 0;
|
||||
float current_temperature_bed = 0.0;
|
||||
|
||||
|
|
@ -1186,7 +1184,7 @@ FORCE_INLINE static void applyBabysteps() {
|
|||
int curTodo=babystepsTodo[axis]; //get rid of volatile for performance
|
||||
|
||||
if(curTodo>0)
|
||||
{
|
||||
{
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
babystep(axis,/*fwd*/true);
|
||||
babystepsTodo[axis]--; //less to do next time
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
|
||||
#include <math.h>
|
||||
#include <util/delay.h>
|
||||
#include "config.h"
|
||||
#include "fastio.h"
|
||||
#include "twi.h"
|
||||
|
|
@ -29,8 +30,24 @@
|
|||
|
||||
void twi_init(void)
|
||||
{
|
||||
// activate internal pullups for twi.
|
||||
// activate internal pullups for SDA
|
||||
SET_INPUT(SDA_PIN);
|
||||
WRITE(SDA_PIN, 1);
|
||||
|
||||
// start with the SDA pulled low
|
||||
WRITE(SCL_PIN, 0);
|
||||
SET_OUTPUT(SCL_PIN);
|
||||
|
||||
// clock 10 cycles to make sure that the sensor is not stuck in a register read.
|
||||
for (uint8_t i = 0; i < 10; i++) {
|
||||
WRITE(SCL_PIN, 1);
|
||||
_delay_us((1000000 / TWI_FREQ) / 2);
|
||||
WRITE(SCL_PIN, 0);
|
||||
_delay_us((1000000 / TWI_FREQ) / 2);
|
||||
}
|
||||
|
||||
// activate internal pullups for SCL
|
||||
SET_INPUT(SCL_PIN);
|
||||
WRITE(SCL_PIN, 1);
|
||||
|
||||
// initialize twi prescaler and bit rate
|
||||
|
|
@ -101,6 +118,25 @@ static uint8_t twi_start(uint8_t address, uint8_t reg)
|
|||
}
|
||||
|
||||
|
||||
uint8_t twi_check(uint8_t address)
|
||||
{
|
||||
// send start condition
|
||||
TWCR = _BV(TWEN) | _BV(TWINT) | _BV(TWSTA);
|
||||
if(twi_wait(TW_START))
|
||||
return 1;
|
||||
|
||||
// send address
|
||||
TWDR = TW_WRITE | (address << 1);
|
||||
TWCR = _BV(TWEN) | _BV(TWINT);
|
||||
if(twi_wait(TW_MT_SLA_ACK))
|
||||
return 2;
|
||||
|
||||
// send stop
|
||||
twi_stop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
uint8_t twi_r8(uint8_t address, uint8_t reg, uint8_t* data)
|
||||
{
|
||||
if(twi_start(address, reg))
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
#define TWI_FREQ 400000L
|
||||
#endif
|
||||
|
||||
#define TWI_TIMEOUT_MS 100
|
||||
#define TWI_TIMEOUT_MS 10
|
||||
|
||||
/*
|
||||
* Function twi_init
|
||||
|
|
@ -44,6 +44,14 @@ void twi_init(void);
|
|||
*/
|
||||
void twi_disable(void);
|
||||
|
||||
/*
|
||||
* Function twi_check
|
||||
* Desc checks if a device exists on the bus
|
||||
* Input address: 7bit i2c device address
|
||||
* Output 0 on device found at address
|
||||
*/
|
||||
uint8_t twi_check(uint8_t address);
|
||||
|
||||
/*
|
||||
* Function twi_r8
|
||||
* Desc read a single byte from a device
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
#define uart2_txcomplete (UCSR2A & (1 << TXC2))
|
||||
#define uart2_txready (UCSR2A & (1 << UDRE2))
|
||||
|
||||
uint8_t uart2_ibuf[14] = {0, 0};
|
||||
uint8_t uart2_ibuf[20] = {0, 0};
|
||||
|
||||
FILE _uart2io = {0};
|
||||
|
||||
|
|
@ -33,13 +33,13 @@ int uart2_getchar(_UNUSED FILE *stream)
|
|||
}
|
||||
|
||||
//uart init (io + FILE stream)
|
||||
void uart2_init(void)
|
||||
void uart2_init(uint32_t baudRate)
|
||||
{
|
||||
DDRH &= ~0x01;
|
||||
PORTH |= 0x01;
|
||||
rbuf_ini(uart2_ibuf, sizeof(uart2_ibuf) - 4);
|
||||
UCSR2A |= (1 << U2X2); // baudrate multiplier
|
||||
UBRR2L = UART_BAUD_SELECT(UART2_BAUD, F_CPU); // select baudrate
|
||||
UBRR2L = UART_BAUD_SELECT(baudRate, F_CPU); // select baudrate
|
||||
UCSR2B = (1 << RXEN2) | (1 << TXEN2); // enable receiver and transmitter
|
||||
UCSR2B |= (1 << RXCIE2); // enable rx interrupt
|
||||
fdev_setup_stream(uart2io, uart2_putchar, uart2_getchar, _FDEV_SETUP_WRITE | _FDEV_SETUP_READ); //setup uart2 i/o stream
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ extern FILE _uart2io;
|
|||
#define uart2io (&_uart2io)
|
||||
|
||||
|
||||
extern void uart2_init(void);
|
||||
extern void uart2_init(uint32_t baudRate);
|
||||
|
||||
extern int8_t uart2_rx_str_P(const char* str);
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -36,7 +36,7 @@ void lcd_reset_alert_level();
|
|||
|
||||
void lcd_adjust_z();
|
||||
void lcd_pick_babystep();
|
||||
void lcd_alright();
|
||||
uint8_t lcd_alright();
|
||||
void show_preheat_nozzle_warning();
|
||||
void lcd_wait_interact();
|
||||
void lcd_loading_filament();
|
||||
|
|
@ -64,20 +64,27 @@ void lcd_crash_detect_enable();
|
|||
void lcd_crash_detect_disable();
|
||||
#endif
|
||||
|
||||
extern const char* lcd_display_message_fullscreen_P(const char *msg, uint8_t &nlines);
|
||||
enum LCDButtonChoice : uint_fast8_t {
|
||||
LCD_LEFT_BUTTON_CHOICE = 0,
|
||||
LCD_MIDDLE_BUTTON_CHOICE = 1,
|
||||
LCD_RIGHT_BUTTON_CHOICE = 2,
|
||||
LCD_BUTTON_TIMEOUT = 0xFF,
|
||||
};
|
||||
|
||||
extern const char* lcd_display_message_fullscreen_P(const char *msg);
|
||||
|
||||
extern void lcd_return_to_status();
|
||||
extern void lcd_wait_for_click();
|
||||
extern bool lcd_wait_for_click_delay(uint16_t nDelay);
|
||||
void lcd_show_choices_prompt_P(uint8_t selected, const char *first_choice, const char *second_choice, uint8_t second_col, const char *third_choice = nullptr);
|
||||
extern void lcd_show_fullscreen_message_and_wait_P(const char *msg);
|
||||
// 0: no, 1: yes, -1: timeouted
|
||||
extern int8_t lcd_show_yes_no_and_wait(bool allow_timeouting = true, bool default_yes = false);
|
||||
// 0: no, 1: yes, -1: timeouted
|
||||
extern int8_t lcd_show_fullscreen_message_yes_no_and_wait_P(const char *msg, bool allow_timeouting = true, bool default_yes = false);
|
||||
extern int8_t lcd_show_multiscreen_message_two_choices_and_wait_P(const char *msg, bool allow_timeouting, bool default_yes,
|
||||
const char *first_choice, const char *second_choice, uint8_t second_col = 7);
|
||||
extern int8_t lcd_show_multiscreen_message_yes_no_and_wait_P(const char *msg, bool allow_timeouting = true, bool default_yes = false);
|
||||
extern uint8_t lcd_show_yes_no_and_wait(bool allow_timeouting = true, uint8_t default_selection = LCD_MIDDLE_BUTTON_CHOICE);
|
||||
extern uint8_t lcd_show_fullscreen_message_yes_no_and_wait_P(const char *msg, bool allow_timeouting = true, uint8_t default_selection = LCD_MIDDLE_BUTTON_CHOICE);
|
||||
extern uint8_t lcd_show_multiscreen_message_with_choices_and_wait_P(
|
||||
const char * const msg, bool allow_timeouting, uint8_t default_selection,
|
||||
const char * const first_choice, const char * const second_choice, const char * const third_choice = nullptr,
|
||||
uint8_t second_col = 7);
|
||||
extern uint8_t lcd_show_multiscreen_message_yes_no_and_wait_P(const char *msg, bool allow_timeouting = true, uint8_t default_selection = LCD_MIDDLE_BUTTON_CHOICE);
|
||||
// Ask the user to move the Z axis up to the end stoppers and let
|
||||
// the user confirm that it has been done.
|
||||
|
||||
|
|
@ -123,6 +130,7 @@ enum class CustomMsg : uint_least8_t
|
|||
M0Wait, //!< M0/M1 Wait command working even from SD
|
||||
M117, //!< M117 Set the status line message on the LCD
|
||||
Resuming, //!< Resuming message
|
||||
MMUProgress, ///< MMU progress message
|
||||
};
|
||||
|
||||
extern CustomMsg custom_message_type;
|
||||
|
|
@ -142,8 +150,7 @@ extern bool FarmOrUserECool();
|
|||
#define SILENT_MODE_OFF SILENT_MODE_POWER
|
||||
#endif
|
||||
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
extern bool bMenuFSDetect;
|
||||
#if defined(FILAMENT_SENSOR) && (FILAMENT_SENSOR_TYPE == FSENSOR_IR_ANALOG)
|
||||
void printf_IRSensorAnalogBoardChange();
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
|
||||
|
|
@ -173,6 +180,7 @@ enum class FilamentAction : uint_least8_t
|
|||
MmuUnLoad,
|
||||
MmuEject,
|
||||
MmuCut,
|
||||
MmuLoadExtruder,
|
||||
Preheat,
|
||||
Lay1Cal,
|
||||
};
|
||||
|
|
@ -183,7 +191,7 @@ extern bool bFilamentAction;
|
|||
void mFilamentItem(uint16_t nTemp,uint16_t nTempBed);
|
||||
void mFilamentItemForce();
|
||||
void lcd_generic_preheat_menu();
|
||||
void unload_filament(bool automatic = false);
|
||||
void unload_filament(float unloadLength, bool automatic = false);
|
||||
|
||||
|
||||
void lcd_wait_for_heater();
|
||||
|
|
@ -207,7 +215,6 @@ void lcd_language();
|
|||
#endif
|
||||
|
||||
void lcd_wizard();
|
||||
bool lcd_autoDepleteEnabled();
|
||||
|
||||
//! @brief Wizard state
|
||||
enum class WizState : uint8_t
|
||||
|
|
@ -234,6 +241,8 @@ void lcd_wizard(WizState state);
|
|||
extern void lcd_experimental_toggle();
|
||||
extern void lcd_experimental_menu();
|
||||
|
||||
uint8_t lcdui_print_extruder(void);
|
||||
|
||||
#ifdef PINDA_TEMP_COMP
|
||||
extern void lcd_pinda_temp_compensation_toggle();
|
||||
#endif //PINDA_TEMP_COMP
|
||||
|
|
|
|||
|
|
@ -227,7 +227,6 @@ void update_current_firmware_version_to_eeprom()
|
|||
}
|
||||
|
||||
|
||||
//-//
|
||||
#define MSG_PRINT_CHECKING_FAILED_TIMEOUT 30
|
||||
|
||||
ClNozzleDiameter oNozzleDiameter=ClNozzleDiameter::_Diameter_400;
|
||||
|
|
@ -236,207 +235,192 @@ ClCheckModel oCheckModel=ClCheckModel::_None;
|
|||
ClCheckVersion oCheckVersion=ClCheckVersion::_None;
|
||||
ClCheckGcode oCheckGcode=ClCheckGcode::_None;
|
||||
|
||||
void fCheckModeInit()
|
||||
{
|
||||
oCheckMode=(ClCheckMode)eeprom_read_byte((uint8_t*)EEPROM_CHECK_MODE);
|
||||
if(oCheckMode==ClCheckMode::_Undef)
|
||||
{
|
||||
oCheckMode=ClCheckMode::_Warn;
|
||||
eeprom_update_byte((uint8_t*)EEPROM_CHECK_MODE,(uint8_t)oCheckMode);
|
||||
}
|
||||
if(farm_mode)
|
||||
{
|
||||
oCheckMode=ClCheckMode::_Strict;
|
||||
if(eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM)==EEPROM_EMPTY_VALUE16)
|
||||
eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,EEPROM_NOZZLE_DIAMETER_uM_DEFAULT);
|
||||
}
|
||||
oNozzleDiameter=(ClNozzleDiameter)eeprom_read_byte((uint8_t*)EEPROM_NOZZLE_DIAMETER);
|
||||
if((oNozzleDiameter==ClNozzleDiameter::_Diameter_Undef)&& !farm_mode)
|
||||
{
|
||||
oNozzleDiameter=ClNozzleDiameter::_Diameter_400;
|
||||
eeprom_update_byte((uint8_t*)EEPROM_NOZZLE_DIAMETER,(uint8_t)oNozzleDiameter);
|
||||
eeprom_update_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM,EEPROM_NOZZLE_DIAMETER_uM_DEFAULT);
|
||||
}
|
||||
oCheckModel=(ClCheckModel)eeprom_read_byte((uint8_t*)EEPROM_CHECK_MODEL);
|
||||
if(oCheckModel==ClCheckModel::_Undef)
|
||||
{
|
||||
oCheckModel=ClCheckModel::_Warn;
|
||||
eeprom_update_byte((uint8_t*)EEPROM_CHECK_MODEL,(uint8_t)oCheckModel);
|
||||
}
|
||||
oCheckVersion=(ClCheckVersion)eeprom_read_byte((uint8_t*)EEPROM_CHECK_VERSION);
|
||||
if(oCheckVersion==ClCheckVersion::_Undef)
|
||||
{
|
||||
oCheckVersion=ClCheckVersion::_Warn;
|
||||
eeprom_update_byte((uint8_t*)EEPROM_CHECK_VERSION,(uint8_t)oCheckVersion);
|
||||
}
|
||||
oCheckGcode=(ClCheckGcode)eeprom_read_byte((uint8_t*)EEPROM_CHECK_GCODE);
|
||||
if(oCheckGcode==ClCheckGcode::_Undef)
|
||||
{
|
||||
oCheckGcode=ClCheckGcode::_Warn;
|
||||
eeprom_update_byte((uint8_t*)EEPROM_CHECK_GCODE,(uint8_t)oCheckGcode);
|
||||
}
|
||||
void fCheckModeInit() {
|
||||
oCheckMode = (ClCheckMode)eeprom_read_byte((uint8_t *)EEPROM_CHECK_MODE);
|
||||
if (oCheckMode == ClCheckMode::_Undef) {
|
||||
oCheckMode = ClCheckMode::_Warn;
|
||||
eeprom_update_byte((uint8_t *)EEPROM_CHECK_MODE, (uint8_t)oCheckMode);
|
||||
}
|
||||
if (farm_mode) {
|
||||
oCheckMode = ClCheckMode::_Strict;
|
||||
if (eeprom_read_word((uint16_t *)EEPROM_NOZZLE_DIAMETER_uM) == EEPROM_EMPTY_VALUE16)
|
||||
eeprom_update_word((uint16_t *)EEPROM_NOZZLE_DIAMETER_uM, EEPROM_NOZZLE_DIAMETER_uM_DEFAULT);
|
||||
}
|
||||
oNozzleDiameter = (ClNozzleDiameter)eeprom_read_byte((uint8_t *)EEPROM_NOZZLE_DIAMETER);
|
||||
if ((oNozzleDiameter == ClNozzleDiameter::_Diameter_Undef) && !farm_mode) {
|
||||
oNozzleDiameter = ClNozzleDiameter::_Diameter_400;
|
||||
eeprom_update_byte((uint8_t *)EEPROM_NOZZLE_DIAMETER, (uint8_t)oNozzleDiameter);
|
||||
eeprom_update_word((uint16_t *)EEPROM_NOZZLE_DIAMETER_uM, EEPROM_NOZZLE_DIAMETER_uM_DEFAULT);
|
||||
}
|
||||
oCheckModel = (ClCheckModel)eeprom_read_byte((uint8_t *)EEPROM_CHECK_MODEL);
|
||||
if (oCheckModel == ClCheckModel::_Undef) {
|
||||
oCheckModel = ClCheckModel::_Warn;
|
||||
eeprom_update_byte((uint8_t *)EEPROM_CHECK_MODEL, (uint8_t)oCheckModel);
|
||||
}
|
||||
oCheckVersion = (ClCheckVersion)eeprom_read_byte((uint8_t *)EEPROM_CHECK_VERSION);
|
||||
if (oCheckVersion == ClCheckVersion::_Undef) {
|
||||
oCheckVersion = ClCheckVersion::_Warn;
|
||||
eeprom_update_byte((uint8_t *)EEPROM_CHECK_VERSION, (uint8_t)oCheckVersion);
|
||||
}
|
||||
oCheckGcode = (ClCheckGcode)eeprom_read_byte((uint8_t *)EEPROM_CHECK_GCODE);
|
||||
if (oCheckGcode == ClCheckGcode::_Undef) {
|
||||
oCheckGcode = ClCheckGcode::_Warn;
|
||||
eeprom_update_byte((uint8_t *)EEPROM_CHECK_GCODE, (uint8_t)oCheckGcode);
|
||||
}
|
||||
}
|
||||
|
||||
void nozzle_diameter_check(uint16_t nDiameter)
|
||||
{
|
||||
uint16_t nDiameter_um;
|
||||
void nozzle_diameter_check(uint16_t nDiameter) {
|
||||
uint16_t nDiameter_um;
|
||||
|
||||
if(oCheckMode==ClCheckMode::_None)
|
||||
return;
|
||||
nDiameter_um=eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM);
|
||||
if(nDiameter==nDiameter_um)
|
||||
return;
|
||||
//SERIAL_ECHO_START;
|
||||
//SERIAL_ECHOLNPGM("Printer nozzle diameter differs from the G-code ...");
|
||||
//SERIAL_ECHOPGM("actual : ");
|
||||
//SERIAL_ECHOLN((float)(nDiameter_um/1000.0));
|
||||
//SERIAL_ECHOPGM("expected: ");
|
||||
//SERIAL_ECHOLN((float)(nDiameter/1000.0));
|
||||
switch(oCheckMode)
|
||||
{
|
||||
case ClCheckMode::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer nozzle diameter differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_i("Printer nozzle diameter differs from the G-code. Continue?"));////MSG_NOZZLE_DIFFERS_CONTINUE c=20 r=5
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckMode::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(_i("Printer nozzle diameter differs from the G-code. Please check the value in settings. Print cancelled."));////MSG_NOZZLE_DIFFERS_CANCELLED c=20 r=9
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckMode::_None:
|
||||
case ClCheckMode::_Undef:
|
||||
break;
|
||||
}
|
||||
if(!farm_mode)
|
||||
{
|
||||
bSettings=false; // flag ('fake parameter') for 'lcd_hw_setup_menu()' function
|
||||
menu_submenu(lcd_hw_setup_menu);
|
||||
}
|
||||
if (oCheckMode == ClCheckMode::_None)
|
||||
return;
|
||||
nDiameter_um = eeprom_read_word((uint16_t *)EEPROM_NOZZLE_DIAMETER_uM);
|
||||
if (nDiameter == nDiameter_um)
|
||||
return;
|
||||
// SERIAL_ECHO_START;
|
||||
// SERIAL_ECHOLNPGM("Printer nozzle diameter differs from the G-code ...");
|
||||
// SERIAL_ECHOPGM("actual : ");
|
||||
// SERIAL_ECHOLN((float)(nDiameter_um/1000.0));
|
||||
// SERIAL_ECHOPGM("expected: ");
|
||||
// SERIAL_ECHOLN((float)(nDiameter/1000.0));
|
||||
switch (oCheckMode) {
|
||||
case ClCheckMode::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer nozzle diameter differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_i("Printer nozzle diameter differs from the G-code. Continue?")); ////MSG_NOZZLE_DIFFERS_CONTINUE c=20 r=5
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckMode::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(_i(
|
||||
"Printer nozzle diameter differs from the G-code. Please check the value in settings. Print cancelled.")); ////MSG_NOZZLE_DIFFERS_CANCELLED c=20 r=9
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckMode::_None:
|
||||
case ClCheckMode::_Undef:
|
||||
break;
|
||||
}
|
||||
if (!farm_mode) {
|
||||
bSettings = false; // flag ('fake parameter') for 'lcd_hw_setup_menu()' function
|
||||
menu_submenu(lcd_hw_setup_menu);
|
||||
}
|
||||
}
|
||||
|
||||
void printer_model_check(uint16_t nPrinterModel)
|
||||
{
|
||||
if(oCheckModel==ClCheckModel::_None)
|
||||
return;
|
||||
if(nPrinterModel==nPrinterType)
|
||||
return;
|
||||
//SERIAL_ECHO_START;
|
||||
//SERIAL_ECHOLNPGM("Printer model differs from the G-code ...");
|
||||
//SERIAL_ECHOPGM("actual : ");
|
||||
//SERIAL_ECHOLN(nPrinterType);
|
||||
//SERIAL_ECHOPGM("expected: ");
|
||||
//SERIAL_ECHOLN(nPrinterModel);
|
||||
switch(oCheckModel)
|
||||
{
|
||||
case ClCheckModel::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer model differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_T(MSG_GCODE_DIFF_PRINTER_CONTINUE));
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckModel::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(_T(MSG_GCODE_DIFF_PRINTER_CANCELLED));
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckModel::_None:
|
||||
case ClCheckModel::_Undef:
|
||||
break;
|
||||
}
|
||||
void printer_model_check(uint16_t nPrinterModel) {
|
||||
if (oCheckModel == ClCheckModel::_None)
|
||||
return;
|
||||
if (nPrinterModel == nPrinterType)
|
||||
return;
|
||||
// SERIAL_ECHO_START;
|
||||
// SERIAL_ECHOLNPGM("Printer model differs from the G-code ...");
|
||||
// SERIAL_ECHOPGM("actual : ");
|
||||
// SERIAL_ECHOLN(nPrinterType);
|
||||
// SERIAL_ECHOPGM("expected: ");
|
||||
// SERIAL_ECHOLN(nPrinterModel);
|
||||
switch (oCheckModel) {
|
||||
case ClCheckModel::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer model differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_T(MSG_GCODE_DIFF_PRINTER_CONTINUE));
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckModel::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(_T(MSG_GCODE_DIFF_PRINTER_CANCELLED));
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckModel::_None:
|
||||
case ClCheckModel::_Undef:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t mCompareValue(uint16_t nX,uint16_t nY)
|
||||
{
|
||||
if(nX>nY)
|
||||
return((uint8_t)ClCompareValue::_Greater);
|
||||
if(nX<nY)
|
||||
return((uint8_t)ClCompareValue::_Less);
|
||||
return((uint8_t)ClCompareValue::_Equal);
|
||||
uint8_t mCompareValue(uint16_t nX, uint16_t nY) {
|
||||
if (nX > nY)
|
||||
return ((uint8_t)ClCompareValue::_Greater);
|
||||
if (nX < nY)
|
||||
return ((uint8_t)ClCompareValue::_Less);
|
||||
return ((uint8_t)ClCompareValue::_Equal);
|
||||
}
|
||||
|
||||
void fw_version_check(const char *pVersion)
|
||||
{
|
||||
uint16_t aVersion[4];
|
||||
uint8_t nCompareValueResult;
|
||||
void fw_version_check(const char *pVersion) {
|
||||
uint16_t aVersion[4];
|
||||
uint8_t nCompareValueResult;
|
||||
|
||||
if(oCheckVersion==ClCheckVersion::_None)
|
||||
return;
|
||||
parse_version(pVersion,aVersion);
|
||||
nCompareValueResult=mCompareValue(aVersion[0],eeprom_read_word((uint16_t*)EEPROM_FIRMWARE_VERSION_MAJOR))<<6;
|
||||
nCompareValueResult+=mCompareValue(aVersion[1],eeprom_read_word((uint16_t*)EEPROM_FIRMWARE_VERSION_MINOR))<<4;
|
||||
nCompareValueResult+=mCompareValue(aVersion[2],eeprom_read_word((uint16_t*)EEPROM_FIRMWARE_VERSION_REVISION))<<2;
|
||||
nCompareValueResult+=mCompareValue(aVersion[3],eeprom_read_word((uint16_t*)EEPROM_FIRMWARE_VERSION_FLAVOR));
|
||||
if(nCompareValueResult==COMPARE_VALUE_EQUAL)
|
||||
return;
|
||||
if((nCompareValueResult<COMPARE_VALUE_EQUAL)&&oCheckVersion==ClCheckVersion::_Warn)
|
||||
return;
|
||||
//SERIAL_ECHO_START;
|
||||
//SERIAL_ECHOLNPGM("Printer FW version differs from the G-code ...");
|
||||
//SERIAL_ECHOPGM("actual : ");
|
||||
//SERIAL_ECHOLN(FW_VERSION);
|
||||
//SERIAL_ECHOPGM("expected: ");
|
||||
//SERIAL_ECHOLN(pVersion);
|
||||
switch(oCheckVersion)
|
||||
{
|
||||
case ClCheckVersion::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer FW version differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_i("G-code sliced for a newer firmware. Continue?"));////MSG_GCODE_NEWER_FIRMWARE_CONTINUE c=20 r=5
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckVersion::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(_i("G-code sliced for a newer firmware. Please update the firmware. Print cancelled."));////MSG_GCODE_NEWER_FIRMWARE_CANCELLED c=20 r=8
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckVersion::_None:
|
||||
case ClCheckVersion::_Undef:
|
||||
break;
|
||||
}
|
||||
if (oCheckVersion == ClCheckVersion::_None)
|
||||
return;
|
||||
parse_version(pVersion, aVersion);
|
||||
nCompareValueResult = mCompareValue(aVersion[0], eeprom_read_word((uint16_t *)EEPROM_FIRMWARE_VERSION_MAJOR)) << 6;
|
||||
nCompareValueResult += mCompareValue(aVersion[1], eeprom_read_word((uint16_t *)EEPROM_FIRMWARE_VERSION_MINOR)) << 4;
|
||||
nCompareValueResult += mCompareValue(aVersion[2], eeprom_read_word((uint16_t *)EEPROM_FIRMWARE_VERSION_REVISION)) << 2;
|
||||
nCompareValueResult += mCompareValue(aVersion[3], eeprom_read_word((uint16_t *)EEPROM_FIRMWARE_VERSION_FLAVOR));
|
||||
if (nCompareValueResult == COMPARE_VALUE_EQUAL)
|
||||
return;
|
||||
if ((nCompareValueResult < COMPARE_VALUE_EQUAL) && oCheckVersion == ClCheckVersion::_Warn)
|
||||
return;
|
||||
// SERIAL_ECHO_START;
|
||||
// SERIAL_ECHOLNPGM("Printer FW version differs from the G-code ...");
|
||||
// SERIAL_ECHOPGM("actual : ");
|
||||
// SERIAL_ECHOLN(FW_VERSION);
|
||||
// SERIAL_ECHOPGM("expected: ");
|
||||
// SERIAL_ECHOLN(pVersion);
|
||||
switch (oCheckVersion) {
|
||||
case ClCheckVersion::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer FW version differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_i("G-code sliced for a newer firmware. Continue?")); ////MSG_GCODE_NEWER_FIRMWARE_CONTINUE c=20 r=5
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckVersion::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(
|
||||
_i("G-code sliced for a newer firmware. Please update the firmware. Print cancelled.")); ////MSG_GCODE_NEWER_FIRMWARE_CANCELLED c=20 r=8
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckVersion::_None:
|
||||
case ClCheckVersion::_Undef:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void gcode_level_check(uint16_t nGcodeLevel)
|
||||
{
|
||||
if(oCheckGcode==ClCheckGcode::_None)
|
||||
return;
|
||||
if(nGcodeLevel==(uint16_t)GCODE_LEVEL)
|
||||
return;
|
||||
if((nGcodeLevel<(uint16_t)GCODE_LEVEL)&&(oCheckGcode==ClCheckGcode::_Warn))
|
||||
return;
|
||||
//SERIAL_ECHO_START;
|
||||
//SERIAL_ECHOLNPGM("Printer G-code level differs from the G-code ...");
|
||||
//SERIAL_ECHOPGM("actual : ");
|
||||
//SERIAL_ECHOLN(GCODE_LEVEL);
|
||||
//SERIAL_ECHOPGM("expected: ");
|
||||
//SERIAL_ECHOLN(nGcodeLevel);
|
||||
switch(oCheckGcode)
|
||||
{
|
||||
case ClCheckGcode::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer G-code level differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_i("G-code sliced for a different level. Continue?"));////MSG_GCODE_DIFF_CONTINUE c=20 r=4
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckGcode::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(_i("G-code sliced for a different level. Please re-slice the model again. Print cancelled."));////MSG_GCODE_DIFF_CANCELLED c=20 r=7
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckGcode::_None:
|
||||
case ClCheckGcode::_Undef:
|
||||
break;
|
||||
}
|
||||
void gcode_level_check(uint16_t nGcodeLevel) {
|
||||
if (oCheckGcode == ClCheckGcode::_None)
|
||||
return;
|
||||
if (nGcodeLevel == (uint16_t)GCODE_LEVEL)
|
||||
return;
|
||||
if ((nGcodeLevel < (uint16_t)GCODE_LEVEL) && (oCheckGcode == ClCheckGcode::_Warn))
|
||||
return;
|
||||
// SERIAL_ECHO_START;
|
||||
// SERIAL_ECHOLNPGM("Printer G-code level differs from the G-code ...");
|
||||
// SERIAL_ECHOPGM("actual : ");
|
||||
// SERIAL_ECHOLN(GCODE_LEVEL);
|
||||
// SERIAL_ECHOPGM("expected: ");
|
||||
// SERIAL_ECHOLN(nGcodeLevel);
|
||||
switch (oCheckGcode) {
|
||||
case ClCheckGcode::_Warn:
|
||||
// lcd_show_fullscreen_message_and_wait_P(_i("Printer G-code level differs from the G-code. Continue?"));
|
||||
lcd_display_message_fullscreen_P(_i("G-code sliced for a different level. Continue?")); ////MSG_GCODE_DIFF_CONTINUE c=20 r=4
|
||||
lcd_wait_for_click_delay(MSG_PRINT_CHECKING_FAILED_TIMEOUT);
|
||||
//???custom_message_type=CUSTOM_MSG_TYPE_STATUS; // display / status-line recovery
|
||||
lcd_update_enable(true); // display / status-line recovery
|
||||
break;
|
||||
case ClCheckGcode::_Strict:
|
||||
lcd_show_fullscreen_message_and_wait_P(
|
||||
_i("G-code sliced for a different level. Please re-slice the model again. Print cancelled.")); ////MSG_GCODE_DIFF_CANCELLED c=20 r=7
|
||||
lcd_print_stop();
|
||||
break;
|
||||
case ClCheckGcode::_None:
|
||||
case ClCheckGcode::_Undef:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
//-// -> cmdqueue ???
|
||||
#define PRINTER_NAME_LENGTH ((sizeof(PRINTER_MMU_NAME)>sizeof(PRINTER_NAME))?(sizeof(PRINTER_MMU_NAME)-1):(sizeof(PRINTER_NAME)-1))
|
||||
#define PRINTER_NAME_LENGTH ((sizeof(PRINTER_MMU_NAME) > sizeof(PRINTER_NAME)) ? (sizeof(PRINTER_MMU_NAME) - 1) : (sizeof(PRINTER_NAME) - 1))
|
||||
#define GCODE_DELIMITER '"'
|
||||
#define ELLIPSIS "..."
|
||||
|
||||
char* code_string(const char* pStr,size_t* nLength)
|
||||
{
|
||||
char *code_string(const char *pStr, size_t *nLength) {
|
||||
char* pStrBegin;
|
||||
char* pStrEnd;
|
||||
|
||||
|
|
@ -451,13 +435,12 @@ if(!pStrEnd)
|
|||
return pStrBegin;
|
||||
}
|
||||
|
||||
void printer_smodel_check(const char* pStrPos)
|
||||
{
|
||||
void printer_smodel_check(const char *pStrPos) {
|
||||
char* pResult;
|
||||
size_t nLength,nPrinterNameLength;
|
||||
|
||||
nPrinterNameLength = strlen_P(sPrinterName);
|
||||
pResult = code_string(pStrPos,&nLength);
|
||||
pResult=code_string(pStrPos,&nLength);
|
||||
|
||||
if(pResult != NULL && nLength == nPrinterNameLength) {
|
||||
// Only compare them if the lengths match
|
||||
|
|
@ -483,20 +466,16 @@ lcd_update_enable(true); // display / status-line recovery
|
|||
}
|
||||
}
|
||||
|
||||
void fSetMmuMode(bool bMMu)
|
||||
{
|
||||
if(bMMu)
|
||||
{
|
||||
nPrinterType=pgm_read_word(&_nPrinterMmuType);
|
||||
sPrinterName=_sPrinterMmuName;
|
||||
}
|
||||
else {
|
||||
nPrinterType=pgm_read_word(&_nPrinterType);
|
||||
sPrinterName=_sPrinterName;
|
||||
}
|
||||
void fSetMmuMode(bool bMMu) {
|
||||
if (bMMu) {
|
||||
nPrinterType = pgm_read_word(&_nPrinterMmuType);
|
||||
sPrinterName = _sPrinterMmuName;
|
||||
} else {
|
||||
nPrinterType = pgm_read_word(&_nPrinterType);
|
||||
sPrinterName = _sPrinterName;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ip4_to_str(char* dest, uint8_t* IP)
|
||||
{
|
||||
sprintf_P(dest, PSTR("%u.%u.%u.%u"), IP[0], IP[1], IP[2], IP[3]);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#ifndef UTIL_H
|
||||
#define UTIL_H
|
||||
#pragma once
|
||||
#include <stdint.h>
|
||||
|
||||
extern const char* FW_VERSION_STR_P();
|
||||
|
||||
|
|
@ -112,5 +112,3 @@ void fSetMmuMode(bool bMMu);
|
|||
|
||||
#define IP4_STR_SIZE 16
|
||||
extern void ip4_to_str(char* dest, uint8_t* IP);
|
||||
|
||||
#endif /* UTIL_H */
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
// Printer revision
|
||||
#define PRINTER_TYPE PRINTER_MK25
|
||||
#define PRINTER_NAME PRINTER_MK25_NAME
|
||||
#define PRINTER_NAME_ALTERNATE PRINTER_MK25S_NAME //the other similar printer to this.
|
||||
#define PRINTER_MMU_TYPE PRINTER_MK25_MMU2
|
||||
#define PRINTER_MMU_NAME PRINTER_MK25_MMU2_NAME
|
||||
#define FILAMENT_SIZE "1_75mm_MK25"
|
||||
|
|
@ -125,7 +126,7 @@
|
|||
|
||||
// Filament sensor
|
||||
#define FILAMENT_SENSOR
|
||||
#define PAT9125
|
||||
#define FILAMENT_SENSOR_TYPE FSENSOR_PAT9125
|
||||
|
||||
#define DEBUG_DCODE2
|
||||
#define DEBUG_DCODE3
|
||||
|
|
@ -135,7 +136,7 @@
|
|||
//#define _NO_ASM
|
||||
#define DEBUG_DCODES //D codes
|
||||
#define DEBUG_STACK_MONITOR //Stack monitor in stepper ISR
|
||||
//#define DEBUG_FSENSOR_LOG //Reports fsensor status to serial
|
||||
//#define DEBUG_CRASHDET_COUNTERS //Display crash-detection counters on LCD
|
||||
//#define DEBUG_RESUME_PRINT //Resume/save print debug enable
|
||||
//#define DEBUG_UVLO_AUTOMATIC_RECOVER // Power panic automatic recovery debug output
|
||||
//#define DEBUG_DISABLE_XMINLIMIT //x min limit ignored
|
||||
|
|
@ -158,8 +159,6 @@
|
|||
//#define CMD_DIAGNOSTICS //Show cmd queue length on printer display
|
||||
#endif /* DEBUG_BUILD */
|
||||
|
||||
//#define FSENSOR_QUALITY
|
||||
|
||||
|
||||
/*------------------------------------
|
||||
EXTRUDER SETTINGS
|
||||
|
|
@ -509,6 +508,11 @@
|
|||
|
||||
#define MMU_IDLER_SENSOR_ATTEMPTS_NR 21 //max. number of attempts to load filament if first load failed; value for max bowden length and case when loading fails right at the beginning
|
||||
|
||||
// MMU Error pause position
|
||||
#define MMU_ERR_X_PAUSE_POS 125
|
||||
#define MMU_ERR_Y_PAUSE_POS 0
|
||||
#define MMU_ERR_Z_PAUSE_LIFT 20
|
||||
|
||||
// Default Arc Interpolation Settings (Now configurable via M214)
|
||||
#define DEFAULT_N_ARC_CORRECTION 25 // Number of interpolated segments between corrections.
|
||||
/* A value of 1 or less for N_ARC_CORRECTION will trigger the use of Sin and Cos for every arc, which will improve accuracy at the
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
// Printer revision
|
||||
#define PRINTER_TYPE PRINTER_MK25
|
||||
#define PRINTER_NAME PRINTER_MK25_NAME
|
||||
#define PRINTER_NAME_ALTERNATE PRINTER_MK25S_NAME //the other similar printer to this.
|
||||
#define PRINTER_MMU_TYPE PRINTER_MK25_MMU2
|
||||
#define PRINTER_MMU_NAME PRINTER_MK25_MMU2_NAME
|
||||
#define FILAMENT_SIZE "1_75mm_MK25"
|
||||
|
|
@ -126,7 +127,7 @@
|
|||
|
||||
// Filament sensor
|
||||
#define FILAMENT_SENSOR
|
||||
#define PAT9125
|
||||
#define FILAMENT_SENSOR_TYPE FSENSOR_PAT9125
|
||||
|
||||
#define DEBUG_DCODE2
|
||||
#define DEBUG_DCODE3
|
||||
|
|
@ -136,7 +137,7 @@
|
|||
//#define _NO_ASM
|
||||
#define DEBUG_DCODES //D codes
|
||||
#define DEBUG_STACK_MONITOR //Stack monitor in stepper ISR
|
||||
//#define DEBUG_FSENSOR_LOG //Reports fsensor status to serial
|
||||
//#define DEBUG_CRASHDET_COUNTERS //Display crash-detection counters on LCD
|
||||
//#define DEBUG_RESUME_PRINT //Resume/save print debug enable
|
||||
//#define DEBUG_UVLO_AUTOMATIC_RECOVER // Power panic automatic recovery debug output
|
||||
//#define DEBUG_DISABLE_XMINLIMIT //x min limit ignored
|
||||
|
|
@ -159,8 +160,6 @@
|
|||
//#define CMD_DIAGNOSTICS //Show cmd queue length on printer display
|
||||
#endif /* DEBUG_BUILD */
|
||||
|
||||
//#define FSENSOR_QUALITY
|
||||
|
||||
|
||||
/*------------------------------------
|
||||
EXTRUDER SETTINGS
|
||||
|
|
@ -510,6 +509,11 @@
|
|||
|
||||
#define MMU_IDLER_SENSOR_ATTEMPTS_NR 21 //max. number of attempts to load filament if first load failed; value for max bowden length and case when loading fails right at the beginning
|
||||
|
||||
// MMU Error pause position
|
||||
#define MMU_ERR_X_PAUSE_POS 125
|
||||
#define MMU_ERR_Y_PAUSE_POS 0
|
||||
#define MMU_ERR_Z_PAUSE_LIFT 20
|
||||
|
||||
//#define HEATBED_ANALYSIS //for meash bed leveling and heatbed analysis D-codes D80 and D81
|
||||
//#define MICROMETER_LOGGING //related to D-codes D80 and D81, currently works on MK2.5 only (MK3 board pin definitions missing)
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
// Printer revision
|
||||
#define PRINTER_TYPE PRINTER_MK25S
|
||||
#define PRINTER_NAME PRINTER_MK25S_NAME
|
||||
#define PRINTER_NAME_ALTERNATE PRINTER_MK25_NAME //the other similar printer to this.
|
||||
#define PRINTER_MMU_TYPE PRINTER_MK25S_MMU2
|
||||
#define PRINTER_MMU_NAME PRINTER_MK25S_MMU2_NAME
|
||||
#define FILAMENT_SIZE "1_75mm_MK25S"
|
||||
|
|
@ -125,7 +126,7 @@
|
|||
|
||||
// Filament sensor
|
||||
#define FILAMENT_SENSOR
|
||||
#define IR_SENSOR
|
||||
#define FILAMENT_SENSOR_TYPE FSENSOR_IR
|
||||
|
||||
#define DEBUG_DCODE2
|
||||
#define DEBUG_DCODE3
|
||||
|
|
@ -135,7 +136,7 @@
|
|||
//#define _NO_ASM
|
||||
#define DEBUG_DCODES //D codes
|
||||
#define DEBUG_STACK_MONITOR //Stack monitor in stepper ISR
|
||||
//#define DEBUG_FSENSOR_LOG //Reports fsensor status to serial
|
||||
//#define DEBUG_CRASHDET_COUNTERS //Display crash-detection counters on LCD
|
||||
//#define DEBUG_RESUME_PRINT //Resume/save print debug enable
|
||||
//#define DEBUG_UVLO_AUTOMATIC_RECOVER // Power panic automatic recovery debug output
|
||||
//#define DEBUG_DISABLE_XMINLIMIT //x min limit ignored
|
||||
|
|
@ -158,8 +159,6 @@
|
|||
//#define CMD_DIAGNOSTICS //Show cmd queue length on printer display
|
||||
#endif /* DEBUG_BUILD */
|
||||
|
||||
//#define FSENSOR_QUALITY
|
||||
|
||||
|
||||
/*------------------------------------
|
||||
EXTRUDER SETTINGS
|
||||
|
|
@ -516,6 +515,11 @@
|
|||
//#define MMU_ALWAYS_CUT
|
||||
#define MMU_IDLER_SENSOR_ATTEMPTS_NR 21 //max. number of attempts to load filament if first load failed; value for max bowden length and case when loading fails right at the beginning
|
||||
|
||||
// MMU Error pause position
|
||||
#define MMU_ERR_X_PAUSE_POS 125
|
||||
#define MMU_ERR_Y_PAUSE_POS 0
|
||||
#define MMU_ERR_Z_PAUSE_LIFT 20
|
||||
|
||||
// Default Arc Interpolation Settings (Now configurable via M214)
|
||||
#define DEFAULT_N_ARC_CORRECTION 25 // Number of interpolated segments between corrections.
|
||||
/* A value of 1 or less for N_ARC_CORRECTION will trigger the use of Sin and Cos for every arc, which will improve accuracy at the
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
// Printer revision
|
||||
#define PRINTER_TYPE PRINTER_MK25S
|
||||
#define PRINTER_NAME PRINTER_MK25S_NAME
|
||||
#define PRINTER_NAME_ALTERNATE PRINTER_MK25_NAME //the other similar printer to this.
|
||||
#define PRINTER_MMU_TYPE PRINTER_MK25S_MMU2
|
||||
#define PRINTER_MMU_NAME PRINTER_MK25S_MMU2_NAME
|
||||
#define FILAMENT_SIZE "1_75mm_MK25S"
|
||||
|
|
@ -126,7 +127,7 @@
|
|||
|
||||
// Filament sensor
|
||||
#define FILAMENT_SENSOR
|
||||
#define IR_SENSOR
|
||||
#define FILAMENT_SENSOR_TYPE FSENSOR_IR
|
||||
|
||||
#define DEBUG_DCODE2
|
||||
#define DEBUG_DCODE3
|
||||
|
|
@ -136,7 +137,7 @@
|
|||
//#define _NO_ASM
|
||||
#define DEBUG_DCODES //D codes
|
||||
#define DEBUG_STACK_MONITOR //Stack monitor in stepper ISR
|
||||
//#define DEBUG_FSENSOR_LOG //Reports fsensor status to serial
|
||||
//#define DEBUG_CRASHDET_COUNTERS //Display crash-detection counters on LCD
|
||||
//#define DEBUG_RESUME_PRINT //Resume/save print debug enable
|
||||
//#define DEBUG_UVLO_AUTOMATIC_RECOVER // Power panic automatic recovery debug output
|
||||
//#define DEBUG_DISABLE_XMINLIMIT //x min limit ignored
|
||||
|
|
@ -159,8 +160,6 @@
|
|||
//#define CMD_DIAGNOSTICS //Show cmd queue length on printer display
|
||||
#endif /* DEBUG_BUILD */
|
||||
|
||||
//#define FSENSOR_QUALITY
|
||||
|
||||
|
||||
/*------------------------------------
|
||||
EXTRUDER SETTINGS
|
||||
|
|
@ -517,6 +516,11 @@
|
|||
//#define MMU_ALWAYS_CUT
|
||||
#define MMU_IDLER_SENSOR_ATTEMPTS_NR 21 //max. number of attempts to load filament if first load failed; value for max bowden length and case when loading fails right at the beginning
|
||||
|
||||
// MMU Error pause position
|
||||
#define MMU_ERR_X_PAUSE_POS 125
|
||||
#define MMU_ERR_Y_PAUSE_POS 0
|
||||
#define MMU_ERR_Z_PAUSE_LIFT 20
|
||||
|
||||
// Default Arc Interpolation Settings (Now configurable via M214)
|
||||
#define DEFAULT_N_ARC_CORRECTION 25 // Number of interpolated segments between corrections.
|
||||
/* A value of 1 or less for N_ARC_CORRECTION will trigger the use of Sin and Cos for every arc, which will improve accuracy at the
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
// Printer revision
|
||||
#define PRINTER_TYPE PRINTER_MK3
|
||||
#define PRINTER_NAME PRINTER_MK3_NAME
|
||||
#define PRINTER_NAME_ALTERNATE PRINTER_MK3S_NAME //the other similar printer to this.
|
||||
#define PRINTER_MMU_TYPE PRINTER_MK3_MMU2
|
||||
#define PRINTER_MMU_NAME PRINTER_MK3_MMU2_NAME
|
||||
#define FILAMENT_SIZE "1_75mm_MK3"
|
||||
|
|
@ -150,7 +151,8 @@
|
|||
|
||||
// Filament sensor
|
||||
#define FILAMENT_SENSOR
|
||||
#define PAT9125
|
||||
#define FILAMENT_SENSOR_TYPE FSENSOR_PAT9125
|
||||
#define FSENSOR_PROBING
|
||||
|
||||
// Backlash -
|
||||
//#define BACKLASH_X
|
||||
|
|
@ -177,7 +179,7 @@
|
|||
//#define _NO_ASM
|
||||
#define DEBUG_DCODES //D codes
|
||||
#define DEBUG_STACK_MONITOR //Stack monitor in stepper ISR
|
||||
//#define DEBUG_FSENSOR_LOG //Reports fsensor status to serial
|
||||
//#define DEBUG_CRASHDET_COUNTERS //Display crash-detection counters on LCD
|
||||
//#define DEBUG_RESUME_PRINT //Resume/save print debug enable
|
||||
//#define DEBUG_UVLO_AUTOMATIC_RECOVER // Power panic automatic recovery debug output
|
||||
//#define DEBUG_DISABLE_XMINLIMIT //x min limit ignored
|
||||
|
|
@ -202,8 +204,6 @@
|
|||
#define CMD_DIAGNOSTICS //Show cmd queue length on printer display
|
||||
#endif /* DEBUG_BUILD */
|
||||
|
||||
//#define FSENSOR_QUALITY
|
||||
|
||||
|
||||
#define LINEARITY_CORRECTION
|
||||
#define TMC2130_LINEARITY_CORRECTION
|
||||
|
|
@ -681,6 +681,11 @@
|
|||
#define MMU_HAS_CUTTER
|
||||
#define MMU_IDLER_SENSOR_ATTEMPTS_NR 21 //max. number of attempts to load filament if first load failed; value for max bowden length and case when loading fails right at the beginning
|
||||
|
||||
// MMU Error pause position
|
||||
#define MMU_ERR_X_PAUSE_POS 125
|
||||
#define MMU_ERR_Y_PAUSE_POS 0
|
||||
#define MMU_ERR_Z_PAUSE_LIFT 20
|
||||
|
||||
// Default Arc Interpolation Settings (Now configurable via M214)
|
||||
#define DEFAULT_N_ARC_CORRECTION 25 // Number of interpolated segments between corrections.
|
||||
/* A value of 1 or less for N_ARC_CORRECTION will trigger the use of Sin and Cos for every arc, which will improve accuracy at the
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
// Printer revision
|
||||
#define PRINTER_TYPE PRINTER_MK3S
|
||||
#define PRINTER_NAME PRINTER_MK3S_NAME
|
||||
#define PRINTER_NAME_ALTERNATE PRINTER_MK3_NAME //the other similar printer to this.
|
||||
#define PRINTER_MMU_TYPE PRINTER_MK3S_MMU2
|
||||
#define PRINTER_MMU_NAME PRINTER_MK3S_MMU2_NAME
|
||||
#define FILAMENT_SIZE "1_75mm_MK3S"
|
||||
|
|
@ -152,7 +153,8 @@
|
|||
|
||||
// Filament sensor
|
||||
#define FILAMENT_SENSOR
|
||||
#define IR_SENSOR
|
||||
#define FILAMENT_SENSOR_TYPE FSENSOR_IR_ANALOG
|
||||
#define FSENSOR_PROBING
|
||||
|
||||
// Backlash -
|
||||
//#define BACKLASH_X
|
||||
|
|
@ -179,7 +181,7 @@
|
|||
//#define _NO_ASM
|
||||
#define DEBUG_DCODES //D codes
|
||||
#define DEBUG_STACK_MONITOR //Stack monitor in stepper ISR
|
||||
//#define DEBUG_FSENSOR_LOG //Reports fsensor status to serial
|
||||
//#define DEBUG_CRASHDET_COUNTERS //Display crash-detection counters on LCD
|
||||
//#define DEBUG_RESUME_PRINT //Resume/save print debug enable
|
||||
//#define DEBUG_UVLO_AUTOMATIC_RECOVER // Power panic automatic recovery debug output
|
||||
//#define DEBUG_DISABLE_XMINLIMIT //x min limit ignored
|
||||
|
|
@ -204,8 +206,6 @@
|
|||
#define CMD_DIAGNOSTICS //Show cmd queue length on printer display
|
||||
#endif /* DEBUG_BUILD */
|
||||
|
||||
//#define FSENSOR_QUALITY
|
||||
|
||||
|
||||
#define LINEARITY_CORRECTION
|
||||
#define TMC2130_LINEARITY_CORRECTION
|
||||
|
|
@ -693,6 +693,11 @@
|
|||
//#define MMU_ALWAYS_CUT
|
||||
#define MMU_IDLER_SENSOR_ATTEMPTS_NR 21 //max. number of attempts to load filament if first load failed; value for max bowden length and case when loading fails right at the beginning
|
||||
|
||||
// MMU Error pause position
|
||||
#define MMU_ERR_X_PAUSE_POS 125
|
||||
#define MMU_ERR_Y_PAUSE_POS 0
|
||||
#define MMU_ERR_Z_PAUSE_LIFT 20
|
||||
|
||||
// Default Arc Interpolation Settings (Now configurable via M214)
|
||||
#define DEFAULT_N_ARC_CORRECTION 25 // Number of interpolated segments between corrections.
|
||||
/* A value of 1 or less for N_ARC_CORRECTION will trigger the use of Sin and Cos for every arc, which will improve accuracy at the
|
||||
|
|
|
|||
|
|
@ -180,14 +180,6 @@ CHANGE FILAMENT SETTINGS
|
|||
ADDITIONAL FEATURES SETTINGS
|
||||
*------------------------------------*/
|
||||
|
||||
// Define Prusa filament runout sensor
|
||||
//#define FILAMENT_RUNOUT_SUPPORT
|
||||
|
||||
#ifdef FILAMENT_RUNOUT_SUPPORT
|
||||
#define FILAMENT_RUNOUT_SENSOR 1
|
||||
#define FILAMENT_RUNOUT_SCRIPT "M600"
|
||||
#endif
|
||||
|
||||
// temperature runaway
|
||||
#define TEMP_RUNAWAY_BED_HYSTERESIS 5
|
||||
#define TEMP_RUNAWAY_BED_TIMEOUT 360
|
||||
|
|
|
|||
|
|
@ -179,14 +179,6 @@ CHANGE FILAMENT SETTINGS
|
|||
ADDITIONAL FEATURES SETTINGS
|
||||
*------------------------------------*/
|
||||
|
||||
// Define Prusa filament runout sensor
|
||||
//#define FILAMENT_RUNOUT_SUPPORT
|
||||
|
||||
#ifdef FILAMENT_RUNOUT_SUPPORT
|
||||
#define FILAMENT_RUNOUT_SENSOR 1
|
||||
#define FILAMENT_RUNOUT_SCRIPT "M600"
|
||||
#endif
|
||||
|
||||
// temperature runaway
|
||||
#define TEMP_RUNAWAY_BED_HYSTERESIS 5
|
||||
#define TEMP_RUNAWAY_BED_TIMEOUT 360
|
||||
|
|
|
|||
|
|
@ -1,146 +0,0 @@
|
|||
/**
|
||||
* @file
|
||||
* @author Marek Bel
|
||||
*/
|
||||
|
||||
#include "catch.hpp"
|
||||
|
||||
#include "../Firmware/AutoDeplete.h"
|
||||
|
||||
TEST_CASE( "AutoDeplete test.", "[AutoDeplete]" )
|
||||
{
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 1);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
|
||||
ad_markDepleted(1);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 2);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markDepleted(3);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 2);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 4);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markDepleted(4);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 2);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 0);
|
||||
CHECK(ad_getAlternative(4) == 0);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markDepleted(4);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 2);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 0);
|
||||
CHECK(ad_getAlternative(4) == 0);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markDepleted(0);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 2);
|
||||
CHECK(ad_getAlternative(1) == 2);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 2);
|
||||
CHECK(ad_getAlternative(4) == 2);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markDepleted(2);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 1);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == true);
|
||||
|
||||
ad_markDepleted(2);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 1);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == true);
|
||||
|
||||
ad_markLoaded(4);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 4);
|
||||
CHECK(ad_getAlternative(1) == 4);
|
||||
CHECK(ad_getAlternative(2) == 4);
|
||||
CHECK(ad_getAlternative(3) == 4);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markLoaded(0);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 4);
|
||||
CHECK(ad_getAlternative(2) == 4);
|
||||
CHECK(ad_getAlternative(3) == 4);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markLoaded(3);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 3);
|
||||
CHECK(ad_getAlternative(2) == 3);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markLoaded(3);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 3);
|
||||
CHECK(ad_getAlternative(2) == 3);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markLoaded(2);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 2);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markLoaded(1);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 1);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
ad_markLoaded(1);
|
||||
|
||||
CHECK(ad_getAlternative(0) == 0);
|
||||
CHECK(ad_getAlternative(1) == 1);
|
||||
CHECK(ad_getAlternative(2) == 2);
|
||||
CHECK(ad_getAlternative(3) == 3);
|
||||
CHECK(ad_getAlternative(4) == 4);
|
||||
CHECK(ad_allDepleted() == false);
|
||||
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ for lang in $LANGUAGES; do
|
|||
binfile="$TMPDIR/lang_$lang.bin"
|
||||
|
||||
color 4 "compiling language \"$lang\" from $pofile" >&2
|
||||
./lang-check.py --map "$MAP" "$pofile"
|
||||
./lang-check.py --map "$MAP" "$pofile" --no-suggest
|
||||
if [ "$?" != 0 ]; then
|
||||
color 1 "$pofile: NG! - translation contains warnings or errors" >&2
|
||||
fi
|
||||
|
|
@ -61,8 +61,9 @@ for lang in $LANGUAGES; do
|
|||
./lang-build.py "$MAP" "$pofile" "$binfile"
|
||||
|
||||
# ensure each catalog fits the reserved size
|
||||
if [[ $(stat -c '%s' "$binfile") -gt $maxsize ]]; then
|
||||
color 1 "$pofile: NG! - language data exceeds $maxsize bytes" >&2
|
||||
currentsize=$(stat -c '%s' "$binfile")
|
||||
if [[ $currentsize -gt $maxsize ]]; then
|
||||
color 1 "$pofile: NG! - language data exceeds $maxsize bytes, it uses $currentsize" >&2
|
||||
finish 1
|
||||
fi
|
||||
done
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ def ign_char_first(c):
|
|||
def ign_char_last(c):
|
||||
return c.isalnum() or c in {'.', "'"}
|
||||
|
||||
def check_translation(entry, msgids, is_pot, no_warning, no_suggest, warn_empty, warn_same, information):
|
||||
def check_translation(entry, msgids, is_pot, no_warning, no_suggest, warn_empty, warn_same, information, shorter):
|
||||
"""Check strings to display definition."""
|
||||
|
||||
# do not check obsolete/deleted entriees
|
||||
|
|
@ -221,7 +221,7 @@ def check_translation(entry, msgids, is_pot, no_warning, no_suggest, warn_empty,
|
|||
print_wrapped(wrapped_source, rows, cols)
|
||||
print()
|
||||
|
||||
# Check for translation lenght
|
||||
# Check for translation length too long
|
||||
if (rows_count_translation > rows) or (rows == 1 and len(translation) > cols):
|
||||
errors += 1
|
||||
print(red('[E]: Text is longer than definition on line %d: cols=%d rows=%d (rows diff=%d)'
|
||||
|
|
@ -230,6 +230,14 @@ def check_translation(entry, msgids, is_pot, no_warning, no_suggest, warn_empty,
|
|||
wrapped_source, wrapped_translation,
|
||||
rows, cols)
|
||||
|
||||
# Check for translation length shorter
|
||||
if shorter and (rows_count_translation < rows-1):
|
||||
print(yellow('[S]: Text is shorter than definition on line %d: cols=%d rows=%d (rows diff=%d)'
|
||||
% (line, cols, rows, rows_count_translation-rows)))
|
||||
print_source_translation(source, translation,
|
||||
wrapped_source, wrapped_translation,
|
||||
rows, cols)
|
||||
|
||||
# Different count of % sequences
|
||||
if source.count('%') != translation.count('%') and len(translation) > 0:
|
||||
errors += 1
|
||||
|
|
@ -313,6 +321,9 @@ def main():
|
|||
parser.add_argument(
|
||||
"--warn-same", action="store_true",
|
||||
help="Warn about one-word translations which are identical to the source")
|
||||
parser.add_argument(
|
||||
"--shorter", action="store_true",
|
||||
help="Show message if it is shorter than expected.")
|
||||
|
||||
# load the translations
|
||||
args = parser.parse_args()
|
||||
|
|
@ -333,7 +344,7 @@ def main():
|
|||
status = True
|
||||
for translation in polib.pofile(args.po):
|
||||
status &= check_translation(translation, msgids, args.pot, args.no_warning, args.no_suggest,
|
||||
args.warn_empty, args.warn_same, args.information)
|
||||
args.warn_empty, args.warn_same, args.information, args.shorter)
|
||||
return 0 if status else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
# Mapping from LCD source encoding to unicode characters
|
||||
CUSTOM_CHARS = {
|
||||
'\x06': '⏬',
|
||||
'\x04': '🔃',
|
||||
'\xe4': 'µ',
|
||||
'\xdf': '°',
|
||||
|
|
|
|||
1530
lang/po/Firmware.pot
1530
lang/po/Firmware.pot
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue