Merge remote-tracking branch 'upstream/MK3' into build-with-cmake
This commit is contained in:
commit
c522330433
|
|
@ -15,6 +15,7 @@
|
|||
/lang/tmp/
|
||||
/lang/Firmware-intl.hex
|
||||
/lang/Firmware-intl-en_*.hex
|
||||
/lang/*.map
|
||||
|
||||
# Temporary files and directories
|
||||
*[~#]
|
||||
|
|
|
|||
|
|
@ -9,12 +9,13 @@ before_install:
|
|||
- sudo iptables -A OUTPUT -o lo -j ACCEPT
|
||||
- sudo iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
script:
|
||||
- bash -x test.sh
|
||||
- cp Firmware/variants/1_75mm_MK3S-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h
|
||||
- bash -x build.sh || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full variant failed" && false; }
|
||||
- bash -x build.sh EN_ONLY || { echo "1_75mm_MK3S-EINSy10a-E3Dv6full EN_ONLY failed" && false; }
|
||||
- rm Firmware/Configuration_prusa.h
|
||||
- cp Firmware/variants/1_75mm_MK3-EINSy10a-E3Dv6full.h Firmware/Configuration_prusa.h
|
||||
- bash -x build.sh || { echo "1_75mm_MK3-EINSy10a-E3Dv6full variant failed" && false; }
|
||||
- bash -x build.sh EN_ONLY || { echo "1_75mm_MK3-EINSy10a-E3Dv6full EN_ONLY failed" && false; }
|
||||
- rm Firmware/Configuration_prusa.h
|
||||
- cp Firmware/variants/1_75mm_MK25S-RAMBo13a-E3Dv6full.h Firmware/Configuration_prusa.h
|
||||
- bash -x build.sh || { echo "1_75mm_MK25S-RAMBo13a-E3Dv6full variant failed" && false; }
|
||||
|
|
|
|||
|
|
@ -100,61 +100,74 @@ add_compile_options(-g)
|
|||
# Firmware - get file lists.
|
||||
#
|
||||
SET(FW_SOURCES
|
||||
adc.c
|
||||
bootapp.c
|
||||
timer02.c
|
||||
sm4.c
|
||||
spi.c
|
||||
rbuf.c
|
||||
swi2c.c
|
||||
language.c
|
||||
tone04.c
|
||||
uart2.c
|
||||
xflash.c
|
||||
Marlin_main.cpp
|
||||
AutoDeplete.cpp
|
||||
Configuration.cpp
|
||||
adc.cpp
|
||||
backlight.cpp
|
||||
BlinkM.cpp
|
||||
Dcodes.cpp
|
||||
MarlinSerial.cpp
|
||||
bootapp.c
|
||||
cardreader.cpp
|
||||
cmdqueue.cpp
|
||||
Configuration.cpp
|
||||
ConfigurationStore.cpp
|
||||
conv2str.cpp
|
||||
Dcodes.cpp
|
||||
eeprom.cpp
|
||||
fancheck.cpp
|
||||
Filament_sensor.cpp
|
||||
first_lay_cal.cpp
|
||||
heatbed_pwm.cpp
|
||||
la10compat.cpp
|
||||
language.c
|
||||
lcd.cpp
|
||||
Marlin_main.cpp
|
||||
MarlinSerial.cpp
|
||||
menu.cpp
|
||||
mesh_bed_calibration.cpp
|
||||
mesh_bed_leveling.cpp
|
||||
messages.cpp
|
||||
mmu2.cpp
|
||||
mmu2_crc.cpp
|
||||
mmu2_error_converter.cpp
|
||||
mmu2_fsensor.cpp
|
||||
mmu2_log.cpp
|
||||
mmu2_power.cpp
|
||||
mmu2_progress_converter.cpp
|
||||
mmu2_protocol.cpp
|
||||
mmu2_protocol_logic.cpp
|
||||
mmu2_reporting.cpp
|
||||
mmu2_serial.cpp
|
||||
motion_control.cpp
|
||||
optiboot_xflash.cpp
|
||||
pat9125.cpp
|
||||
planner.cpp
|
||||
Prusa_farm.cpp
|
||||
qr_solve.cpp
|
||||
rbuf.c
|
||||
Sd2Card.cpp
|
||||
SdBaseFile.cpp
|
||||
SdFatUtil.cpp
|
||||
SdFile.cpp
|
||||
SdVolume.cpp
|
||||
Servo.cpp
|
||||
Timer.cpp
|
||||
backlight.cpp
|
||||
cardreader.cpp
|
||||
cmdqueue.cpp
|
||||
conv2str.cpp
|
||||
eeprom.cpp
|
||||
first_lay_cal.cpp
|
||||
fsensor.cpp
|
||||
heatbed_pwm.cpp
|
||||
la10compat.cpp
|
||||
lcd.cpp
|
||||
menu.cpp
|
||||
mesh_bed_calibration.cpp
|
||||
mesh_bed_leveling.cpp
|
||||
messages.cpp
|
||||
mmu.cpp
|
||||
motion_control.cpp
|
||||
optiboot_xflash.cpp
|
||||
pat9125.cpp
|
||||
planner.cpp
|
||||
qr_solve.cpp
|
||||
sm4.c
|
||||
sound.cpp
|
||||
speed_lookuptable.cpp
|
||||
spi.c
|
||||
SpoolJoin.cpp
|
||||
stepper.cpp
|
||||
swi2c.c
|
||||
swspi.cpp
|
||||
Tcodes.cpp
|
||||
temperature.cpp
|
||||
timer02.c
|
||||
Timer.cpp
|
||||
tmc2130.cpp
|
||||
tone04.c
|
||||
twi.cpp
|
||||
uart2.c
|
||||
ultralcd.cpp
|
||||
util.cpp
|
||||
vector_3.cpp
|
||||
xflash.c
|
||||
xflash_dump.cpp
|
||||
xyzcal.cpp
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
@ -17,9 +17,9 @@ extern PGM_P sPrinterName;
|
|||
|
||||
// Firmware version
|
||||
#define FW_MAJOR 3
|
||||
#define FW_MINOR 11
|
||||
#define FW_REVISION 1
|
||||
//#define FW_FLAVOR RC //uncomment if DEBUG, DEVEL, APLHA, BETA or RC
|
||||
#define FW_MINOR 13
|
||||
#define FW_REVISION 0
|
||||
//#define FW_FLAVOR RC //uncomment if DEBUG, DEVEL, ALPHA, BETA or RC
|
||||
//#define FW_FLAVERSION 1 //uncomment if FW_FLAVOR is defined and versioning is needed.
|
||||
#ifndef FW_FLAVOR
|
||||
#define FW_VERSION STR(FW_MAJOR) "." STR(FW_MINOR) "." STR(FW_REVISION)
|
||||
|
|
@ -444,10 +444,12 @@ your extruder heater takes 2 minutes to hit the target on heating.
|
|||
// Custom M code points
|
||||
#define CUSTOM_M_CODES
|
||||
#ifdef CUSTOM_M_CODES
|
||||
#ifdef ENABLE_AUTO_BED_LEVELING
|
||||
#define CUSTOM_M_CODE_SET_Z_PROBE_OFFSET 851
|
||||
#define Z_PROBE_OFFSET_RANGE_MIN -15
|
||||
#define Z_PROBE_OFFSET_RANGE_MAX -5
|
||||
#endif
|
||||
#endif // ENABLE_AUTO_BED_LEVELING
|
||||
#endif // CUSTOM_M_CODES
|
||||
|
||||
|
||||
// EEPROM
|
||||
|
|
@ -565,5 +567,4 @@ enum CalibrationStatus
|
|||
#include "Configuration_adv.h"
|
||||
#include "thermistortables.h"
|
||||
|
||||
|
||||
#endif //__CONFIGURATION_H
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ void Config_StoreSettings()
|
|||
|
||||
if (EEPROM_writeData(reinterpret_cast<uint8_t*>(EEPROM_M500_base),reinterpret_cast<uint8_t*>(&cs),sizeof(cs),0), "cs, invalid version")
|
||||
{
|
||||
#ifdef TEMP_MODEL
|
||||
temp_model_save_settings();
|
||||
#endif
|
||||
strcpy(cs.version,EEPROM_VERSION); //!< validate data if write succeed
|
||||
EEPROM_writeData(reinterpret_cast<uint8_t*>(EEPROM_M500_base->version), reinterpret_cast<uint8_t*>(cs.version), sizeof(cs.version), "cs.version valid");
|
||||
}
|
||||
|
|
@ -173,6 +176,9 @@ void Config_PrintSettings(uint8_t level)
|
|||
printf_P(PSTR(
|
||||
"%SArc Settings: P:Max length(mm) S:Min length (mm) N:Corrections R:Min segments F:Segments/sec.\n%S M214 P%.2f S%.2f N%d R%d F%d\n"),
|
||||
echomagic, echomagic, cs.mm_per_arc_segment, cs.min_mm_per_arc_segment, cs.n_arc_correction, cs.min_arc_segments, cs.arc_segments_per_sec);
|
||||
#ifdef TEMP_MODEL
|
||||
temp_model_report_settings();
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
|
|
@ -321,6 +327,10 @@ bool Config_RetrieveSettings()
|
|||
|
||||
// Call updatePID (similar to when we have processed M301)
|
||||
updatePID();
|
||||
#ifdef TEMP_MODEL
|
||||
temp_model_load_settings();
|
||||
#endif
|
||||
|
||||
SERIAL_ECHO_START;
|
||||
SERIAL_ECHOLNPGM("Stored settings retrieved");
|
||||
}
|
||||
|
|
@ -353,6 +363,9 @@ void Config_ResetDefault()
|
|||
#ifdef PIDTEMP
|
||||
updatePID();
|
||||
#endif//PIDTEMP
|
||||
#ifdef TEMP_MODEL
|
||||
temp_model_reset_settings();
|
||||
#endif
|
||||
|
||||
calculate_extruder_multipliers();
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ typedef struct
|
|||
unsigned long minsegmenttime;
|
||||
float max_jerk[4]; //!< Jerk is a maximum immediate velocity change.
|
||||
float add_homing[3];
|
||||
float zprobe_zoffset;
|
||||
float zprobe_zoffset; //!< Only used with define ENABLE_AUTO_BED_LEVELING
|
||||
float Kp;
|
||||
float Ki;
|
||||
float Kd;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ void print_hex_word(daddr_t val)
|
|||
print_hex_byte(val & 0xFF);
|
||||
}
|
||||
|
||||
int parse_hex(char* hex, uint8_t* data, int count)
|
||||
int parse_hex(const char* hex, uint8_t* data, int count)
|
||||
{
|
||||
int parsed = 0;
|
||||
while (*hex)
|
||||
|
|
@ -573,6 +573,7 @@ void dcode_9()
|
|||
for (uint8_t i = 0; i < ADC_CHAN_CNT; i++)
|
||||
printf_P(PSTR("\tADC%d=%4d\t(%S)\n"), i, dcode_9_ADC_val(i) >> 4, dcode_9_ADC_name(i));
|
||||
}
|
||||
#if 0
|
||||
else
|
||||
{
|
||||
uint8_t index = 0xff;
|
||||
|
|
@ -583,11 +584,12 @@ void dcode_9()
|
|||
if (code_seen('V')) // value to be written as simulated
|
||||
{
|
||||
adc_sim_mask |= (1 << index);
|
||||
adc_values[index] = (((int)code_value()) << 4);
|
||||
adc_values[index] = ((uint16_t)code_value_short() << 4);
|
||||
printf_P(PSTR("ADC%d=%4d\n"), index, adc_values[index] >> 4);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/*!
|
||||
|
|
@ -863,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
|
||||
|
|
@ -876,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()
|
||||
{
|
||||
|
|
@ -910,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
|
||||
|
|
@ -220,9 +221,6 @@ void manage_inactivity(bool ignore_stepper_queue=false);
|
|||
#endif
|
||||
|
||||
|
||||
#define FARM_FILAMENT_COLOR_NONE 99;
|
||||
|
||||
|
||||
enum AxisEnum {X_AXIS=0, Y_AXIS=1, Z_AXIS=2, E_AXIS=3, X_HEAD=4, Y_HEAD=5};
|
||||
#define X_AXIS_MASK 1
|
||||
#define Y_AXIS_MASK 2
|
||||
|
|
@ -236,14 +234,13 @@ void FlushSerialRequestResend();
|
|||
void ClearToSend();
|
||||
void update_currents();
|
||||
|
||||
void get_coordinates();
|
||||
void prepare_move();
|
||||
void kill(const char *full_screen_message = NULL, unsigned char id = 0);
|
||||
void finishAndDisableSteppers();
|
||||
|
||||
void UnconditionalStop(); // Stop heaters, motion and clear current print status
|
||||
void Stop(); // Emergency stop used by overtemp functions which allows recovery
|
||||
bool IsStopped(); // Returns true if the print has been stopped
|
||||
void UnconditionalStop(); // Stop heaters, motion and clear current print status
|
||||
void ThermalStop(bool allow_pause = false); // Emergency stop used by overtemp functions which allows
|
||||
// recovery (with pause=true)
|
||||
bool IsStopped(); // Returns true if the print has been stopped
|
||||
|
||||
//put an ASCII command at the end of the current buffer, read from flash
|
||||
#define enquecommand_P(cmd) enquecommand(cmd, true)
|
||||
|
|
@ -251,31 +248,9 @@ bool IsStopped(); // Returns true if the print has been stopped
|
|||
//put an ASCII command at the begin of the current buffer, read from flash
|
||||
#define enquecommand_front_P(cmd) enquecommand_front(cmd, true)
|
||||
|
||||
void prepare_arc_move(bool isclockwise);
|
||||
void clamp_to_software_endstops(float target[3]);
|
||||
void refresh_cmd_timeout(void);
|
||||
|
||||
// Timer counter, incremented by the 1ms Arduino timer.
|
||||
// The standard Arduino timer() function returns this value atomically
|
||||
// by disabling / enabling interrupts. This is costly, if the interrupts are known
|
||||
// to be disabled.
|
||||
#ifdef SYSTEM_TIMER_2
|
||||
extern volatile unsigned long timer2_millis;
|
||||
#else //SYSTEM_TIMER_2
|
||||
extern volatile unsigned long timer0_millis;
|
||||
#endif //SYSTEM_TIMER_2
|
||||
|
||||
// An unsynchronized equivalent to a standard Arduino _millis() function.
|
||||
// To be used inside an interrupt routine.
|
||||
|
||||
FORCE_INLINE unsigned long millis_nc() {
|
||||
#ifdef SYSTEM_TIMER_2
|
||||
return timer2_millis;
|
||||
#else //SYSTEM_TIMER_2
|
||||
return timer0_millis;
|
||||
#endif //SYSTEM_TIMER_2
|
||||
}
|
||||
|
||||
#ifdef FAST_PWM_FAN
|
||||
void setPwmFrequency(uint8_t pin, int val);
|
||||
#endif
|
||||
|
|
@ -306,27 +281,25 @@ 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();
|
||||
void prepare_move(uint16_t start_segment_idx = 0);
|
||||
void prepare_arc_move(bool isclockwise, uint16_t start_segment_idx = 0);
|
||||
uint16_t restore_interrupted_gcode();
|
||||
|
||||
#ifdef TMC2130
|
||||
void homeaxis(uint8_t axis, uint8_t cnt = 1, uint8_t* pstep = 0);
|
||||
#else
|
||||
void homeaxis(uint8_t axis, uint8_t cnt = 1);
|
||||
#endif //TMC2130
|
||||
|
||||
|
||||
#ifdef FAN_SOFT_PWM
|
||||
extern unsigned char fanSpeedSoftPwm;
|
||||
#endif
|
||||
|
||||
#ifdef FWRETRACT
|
||||
extern bool retracted[EXTRUDERS];
|
||||
extern float retract_length_swap;
|
||||
extern float retract_recover_length_swap;
|
||||
#endif
|
||||
|
||||
|
||||
extern uint8_t host_keepalive_interval;
|
||||
|
||||
extern unsigned long starttime;
|
||||
|
|
@ -336,19 +309,15 @@ extern bool homing_flag;
|
|||
extern bool loading_flag;
|
||||
extern unsigned long total_filament_used;
|
||||
void save_statistics(unsigned long _total_filament_used, unsigned long _total_print_time);
|
||||
extern uint8_t status_number;
|
||||
extern uint8_t heating_status_counter;
|
||||
extern unsigned long PingTime;
|
||||
extern bool no_response;
|
||||
extern uint8_t important_status;
|
||||
extern uint8_t saved_filament_type;
|
||||
|
||||
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;
|
||||
|
|
@ -364,6 +333,10 @@ extern uint8_t saved_printing_type;
|
|||
#define PRINTING_TYPE_USB 1
|
||||
#define PRINTING_TYPE_NONE 2
|
||||
|
||||
extern float saved_extruder_temperature; //!< Active extruder temperature
|
||||
extern float saved_bed_temperature; //!< Bed temperature
|
||||
extern int saved_fan_speed; //!< Print fan speed
|
||||
|
||||
//save/restore printing in case that mmu is not responding
|
||||
extern bool mmu_print_saved;
|
||||
|
||||
|
|
@ -383,7 +356,8 @@ extern uint16_t gcode_in_progress;
|
|||
extern LongTimer safetyTimer;
|
||||
|
||||
#define PRINT_PERCENT_DONE_INIT 0xff
|
||||
#define PRINTER_ACTIVE (IS_SD_PRINTING || usb_timer.running() || isPrintPaused || (custom_message_type == CustomMsg::TempCal) || saved_printing || (lcd_commands_type == LcdCommands::Layer1Cal) || mmu_print_saved || homing_flag || mesh_bed_leveling_flag)
|
||||
|
||||
extern bool printer_active();
|
||||
|
||||
//! Beware - mcode_in_progress is set as soon as the command gets really processed,
|
||||
//! which is not the same as posting the M600 command into the command queue
|
||||
|
|
@ -440,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();
|
||||
|
||||
|
|
@ -467,6 +442,7 @@ extern uint8_t calc_percent_done();
|
|||
|
||||
#define KEEPALIVE_STATE(n) do { busy_state = n;} while (0)
|
||||
extern void host_keepalive();
|
||||
extern void host_autoreport();
|
||||
//extern MarlinBusyState busy_state;
|
||||
extern int8_t busy_state;
|
||||
|
||||
|
|
@ -487,12 +463,10 @@ 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))
|
||||
|
||||
void proc_commands();
|
||||
|
||||
|
||||
void M600_load_filament();
|
||||
void M600_load_filament_movements();
|
||||
|
|
@ -500,11 +474,11 @@ 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();
|
||||
void pullup_error(bool fromTempISR);
|
||||
|
||||
extern uint32_t IP_address;
|
||||
|
||||
|
|
|
|||
|
|
@ -37,11 +37,10 @@
|
|||
|
||||
// These are macros to build serial port register names for the selected SERIAL_PORT (C preprocessor
|
||||
// requires two levels of indirection to expand macro values properly)
|
||||
#define SERIAL_REGNAME(registerbase,number,suffix) SERIAL_REGNAME_INTERNAL(registerbase,number,suffix)
|
||||
#if SERIAL_PORT == 0 && (!defined(UBRR0H) || !defined(UDR0)) // use un-numbered registers if necessary
|
||||
#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##suffix
|
||||
#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME_SHORT(registerbase, suffix)
|
||||
#else
|
||||
#define SERIAL_REGNAME_INTERNAL(registerbase,number,suffix) registerbase##number##suffix
|
||||
#define SERIAL_REGNAME(registerbase,number,suffix) _REGNAME(registerbase, number, suffix)
|
||||
#endif
|
||||
|
||||
// Registers used by MarlinSerial class (these are expanded
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,500 @@
|
|||
#include "Prusa_farm.h"
|
||||
#include "macros.h"
|
||||
#include "Marlin.h"
|
||||
#include "cmdqueue.h"
|
||||
#include "temperature.h"
|
||||
#include "cardreader.h"
|
||||
#include "conv2str.h"
|
||||
#include "util.h"
|
||||
#include "ultralcd.h"
|
||||
#include "Filament_sensor.h"
|
||||
|
||||
#ifdef PRUSA_FARM
|
||||
uint8_t farm_mode = 0;
|
||||
|
||||
static ShortTimer NcTime;
|
||||
static uint8_t farm_timer = 8;
|
||||
static uint8_t status_number = 0;
|
||||
static bool no_response = false;
|
||||
#ifdef PRUSA_M28
|
||||
#define CHUNK_SIZE 64 // bytes
|
||||
#define SAFETY_MARGIN 1
|
||||
bool prusa_sd_card_upload = false;
|
||||
char chunk[CHUNK_SIZE+SAFETY_MARGIN];
|
||||
#endif
|
||||
|
||||
|
||||
static void prusa_statistics_err(char c);
|
||||
static void prusa_stat_printerstatus(uint8_t _status);
|
||||
static void prusa_stat_farm_number();
|
||||
static void prusa_stat_diameter();
|
||||
static void prusa_stat_temperatures();
|
||||
static void prusa_stat_printinfo();
|
||||
static void lcd_send_status();
|
||||
#ifdef FARM_CONNECT_MESSAGE
|
||||
static void proc_commands();
|
||||
static void lcd_connect_printer();
|
||||
#endif //FARM_CONNECT_MESSAGE
|
||||
#ifdef PRUSA_M28
|
||||
static void trace();
|
||||
#endif
|
||||
|
||||
|
||||
static void prusa_statistics_err(char c) {
|
||||
SERIAL_ECHOPGM("{[ERR:");
|
||||
SERIAL_ECHO(c);
|
||||
SERIAL_ECHO(']');
|
||||
prusa_stat_farm_number();
|
||||
}
|
||||
|
||||
static void prusa_statistics_case0(uint8_t statnr) {
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_printerstatus(statnr);
|
||||
prusa_stat_farm_number();
|
||||
prusa_stat_printinfo();
|
||||
}
|
||||
|
||||
static void prusa_stat_printerstatus(uint8_t _status) {
|
||||
SERIAL_ECHOPGM("[PRN:");
|
||||
SERIAL_ECHO(_status);
|
||||
SERIAL_ECHO(']');
|
||||
}
|
||||
|
||||
static void prusa_stat_farm_number() {
|
||||
SERIAL_ECHOPGM("[PFN:0]");
|
||||
}
|
||||
|
||||
static void prusa_stat_diameter() {
|
||||
SERIAL_ECHOPGM("[DIA:");
|
||||
SERIAL_ECHO(eeprom_read_word((uint16_t*)EEPROM_NOZZLE_DIAMETER_uM));
|
||||
SERIAL_ECHO(']');
|
||||
}
|
||||
|
||||
static void prusa_stat_temperatures() {
|
||||
SERIAL_ECHOPGM("[ST0:");
|
||||
SERIAL_ECHO(target_temperature[0]);
|
||||
SERIAL_ECHOPGM("][STB:");
|
||||
SERIAL_ECHO(target_temperature_bed);
|
||||
SERIAL_ECHOPGM("][AT0:");
|
||||
SERIAL_ECHO(current_temperature[0]);
|
||||
SERIAL_ECHOPGM("][ATB:");
|
||||
SERIAL_ECHO(current_temperature_bed);
|
||||
SERIAL_ECHO(']');
|
||||
}
|
||||
|
||||
static void prusa_stat_printinfo() {
|
||||
SERIAL_ECHOPGM("[TFU:");
|
||||
SERIAL_ECHO(total_filament_used);
|
||||
SERIAL_ECHOPGM("][PCD:");
|
||||
SERIAL_ECHO(itostr3(card.percentDone()));
|
||||
SERIAL_ECHOPGM("][FEM:");
|
||||
SERIAL_ECHO(itostr3(feedmultiply));
|
||||
SERIAL_ECHOPGM("][FNM:");
|
||||
SERIAL_ECHO(card.longFilename[0] ? card.longFilename : card.filename);
|
||||
SERIAL_ECHOPGM("][TIM:");
|
||||
if (starttime != 0) {
|
||||
SERIAL_ECHO(_millis() / 1000 - starttime / 1000);
|
||||
}
|
||||
else {
|
||||
SERIAL_ECHO(0);
|
||||
}
|
||||
SERIAL_ECHOPGM("][FWR:");
|
||||
SERIAL_ECHORPGM(FW_VERSION_STR_P());
|
||||
SERIAL_ECHO(']');
|
||||
prusa_stat_diameter();
|
||||
}
|
||||
|
||||
static void lcd_send_status() {
|
||||
if (farm_mode && no_response && (NcTime.expired(NC_TIME * 1000))) {
|
||||
//send important status messages periodicaly
|
||||
prusa_statistics(8);
|
||||
NcTime.start();
|
||||
#ifdef FARM_CONNECT_MESSAGE
|
||||
lcd_connect_printer();
|
||||
#endif //FARM_CONNECT_MESSAGE
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef FARM_CONNECT_MESSAGE
|
||||
static void proc_commands() {
|
||||
if (buflen) {
|
||||
process_commands();
|
||||
if (!cmdbuffer_front_already_processed)
|
||||
cmdqueue_pop_front();
|
||||
cmdbuffer_front_already_processed = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void lcd_connect_printer() {
|
||||
lcd_update_enable(false);
|
||||
lcd_clear();
|
||||
|
||||
int i = 0;
|
||||
int t = 0;
|
||||
lcd_puts_at_P(0, 0, PSTR("Connect printer to"));
|
||||
lcd_puts_at_P(0, 1, PSTR("monitoring or hold"));
|
||||
lcd_puts_at_P(0, 2, PSTR("the knob to continue"));
|
||||
while (no_response) {
|
||||
i++;
|
||||
t++;
|
||||
delay_keep_alive(100);
|
||||
proc_commands();
|
||||
if (t == 10) {
|
||||
prusa_statistics(8);
|
||||
t = 0;
|
||||
}
|
||||
if (READ(BTN_ENC)) { //if button is not pressed
|
||||
i = 0;
|
||||
lcd_puts_at_P(0, 3, PSTR(" "));
|
||||
}
|
||||
if (i != 0)
|
||||
lcd_putc_at((i * 20) / (NC_BUTTON_LONG_PRESS * 10), 3, LCD_STR_SOLID_BLOCK[0]);
|
||||
if (i == NC_BUTTON_LONG_PRESS * 10)
|
||||
no_response = false;
|
||||
}
|
||||
lcd_update_enable(true);
|
||||
lcd_update(2);
|
||||
}
|
||||
#endif //FARM_CONNECT_MESSAGE
|
||||
|
||||
#ifdef PRUSA_M28
|
||||
static void trace() {
|
||||
Sound_MakeCustom(25,440,true);
|
||||
}
|
||||
|
||||
void serial_read_stream() {
|
||||
|
||||
setAllTargetHotends(0);
|
||||
setTargetBed(0);
|
||||
|
||||
lcd_clear();
|
||||
lcd_puts_P(PSTR(" Upload in progress"));
|
||||
|
||||
// first wait for how many bytes we will receive
|
||||
uint32_t bytesToReceive;
|
||||
|
||||
// receive the four bytes
|
||||
char bytesToReceiveBuffer[4];
|
||||
for (int i=0; i<4; i++) {
|
||||
int data;
|
||||
while ((data = MYSERIAL.read()) == -1) {};
|
||||
bytesToReceiveBuffer[i] = data;
|
||||
|
||||
}
|
||||
|
||||
// make it a uint32
|
||||
memcpy(&bytesToReceive, &bytesToReceiveBuffer, 4);
|
||||
|
||||
// we're ready, notify the sender
|
||||
MYSERIAL.write('+');
|
||||
|
||||
// lock in the routine
|
||||
uint32_t receivedBytes = 0;
|
||||
while (prusa_sd_card_upload) {
|
||||
int i;
|
||||
for (i=0; i<CHUNK_SIZE; i++) {
|
||||
int data;
|
||||
|
||||
// check if we're not done
|
||||
if (receivedBytes == bytesToReceive) {
|
||||
break;
|
||||
}
|
||||
|
||||
// read the next byte
|
||||
while ((data = MYSERIAL.read()) == -1) {};
|
||||
receivedBytes++;
|
||||
|
||||
// save it to the chunk
|
||||
chunk[i] = data;
|
||||
}
|
||||
|
||||
// write the chunk to SD
|
||||
card.write_command_no_newline(&chunk[0]);
|
||||
|
||||
// notify the sender we're ready for more data
|
||||
MYSERIAL.write('+');
|
||||
|
||||
// for safety
|
||||
manage_heater();
|
||||
|
||||
// check if we're done
|
||||
if(receivedBytes == bytesToReceive) {
|
||||
trace(); // beep
|
||||
card.closefile();
|
||||
prusa_sd_card_upload = false;
|
||||
SERIAL_PROTOCOLLNRPGM(MSG_FILE_SAVED);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif //PRUSA_M28
|
||||
|
||||
|
||||
void prusa_statistics(uint8_t _message) {
|
||||
const uint8_t _fil_nr = 0;
|
||||
if (!farm_mode)
|
||||
return;
|
||||
|
||||
switch (_message) {
|
||||
case 0: // default message
|
||||
if (busy_state == PAUSED_FOR_USER) {
|
||||
prusa_statistics_case0(15);
|
||||
}
|
||||
else if (isPrintPaused) {
|
||||
prusa_statistics_case0(14);
|
||||
}
|
||||
else if (IS_SD_PRINTING || loading_flag) {
|
||||
prusa_statistics_case0(4);
|
||||
}
|
||||
else {
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_printerstatus(1);
|
||||
prusa_stat_farm_number();
|
||||
prusa_stat_diameter();
|
||||
status_number = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1: // 1 heating
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_printerstatus(2);
|
||||
prusa_stat_farm_number();
|
||||
status_number = 2;
|
||||
farm_timer = 1;
|
||||
break;
|
||||
|
||||
case 2: // heating done
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_printerstatus(3);
|
||||
prusa_stat_farm_number();
|
||||
SERIAL_ECHOLN('}');
|
||||
status_number = 3;
|
||||
farm_timer = 1;
|
||||
|
||||
if (IS_SD_PRINTING || loading_flag) {
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_printerstatus(4);
|
||||
prusa_stat_farm_number();
|
||||
status_number = 4;
|
||||
}
|
||||
else {
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_printerstatus(3);
|
||||
prusa_stat_farm_number();
|
||||
status_number = 3;
|
||||
}
|
||||
farm_timer = 1;
|
||||
break;
|
||||
|
||||
case 3: // filament change
|
||||
// must do a return here to prevent doing SERIAL_ECHOLN("}") at the very end of this function
|
||||
// saved a considerable amount of FLASH
|
||||
return;
|
||||
break;
|
||||
case 4: // print succesfull
|
||||
SERIAL_ECHOPGM("{[RES:1][FIL:");
|
||||
MYSERIAL.print(int(_fil_nr));
|
||||
SERIAL_ECHO(']');
|
||||
prusa_stat_printerstatus(status_number);
|
||||
prusa_stat_farm_number();
|
||||
farm_timer = 2;
|
||||
break;
|
||||
case 5: // print not succesfull
|
||||
SERIAL_ECHOPGM("{[RES:0][FIL:");
|
||||
MYSERIAL.print(int(_fil_nr));
|
||||
SERIAL_ECHO(']');
|
||||
prusa_stat_printerstatus(status_number);
|
||||
prusa_stat_farm_number();
|
||||
farm_timer = 2;
|
||||
break;
|
||||
case 6: // print done
|
||||
SERIAL_ECHOPGM("{[PRN:8]");
|
||||
prusa_stat_farm_number();
|
||||
status_number = 8;
|
||||
farm_timer = 2;
|
||||
break;
|
||||
case 7: // print done - stopped
|
||||
SERIAL_ECHOPGM("{[PRN:9]");
|
||||
prusa_stat_farm_number();
|
||||
status_number = 9;
|
||||
farm_timer = 2;
|
||||
break;
|
||||
case 8: // printer started
|
||||
SERIAL_ECHOPGM("{[PRN:0]");
|
||||
prusa_stat_farm_number();
|
||||
status_number = 0;
|
||||
farm_timer = 2;
|
||||
break;
|
||||
case 20: // echo farm no
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_printerstatus(status_number);
|
||||
prusa_stat_farm_number();
|
||||
farm_timer = 4;
|
||||
break;
|
||||
case 21: // temperatures
|
||||
SERIAL_ECHO('{');
|
||||
prusa_stat_temperatures();
|
||||
prusa_stat_farm_number();
|
||||
prusa_stat_printerstatus(status_number);
|
||||
break;
|
||||
case 22: // waiting for filament change
|
||||
SERIAL_ECHOPGM("{[PRN:5]");
|
||||
prusa_stat_farm_number();
|
||||
status_number = 5;
|
||||
break;
|
||||
|
||||
case 90: // Error - Thermal Runaway
|
||||
prusa_statistics_err('1');
|
||||
break;
|
||||
case 91: // Error - Thermal Runaway Preheat
|
||||
prusa_statistics_err('2');
|
||||
break;
|
||||
case 92: // Error - Min temp
|
||||
prusa_statistics_err('3');
|
||||
break;
|
||||
case 93: // Error - Max temp
|
||||
prusa_statistics_err('4');
|
||||
break;
|
||||
|
||||
case 99: // heartbeat
|
||||
SERIAL_ECHOPGM("{[PRN:99]");
|
||||
prusa_stat_temperatures();
|
||||
prusa_stat_farm_number();
|
||||
break;
|
||||
}
|
||||
SERIAL_ECHOLN('}');
|
||||
}
|
||||
|
||||
void prusa_statistics_update_from_status_screen() {
|
||||
if (farm_mode) {
|
||||
farm_timer--;
|
||||
if (farm_timer < 1) {
|
||||
farm_timer = 10;
|
||||
prusa_statistics(0);
|
||||
}
|
||||
switch (farm_timer) {
|
||||
case 8:
|
||||
prusa_statistics(21);
|
||||
if(loading_flag)
|
||||
prusa_statistics(22);
|
||||
break;
|
||||
case 5:
|
||||
if (IS_SD_PRINTING)
|
||||
prusa_statistics(20);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void prusa_statistics_update_from_lcd_update() {
|
||||
lcd_send_status();
|
||||
}
|
||||
|
||||
void farm_mode_init() {
|
||||
farm_mode = eeprom_read_byte((uint8_t*)EEPROM_FARM_MODE);
|
||||
if (farm_mode == 0xFF) {
|
||||
farm_mode = false; //if farm_mode has not been stored to eeprom yet and farm number is set to zero or EEPROM is fresh, deactivate farm mode
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode);
|
||||
}
|
||||
else if (farm_mode) {
|
||||
no_response = true; //we need confirmation by recieving PRUSA thx
|
||||
prusa_statistics(8);
|
||||
#ifdef HAS_SECOND_SERIAL_PORT
|
||||
selectedSerialPort = 1;
|
||||
#endif //HAS_SECOND_SERIAL_PORT
|
||||
MYSERIAL.begin(BAUDRATE);
|
||||
#ifdef FILAMENT_SENSOR
|
||||
//to be converted to Filament_sensor.h...
|
||||
//disabled filament autoload (PFW360)
|
||||
fsensor.setAutoLoadEnabled(false);
|
||||
#endif //FILAMENT_SENSOR
|
||||
// ~ FanCheck -> on
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED, true);
|
||||
}
|
||||
}
|
||||
|
||||
bool farm_prusa_code_seen() {
|
||||
if (!farm_mode)
|
||||
return false;
|
||||
|
||||
if (code_seen_P(PSTR("PRN"))) { // PRUSA PRN
|
||||
printf_P(_N("%u"), status_number);
|
||||
}
|
||||
else if (code_seen_P(PSTR("thx"))) { // PRUSA thx
|
||||
no_response = false;
|
||||
}
|
||||
#ifdef PRUSA_M28
|
||||
else if (code_seen_P(PSTR("M28"))) { // PRUSA M28
|
||||
trace();
|
||||
prusa_sd_card_upload = true;
|
||||
card.openFileWrite(strchr_pointer+4);
|
||||
}
|
||||
#endif //PRUSA_M28
|
||||
else if (code_seen_P(PSTR("fv"))) { // PRUSA fv
|
||||
// get file version
|
||||
#ifdef SDSUPPORT
|
||||
card.openFileReadFilteredGcode(strchr_pointer + 3, true);
|
||||
while (true) {
|
||||
uint16_t readByte = card.getFilteredGcodeChar();
|
||||
MYSERIAL.write(readByte);
|
||||
if (readByte == '\n') {
|
||||
break;
|
||||
}
|
||||
}
|
||||
card.closefile();
|
||||
#endif // SDSUPPORT
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void farm_gcode_g98() {
|
||||
farm_mode = 1;
|
||||
eeprom_update_byte((unsigned char *)EEPROM_FARM_MODE, farm_mode);
|
||||
SilentModeMenu = SILENT_MODE_OFF;
|
||||
eeprom_update_byte((unsigned char *)EEPROM_SILENT, SilentModeMenu);
|
||||
fCheckModeInit(); // alternatively invoke printer reset
|
||||
}
|
||||
|
||||
void farm_gcode_g99() {
|
||||
farm_disable();
|
||||
lcd_update(2);
|
||||
fCheckModeInit(); // alternatively invoke printer reset
|
||||
}
|
||||
|
||||
void farm_disable() {
|
||||
farm_mode = false;
|
||||
eeprom_update_byte((uint8_t*)EEPROM_FARM_MODE, farm_mode);
|
||||
}
|
||||
|
||||
#else //PRUSA_FARM
|
||||
|
||||
void prusa_statistics(_UNUSED uint8_t message) {
|
||||
}
|
||||
|
||||
void prusa_statistics_update_from_status_screen() {
|
||||
}
|
||||
|
||||
void prusa_statistics_update_from_lcd_update() {
|
||||
}
|
||||
|
||||
void farm_mode_init() {
|
||||
}
|
||||
|
||||
bool farm_prusa_code_seen() {
|
||||
return false;
|
||||
}
|
||||
|
||||
void farm_gcode_g98() {
|
||||
}
|
||||
|
||||
void farm_gcode_g99() {
|
||||
}
|
||||
|
||||
void farm_disable() {
|
||||
}
|
||||
|
||||
#endif //PRUSA_FARM
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "config.h"
|
||||
|
||||
#define FARM_PREHEAT_HOTEND_TEMP 250
|
||||
#define FARM_PREHEAT_HPB_TEMP 80
|
||||
|
||||
#define FARM_DEFAULT_SAFETYTIMER_TIME_ms (45*60*1000ul)
|
||||
#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
|
||||
#define farm_mode 0
|
||||
#endif
|
||||
|
||||
#ifdef PRUSA_M28
|
||||
extern bool prusa_sd_card_upload;
|
||||
extern void serial_read_stream();
|
||||
#endif
|
||||
extern void prusa_statistics(uint8_t _message);
|
||||
extern void prusa_statistics_update_from_status_screen();
|
||||
extern void prusa_statistics_update_from_lcd_update();
|
||||
extern void farm_mode_init();
|
||||
extern bool farm_prusa_code_seen();
|
||||
extern void farm_gcode_g98();
|
||||
extern void farm_gcode_g99();
|
||||
extern void farm_disable();
|
||||
|
|
@ -1015,7 +1015,7 @@ void SdBaseFile::printFatTime( uint16_t fatTime) {
|
|||
* the value zero, false, is returned for failure.
|
||||
*/
|
||||
bool SdBaseFile::printName() {
|
||||
char name[13];
|
||||
char name[FILENAME_LENGTH];
|
||||
if (!getFilename(name)) return false;
|
||||
MYSERIAL.print(name);
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -77,5 +77,11 @@ T Timer<T>::elapsed() {
|
|||
return m_isRunning ? (_millis() - m_started) : 0;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool Timer<T>::expired_cont(T msPeriod)
|
||||
{
|
||||
return !m_isRunning || expired(msPeriod);
|
||||
}
|
||||
|
||||
template class Timer<unsigned long>;
|
||||
template class Timer<unsigned short>;
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ public:
|
|||
void start();
|
||||
void stop(){m_isRunning = false;}
|
||||
bool running()const {return m_isRunning;}
|
||||
bool expired(T msPeriod);
|
||||
T elapsed();
|
||||
bool expired(T msPeriod); // returns true only once after expiration, then stops running
|
||||
T elapsed(); // returns the time in milliseconds since the timer was started or 0 otherwise
|
||||
bool expired_cont(T msPeriod); // return true when continuosly when expired / not running
|
||||
protected:
|
||||
T started()const {return m_started;}
|
||||
private:
|
||||
|
|
|
|||
|
|
@ -1,95 +0,0 @@
|
|||
//adc.c
|
||||
|
||||
#include "adc.h"
|
||||
#include <stdio.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include "pins.h"
|
||||
|
||||
uint8_t adc_state;
|
||||
uint8_t adc_count;
|
||||
uint16_t adc_values[ADC_CHAN_CNT];
|
||||
uint16_t adc_sim_mask;
|
||||
|
||||
|
||||
#ifdef ADC_CALLBACK
|
||||
extern void ADC_CALLBACK(void);
|
||||
#endif //ADC_CALLBACK
|
||||
|
||||
|
||||
void adc_init(void)
|
||||
{
|
||||
puts_P(PSTR("adc_init"));
|
||||
adc_sim_mask = 0x00;
|
||||
ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0);
|
||||
ADMUX |= (1 << REFS0);
|
||||
ADCSRA |= (1 << ADEN);
|
||||
// ADCSRA |= (1 << ADIF) | (1 << ADSC);
|
||||
DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff);
|
||||
DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8);
|
||||
adc_reset();
|
||||
// adc_sim_mask = 0b0101;
|
||||
// adc_sim_mask = 0b100101;
|
||||
// adc_values[0] = 1023 * 16;
|
||||
// adc_values[2] = 1023 * 16;
|
||||
// adc_values[5] = 1002 * 16;
|
||||
}
|
||||
|
||||
void adc_reset(void)
|
||||
{
|
||||
adc_state = 0;
|
||||
adc_count = 0;
|
||||
uint8_t i; for (i = 0; i < ADC_CHAN_CNT; i++)
|
||||
if ((adc_sim_mask & (1 << i)) == 0)
|
||||
adc_values[i] = 0;
|
||||
}
|
||||
|
||||
void adc_setmux(uint8_t ch)
|
||||
{
|
||||
ch &= 0x0f;
|
||||
if (ch & 0x08) ADCSRB |= (1 << MUX5);
|
||||
else ADCSRB &= ~(1 << MUX5);
|
||||
ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07);
|
||||
}
|
||||
|
||||
uint8_t adc_chan(uint8_t index)
|
||||
{
|
||||
uint8_t chan = 0;
|
||||
uint16_t mask = 1;
|
||||
while (mask)
|
||||
{
|
||||
if ((mask & ADC_CHAN_MSK) && (index-- == 0)) break;
|
||||
mask <<= 1;
|
||||
chan++;
|
||||
}
|
||||
return chan;
|
||||
}
|
||||
|
||||
void adc_cycle(void)
|
||||
{
|
||||
if (adc_state & 0x80)
|
||||
{
|
||||
uint8_t index = adc_state & 0x0f;
|
||||
if ((adc_sim_mask & (1 << index)) == 0)
|
||||
adc_values[index] += ADC;
|
||||
if (++index >= ADC_CHAN_CNT)
|
||||
{
|
||||
index = 0;
|
||||
adc_count++;
|
||||
if (adc_count >= ADC_OVRSAMPL)
|
||||
{
|
||||
#ifdef ADC_CALLBACK
|
||||
ADC_CALLBACK();
|
||||
#endif //ADC_CALLBACK
|
||||
adc_reset();
|
||||
}
|
||||
}
|
||||
adc_setmux(adc_chan(index));
|
||||
adc_state = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
ADCSRA |= (1 << ADSC); //start conversion
|
||||
adc_state |= 0x80;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
#include "adc.h"
|
||||
#include <stdio.h>
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
#include <avr/pgmspace.h>
|
||||
#include <string.h>
|
||||
#include "pins.h"
|
||||
|
||||
static uint8_t adc_count; //used for oversampling
|
||||
static uint8_t adc_channel_idx; //bitmask index
|
||||
volatile uint8_t adc_channel; //regular index
|
||||
volatile uint16_t adc_values[ADC_CHAN_CNT];
|
||||
|
||||
static void adc_reset();
|
||||
static void adc_setmux(uint8_t ch);
|
||||
|
||||
void adc_init()
|
||||
{
|
||||
puts_P(PSTR("adc_init"));
|
||||
DIDR0 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) & 0xff); //disable digital inputs PORTF
|
||||
DIDR2 = ((ADC_CHAN_MSK & ADC_DIDR_MSK) >> 8); //disable digital inputs PORTK
|
||||
ADMUX |= (1 << REFS0); //use AVCC as reference
|
||||
|
||||
//enable ADC, set prescaler/128, enable interrupt
|
||||
ADCSRA = (1 << ADEN) | (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0) | (1 << ADIF) | (1 << ADIE);
|
||||
}
|
||||
|
||||
static void adc_reset()
|
||||
{
|
||||
static const uint8_t first_channel_idx = 0;
|
||||
static_assert((1 << first_channel_idx) & ADC_CHAN_MSK);
|
||||
|
||||
ADCSRA &= ~(1 << ADSC); //stop conversion just in case
|
||||
adc_count = 0;
|
||||
adc_channel = 0;
|
||||
adc_channel_idx = first_channel_idx;
|
||||
adc_setmux(adc_channel_idx);
|
||||
memset((void*)adc_values, 0, sizeof(adc_values));
|
||||
}
|
||||
|
||||
static void adc_setmux(uint8_t ch)
|
||||
{
|
||||
ch &= 0x0f;
|
||||
if (ch & 0x08) ADCSRB |= (1 << MUX5);
|
||||
else ADCSRB &= ~(1 << MUX5);
|
||||
ADMUX = (ADMUX & ~(0x07)) | (ch & 0x07);
|
||||
}
|
||||
|
||||
void adc_start_cycle() {
|
||||
adc_reset();
|
||||
ADCSRA |= (1 << ADSC); //start conversion
|
||||
}
|
||||
|
||||
#ifdef ADC_CALLBACK
|
||||
extern void ADC_CALLBACK();
|
||||
#endif //ADC_CALLBACK
|
||||
|
||||
ISR(ADC_vect)
|
||||
{
|
||||
adc_values[adc_channel] += ADC;
|
||||
if (++adc_count == ADC_OVRSAMPL)
|
||||
{
|
||||
// go to the next channel
|
||||
if (++adc_channel == ADC_CHAN_CNT) {
|
||||
#ifdef ADC_CALLBACK
|
||||
ADC_CALLBACK();
|
||||
#endif
|
||||
return; // do not start the next measurement since there are no channels remaining
|
||||
}
|
||||
|
||||
// find the next channel
|
||||
while (++adc_channel_idx) {
|
||||
if (ADC_CHAN_MSK & (1 << adc_channel_idx)) {
|
||||
adc_setmux(adc_channel_idx);
|
||||
adc_count = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ADCSRA |= (1 << ADSC); //start conversion
|
||||
}
|
||||
|
|
@ -1,15 +1,8 @@
|
|||
//adc.h
|
||||
#ifndef _ADC_H
|
||||
#define _ADC_H
|
||||
#pragma once
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "config.h"
|
||||
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif //defined(__cplusplus)
|
||||
|
||||
/*
|
||||
http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html
|
||||
*/
|
||||
|
|
@ -22,24 +15,11 @@ http://resnet.uoregon.edu/~gurney_j/jmpc/bitwise.html
|
|||
# error "ADC_CHAN_MSK oes not match ADC_CHAN_CNT"
|
||||
#endif
|
||||
|
||||
extern uint8_t adc_state;
|
||||
extern uint8_t adc_count;
|
||||
extern uint16_t adc_values[ADC_CHAN_CNT];
|
||||
extern uint16_t adc_sim_mask;
|
||||
#define VOLT_DIV_REF 5 //[V]
|
||||
|
||||
extern volatile uint8_t adc_channel;
|
||||
extern volatile uint16_t adc_values[ADC_CHAN_CNT];
|
||||
|
||||
extern void adc_init(void);
|
||||
|
||||
extern void adc_reset(void);
|
||||
|
||||
extern void adc_setmux(uint8_t ch);
|
||||
|
||||
extern uint8_t adc_chan(uint8_t index);
|
||||
|
||||
extern void adc_cycle(void);
|
||||
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif //defined(__cplusplus)
|
||||
#endif //_ADC_H
|
||||
extern void adc_init();
|
||||
extern void adc_start_cycle(); //should be called from an atomic context only
|
||||
static inline bool adc_cycle_done() { return adc_channel >= ADC_CHAN_CNT; }
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
#include "stepper.h"
|
||||
#include "temperature.h"
|
||||
#include "language.h"
|
||||
#include "Prusa_farm.h"
|
||||
|
||||
#ifdef SDSUPPORT
|
||||
|
||||
|
|
@ -290,7 +291,7 @@ void CardReader::getDirName(char* name, uint8_t level)
|
|||
workDirParents[level].getFilename(name);
|
||||
}
|
||||
|
||||
uint16_t CardReader::getWorkDirDepth() {
|
||||
uint8_t CardReader::getWorkDirDepth() {
|
||||
return workDirDepth;
|
||||
}
|
||||
|
||||
|
|
@ -304,7 +305,7 @@ void CardReader::getAbsFilename(char *t)
|
|||
while(*t!=0 && cnt< MAXPATHNAMELENGTH)
|
||||
{t++;cnt++;} //crawl counter forward.
|
||||
}
|
||||
if(cnt<MAXPATHNAMELENGTH-13)
|
||||
if(cnt < MAXPATHNAMELENGTH - FILENAME_LENGTH)
|
||||
file.getFilename(t);
|
||||
else
|
||||
t[0]=0;
|
||||
|
|
@ -738,7 +739,7 @@ bool CardReader::chdir(const char * relpath, bool doPresort)
|
|||
puts(relpath);
|
||||
|
||||
if (workDirDepth < MAX_DIR_DEPTH) {
|
||||
for (int d = ++workDirDepth; d--;)
|
||||
for (uint8_t d = ++workDirDepth; d--;)
|
||||
workDirParents[d+1] = workDirParents[d];
|
||||
workDirParents[0]=*parent;
|
||||
}
|
||||
|
|
@ -760,7 +761,7 @@ void CardReader::updir()
|
|||
{
|
||||
--workDirDepth;
|
||||
workDir = workDirParents[0];
|
||||
for (unsigned int d = 0; d < workDirDepth; d++)
|
||||
for (uint8_t d = 0; d < workDirDepth; d++)
|
||||
{
|
||||
workDirParents[d] = workDirParents[d+1];
|
||||
}
|
||||
|
|
@ -858,7 +859,7 @@ void CardReader::presort() {
|
|||
#endif
|
||||
|
||||
uint16_t counter = 0;
|
||||
menu_progressbar_init(fileCnt * fileCnt / 2, _i("Sorting files"));
|
||||
menu_progressbar_init(fileCnt * fileCnt / 2, _T(MSG_SORTING_FILES));
|
||||
|
||||
for (uint16_t i = 1; i < fileCnt; ++i){
|
||||
// if (!IS_SD_INSERTED) return;
|
||||
|
|
@ -925,7 +926,7 @@ void CardReader::presort() {
|
|||
#endif
|
||||
|
||||
uint16_t counter = 0;
|
||||
menu_progressbar_init(0.5*(fileCnt - 1)*(fileCnt), _i("Sorting files"));
|
||||
menu_progressbar_init(0.5*(fileCnt - 1)*(fileCnt), _T(MSG_SORTING_FILES));
|
||||
|
||||
for (uint16_t i = fileCnt; --i;) {
|
||||
if (!IS_SD_INSERTED) return;
|
||||
|
|
@ -1008,9 +1009,10 @@ void CardReader::flush_presort() {
|
|||
void CardReader::printingHasFinished()
|
||||
{
|
||||
st_synchronize();
|
||||
file.close();
|
||||
|
||||
if(file_subcall_ctr>0) //heading up to a parent file that called current as a procedure.
|
||||
{
|
||||
file.close();
|
||||
file_subcall_ctr--;
|
||||
openFileReadFilteredGcode(filenames[file_subcall_ctr],true);
|
||||
setIndex(filespos[file_subcall_ctr]);
|
||||
|
|
@ -1018,8 +1020,6 @@ void CardReader::printingHasFinished()
|
|||
}
|
||||
else
|
||||
{
|
||||
quickStop();
|
||||
file.close();
|
||||
sdprinting = false;
|
||||
if(SD_FINISHED_STEPPERRELEASE)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,7 +53,7 @@ public:
|
|||
void getAbsFilename(char *t);
|
||||
void printAbsFilenameFast();
|
||||
void getDirName(char* name, uint8_t level);
|
||||
uint16_t getWorkDirDepth();
|
||||
uint8_t getWorkDirDepth();
|
||||
|
||||
|
||||
void ls(ls_param params);
|
||||
|
|
@ -89,7 +89,7 @@ public:
|
|||
bool logging;
|
||||
bool sdprinting ;
|
||||
bool cardOK ;
|
||||
char filename[13];
|
||||
char filename[FILENAME_LENGTH];
|
||||
// There are scenarios when simple modification time is not enough (on MS Windows)
|
||||
// Therefore these timestamps hold the most recent one of creation/modification date/times
|
||||
uint16_t crmodTime, crmodDate;
|
||||
|
|
@ -103,7 +103,7 @@ public:
|
|||
char dir_names[MAX_DIR_DEPTH][9];
|
||||
private:
|
||||
SdFile root,*curDir,workDir,workDirParents[MAX_DIR_DEPTH];
|
||||
uint16_t workDirDepth;
|
||||
uint8_t workDirDepth;
|
||||
|
||||
// Sort files and folders alphabetically.
|
||||
#ifdef SDCARD_SORT_ALPHA
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include <util/atomic.h>
|
||||
#include "cmdqueue.h"
|
||||
#include "cardreader.h"
|
||||
#include "ultralcd.h"
|
||||
#include "Prusa_farm.h"
|
||||
|
||||
// Reserve BUFSIZE lines of length MAX_CMD_SIZE plus CMDBUFFER_RESERVE_FRONT.
|
||||
char cmdbuffer[BUFSIZE * (MAX_CMD_SIZE + 1) + CMDBUFFER_RESERVE_FRONT];
|
||||
|
|
@ -25,11 +27,7 @@ bool comment_mode = false;
|
|||
char *strchr_pointer; // just a pointer to find chars in the command string like X, Y, Z, E, etc
|
||||
|
||||
ShortTimer serialTimeoutTimer;
|
||||
|
||||
long gcode_N = 0;
|
||||
long gcode_LastN = 0;
|
||||
long Stopped_gcode_LastN = 0;
|
||||
|
||||
uint32_t sdpos_atomic = 0;
|
||||
|
||||
|
||||
|
|
@ -155,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)
|
||||
|
|
@ -165,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,20 +334,10 @@ void repeatcommand_front()
|
|||
cmdbuffer_front_already_processed = true;
|
||||
}
|
||||
|
||||
void proc_commands() {
|
||||
if (buflen)
|
||||
{
|
||||
process_commands();
|
||||
if (!cmdbuffer_front_already_processed)
|
||||
cmdqueue_pop_front();
|
||||
cmdbuffer_front_already_processed = false;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -413,11 +369,11 @@ void get_command()
|
|||
cmdbuffer[bufindw+serial_count+CMDHDRSIZE] = 0; //terminate string
|
||||
if(!comment_mode){
|
||||
|
||||
gcode_N = 0;
|
||||
long gcode_N = -1;
|
||||
|
||||
// Line numbers must be first in buffer
|
||||
|
||||
if ((strstr(cmdbuffer+bufindw+CMDHDRSIZE, "PRUSA") == NULL) &&
|
||||
if ((strstr_P(cmdbuffer+bufindw+CMDHDRSIZE, PSTR("PRUSA")) == NULL) &&
|
||||
(cmdbuffer[bufindw+CMDHDRSIZE] == 'N')) {
|
||||
|
||||
// Line number met. When sending a G-code over a serial line, each line may be stamped with its index,
|
||||
|
|
@ -464,8 +420,6 @@ void get_command()
|
|||
|
||||
// Don't parse N again with code_seen('N')
|
||||
cmdbuffer[bufindw + CMDHDRSIZE] = '$';
|
||||
//if no errors, continue parsing
|
||||
gcode_LastN = gcode_N;
|
||||
}
|
||||
// if we don't receive 'N' but still see '*'
|
||||
if ((cmdbuffer[bufindw + CMDHDRSIZE] != 'N') && (cmdbuffer[bufindw + CMDHDRSIZE] != '$') && (strchr(cmdbuffer+bufindw+CMDHDRSIZE, '*') != NULL))
|
||||
|
|
@ -478,35 +432,49 @@ void get_command()
|
|||
serial_count = 0;
|
||||
return;
|
||||
}
|
||||
// Handle KILL early, even when Stopped
|
||||
if(strcmp(cmdbuffer+bufindw+CMDHDRSIZE, "M112") == 0)
|
||||
kill(MSG_M112_KILL, 2);
|
||||
// Handle the USB timer
|
||||
if ((strchr_pointer = strchr(cmdbuffer+bufindw+CMDHDRSIZE, 'G')) != NULL) {
|
||||
if (!IS_SD_PRINTING) {
|
||||
usb_timer.start();
|
||||
}
|
||||
if (Stopped == true) {
|
||||
if (code_value_uint8() <= 3) {
|
||||
SERIAL_ERRORLNRPGM(MSG_ERR_STOPPED);
|
||||
LCD_MESSAGERPGM(_T(MSG_STOPPED));
|
||||
}
|
||||
}
|
||||
} // end of 'G' command
|
||||
}
|
||||
if (Stopped == true) {
|
||||
// Stopped can be set either during error states (thermal error: cannot continue), or
|
||||
// when a printer-initiated action is processed. In such case the printer will send to
|
||||
// the host an action, but cannot know if the action has been processed while new
|
||||
// commands are being sent. In this situation we just drop the command while issuing
|
||||
// periodic "busy" messages in the main loop. Since we're not incrementing the received
|
||||
// line number, a request for resend will happen (if necessary), ensuring we don't skip
|
||||
// commands whenever Stopped is cleared and processing resumes.
|
||||
serial_count = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
// Command is complete: store the current line into buffer, move to the next line.
|
||||
|
||||
//If command was e-stop process now
|
||||
if(strcmp(cmdbuffer+bufindw+CMDHDRSIZE, "M112") == 0)
|
||||
kill(MSG_M112_KILL, 2);
|
||||
|
||||
// Store the current line into buffer, move to the next line.
|
||||
// Store type of entry
|
||||
cmdbuffer[bufindw] = gcode_N ? CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR : CMDBUFFER_CURRENT_TYPE_USB;
|
||||
cmdbuffer[bufindw] = gcode_N >= 0 ? CMDBUFFER_CURRENT_TYPE_USB_WITH_LINENR : CMDBUFFER_CURRENT_TYPE_USB;
|
||||
|
||||
#ifdef CMDBUFFER_DEBUG
|
||||
SERIAL_ECHO_START;
|
||||
SERIAL_ECHOPGM("Storing a command line to buffer: ");
|
||||
SERIAL_ECHO(cmdbuffer+bufindw+CMDHDRSIZE);
|
||||
SERIAL_ECHOLNPGM("");
|
||||
#endif /* CMDBUFFER_DEBUG */
|
||||
|
||||
// Store command itself
|
||||
bufindw += strlen(cmdbuffer+bufindw+CMDHDRSIZE) + (1 + CMDHDRSIZE);
|
||||
if (bufindw == sizeof(cmdbuffer))
|
||||
bufindw = 0;
|
||||
++ buflen;
|
||||
|
||||
// Update the processed gcode line
|
||||
if (gcode_N >= 0)
|
||||
gcode_LastN = gcode_N;
|
||||
|
||||
#ifdef CMDBUFFER_DEBUG
|
||||
SERIAL_ECHOPGM("Number of commands in the buffer: ");
|
||||
SERIAL_ECHO(buflen);
|
||||
|
|
@ -516,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 {
|
||||
|
|
@ -534,7 +502,7 @@ void get_command()
|
|||
}
|
||||
|
||||
#ifdef SDSUPPORT
|
||||
if(!card.sdprinting || serial_count!=0){
|
||||
if(!card.sdprinting || !card.isFileOpen() || serial_count!=0){
|
||||
// If there is a half filled buffer from serial line, wait until return before
|
||||
// continuing with the serial line.
|
||||
return;
|
||||
|
|
@ -572,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
|
||||
}
|
||||
|
|
@ -614,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
|
||||
|
|
@ -631,6 +599,10 @@ void get_command()
|
|||
// cleared by printingHasFinished after peforming all remaining moves.
|
||||
if(!cmdqueue_calc_sd_length())
|
||||
{
|
||||
// queue is complete, but before we process EOF commands prevent
|
||||
// re-entry by disabling SD processing from any st_synchronize call
|
||||
card.closefile();
|
||||
|
||||
SERIAL_PROTOCOLLNRPGM(_n("Done printing file"));////MSG_FILE_PRINTED
|
||||
stoptime=_millis();
|
||||
char time[30];
|
||||
|
|
|
|||
|
|
@ -52,9 +52,7 @@ extern int serial_count;
|
|||
extern bool comment_mode;
|
||||
extern char *strchr_pointer;
|
||||
|
||||
extern long gcode_N;
|
||||
extern long gcode_LastN;
|
||||
extern long Stopped_gcode_LastN;
|
||||
|
||||
extern bool cmdqueue_pop_front();
|
||||
extern void cmdqueue_reset();
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@
|
|||
#include "pins.h"
|
||||
|
||||
#if (defined(VOLT_IR_PIN) && defined(IR_SENSOR))
|
||||
# define IR_SENSOR_ANALOG
|
||||
// TODO: IR_SENSOR_ANALOG currently disabled as being incompatible with the new thermal regulation
|
||||
// # define IR_SENSOR_ANALOG
|
||||
#endif
|
||||
|
||||
//ADC configuration
|
||||
|
|
@ -20,7 +21,7 @@
|
|||
#define ADC_CHAN_CNT 8 //number of used channels)
|
||||
#endif //!IR_SENSOR_ANALOG
|
||||
#define ADC_OVRSAMPL 16 //oversampling multiplier
|
||||
#define ADC_CALLBACK adc_ready //callback function ()
|
||||
#define ADC_CALLBACK adc_callback //callback function ()
|
||||
|
||||
//SWI2C configuration
|
||||
//#define SWI2C_SDA 20 //SDA on P3
|
||||
|
|
@ -42,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]
|
||||
|
|
@ -62,7 +68,9 @@
|
|||
#define LANG_MODE 1 // sec. language support
|
||||
#endif
|
||||
|
||||
#define LANG_SIZE_RESERVED 0x3000 // reserved space for secondary language (12288 bytes). Maximum 32768 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
|
||||
|
||||
#if (LANG_SIZE_RESERVED % 256)
|
||||
#error "LANG_SIZE_RESERVED should be a multiple of a page size"
|
||||
|
|
@ -82,9 +90,12 @@
|
|||
//#define COMMUNITY_LANG_GROUP1_DA // Community Danish language
|
||||
//#define COMMUNITY_LANG_GROUP1_SL // Community Slovanian language
|
||||
//#define COMMUNITY_LANG_GROUP1_LB // Community Luxembourgish language
|
||||
//#define COMMUNITY_LANG_GROUP1_LT // Community Lithuanian language
|
||||
#endif //COMMUNITY_LANG_GROUP 1
|
||||
|
||||
#if (COMMUNITY_LANG_GROUP == 2)
|
||||
#define COMMUNITY_LANG_GROUP2_LT // Community Lithuanian language
|
||||
//#define COMMUNITY_LANG_GROUP1_QR // Community new language //..use this as a template and replace 'QR'
|
||||
#endif
|
||||
#endif //COMMUNITY_LANG_GROUP 2
|
||||
|
||||
#if (COMMUNITY_LANG_GROUP >=1 )
|
||||
#define COMMUNITY_LANGUAGE_SUPPORT
|
||||
|
|
@ -111,4 +122,11 @@
|
|||
#define EMERGENCY_HANDLERS
|
||||
#endif
|
||||
|
||||
//FARM_MODE
|
||||
#if ( LANG_MODE == 0 ) && defined(XFLASH) //Save resources on EINSY and disable FARM_MODE on multi-language version
|
||||
#define PRUSA_FARM
|
||||
#endif //PRUSA_FARM only in english on EINSYs
|
||||
#ifndef XFLASH //enable FARM_MODE on miniRAMBo boards
|
||||
#define PRUSA_FARM
|
||||
#endif
|
||||
#endif //_CONFIG_H
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ char *ftostr43(const float &x, uint8_t offset)
|
|||
//Float to string with 1.23 format
|
||||
char *ftostr12ns(const float &x)
|
||||
{
|
||||
long xx = x * 100;
|
||||
int xx = x * 100;
|
||||
|
||||
xx = abs(xx);
|
||||
conv[0] = (xx / 100) % 10 + '0';
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -142,9 +142,8 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
|
|||
| 0x0F75h 3957 | uint16 | EEPROM_UVLO_MESH_BED_LEVELING | ??? | ff ffh 65535 | Power Panic Mesh Bed Leveling | ??? | D3 Ax0f75 C18
|
||||
| 0x0F73h 3955 | uint16 | EEPROM_UVLO_Z_MICROSTEPS | ??? | ff ffh 65535 | Power Panic Z microsteps | ??? | D3 Ax0f73 C2
|
||||
| 0x0F72h 3954 | uint8 | EEPROM_UVLO_E_ABS | ??? | ffh 255 | Power Panic ??? position | ??? | D3 Ax0f72 C1
|
||||
| 0x0F6Eh 3950 | foat | EEPROM_UVLO_CURRENT_POSITION_E | ??? | ff ff ff ffh | Power Panic E position | ??? | D3 Ax0f6e C4
|
||||
| 0x0F6Dh 3949 | ??? | _EEPROM_FREE_NR2_ | ??? | ffh 255 | _Free EEPROM space_ | _free space_ | D3 Ax0f6d C1
|
||||
| 0x0F6Ch 3948 | ??? | _EEPROM_FREE_NR3_ | ??? | ffh 255 | _Free EEPROM space_ | _free space_ | D3 Ax0f6c C1
|
||||
| 0x0F6Eh 3950 | float | EEPROM_UVLO_CURRENT_POSITION_E | ??? | ff ff ff ffh | Power Panic E position | ??? | D3 Ax0f6e C4
|
||||
| 0x0F6Ch 3948 | uint16_t | EEPROM_UVLO_SAVED_SEGMENT_IDX | all | ff ffh 65535 | Power Panic index of multi-segment move | ??? | D3 Ax0f6c C2
|
||||
| 0x0F6Bh 3947 | ??? | _EEPROM_FREE_NR4_ | ??? | ffh 255 | _Free EEPROM space_ | _free space_ | D3 Ax0f6b C1
|
||||
| 0x0F6Ah 3946 | ??? | _EEPROM_FREE_NR5_ | ??? | ffh 255 | _Free EEPROM space_ | _free space_ | D3 Ax0f6a C1
|
||||
| 0x0F69h 3945 | uint8 | EEPROM_CRASH_DET | ffh 255 | ffh 255 | Crash detection: __enabled__ | LCD menu | D3 Ax0f69 C1
|
||||
|
|
@ -217,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
|
||||
|
|
@ -298,11 +297,11 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
|
|||
| ^ | ^ | ^ | 01h 1 | ^ | Filament Sensor type IR 0.4 or newer | ^ | ^
|
||||
| 0x0D47 3399 | uint8 | EEPROM_FSENSOR_ACTION_NA | 00h 0 | ffh 255 | Filament Sensor action: __Continue__ | LCD menu | D3 Ax0d47 C1
|
||||
| ^ | ^ | ^ | 01h 1 | ^ | Filament Sensor action: __Pause__ | ^ | ^
|
||||
| 0x0D37 3383 | float | EEPROM_UVLO_SAVED_TARGET | ??? | ff ff ff ffh | Power panic saved target all-axis | ??? | D3 Ax0d37 C16
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved target e-axis | ^ | D3 Ax0d43 C4
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved target z-axis | ^ | D3 Ax0d3f C4
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved target y-axis | ^ | D3 Ax0d3b C4
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved target x-axis | ^ | D3 Ax0d37 C4
|
||||
| 0x0D37 3383 | float | EEPROM_UVLO_SAVED_START_POSITION | ??? | ff ff ff ffh | Power panic saved start position all-axis | ??? | D3 Ax0d37 C16
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved start position e-axis | ^ | D3 Ax0d43 C4
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved start position z-axis | ^ | D3 Ax0d3f C4
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved start position y-axis | ^ | D3 Ax0d3b C4
|
||||
| ^ | ^ | ^ | ??? | ^ | Power panic saved start position x-axis | ^ | D3 Ax0d37 C4
|
||||
| 0x0D35 3381 | uint16 | EEPROM_UVLO_FEEDMULTIPLY | ??? | ff ffh 65355 | Power panic saved feed multiplier | ??? | D3 Ax0d35 C2
|
||||
| 0x0D34 3380 | uint8 | EEPROM_BACKLIGHT_LEVEL_HIGH | 00h - ffh | 82h 130 | LCD backlight bright: __128__ Dim value to 255 | LCD menu | D3 Ax0d34 C1
|
||||
| 0x0D33 3379 | uint8 | EEPROM_BACKLIGHT_LEVEL_LOW | 00h - ffh | 32h 50 | LCD backlight dim: __50__ 0 to Bright value | LCD menu | D3 Ax0d33 C1
|
||||
|
|
@ -334,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
|
||||
| :--: | :--: | :--: | :--: | :--: | :--: | :--: | :--:
|
||||
|
|
@ -398,15 +399,14 @@ static_assert(sizeof(Sheets) == EEPROM_SHEETS_SIZEOF, "Sizeof(Sheets) is not EEP
|
|||
#define EEPROM_UVLO_FAN_SPEED (EEPROM_UVLO_FEEDRATE - 1)
|
||||
#define EEPROM_FAN_CHECK_ENABLED (EEPROM_UVLO_FAN_SPEED - 1)
|
||||
#define EEPROM_UVLO_MESH_BED_LEVELING (EEPROM_FAN_CHECK_ENABLED - 9*2)
|
||||
|
||||
#define EEPROM_UVLO_Z_MICROSTEPS (EEPROM_UVLO_MESH_BED_LEVELING - 2) // uint16_t (could be removed)
|
||||
#define EEPROM_UVLO_E_ABS (EEPROM_UVLO_Z_MICROSTEPS - 1)
|
||||
#define EEPROM_UVLO_CURRENT_POSITION_E (EEPROM_UVLO_E_ABS - 4) //float for current position in E
|
||||
#define EEPROM_UVLO_SAVED_SEGMENT_IDX (EEPROM_UVLO_CURRENT_POSITION_E - 2) //uint16_t
|
||||
|
||||
#define EEPROM_FREE_NR2 (EEPROM_UVLO_CURRENT_POSITION_E - 1) // FREE EEPROM SPACE
|
||||
#define EEPROM_FREE_NR3 (EEPROM_FREE_NR2 - 1) // FREE EEPROM SPACE
|
||||
#define EEPROM_FREE_NR4 (EEPROM_FREE_NR3 - 1) // FREE EEPROM SPACE
|
||||
#define EEPROM_FREE_NR4 (EEPROM_UVLO_SAVED_SEGMENT_IDX - 1) // FREE EEPROM SPACE
|
||||
#define EEPROM_FREE_NR5 (EEPROM_FREE_NR4 - 1) // FREE EEPROM SPACE
|
||||
|
||||
// Crash detection mode EEPROM setting
|
||||
#define EEPROM_CRASH_DET (EEPROM_FREE_NR5 - 1) // uint8 (orig EEPROM_UVLO_MESH_BED_LEVELING-12)
|
||||
// Crash detection counter Y (last print)
|
||||
|
|
@ -494,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
|
||||
|
|
@ -526,8 +526,8 @@ static Sheets * const EEPROM_Sheets_base = (Sheets*)(EEPROM_SHEETS_BASE);
|
|||
#define EEPROM_FSENSOR_PCB (EEPROM_SHEETS_BASE-1) // uint8
|
||||
#define EEPROM_FSENSOR_ACTION_NA (EEPROM_FSENSOR_PCB-1) // uint8
|
||||
|
||||
#define EEPROM_UVLO_SAVED_TARGET (EEPROM_FSENSOR_ACTION_NA - 4*4) // 4 x float for saved target for all axes
|
||||
#define EEPROM_UVLO_FEEDMULTIPLY (EEPROM_UVLO_SAVED_TARGET - 2) // uint16_t for feedmultiply
|
||||
#define EEPROM_UVLO_SAVED_START_POSITION (EEPROM_FSENSOR_ACTION_NA - 4*4) // 4 x float for saved start position for all axes
|
||||
#define EEPROM_UVLO_FEEDMULTIPLY (EEPROM_UVLO_SAVED_START_POSITION - 2) // uint16_t for feedmultiply
|
||||
|
||||
#define EEPROM_BACKLIGHT_LEVEL_HIGH (EEPROM_UVLO_FEEDMULTIPLY-1) // uint8
|
||||
#define EEPROM_BACKLIGHT_LEVEL_LOW (EEPROM_BACKLIGHT_LEVEL_HIGH-1) // uint8
|
||||
|
|
@ -550,8 +550,18 @@ static Sheets * const EEPROM_Sheets_base = (Sheets*)(EEPROM_SHEETS_BASE);
|
|||
#define EEPROM_ECOOL_ENABLE (EEPROM_JOB_ID-1) // uint8_t
|
||||
#define EEPROM_FW_CRASH_FLAG (EEPROM_ECOOL_ENABLE-1) // uint8_t
|
||||
|
||||
#define EEPROM_TEMP_MODEL_ENABLE (EEPROM_FW_CRASH_FLAG-1) // uint8_t
|
||||
#define EEPROM_TEMP_MODEL_P (EEPROM_TEMP_MODEL_ENABLE-4) // float
|
||||
#define EEPROM_TEMP_MODEL_C (EEPROM_TEMP_MODEL_P-4) // float
|
||||
#define EEPROM_TEMP_MODEL_R (EEPROM_TEMP_MODEL_C-4*16) // float[16]
|
||||
#define EEPROM_TEMP_MODEL_Ta_corr (EEPROM_TEMP_MODEL_R-4) // float
|
||||
#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_FW_CRASH_FLAG
|
||||
#define EEPROM_LAST_ITEM EEPROM_MMU_ENABLED
|
||||
// !!!!!
|
||||
// !!!!! this is end of EEPROM section ... all updates MUST BE inserted before this mark !!!!!
|
||||
// !!!!!
|
||||
|
|
|
|||
|
|
@ -0,0 +1,300 @@
|
|||
// fan control and check
|
||||
#include "fancheck.h"
|
||||
#include "cardreader.h"
|
||||
#include "ultralcd.h"
|
||||
#include "sound.h"
|
||||
#include "messages.h"
|
||||
#include "temperature.h"
|
||||
#include "stepper.h"
|
||||
|
||||
#define FAN_CHECK_PERIOD 5000 //5s
|
||||
#define FAN_CHECK_DURATION 100 //100ms
|
||||
|
||||
#ifdef FANCHECK
|
||||
volatile uint8_t fan_check_error = EFCE_OK;
|
||||
#endif
|
||||
|
||||
#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
|
||||
#ifdef EXTRUDER_ALTFAN_DETECT
|
||||
static struct
|
||||
{
|
||||
uint8_t isAltfan : 1;
|
||||
uint8_t altfanOverride : 1;
|
||||
} altfanStatus;
|
||||
#endif //EXTRUDER_ALTFAN_DETECT
|
||||
|
||||
unsigned long extruder_autofan_last_check = _millis();
|
||||
bool fan_measuring = false;
|
||||
static uint8_t fanState = 0;
|
||||
#endif
|
||||
|
||||
#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
|
||||
#if defined(FAN_PIN) && FAN_PIN > -1
|
||||
#if EXTRUDER_0_AUTO_FAN_PIN == FAN_PIN
|
||||
#error "You cannot set EXTRUDER_0_AUTO_FAN_PIN equal to FAN_PIN"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void setExtruderAutoFanState(uint8_t state)
|
||||
{
|
||||
//If bit 1 is set (0x02), then the extruder fan speed won't be adjusted according to temperature. Useful for forcing
|
||||
//the fan to either On or Off during certain tests/errors.
|
||||
|
||||
fanState = state;
|
||||
newFanSpeed = 0;
|
||||
if (fanState & 0x01)
|
||||
{
|
||||
#ifdef EXTRUDER_ALTFAN_DETECT
|
||||
if (altfanStatus.isAltfan && !altfanStatus.altfanOverride) newFanSpeed = EXTRUDER_ALTFAN_SPEED_SILENT;
|
||||
else newFanSpeed = EXTRUDER_AUTO_FAN_SPEED;
|
||||
#else //EXTRUDER_ALTFAN_DETECT
|
||||
newFanSpeed = EXTRUDER_AUTO_FAN_SPEED;
|
||||
#endif //EXTRUDER_ALTFAN_DETECT
|
||||
}
|
||||
timer4_set_fan0(newFanSpeed);
|
||||
}
|
||||
|
||||
#if (defined(FANCHECK) && (((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1)))))
|
||||
|
||||
void countFanSpeed()
|
||||
{
|
||||
//SERIAL_ECHOPGM("edge counter 1:"); MYSERIAL.println(fan_edge_counter[1]);
|
||||
fan_speed[0] = (fan_edge_counter[0] * (float(250) / (_millis() - extruder_autofan_last_check)));
|
||||
fan_speed[1] = (fan_edge_counter[1] * (float(250) / (_millis() - extruder_autofan_last_check)));
|
||||
/*SERIAL_ECHOPGM("time interval: "); MYSERIAL.println(_millis() - extruder_autofan_last_check);
|
||||
SERIAL_ECHOPGM("extruder fan speed:"); MYSERIAL.print(fan_speed[0]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[0]);
|
||||
SERIAL_ECHOPGM("print fan speed:"); MYSERIAL.print(fan_speed[1]); SERIAL_ECHOPGM("; edge counter:"); MYSERIAL.println(fan_edge_counter[1]);
|
||||
SERIAL_ECHOLNPGM(" ");*/
|
||||
fan_edge_counter[0] = 0;
|
||||
fan_edge_counter[1] = 0;
|
||||
}
|
||||
|
||||
//! Prints serialMsg to serial port, displays lcdMsg onto the LCD and beeps.
|
||||
//! Extracted from fanSpeedError to save some space.
|
||||
//! @param serialMsg pointer into PROGMEM, this text will be printed to the serial port
|
||||
//! @param lcdMsg pointer into PROGMEM, this text will be printed onto the LCD
|
||||
static void fanSpeedErrorBeep(const char *serialMsg, const char *lcdMsg){
|
||||
SERIAL_ECHOLNRPGM(serialMsg);
|
||||
if (get_message_level() == 0) {
|
||||
Sound_MakeCustom(200,0,true);
|
||||
LCD_ALERTMESSAGERPGM(lcdMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void fanSpeedError(unsigned char _fan) {
|
||||
if (fan_check_error == EFCE_REPORTED) return;
|
||||
fan_check_error = EFCE_REPORTED;
|
||||
|
||||
if (IS_SD_PRINTING || usb_timer.running()) {
|
||||
// A print is ongoing, pause the print normally
|
||||
if(!isPrintPaused) {
|
||||
if (usb_timer.running())
|
||||
lcd_pause_usb_print();
|
||||
else
|
||||
lcd_pause_print();
|
||||
}
|
||||
}
|
||||
else {
|
||||
// Nothing is going on, but still turn off heaters and report the error
|
||||
setTargetHotend0(0);
|
||||
heating_status = HeatingStatus::NO_HEATING;
|
||||
}
|
||||
switch (_fan) {
|
||||
case 0: // extracting the same code from case 0 and case 1 into a function saves 72B
|
||||
fanSpeedErrorBeep(PSTR("Extruder fan speed is lower than expected"), MSG_FANCHECK_EXTRUDER);
|
||||
break;
|
||||
case 1:
|
||||
fanSpeedErrorBeep(PSTR("Print fan speed is lower than expected"), MSG_FANCHECK_PRINT);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void checkFanSpeed()
|
||||
{
|
||||
uint8_t max_fan_errors[2];
|
||||
#ifdef FAN_SOFT_PWM
|
||||
max_fan_errors[1] = 3; // 15 seconds (Print fan)
|
||||
max_fan_errors[0] = 2; // 10 seconds (Extruder fan)
|
||||
#else //FAN_SOFT_PWM
|
||||
max_fan_errors[1] = 15; // 15 seconds (Print fan)
|
||||
max_fan_errors[0] = 5; // 5 seconds (Extruder fan)
|
||||
#endif //FAN_SOFT_PWM
|
||||
|
||||
if(fans_check_enabled)
|
||||
fans_check_enabled = (eeprom_read_byte((uint8_t*)EEPROM_FAN_CHECK_ENABLED) > 0);
|
||||
static uint8_t fan_speed_errors[2] = { 0,0 };
|
||||
#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 >-1))
|
||||
if ((fan_speed[0] < 20) && (current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE)){ fan_speed_errors[0]++;}
|
||||
else fan_speed_errors[0] = 0;
|
||||
#endif
|
||||
#if (defined(FANCHECK) && defined(TACH_1) && (TACH_1 >-1))
|
||||
if ((fan_speed[1] < 5) && ((blocks_queued() ? block_buffer[block_buffer_tail].fan_speed : fanSpeed) > MIN_PRINT_FAN_SPEED)) fan_speed_errors[1]++;
|
||||
else fan_speed_errors[1] = 0;
|
||||
#endif
|
||||
|
||||
// drop the fan_check_error flag when both fans are ok
|
||||
if( fan_speed_errors[0] == 0 && fan_speed_errors[1] == 0 && fan_check_error == EFCE_REPORTED){
|
||||
// we may even send some info to the LCD from here
|
||||
fan_check_error = EFCE_FIXED;
|
||||
}
|
||||
if ((fan_check_error == EFCE_FIXED) && !printer_active()){
|
||||
fan_check_error = EFCE_OK; //if the issue is fixed while the printer is doing nothing, reenable processing immediately.
|
||||
lcd_reset_alert_level(); //for another fan speed error
|
||||
}
|
||||
if (fans_check_enabled && (fan_check_error == EFCE_OK))
|
||||
{
|
||||
for (uint8_t fan = 0; fan < 2; fan++)
|
||||
{
|
||||
if (fan_speed_errors[fan] > max_fan_errors[fan])
|
||||
{
|
||||
fan_speed_errors[fan] = 0;
|
||||
fanSpeedError(fan);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1)
|
||||
|
||||
#ifdef EXTRUDER_ALTFAN_DETECT
|
||||
ISR(INT6_vect) {
|
||||
fan_edge_counter[0]++;
|
||||
}
|
||||
|
||||
bool extruder_altfan_detect()
|
||||
{
|
||||
setExtruderAutoFanState(3);
|
||||
|
||||
SET_INPUT(TACH_0);
|
||||
|
||||
uint8_t overrideVal = eeprom_read_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE);
|
||||
if (overrideVal == EEPROM_EMPTY_VALUE)
|
||||
{
|
||||
overrideVal = (calibration_status() == CALIBRATION_STATUS_CALIBRATED) ? 1 : 0;
|
||||
eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, overrideVal);
|
||||
}
|
||||
altfanStatus.altfanOverride = overrideVal;
|
||||
|
||||
CRITICAL_SECTION_START;
|
||||
EICRB &= ~(1 << ISC61);
|
||||
EICRB |= (1 << ISC60);
|
||||
EIMSK |= (1 << INT6);
|
||||
fan_edge_counter[0] = 0;
|
||||
CRITICAL_SECTION_END;
|
||||
extruder_autofan_last_check = _millis();
|
||||
|
||||
_delay(1000);
|
||||
|
||||
EIMSK &= ~(1 << INT6);
|
||||
|
||||
countFanSpeed();
|
||||
altfanStatus.isAltfan = fan_speed[0] > 100;
|
||||
setExtruderAutoFanState(1);
|
||||
return altfanStatus.isAltfan;
|
||||
}
|
||||
|
||||
void altfanOverride_toggle()
|
||||
{
|
||||
altfanStatus.altfanOverride = !altfanStatus.altfanOverride;
|
||||
eeprom_update_byte((uint8_t *)EEPROM_ALTFAN_OVERRIDE, altfanStatus.altfanOverride);
|
||||
}
|
||||
|
||||
bool altfanOverride_get()
|
||||
{
|
||||
return altfanStatus.altfanOverride;
|
||||
}
|
||||
|
||||
#endif //EXTRUDER_ALTFAN_DETECT
|
||||
|
||||
void checkExtruderAutoFans()
|
||||
{
|
||||
#if defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1
|
||||
if (!(fanState & 0x02))
|
||||
{
|
||||
fanState &= ~1;
|
||||
fanState |= current_temperature[0] > EXTRUDER_AUTO_FAN_TEMPERATURE;
|
||||
fanState |= get_temp_error();
|
||||
}
|
||||
setExtruderAutoFanState(fanState);
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // any extruder auto fan pins set
|
||||
|
||||
#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
|
||||
void readFanTach() {
|
||||
#ifdef FAN_SOFT_PWM
|
||||
if (READ(TACH_0) != fan_state[0]) {
|
||||
if(fan_measuring) fan_edge_counter[0] ++;
|
||||
fan_state[0] = !fan_state[0];
|
||||
}
|
||||
#else //FAN_SOFT_PWM
|
||||
if (READ(TACH_0) != fan_state[0]) {
|
||||
fan_edge_counter[0] ++;
|
||||
fan_state[0] = !fan_state[0];
|
||||
}
|
||||
#endif
|
||||
//if (READ(TACH_1) != fan_state[1]) {
|
||||
// fan_edge_counter[1] ++;
|
||||
// fan_state[1] = !fan_state[1];
|
||||
//}
|
||||
}
|
||||
#endif //TACH_0
|
||||
|
||||
void checkFans()
|
||||
{
|
||||
#ifndef DEBUG_DISABLE_FANCHECK
|
||||
#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
|
||||
|
||||
#ifdef FAN_SOFT_PWM
|
||||
#ifdef FANCHECK
|
||||
if ((_millis() - extruder_autofan_last_check > FAN_CHECK_PERIOD) && (!fan_measuring)) {
|
||||
extruder_autofan_last_check = _millis();
|
||||
fanSpeedBckp = fanSpeedSoftPwm;
|
||||
|
||||
if (fanSpeedSoftPwm >= MIN_PRINT_FAN_SPEED) { //if we are in rage where we are doing fan check, set full PWM range for a short time to measure fan RPM by reading tacho signal without modulation by PWM signal
|
||||
// printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm);
|
||||
fanSpeedSoftPwm = 255;
|
||||
}
|
||||
fan_measuring = true;
|
||||
}
|
||||
if ((_millis() - extruder_autofan_last_check > FAN_CHECK_DURATION) && (fan_measuring)) {
|
||||
countFanSpeed();
|
||||
checkFanSpeed();
|
||||
//printf_P(PSTR("fanSpeedSoftPwm 1: %d\n"), fanSpeedSoftPwm);
|
||||
fanSpeedSoftPwm = fanSpeedBckp;
|
||||
//printf_P(PSTR("fan PWM: %d; extr fanSpeed measured: %d; print fan speed measured: %d \n"), fanSpeedBckp, fan_speed[0], fan_speed[1]);
|
||||
extruder_autofan_last_check = _millis();
|
||||
fan_measuring = false;
|
||||
}
|
||||
#endif //FANCHECK
|
||||
checkExtruderAutoFans();
|
||||
#else //FAN_SOFT_PWM
|
||||
if(_millis() - extruder_autofan_last_check > 1000) // only need to check fan state very infrequently
|
||||
{
|
||||
#if (defined(FANCHECK) && ((defined(TACH_0) && (TACH_0 >-1)) || (defined(TACH_1) && (TACH_1 > -1))))
|
||||
countFanSpeed();
|
||||
checkFanSpeed();
|
||||
#endif //(defined(TACH_0) && TACH_0 >-1) || (defined(TACH_1) && TACH_1 > -1)
|
||||
checkExtruderAutoFans();
|
||||
extruder_autofan_last_check = _millis();
|
||||
}
|
||||
#endif //FAN_SOFT_PWM
|
||||
|
||||
#endif
|
||||
#endif //DEBUG_DISABLE_FANCHECK
|
||||
}
|
||||
|
||||
void hotendFanSetFullSpeed()
|
||||
{
|
||||
#ifdef EXTRUDER_ALTFAN_DETECT
|
||||
altfanStatus.altfanOverride = 1; //full speed
|
||||
#endif //EXTRUDER_ALTFAN_DETECT
|
||||
setExtruderAutoFanState(3);
|
||||
SET_OUTPUT(FAN_PIN);
|
||||
#ifdef FAN_SOFT_PWM
|
||||
fanSpeedSoftPwm = 255;
|
||||
#else //FAN_SOFT_PWM
|
||||
analogWrite(FAN_PIN, 255);
|
||||
#endif //FAN_SOFT_PWM
|
||||
fanSpeed = 255;
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// fan control and check
|
||||
#pragma once
|
||||
|
||||
#include "Configuration.h"
|
||||
#include "config.h"
|
||||
|
||||
#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
|
||||
enum {
|
||||
EFCE_OK = 0, //!< normal operation, both fans are ok
|
||||
EFCE_FIXED, //!< previous fan error was fixed
|
||||
EFCE_REPORTED //!< fan error detected and reported to LCD and serial
|
||||
};
|
||||
extern volatile uint8_t fan_check_error;
|
||||
|
||||
void readFanTach();
|
||||
#endif //(defined(TACH_0))
|
||||
|
||||
#ifdef EXTRUDER_ALTFAN_DETECT
|
||||
extern bool extruder_altfan_detect();
|
||||
extern void altfanOverride_toggle();
|
||||
extern bool altfanOverride_get();
|
||||
#endif //EXTRUDER_ALTFAN_DETECT
|
||||
|
||||
#if (defined(EXTRUDER_0_AUTO_FAN_PIN) && EXTRUDER_0_AUTO_FAN_PIN > -1)
|
||||
#ifdef FAN_SOFT_PWM
|
||||
extern bool fan_measuring;
|
||||
#endif //FAN_SOFT_PWM
|
||||
|
||||
extern unsigned long extruder_autofan_last_check;
|
||||
void setExtruderAutoFanState(uint8_t state);
|
||||
void checkExtruderAutoFans();
|
||||
#endif
|
||||
|
||||
void checkFans();
|
||||
void hotendFanSetFullSpeed();
|
||||
|
|
@ -8,33 +8,25 @@
|
|||
#include "language.h"
|
||||
#include "Marlin.h"
|
||||
#include "cmdqueue.h"
|
||||
#include "mmu.h"
|
||||
#include "mmu2.h"
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
//! @brief Wait for preheat
|
||||
void lay1cal_wait_preheat()
|
||||
{
|
||||
static const char cmd_preheat_0[] PROGMEM = "M107";
|
||||
static const char cmd_preheat_1[] PROGMEM = "M190";
|
||||
static const char cmd_preheat_2[] PROGMEM = "M109";
|
||||
static const char cmd_preheat_4[] PROGMEM = "G28";
|
||||
static const char cmd_preheat_5[] PROGMEM = "G92 E0.0";
|
||||
|
||||
const char * const preheat_cmd[] =
|
||||
{
|
||||
cmd_preheat_0,
|
||||
cmd_preheat_1,
|
||||
cmd_preheat_2,
|
||||
_T(MSG_M117_V2_CALIBRATION),
|
||||
cmd_preheat_4,
|
||||
cmd_preheat_5,
|
||||
PSTR("M107"),
|
||||
PSTR("M190"),
|
||||
PSTR("M109"),
|
||||
PSTR("G28"),
|
||||
PSTR("G92 E0.0")
|
||||
};
|
||||
|
||||
for (uint8_t i = 0; i < (sizeof(preheat_cmd)/sizeof(preheat_cmd[0])); ++i)
|
||||
{
|
||||
enquecommand_P(preheat_cmd[i]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//! @brief Load filament
|
||||
|
|
@ -42,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"));
|
||||
|
|
@ -81,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,788 +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
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
if(!bIRsensorStateFlag)
|
||||
{
|
||||
bIRsensorStateFlag=true;
|
||||
tIRsensorCheckTimer.start();
|
||||
}
|
||||
else
|
||||
{
|
||||
if(tIRsensorCheckTimer.expired(IR_SENSOR_STEADY))
|
||||
{
|
||||
uint8_t nMUX1,nMUX2;
|
||||
uint16_t nADC;
|
||||
bIRsensorStateFlag=false;
|
||||
// sequence for direct data reading from AD converter
|
||||
DISABLE_TEMPERATURE_INTERRUPT();
|
||||
nMUX1=ADMUX; // ADMUX saving
|
||||
nMUX2=ADCSRB;
|
||||
adc_setmux(VOLT_IR_PIN);
|
||||
ADCSRA|=(1<<ADSC); // first conversion after ADMUX change discarded (preventively)
|
||||
while(ADCSRA&(1<<ADSC))
|
||||
;
|
||||
ADCSRA|=(1<<ADSC); // second conversion used
|
||||
while(ADCSRA&(1<<ADSC))
|
||||
;
|
||||
nADC=ADC;
|
||||
ADMUX=nMUX1; // ADMUX restoring
|
||||
ADCSRB=nMUX2;
|
||||
ENABLE_TEMPERATURE_INTERRUPT();
|
||||
// end of sequence for ...
|
||||
// Detection of correct function of fsensor v04 - it must NOT read >4.6V
|
||||
// If it does, it means a disconnected cables or faulty board
|
||||
if( (oFsensorPCB == ClFsensorPCB::_Rev04) && ( (nADC*OVERSAMPLENR) > IRsensor_Hopen_TRESHOLD ) )
|
||||
{
|
||||
fsensor_disable();
|
||||
fsensor_not_responding = true;
|
||||
printf_P(PSTR("IR sensor not responding (%d)!\n"),1);
|
||||
if((ClFsensorActionNA)eeprom_read_byte((uint8_t*)EEPROM_FSENSOR_ACTION_NA)==ClFsensorActionNA::_Pause)
|
||||
|
||||
// if we are printing and FS action is set to "Pause", force pause the print
|
||||
if(oFsensorActionNA==ClFsensorActionNA::_Pause)
|
||||
lcd_pause_print();
|
||||
}
|
||||
else
|
||||
{
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
fsensor_checkpoint_print();
|
||||
fsensor_enque_M600();
|
||||
#ifdef IR_SENSOR_ANALOG
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{ // IR_SENSOR_PIN ~ L
|
||||
bIRsensorStateFlag=false;
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
}
|
||||
}
|
||||
#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
|
||||
|
|
@ -240,9 +240,9 @@ const char* lang_get_name_by_code(uint16_t code)
|
|||
#ifdef COMMUNITY_LANG_GROUP1_HR
|
||||
case LANG_CODE_HR: return _n("Hrvatski"); //community Croatian contribution
|
||||
#endif // COMMUNITY_LANG_GROUP1_HR
|
||||
#ifdef COMMUNITY_LANG_GROUP1_LT
|
||||
#ifdef COMMUNITY_LANG_GROUP2_LT
|
||||
case LANG_CODE_LT: return _n("Lietuviu"); //community Lithuanian contribution
|
||||
#endif // COMMUNITY_LANG_GROUP1_LT
|
||||
#endif // COMMUNITY_LANG_GROUP2_LT
|
||||
#ifdef COMMUNITY_LANG_GROUP1_RO
|
||||
case LANG_CODE_RO: return _n("Romana"); //community Romanian contribution
|
||||
#endif // COMMUNITY_LANG_GROUP1_RO
|
||||
|
|
|
|||
|
|
@ -26,10 +26,10 @@
|
|||
#define PROGMEM_I1 __attribute__((section(".progmem1")))
|
||||
#define PROGMEM_N1 __attribute__((section(".progmem2")))
|
||||
#define _I(s) (__extension__({static const char __c[] PROGMEM_I1 = s; &__c[0];}))
|
||||
#define ISTR(s) s
|
||||
#define _i(s) _I(s)
|
||||
#define _T(s) s
|
||||
#define _O(s) s
|
||||
#define ISTR(s) (s) // declare a translatable string
|
||||
#define _i(s) _I(s) // declare a translatable string and return the translated form
|
||||
#define _T(s) (s) // return translated string from reference
|
||||
#define _O(s) (s) // return original (untranslated) string from reference
|
||||
#else //(LANG_MODE == 0)
|
||||
// section .loc_sec (originaly .progmem0) will be used for localized translated strings
|
||||
#define PROGMEM_I2 __attribute__((section(".loc_sec")))
|
||||
|
|
@ -43,8 +43,10 @@
|
|||
#define _T(s) lang_get_translation(s)
|
||||
#define _O(s) (s + 2)
|
||||
#endif //(LANG_MODE == 0)
|
||||
|
||||
#define _N(s) (__extension__({static const char __c[] PROGMEM_N1 = s; &__c[0];}))
|
||||
#define _n(s) _N(s)
|
||||
#define _n(s) _N(s) // declare and return untranslated string
|
||||
#define _R(s) (s) // return reference to translatable string (for warning suppression)
|
||||
|
||||
/** @brief lang_table_header_t structure - (size= 16byte) */
|
||||
typedef struct
|
||||
|
|
@ -121,9 +123,9 @@ typedef struct
|
|||
#ifdef COMMUNITY_LANG_GROUP1_HR
|
||||
#define LANG_CODE_HR 0x6872 //!<'hr'
|
||||
#endif // COMMUNITY_LANG_GROUP1_HR
|
||||
#ifdef COMMUNITY_LANG_GROUP1_LT
|
||||
#ifdef COMMUNITY_LANG_GROUP2_LT
|
||||
#define LANG_CODE_LT 0x6C74 //!<'lt'
|
||||
#endif // COMMUNITY_LANG_GROUP1_LT
|
||||
#endif // COMMUNITY_LANG_GROUP2_LT
|
||||
#ifdef COMMUNITY_LANG_GROUP1_SK
|
||||
#define LANG_CODE_SK 0x736b //!<'sk'
|
||||
#endif // COMMUNITY_LANG_GROUP1_SK
|
||||
|
|
|
|||
|
|
@ -528,6 +528,12 @@ void lcd_print(const char* s)
|
|||
while (*s) lcd_write(*(s++));
|
||||
}
|
||||
|
||||
void lcd_print_pad(const char* s, uint8_t len)
|
||||
{
|
||||
while (len-- && *s) lcd_write(*(s++));
|
||||
lcd_space(len);
|
||||
}
|
||||
|
||||
void lcd_print(char c, int base)
|
||||
{
|
||||
lcd_print((long) c, base);
|
||||
|
|
@ -638,7 +644,6 @@ uint8_t lcd_button_pressed = 0;
|
|||
uint8_t lcd_update_enabled = 1;
|
||||
|
||||
uint32_t lcd_next_update_millis = 0;
|
||||
uint8_t lcd_status_update_delay = 0;
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ extern void lcd_printNumber(unsigned long n, uint8_t base);
|
|||
extern void lcd_printFloat(double number, uint8_t digits);
|
||||
|
||||
extern void lcd_print(const char*);
|
||||
extern void lcd_print_pad(const char*, uint8_t len);
|
||||
extern void lcd_print(char, int = 0);
|
||||
extern void lcd_print(unsigned char, int = 0);
|
||||
extern void lcd_print(int, int = 10);
|
||||
|
|
@ -107,8 +108,6 @@ extern LongTimer lcd_timeoutToStatus;
|
|||
|
||||
extern uint32_t lcd_next_update_millis;
|
||||
|
||||
extern uint8_t lcd_status_update_delay;
|
||||
|
||||
extern lcd_longpress_func_t lcd_longpress_func;
|
||||
extern bool lcd_longpress_trigger;
|
||||
|
||||
|
|
@ -191,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
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,9 @@
|
|||
#define CRITICAL_SECTION_END SREG = _sreg;
|
||||
#endif //CRITICAL_SECTION_START
|
||||
|
||||
#define _REGNAME(registerbase,number,suffix) registerbase##number##suffix
|
||||
#define _REGNAME_SHORT(registerbase,suffix) registerbase##suffix
|
||||
|
||||
// Macros to make a string from a macro
|
||||
#define STRINGIFY_(M) #M
|
||||
#define STRINGIFY(M) STRINGIFY_(M)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ uint8_t menu_data[MENU_DATA_SIZE];
|
|||
#endif
|
||||
|
||||
uint8_t menu_depth = 0;
|
||||
uint8_t menu_block_entering_on_serious_errors = SERIOUS_ERR_NONE;
|
||||
uint8_t menu_block_mask = MENU_BLOCK_NONE;
|
||||
uint8_t menu_line = 0;
|
||||
uint8_t menu_item = 0;
|
||||
uint8_t menu_row = 0;
|
||||
|
|
|
|||
|
|
@ -29,26 +29,26 @@ extern uint8_t menu_data[MENU_DATA_SIZE];
|
|||
|
||||
extern uint8_t menu_depth;
|
||||
|
||||
//! definition of serious errors possibly blocking the main menu
|
||||
//! definition of reasons blocking the main menu
|
||||
//! Use them as bit mask, so that the code may set various errors at the same time
|
||||
enum ESeriousErrors {
|
||||
SERIOUS_ERR_NONE = 0,
|
||||
SERIOUS_ERR_MINTEMP_HEATER = 0x01,
|
||||
SERIOUS_ERR_MINTEMP_BED = 0x02
|
||||
MENU_BLOCK_NONE = 0,
|
||||
MENU_BLOCK_THERMAL_ERROR = 0x01,
|
||||
#ifdef TEMP_MODEL
|
||||
MENU_BLOCK_TEMP_MODEL_AUTOTUNE = 0x02,
|
||||
#endif
|
||||
}; // and possibly others in the future.
|
||||
|
||||
//! this is a flag for disabling entering the main menu. If this is set
|
||||
//! to anything != 0, the only the main status screen will be shown on the
|
||||
//! LCD and the user will be prevented from entering the menu.
|
||||
//! Now used only to block doing anything with the printer when there is
|
||||
//! the infamous MINTEMP error (SERIOUS_ERR_MINTEMP).
|
||||
extern uint8_t menu_block_entering_on_serious_errors;
|
||||
//! this is a flag for disabling entering the main menu and longpress. If this is set to anything !=
|
||||
//! 0, the only the main status screen will be shown on the LCD and the user will be prevented from
|
||||
//! entering the menu.
|
||||
extern uint8_t menu_block_mask;
|
||||
|
||||
//! a pair of macros for manipulating the serious errors
|
||||
//! a pair of macros for manipulating menu entry
|
||||
//! a c++ class would have been better
|
||||
#define menu_set_serious_error(x) menu_block_entering_on_serious_errors |= x;
|
||||
#define menu_unset_serious_error(x) menu_block_entering_on_serious_errors &= ~x;
|
||||
#define menu_is_serious_error(x) (menu_block_entering_on_serious_errors & x) != 0
|
||||
#define menu_set_block(x) menu_block_mask |= x;
|
||||
#define menu_unset_block(x) menu_block_mask &= ~x;
|
||||
#define menu_is_blocked(x) (menu_block_mask & x) != 0
|
||||
|
||||
extern uint8_t menu_line;
|
||||
extern uint8_t menu_item;
|
||||
|
|
|
|||
|
|
@ -418,9 +418,9 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
|
|||
for (uint8_t i = 0; i < npts; ++i) {
|
||||
float x = vec_x[0] * measured_pts[i * 2] + vec_y[0] * measured_pts[i * 2 + 1] + cntr[0];
|
||||
float y = vec_x[1] * measured_pts[i * 2] + vec_y[1] * measured_pts[i * 2 + 1] + cntr[1];
|
||||
float errX = sqr(pgm_read_float(true_pts + i * 2) - x);
|
||||
float errY = sqr(pgm_read_float(true_pts + i * 2 + 1) - y);
|
||||
float err = sqrt(errX + errY);
|
||||
float errX = pgm_read_float(true_pts + i * 2) - x;
|
||||
float errY = pgm_read_float(true_pts + i * 2 + 1) - y;
|
||||
float err = hypot(errX, errY);
|
||||
#ifdef SUPPORT_VERBOSITY
|
||||
if (verbosity_level >= 10) {
|
||||
SERIAL_ECHOPGM("point #");
|
||||
|
|
@ -434,15 +434,15 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
|
|||
if(verbosity_level >= 20) SERIAL_ECHOPGM("Point on first row");
|
||||
#endif // SUPPORT_VERBOSITY
|
||||
float w = point_weight_y(i, measured_pts[2 * i + 1]);
|
||||
if (sqrt(errX) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X ||
|
||||
(w != 0.f && sqrt(errY) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y)) {
|
||||
if (errX > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X ||
|
||||
(w != 0.f && errY > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y)) {
|
||||
result = BED_SKEW_OFFSET_DETECTION_FITTING_FAILED;
|
||||
#ifdef SUPPORT_VERBOSITY
|
||||
if (verbosity_level >= 20) {
|
||||
SERIAL_ECHOPGM(", weigth Y: ");
|
||||
MYSERIAL.print(w);
|
||||
if (sqrt(errX) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X) SERIAL_ECHOPGM(", error X > max. error X");
|
||||
if (w != 0.f && sqrt(errY) > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y) SERIAL_ECHOPGM(", error Y > max. error Y");
|
||||
if (errX > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_X) SERIAL_ECHOPGM(", error X > max. error X");
|
||||
if (w != 0.f && errY > BED_CALIBRATION_POINT_OFFSET_MAX_1ST_ROW_Y) SERIAL_ECHOPGM(", error Y > max. error Y");
|
||||
}
|
||||
#endif // SUPPORT_VERBOSITY
|
||||
}
|
||||
|
|
@ -477,9 +477,9 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
|
|||
SERIAL_ECHOPGM("error: ");
|
||||
MYSERIAL.print(err);
|
||||
SERIAL_ECHOPGM(", error X: ");
|
||||
MYSERIAL.print(sqrt(errX));
|
||||
MYSERIAL.print(errX);
|
||||
SERIAL_ECHOPGM(", error Y: ");
|
||||
MYSERIAL.print(sqrt(errY));
|
||||
MYSERIAL.print(errY);
|
||||
SERIAL_ECHOLNPGM("");
|
||||
SERIAL_ECHOLNPGM("");
|
||||
}
|
||||
|
|
@ -645,7 +645,7 @@ BedSkewOffsetDetectionResultType calculate_machine_skew_and_offset_LS(
|
|||
SERIAL_ECHOPGM(", ");
|
||||
MYSERIAL.print(pgm_read_float(true_pts + i * 2 + 1), 5);
|
||||
SERIAL_ECHOPGM("), error: ");
|
||||
MYSERIAL.print(sqrt(sqr(measured_pts[i * 2] - x) + sqr(measured_pts[i * 2 + 1] - y)));
|
||||
MYSERIAL.print( hypot(measured_pts[i * 2] - x, measured_pts[i * 2 + 1] - y) );
|
||||
SERIAL_ECHOLNPGM("");
|
||||
}
|
||||
if (verbosity_level >= 20) {
|
||||
|
|
@ -810,7 +810,7 @@ void world2machine_read_valid(float vec_x[2], float vec_y[2], float cntr[2])
|
|||
else
|
||||
{
|
||||
// Length of the vec_x shall be close to unity.
|
||||
float l = sqrt(vec_x[0] * vec_x[0] + vec_x[1] * vec_x[1]);
|
||||
float l = hypot(vec_x[0], vec_x[1]);
|
||||
if (l < 0.9 || l > 1.1)
|
||||
{
|
||||
#if 0
|
||||
|
|
@ -821,7 +821,7 @@ void world2machine_read_valid(float vec_x[2], float vec_y[2], float cntr[2])
|
|||
reset = true;
|
||||
}
|
||||
// Length of the vec_y shall be close to unity.
|
||||
l = sqrt(vec_y[0] * vec_y[0] + vec_y[1] * vec_y[1]);
|
||||
l = hypot(vec_y[0], vec_y[1]);
|
||||
if (l < 0.9 || l > 1.1)
|
||||
{
|
||||
#if 0
|
||||
|
|
@ -832,7 +832,7 @@ void world2machine_read_valid(float vec_x[2], float vec_y[2], float cntr[2])
|
|||
reset = true;
|
||||
}
|
||||
// Correction of the zero point shall be reasonably small.
|
||||
l = sqrt(cntr[0] * cntr[0] + cntr[1] * cntr[1]);
|
||||
l = hypot(cntr[0], cntr[1]);
|
||||
if (l > 15.f)
|
||||
{
|
||||
#if 0
|
||||
|
|
@ -974,7 +974,7 @@ bool find_bed_induction_sensor_point_z(float minimum_z, uint8_t n_iter, int
|
|||
goto error;
|
||||
}
|
||||
#ifdef TMC2130
|
||||
if (READ(Z_TMC2130_DIAG) != 0)
|
||||
if (!READ(Z_TMC2130_DIAG))
|
||||
{
|
||||
//printf_P(PSTR("crash detected 1, current_pos[Z]: %f \n"), current_position[Z_AXIS]);
|
||||
goto error; //crash Z detected
|
||||
|
|
@ -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.
|
||||
|
|
@ -1011,7 +1010,7 @@ bool find_bed_induction_sensor_point_z(float minimum_z, uint8_t n_iter, int
|
|||
goto error;
|
||||
}
|
||||
#ifdef TMC2130
|
||||
if (READ(Z_TMC2130_DIAG) != 0) {
|
||||
if (!READ(Z_TMC2130_DIAG)) {
|
||||
//printf_P(PSTR("crash detected 2, current_pos[Z]: %f \n"), current_position[Z_AXIS]);
|
||||
goto error; //crash Z detected
|
||||
}
|
||||
|
|
@ -1579,7 +1578,7 @@ inline bool improve_bed_induction_sensor_point()
|
|||
// Trim the vector from center_old_[x,y] to destination[x,y] by the bed dimensions.
|
||||
float vx = destination[X_AXIS] - center_old_x;
|
||||
float vy = destination[Y_AXIS] - center_old_y;
|
||||
float l = sqrt(vx*vx+vy*vy);
|
||||
float l = hypot(vx, vy);
|
||||
float t;
|
||||
if (destination[X_AXIS] < X_MIN_POS) {
|
||||
// Exiting the bed at xmin.
|
||||
|
|
@ -2441,16 +2440,16 @@ BedSkewOffsetDetectionResultType find_bed_offset_and_skew(int8_t verbosity_level
|
|||
#ifdef SUPPORT_VERBOSITY
|
||||
if (verbosity_level >= 10) {
|
||||
// Length of the vec_x
|
||||
float l = sqrt(vec_x[0] * vec_x[0] + vec_x[1] * vec_x[1]);
|
||||
float l = hypot(vec_x[0], vec_x[1]);
|
||||
SERIAL_ECHOLNPGM("X vector length:");
|
||||
MYSERIAL.println(l);
|
||||
|
||||
// Length of the vec_y
|
||||
l = sqrt(vec_y[0] * vec_y[0] + vec_y[1] * vec_y[1]);
|
||||
l = hypot(vec_y[0], vec_y[1]);
|
||||
SERIAL_ECHOLNPGM("Y vector length:");
|
||||
MYSERIAL.println(l);
|
||||
// Zero point correction
|
||||
l = sqrt(cntr[0] * cntr[0] + cntr[1] * cntr[1]);
|
||||
l = hypot(cntr[0], cntr[1]);
|
||||
SERIAL_ECHOLNPGM("Zero point correction:");
|
||||
MYSERIAL.println(l);
|
||||
|
||||
|
|
@ -2528,7 +2527,7 @@ BedSkewOffsetDetectionResultType improve_bed_offset_and_skew(int8_t method, int8
|
|||
bool endstop_z_enabled = enable_z_endstop(false);
|
||||
|
||||
#ifdef MESH_BED_CALIBRATION_SHOW_LCD
|
||||
lcd_display_message_fullscreen_P(_i("Improving bed calibration point"));////MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1 c=60
|
||||
lcd_display_message_fullscreen_P(_i("Improving bed calibration point"));////MSG_IMPROVE_BED_OFFSET_AND_SKEW_LINE1 c=20 r=4
|
||||
#endif /* MESH_BED_CALIBRATION_SHOW_LCD */
|
||||
|
||||
// Collect a matrix of 9x9 points.
|
||||
|
|
@ -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.
|
||||
|
|
@ -2857,7 +2853,7 @@ bool sample_mesh_and_store_reference()
|
|||
homeaxis(Z_AXIS);
|
||||
|
||||
#ifdef TMC2130
|
||||
if (!axis_known_position[Z_AXIS] && (READ(Z_TMC2130_DIAG) != 0)) //Z crash
|
||||
if (!axis_known_position[Z_AXIS] && (!READ(Z_TMC2130_DIAG))) //Z crash
|
||||
{
|
||||
kill(_T(MSG_BED_LEVELING_FAILED_POINT_LOW));
|
||||
return false;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "Configuration_prusa.h"
|
||||
|
||||
//internationalized messages
|
||||
const char MSG_ALWAYS[] PROGMEM_I1 = ISTR("Always"); ////MSG_ALWAYS c=6
|
||||
const char MSG_AUTO_HOME[] PROGMEM_I1 = ISTR("Auto home"); ////MSG_AUTO_HOME c=18
|
||||
const char MSG_BABYSTEP_Z[] PROGMEM_I1 = ISTR("Live adjust Z"); ////MSG_BABYSTEP_Z c=18
|
||||
const char MSG_BABYSTEP_Z_NOT_SET[] PROGMEM_I1 = ISTR("Distance between tip of the nozzle and the bed surface has not been set yet. Please follow the manual, chapter First steps, section First layer calibration."); ////MSG_BABYSTEP_Z_NOT_SET c=20 r=12
|
||||
|
|
@ -14,7 +15,7 @@ const char MSG_BED_HEATING[] PROGMEM_I1 = ISTR("Bed Heating"); ////MSG_BED_HEATI
|
|||
const char MSG_BED_LEVELING_FAILED_POINT_LOW[] PROGMEM_I1 = ISTR("Bed leveling failed. Sensor didn't trigger. Debris on nozzle? Waiting for reset."); ////MSG_BED_LEVELING_FAILED_POINT_LOW c=20 r=6
|
||||
const char MSG_BED_SKEW_OFFSET_DETECTION_FITTING_FAILED[] PROGMEM_I1 = ISTR("XYZ calibration failed. Please consult the manual."); ////MSG_BED_SKEW_OFFSET_DETECTION_FITTING_FAILED c=20 r=8
|
||||
const char MSG_BELT_STATUS[] PROGMEM_I1 = ISTR("Belt status");////MSG_BELT_STATUS c=18
|
||||
const char MSG_CANCEL[] PROGMEM_I1 = ISTR(">Cancel");////MSG_CANCEL c=9
|
||||
const char MSG_CANCEL[] PROGMEM_I1 = ISTR(">Cancel");////MSG_CANCEL c=10
|
||||
const char MSG_CALIBRATE_Z_AUTO[] PROGMEM_I1 = ISTR("Calibrating Z"); ////MSG_CALIBRATE_Z_AUTO c=20 r=2
|
||||
const char MSG_CARD_MENU[] PROGMEM_I1 = ISTR("Print from SD"); ////MSG_CARD_MENU c=18
|
||||
const char MSG_CHECKING_X[] PROGMEM_I1 = ISTR("Checking X axis"); ////MSG_CHECKING_X c=20
|
||||
|
|
@ -40,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
|
||||
|
|
@ -51,10 +54,11 @@ 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_M117_V2_CALIBRATION[] PROGMEM_I1 = ISTR("M117 First layer cal."); ////MSG_M117_V2_CALIBRATION c=25
|
||||
const char MSG_MAIN[] PROGMEM_I1 = ISTR("Main"); ////MSG_MAIN c=18
|
||||
const char MSG_BACK[] PROGMEM_I1 = ISTR("Back"); ////MSG_BACK c=18
|
||||
const char MSG_SHEET[] PROGMEM_I1 = ISTR("Sheet"); ////MSG_SHEET c=10
|
||||
|
|
@ -92,6 +96,8 @@ const char MSG_SELFTEST_MOTOR[] PROGMEM_I1 = ISTR("Motor"); ////MSG_SELFTEST_MOT
|
|||
const char MSG_SELFTEST_FILAMENT_SENSOR[] PROGMEM_I1 = ISTR("Filament sensor"); ////MSG_SELFTEST_FILAMENT_SENSOR c=17
|
||||
const char MSG_SELFTEST_WIRINGERROR[] PROGMEM_I1 = ISTR("Wiring error"); ////MSG_SELFTEST_WIRINGERROR c=18
|
||||
const char MSG_SETTINGS[] PROGMEM_I1 = ISTR("Settings"); ////MSG_SETTINGS c=18
|
||||
const char MSG_SELECT_LANGUAGE[] PROGMEM_I1 = ISTR("Select language"); ////MSG_SELECT_LANGUAGE c=18
|
||||
const char MSG_SORTING_FILES[] PROGMEM_I1 = ISTR("Sorting files"); ////MSG_SORTING_FILES c=20
|
||||
const char MSG_TOTAL[] PROGMEM_I1 = ISTR("Total"); ////MSG_TOTAL c=6
|
||||
const char MSG_TOTAL_FAILURES[] PROGMEM_I1 = ISTR("Total failures"); ////MSG_TOTAL_FAILURES c=20
|
||||
const char MSG_HW_SETUP[] PROGMEM_I1 = ISTR("HW Setup"); ////MSG_HW_SETUP c=18
|
||||
|
|
@ -106,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
|
||||
|
|
@ -114,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
|
||||
|
|
@ -153,18 +159,23 @@ 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
|
||||
const char MSG_IR_UNKNOWN[] PROGMEM_I1 = ISTR("unknown state");////MSG_IR_UNKNOWN c=18
|
||||
#endif
|
||||
extern const char MSG_PAUSED_THERMAL_ERROR[] PROGMEM_I1 = ISTR("PAUSED THERMAL ERROR");////MSG_PAUSED_THERMAL_ERROR c=20
|
||||
#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 = ISTR("SpoolJoin"); ////MSG_AUTO_DEPLETE c=13
|
||||
const char MSG_FIRMWARE[] PROGMEM_N1 = ISTR("Firmware"); ////MSG_FIRMWARE c=8
|
||||
const char MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY[] PROGMEM_N1 = ISTR("FlashAir"); ////MSG_TOSHIBA_FLASH_AIR_COMPATIBILITY c=8
|
||||
const char MSG_PINDA[] PROGMEM_N1 = ISTR("PINDA");////MSG_PINDA c=5
|
||||
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
|
||||
const char MSG_WELCOME[] PROGMEM_N1 = WELCOME_MSG;
|
||||
const char MSG_SD_WORKDIR_FAIL[] PROGMEM_N1 = "workDir open failed"; ////
|
||||
const char MSG_BROWNOUT_RESET[] PROGMEM_N1 = " Brown out Reset"; ////
|
||||
|
|
@ -177,7 +188,9 @@ const char MSG_WATCHDOG_RESET[] PROGMEM_N1 = " Watchdog Reset"; ////
|
|||
const char MSG_Z_MAX[] PROGMEM_N1 = "z_max: "; ////
|
||||
const char MSG_Z_MIN[] PROGMEM_N1 = "z_min: "; ////
|
||||
const char MSG_ZPROBE_OUT[] PROGMEM_N1 = "Z probe out. bed"; ////
|
||||
#ifdef ENABLE_AUTO_BED_LEVELING
|
||||
const char MSG_ZPROBE_ZOFFSET[] PROGMEM_N1 = "Z Offset"; ////
|
||||
#endif
|
||||
const char MSG_TMC_OVERTEMP[] PROGMEM_N1 = "TMC DRIVER OVERTEMP"; ////
|
||||
const char MSG_Enqueing[] PROGMEM_N1 = "enqueing \""; ////
|
||||
const char MSG_ENDSTOPS_HIT[] PROGMEM_N1 = "endstops hit: "; ////
|
||||
|
|
@ -186,11 +199,11 @@ const char MSG_OK[] PROGMEM_N1 = "ok"; ////
|
|||
const char MSG_SD_OPEN_FILE_FAIL[] PROGMEM_N1 = "open failed, File: "; ////
|
||||
const char MSG_ENDSTOP_OPEN[] PROGMEM_N1 = "open"; ////
|
||||
const char MSG_POWERUP[] PROGMEM_N1 = "PowerUp"; ////
|
||||
const char MSG_ERR_STOPPED[] PROGMEM_N1 = "Printer stopped due to errors. Fix the error and use M999 to restart. (Temperature is reset. Set it after restarting)"; ////
|
||||
const char MSG_ERR_STOPPED[] PROGMEM_N1 = "Printer stopped due to errors. Supervision required."; ////
|
||||
const char MSG_ENDSTOP_HIT[] PROGMEM_N1 = "TRIGGERED"; ////
|
||||
const char MSG_OCTOPRINT_PAUSE[] PROGMEM_N1 = "// action:pause"; ////
|
||||
const char MSG_OCTOPRINT_ASK_PAUSE[] PROGMEM_N1 = "// action:pause"; ////
|
||||
const char MSG_OCTOPRINT_PAUSED[] PROGMEM_N1 = "// action:paused"; ////
|
||||
const char MSG_OCTOPRINT_RESUME[] PROGMEM_N1 = "// action:resume"; ////
|
||||
const char MSG_OCTOPRINT_ASK_RESUME[] PROGMEM_N1 = "// action:resume"; ////
|
||||
const char MSG_OCTOPRINT_RESUMED[] PROGMEM_N1 = "// action:resumed"; ////
|
||||
const char MSG_OCTOPRINT_CANCEL[] PROGMEM_N1 = "// action:cancel"; ////
|
||||
const char MSG_FANCHECK_EXTRUDER[] PROGMEM_N1 = "Err: EXTR. FAN ERROR"; ////c=20
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ extern "C" {
|
|||
|
||||
// LCD Menu Messages
|
||||
//internationalized messages
|
||||
extern const char MSG_ALWAYS[];
|
||||
extern const char MSG_AUTO_HOME[];
|
||||
extern const char MSG_BABYSTEP_Z[];
|
||||
extern const char MSG_BABYSTEP_Z_NOT_SET[];
|
||||
|
|
@ -46,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[];
|
||||
|
|
@ -57,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[];
|
||||
|
|
@ -98,6 +103,8 @@ extern const char MSG_SELFTEST_MOTOR[];
|
|||
extern const char MSG_SELFTEST_FILAMENT_SENSOR[];
|
||||
extern const char MSG_SELFTEST_WIRINGERROR[];
|
||||
extern const char MSG_SETTINGS[];
|
||||
extern const char MSG_SELECT_LANGUAGE[];
|
||||
extern const char MSG_SORTING_FILES[];
|
||||
extern const char MSG_TOTAL[];
|
||||
extern const char MSG_TOTAL_FAILURES[];
|
||||
extern const char MSG_HW_SETUP[];
|
||||
|
|
@ -127,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[];
|
||||
|
|
@ -163,11 +170,16 @@ 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[];
|
||||
#endif
|
||||
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[];
|
||||
|
|
@ -180,7 +192,9 @@ extern const char MSG_WATCHDOG_RESET[];
|
|||
extern const char MSG_Z_MAX[];
|
||||
extern const char MSG_Z_MIN[];
|
||||
extern const char MSG_ZPROBE_OUT[];
|
||||
#ifdef ENABLE_AUTO_BED_LEVELING
|
||||
extern const char MSG_ZPROBE_ZOFFSET[];
|
||||
#endif
|
||||
extern const char MSG_TMC_OVERTEMP[];
|
||||
extern const char MSG_Enqueing[];
|
||||
extern const char MSG_ENDSTOPS_HIT[];
|
||||
|
|
@ -193,9 +207,9 @@ extern const char MSG_ERR_STOPPED[];
|
|||
extern const char MSG_ENDSTOP_HIT[];
|
||||
extern const char MSG_EJECT_FILAMENT[];
|
||||
extern const char MSG_CUT_FILAMENT[];
|
||||
extern const char MSG_OCTOPRINT_PAUSE[];
|
||||
extern const char MSG_OCTOPRINT_ASK_PAUSE[];
|
||||
extern const char MSG_OCTOPRINT_PAUSED[];
|
||||
extern const char MSG_OCTOPRINT_RESUME[];
|
||||
extern const char MSG_OCTOPRINT_ASK_RESUME[];
|
||||
extern const char MSG_OCTOPRINT_RESUMED[];
|
||||
extern const char MSG_OCTOPRINT_CANCEL[];
|
||||
extern const char MSG_FANCHECK_EXTRUDER[];
|
||||
|
|
|
|||
1266
Firmware/mmu.cpp
1266
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
|
||||
|
|
@ -26,13 +26,16 @@
|
|||
|
||||
// The arc is approximated by generating a huge number of tiny, linear segments. The length of each
|
||||
// segment is configured in settings.mm_per_arc_segment.
|
||||
void mc_arc(float* position, float* target, float* offset, float feed_rate, float radius, bool isclockwise, uint8_t extruder)
|
||||
void mc_arc(const float* position, float* target, const float* offset, float feed_rate, float radius, bool isclockwise, uint8_t extruder, uint16_t start_segment_idx)
|
||||
{
|
||||
float start_position[4];
|
||||
memcpy(start_position, position, sizeof(start_position));
|
||||
|
||||
float r_axis_x = -offset[X_AXIS]; // Radius vector from center to current location
|
||||
float r_axis_y = -offset[Y_AXIS];
|
||||
float center_axis_x = position[X_AXIS] - r_axis_x;
|
||||
float center_axis_y = position[Y_AXIS] - r_axis_y;
|
||||
float travel_z = target[Z_AXIS] - position[Z_AXIS];
|
||||
float center_axis_x = start_position[X_AXIS] - r_axis_x;
|
||||
float center_axis_y = start_position[Y_AXIS] - r_axis_y;
|
||||
float travel_z = target[Z_AXIS] - start_position[Z_AXIS];
|
||||
float rt_x = target[X_AXIS] - center_axis_x;
|
||||
float rt_y = target[Y_AXIS] - center_axis_y;
|
||||
// 20200419 - Add a variable that will be used to hold the arc segment length
|
||||
|
|
@ -40,7 +43,7 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
|
|||
// 20210109 - Add a variable to hold the n_arc_correction value
|
||||
unsigned char n_arc_correction = cs.n_arc_correction;
|
||||
|
||||
// CCW angle between position and target from circle center. Only one atan2() trig computation required.
|
||||
// CCW angle between start_position and target from circle center. Only one atan2() trig computation required.
|
||||
float angular_travel_total = atan2(r_axis_x * rt_y - r_axis_y * rt_x, r_axis_x * rt_x + r_axis_y * rt_y);
|
||||
if (angular_travel_total < 0) { angular_travel_total += 2 * M_PI; }
|
||||
|
||||
|
|
@ -76,7 +79,7 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
|
|||
|
||||
//20141002:full circle for G03 did not work, e.g. G03 X80 Y80 I20 J0 F2000 is giving an Angle of zero so head is not moving
|
||||
//to compensate when start pos = target pos && angle is zero -> angle = 2Pi
|
||||
if (position[X_AXIS] == target[X_AXIS] && position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0)
|
||||
if (start_position[X_AXIS] == target[X_AXIS] && start_position[Y_AXIS] == target[Y_AXIS] && angular_travel_total == 0)
|
||||
{
|
||||
angular_travel_total += 2 * M_PI;
|
||||
}
|
||||
|
|
@ -113,13 +116,13 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
|
|||
*/
|
||||
|
||||
// If there is only one segment, no need to do a bunch of work since this is a straight line!
|
||||
if (segments > 1)
|
||||
if (segments > 1 && start_segment_idx)
|
||||
{
|
||||
// Calculate theta per segments, and linear (z) travel per segment, e travel per segment
|
||||
// as well as the small angle approximation for sin and cos.
|
||||
const float theta_per_segment = angular_travel_total / segments,
|
||||
linear_per_segment = travel_z / (segments),
|
||||
segment_extruder_travel = (target[E_AXIS] - position[E_AXIS]) / (segments),
|
||||
segment_extruder_travel = (target[E_AXIS] - start_position[E_AXIS]) / (segments),
|
||||
sq_theta_per_segment = theta_per_segment * theta_per_segment,
|
||||
sin_T = theta_per_segment - sq_theta_per_segment * theta_per_segment / 6,
|
||||
cos_T = 1 - 0.5f * sq_theta_per_segment;
|
||||
|
|
@ -142,21 +145,22 @@ void mc_arc(float* position, float* target, float* offset, float feed_rate, floa
|
|||
}
|
||||
|
||||
// Update Position
|
||||
position[X_AXIS] = center_axis_x + r_axis_x;
|
||||
position[Y_AXIS] = center_axis_y + r_axis_y;
|
||||
position[Z_AXIS] += linear_per_segment;
|
||||
position[E_AXIS] += segment_extruder_travel;
|
||||
start_position[X_AXIS] = center_axis_x + r_axis_x;
|
||||
start_position[Y_AXIS] = center_axis_y + r_axis_y;
|
||||
start_position[Z_AXIS] += linear_per_segment;
|
||||
start_position[E_AXIS] += segment_extruder_travel;
|
||||
// Clamp to the calculated position.
|
||||
clamp_to_software_endstops(position);
|
||||
clamp_to_software_endstops(start_position);
|
||||
// Insert the segment into the buffer
|
||||
plan_buffer_line(position[X_AXIS], position[Y_AXIS], position[Z_AXIS], position[E_AXIS], feed_rate, extruder, position);
|
||||
if (i >= start_segment_idx)
|
||||
plan_buffer_line(start_position[X_AXIS], start_position[Y_AXIS], start_position[Z_AXIS], start_position[E_AXIS], feed_rate, extruder, position, i);
|
||||
// Handle the situation where the planner is aborted hard.
|
||||
if (waiting_inside_plan_buffer_line_print_aborted)
|
||||
if (planner_aborted)
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Clamp to the target position.
|
||||
clamp_to_software_endstops(target);
|
||||
// Ensure last segment arrives at target location.
|
||||
plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, target);
|
||||
plan_buffer_line(target[X_AXIS], target[Y_AXIS], target[Z_AXIS], target[E_AXIS], feed_rate, extruder, position, 0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@
|
|||
// offset == offset from current xyz, axis_XXX defines circle plane in tool space, axis_linear is
|
||||
// the direction of helical travel, radius == circle radius, isclockwise boolean. Used
|
||||
// for vector transformation direction.
|
||||
void mc_arc(float *position, float *target, float *offset, float feed_rate, float radius,
|
||||
bool isclockwise, uint8_t extruder);
|
||||
|
||||
void mc_arc(const float *position, float *target, const float *offset, float feed_rate, float radius, bool isclockwise, uint8_t extruder, uint16_t start_segment_idx);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -78,6 +78,9 @@
|
|||
#define VOLT_IR_PIN 8 //A8
|
||||
|
||||
|
||||
#define TEMP_TIM 5
|
||||
|
||||
|
||||
#define E0_TMC2130_CS 66
|
||||
#define E0_TMC2130_DIAG 65
|
||||
#define E0_STEP_PIN 34
|
||||
|
|
|
|||
|
|
@ -57,6 +57,8 @@
|
|||
#define TEMP_PINDA_PIN 1 //A1
|
||||
|
||||
|
||||
#define TEMP_TIM 3
|
||||
|
||||
|
||||
#define E0_STEP_PIN 34
|
||||
#define E0_DIR_PIN 43
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@
|
|||
#define TEMP_PINDA_PIN 1 //A1
|
||||
|
||||
|
||||
#define TEMP_TIM 3
|
||||
|
||||
|
||||
#define E0_STEP_PIN 34
|
||||
#define E0_DIR_PIN 43
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
#include "planner.h"
|
||||
#include "stepper.h"
|
||||
#include "temperature.h"
|
||||
#include "fancheck.h"
|
||||
#include "ultralcd.h"
|
||||
#include "language.h"
|
||||
#include "ConfigurationStore.h"
|
||||
|
|
@ -68,6 +69,9 @@
|
|||
#include "tmc2130.h"
|
||||
#endif //TMC2130
|
||||
|
||||
#include <util/atomic.h>
|
||||
|
||||
|
||||
//===========================================================================
|
||||
//=============================public variables ============================
|
||||
//===========================================================================
|
||||
|
|
@ -95,8 +99,6 @@ static float previous_speed[NUM_AXIS]; // Speed of previous path line segment
|
|||
static float previous_nominal_speed; // Nominal speed of previous path line segment
|
||||
static float previous_safe_speed; // Exit speed limited by a jerk to full halt of a previous last segment.
|
||||
|
||||
uint8_t maxlimit_status;
|
||||
|
||||
#ifdef AUTOTEMP
|
||||
float autotemp_max=250;
|
||||
float autotemp_min=210;
|
||||
|
|
@ -592,17 +594,7 @@ void check_axes_activity()
|
|||
#endif
|
||||
}
|
||||
|
||||
bool waiting_inside_plan_buffer_line_print_aborted = false;
|
||||
/*
|
||||
void planner_abort_soft()
|
||||
{
|
||||
// Empty the queue.
|
||||
while (blocks_queued()) plan_discard_current_block();
|
||||
// Relay to planner wait routine, that the current line shall be canceled.
|
||||
waiting_inside_plan_buffer_line_print_aborted = true;
|
||||
//current_position[i]
|
||||
}
|
||||
*/
|
||||
bool planner_aborted = false;
|
||||
|
||||
#ifdef PLANNER_DIAGNOSTICS
|
||||
static inline void planner_update_queue_min_counter()
|
||||
|
|
@ -615,12 +607,8 @@ static inline void planner_update_queue_min_counter()
|
|||
|
||||
extern volatile uint32_t step_events_completed; // The number of step events executed in the current block
|
||||
|
||||
void planner_abort_hard()
|
||||
void planner_reset_position()
|
||||
{
|
||||
// Abort the stepper routine and flush the planner queue.
|
||||
DISABLE_STEPPER_DRIVER_INTERRUPT();
|
||||
|
||||
// Now the front-end (the Marlin_main.cpp with its current_position) is out of sync.
|
||||
// First update the planner's current position in the physical motor steps.
|
||||
position[X_AXIS] = st_get_position(X_AXIS);
|
||||
position[Y_AXIS] = st_get_position(Y_AXIS);
|
||||
|
|
@ -632,6 +620,7 @@ void planner_abort_hard()
|
|||
current_position[Y_AXIS] = st_get_position_mm(Y_AXIS);
|
||||
current_position[Z_AXIS] = st_get_position_mm(Z_AXIS);
|
||||
current_position[E_AXIS] = st_get_position_mm(E_AXIS);
|
||||
|
||||
// Apply the mesh bed leveling correction to the Z axis.
|
||||
#ifdef MESH_BED_LEVELING
|
||||
if (mbl.active) {
|
||||
|
|
@ -664,24 +653,36 @@ void planner_abort_hard()
|
|||
#endif
|
||||
}
|
||||
#endif
|
||||
// Clear the planner queue, reset and re-enable the stepper timer.
|
||||
quickStop();
|
||||
|
||||
// Apply inverse world correction matrix.
|
||||
machine2world(current_position[X_AXIS], current_position[Y_AXIS]);
|
||||
memcpy(destination, current_position, sizeof(destination));
|
||||
set_destination_to_current();
|
||||
#ifdef LIN_ADVANCE
|
||||
memcpy(position_float, current_position, sizeof(position_float));
|
||||
#endif
|
||||
}
|
||||
|
||||
void planner_abort_hard()
|
||||
{
|
||||
// Abort the stepper routine and flush the planner queue.
|
||||
DISABLE_STEPPER_DRIVER_INTERRUPT();
|
||||
|
||||
// Now the front-end (the Marlin_main.cpp with its current_position) is out of sync.
|
||||
planner_reset_position();
|
||||
|
||||
// Relay to planner wait routine that the current line shall be canceled.
|
||||
planner_aborted = true;
|
||||
|
||||
// Clear the planner queue, reset and re-enable the stepper timer.
|
||||
quickStop();
|
||||
|
||||
// Resets planner junction speeds. Assumes start from rest.
|
||||
previous_nominal_speed = 0.0;
|
||||
memset(previous_speed, 0, sizeof(previous_speed));
|
||||
|
||||
// Reset position sync requests
|
||||
plan_reset_next_e_queue = false;
|
||||
plan_reset_next_e_sched = false;
|
||||
|
||||
// Relay to planner wait routine, that the current line shall be canceled.
|
||||
waiting_inside_plan_buffer_line_print_aborted = true;
|
||||
}
|
||||
|
||||
void plan_buffer_line_curposXYZE(float feed_rate) {
|
||||
|
|
@ -700,14 +701,17 @@ float junction_deviation = 0.1;
|
|||
// Add a new linear movement to the buffer. steps_x, _y and _z is the absolute position in
|
||||
// mm. Microseconds specify how many microseconds the move should take to perform. To aid acceleration
|
||||
// calculation the caller must also provide the physical length of the line in millimeters.
|
||||
void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_target)
|
||||
void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_start_position, uint16_t segment_idx)
|
||||
{
|
||||
// Calculate the buffer head after we push this byte
|
||||
// CRITICAL_SECTION_START; //prevent stack overflow in ISR
|
||||
// printf_P(PSTR("plan_buffer_line(%f, %f, %f, %f, %f, %u, [%f,%f,%f,%f], %u)\n"), x, y, z, e, feed_rate, extruder, gcode_start_position[0], gcode_start_position[1], gcode_start_position[2], gcode_start_position[3], segment_idx);
|
||||
// CRITICAL_SECTION_END;
|
||||
|
||||
// Calculate the buffer head after we push this byte
|
||||
uint8_t next_buffer_head = next_block_index(block_buffer_head);
|
||||
|
||||
// If the buffer is full: good! That means we are well ahead of the robot.
|
||||
// If the buffer is full: good! That means we are well ahead of the robot.
|
||||
// Rest here until there is room in the buffer.
|
||||
waiting_inside_plan_buffer_line_print_aborted = false;
|
||||
if (block_buffer_tail == next_buffer_head) {
|
||||
do {
|
||||
manage_heater();
|
||||
|
|
@ -715,18 +719,14 @@ void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate
|
|||
manage_inactivity(false);
|
||||
lcd_update(0);
|
||||
} while (block_buffer_tail == next_buffer_head);
|
||||
if (waiting_inside_plan_buffer_line_print_aborted) {
|
||||
// Inside the lcd_update(0) routine the print has been aborted.
|
||||
// Cancel the print, do not plan the current line this routine is waiting on.
|
||||
#ifdef PLANNER_DIAGNOSTICS
|
||||
planner_update_queue_min_counter();
|
||||
#endif /* PLANNER_DIAGNOSTICS */
|
||||
return;
|
||||
}
|
||||
}
|
||||
#ifdef PLANNER_DIAGNOSTICS
|
||||
planner_update_queue_min_counter();
|
||||
#endif /* PLANNER_DIAGNOSTICS */
|
||||
if(planner_aborted) {
|
||||
// avoid planning the block early if aborted
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare to set up new block
|
||||
block_t *block = &block_buffer[block_buffer_head];
|
||||
|
|
@ -737,16 +737,14 @@ void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate
|
|||
// Set sdlen for calculating sd position
|
||||
block->sdlen = 0;
|
||||
|
||||
// Save original destination of the move
|
||||
if (gcode_target)
|
||||
memcpy(block->gcode_target, gcode_target, sizeof(block_t::gcode_target));
|
||||
// Save original start position of the move
|
||||
if (gcode_start_position)
|
||||
memcpy(block->gcode_start_position, gcode_start_position, sizeof(block_t::gcode_start_position));
|
||||
else
|
||||
{
|
||||
block->gcode_target[X_AXIS] = x;
|
||||
block->gcode_target[Y_AXIS] = y;
|
||||
block->gcode_target[Z_AXIS] = z;
|
||||
block->gcode_target[E_AXIS] = e;
|
||||
}
|
||||
memcpy(block->gcode_start_position, current_position, sizeof(block_t::gcode_start_position));
|
||||
|
||||
// Save the index of this segment (when a single G0/1/2/3 command plans multiple segments)
|
||||
block->segment_idx = segment_idx;
|
||||
|
||||
// Save the global feedrate at scheduling time
|
||||
block->gcode_feedrate = feedrate;
|
||||
|
|
@ -913,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))
|
||||
|
|
@ -1331,8 +1327,12 @@ Having the real displacement of the head, we can calculate the total movement le
|
|||
if (block->step_event_count.wide <= 32767)
|
||||
block->flag |= BLOCK_FLAG_DDA_LOWRES;
|
||||
|
||||
// Move the buffer head. From now the block may be picked up by the stepper interrupt controller.
|
||||
block_buffer_head = next_buffer_head;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
// Move the buffer head ensuring the current block hasn't been cancelled from an isr context
|
||||
// (this is possible both during crash detection *and* uvlo, thus needing a global cli)
|
||||
if(planner_aborted) return;
|
||||
block_buffer_head = next_buffer_head;
|
||||
}
|
||||
|
||||
// Update position
|
||||
memcpy(position, target, sizeof(target)); // position[] = target[]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -122,7 +121,8 @@ typedef struct {
|
|||
#endif
|
||||
|
||||
// Save/recovery state data
|
||||
float gcode_target[NUM_AXIS]; // Target (abs mm) of the original Gcode instruction
|
||||
float gcode_start_position[NUM_AXIS]; // Start (abs mm) of the original Gcode instruction
|
||||
uint16_t segment_idx; // The index of the for loop that generates segments
|
||||
uint16_t gcode_feedrate; // Default and/or move feedrate
|
||||
uint16_t sdlen; // Length of the Gcode instruction
|
||||
} block_t;
|
||||
|
|
@ -159,7 +159,7 @@ void plan_buffer_line_destinationXYZE(float feed_rate);
|
|||
|
||||
void plan_set_position_curposXYZE();
|
||||
|
||||
void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_target = NULL);
|
||||
void plan_buffer_line(float x, float y, float z, const float &e, float feed_rate, uint8_t extruder, const float* gcode_start_position = NULL, uint16_t segment_idx = 0);
|
||||
//void plan_buffer_line(const float &x, const float &y, const float &z, const float &e, float feed_rate, const uint8_t &extruder);
|
||||
#endif // ENABLE_AUTO_BED_LEVELING
|
||||
|
||||
|
|
@ -255,11 +255,14 @@ FORCE_INLINE bool planner_queue_full() {
|
|||
return block_buffer_tail == next_block_index;
|
||||
}
|
||||
|
||||
// Reset machine position from stepper counters
|
||||
extern void planner_reset_position();
|
||||
|
||||
// Abort the stepper routine, clean up the block queue,
|
||||
// wait for the steppers to stop,
|
||||
// update planner's current position and the current_position of the front end.
|
||||
extern void planner_abort_hard();
|
||||
extern bool waiting_inside_plan_buffer_line_print_aborted;
|
||||
extern bool planner_aborted;
|
||||
|
||||
#ifdef PREVENT_DANGEROUS_EXTRUDE
|
||||
extern int extrude_min_temp;
|
||||
|
|
|
|||
|
|
@ -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,14 +36,13 @@
|
|||
#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"
|
||||
|
||||
#ifdef DEBUG_STACK_MONITOR
|
||||
uint16_t SP_min = 0x21FF;
|
||||
#endif //DEBUG_STACK_MONITOR
|
||||
|
|
@ -287,15 +286,6 @@ ISR(TIMER1_COMPA_vect) {
|
|||
if (sp < SP_min) SP_min = sp;
|
||||
#endif //DEBUG_STACK_MONITOR
|
||||
|
||||
#ifdef DEBUG_PULLUP_CRASH
|
||||
// check for faulty pull-ups enabled on thermistor inputs
|
||||
if ((PORTF & (uint8_t)(ADC_DIDR_MSK & 0xff)) || (PORTK & (uint8_t)((ADC_DIDR_MSK >> 8) & 0xff)))
|
||||
pullup_error(false);
|
||||
#else
|
||||
PORTF &= ~(uint8_t)(ADC_DIDR_MSK & 0xff);
|
||||
PORTK &= ~(uint8_t)((ADC_DIDR_MSK >> 8) & 0xff);
|
||||
#endif // DEBUG_PULLUP_CRASH
|
||||
|
||||
#ifdef LIN_ADVANCE
|
||||
advance_isr_scheduler();
|
||||
#else
|
||||
|
|
@ -464,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.
|
||||
|
|
@ -502,7 +489,7 @@ FORCE_INLINE void stepper_check_endstops()
|
|||
#if ( (defined(X_MIN_PIN) && (X_MIN_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_XMINLIMIT)
|
||||
#ifdef TMC2130_SG_HOMING
|
||||
// Stall guard homing turned on
|
||||
SET_BIT_TO(_endstop, X_AXIS, (READ(X_TMC2130_DIAG) != 0));
|
||||
SET_BIT_TO(_endstop, X_AXIS, (!READ(X_TMC2130_DIAG)));
|
||||
#else
|
||||
// Normal homing
|
||||
SET_BIT_TO(_endstop, X_AXIS, (READ(X_MIN_PIN) != X_MIN_ENDSTOP_INVERTING));
|
||||
|
|
@ -519,7 +506,7 @@ FORCE_INLINE void stepper_check_endstops()
|
|||
#if ( (defined(X_MAX_PIN) && (X_MAX_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_XMAXLIMIT)
|
||||
#ifdef TMC2130_SG_HOMING
|
||||
// Stall guard homing turned on
|
||||
SET_BIT_TO(_endstop, X_AXIS + 4, (READ(X_TMC2130_DIAG) != 0));
|
||||
SET_BIT_TO(_endstop, X_AXIS + 4, (!READ(X_TMC2130_DIAG)));
|
||||
#else
|
||||
// Normal homing
|
||||
SET_BIT_TO(_endstop, X_AXIS + 4, (READ(X_MAX_PIN) != X_MAX_ENDSTOP_INVERTING));
|
||||
|
|
@ -543,7 +530,7 @@ FORCE_INLINE void stepper_check_endstops()
|
|||
#if ( (defined(Y_MIN_PIN) && (Y_MIN_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_YMINLIMIT)
|
||||
#ifdef TMC2130_SG_HOMING
|
||||
// Stall guard homing turned on
|
||||
SET_BIT_TO(_endstop, Y_AXIS, (READ(Y_TMC2130_DIAG) != 0));
|
||||
SET_BIT_TO(_endstop, Y_AXIS, (!READ(Y_TMC2130_DIAG)));
|
||||
#else
|
||||
// Normal homing
|
||||
SET_BIT_TO(_endstop, Y_AXIS, (READ(Y_MIN_PIN) != Y_MIN_ENDSTOP_INVERTING));
|
||||
|
|
@ -560,7 +547,7 @@ FORCE_INLINE void stepper_check_endstops()
|
|||
#if ( (defined(Y_MAX_PIN) && (Y_MAX_PIN > -1)) || defined(TMC2130_SG_HOMING) ) && !defined(DEBUG_DISABLE_YMAXLIMIT)
|
||||
#ifdef TMC2130_SG_HOMING
|
||||
// Stall guard homing turned on
|
||||
SET_BIT_TO(_endstop, Y_AXIS + 4, (READ(Y_TMC2130_DIAG) != 0));
|
||||
SET_BIT_TO(_endstop, Y_AXIS + 4, (!READ(Y_TMC2130_DIAG)));
|
||||
#else
|
||||
// Normal homing
|
||||
SET_BIT_TO(_endstop, Y_AXIS + 4, (READ(Y_MAX_PIN) != Y_MAX_ENDSTOP_INVERTING));
|
||||
|
|
@ -586,7 +573,7 @@ FORCE_INLINE void stepper_check_endstops()
|
|||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
|
||||
else
|
||||
#endif //TMC2130_STEALTH_Z
|
||||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (READ(Z_TMC2130_DIAG) != 0));
|
||||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (!READ(Z_TMC2130_DIAG)));
|
||||
#else
|
||||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
|
||||
#endif //TMC2130_SG_HOMING
|
||||
|
|
@ -608,7 +595,7 @@ FORCE_INLINE void stepper_check_endstops()
|
|||
SET_BIT_TO(_endstop, Z_AXIS + 4, 0);
|
||||
else
|
||||
#endif //TMC2130_STEALTH_Z
|
||||
SET_BIT_TO(_endstop, Z_AXIS + 4, (READ(Z_TMC2130_DIAG) != 0));
|
||||
SET_BIT_TO(_endstop, Z_AXIS + 4, (!READ(Z_TMC2130_DIAG)));
|
||||
#else
|
||||
SET_BIT_TO(_endstop, Z_AXIS + 4, (READ(Z_MAX_PIN) != Z_MAX_ENDSTOP_INVERTING));
|
||||
#endif //TMC2130_SG_HOMING
|
||||
|
|
@ -641,7 +628,7 @@ FORCE_INLINE void stepper_check_endstops()
|
|||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
|
||||
else
|
||||
#endif //TMC2130_STEALTH_Z
|
||||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (READ(Z_TMC2130_DIAG) != 0));
|
||||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING) || (!READ(Z_TMC2130_DIAG)));
|
||||
#else
|
||||
SET_BIT_TO(_endstop, Z_AXIS, (READ(Z_MIN_PIN) != Z_MIN_ENDSTOP_INVERTING));
|
||||
#endif //TMC2130_SG_HOMING
|
||||
|
|
@ -711,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
|
||||
}
|
||||
|
|
@ -773,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
|
||||
}
|
||||
|
|
@ -970,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
|
||||
|
|
@ -1080,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
|
||||
|
|
@ -1186,22 +1153,6 @@ void st_init()
|
|||
#endif
|
||||
|
||||
//endstops and pullups
|
||||
|
||||
#ifdef TMC2130_SG_HOMING
|
||||
SET_INPUT(X_TMC2130_DIAG);
|
||||
WRITE(X_TMC2130_DIAG,HIGH);
|
||||
|
||||
SET_INPUT(Y_TMC2130_DIAG);
|
||||
WRITE(Y_TMC2130_DIAG,HIGH);
|
||||
|
||||
SET_INPUT(Z_TMC2130_DIAG);
|
||||
WRITE(Z_TMC2130_DIAG,HIGH);
|
||||
|
||||
SET_INPUT(E0_TMC2130_DIAG);
|
||||
WRITE(E0_TMC2130_DIAG,HIGH);
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(X_MIN_PIN) && X_MIN_PIN > -1
|
||||
SET_INPUT(X_MIN_PIN);
|
||||
#ifdef ENDSTOPPULLUP_XMIN
|
||||
|
|
@ -1691,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);
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#define FIRMWARE_SYSTEM_TIMER_H_
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "macros.h"
|
||||
#define SYSTEM_TIMER_2
|
||||
|
||||
#ifdef SYSTEM_TIMER_2
|
||||
|
|
@ -26,4 +27,24 @@
|
|||
#define timer02_set_pwm0(pwm0)
|
||||
#endif //SYSTEM_TIMER_2
|
||||
|
||||
// Timer counter, incremented by the 1ms Arduino timer.
|
||||
// The standard Arduino timer() function returns this value atomically
|
||||
// by disabling / enabling interrupts. This is costly, if the interrupts are known
|
||||
// to be disabled.
|
||||
#ifdef SYSTEM_TIMER_2
|
||||
extern volatile unsigned long timer2_millis;
|
||||
#else //SYSTEM_TIMER_2
|
||||
extern volatile unsigned long timer0_millis;
|
||||
#endif //SYSTEM_TIMER_2
|
||||
|
||||
// An unsynchronized equivalent to a standard Arduino _millis() function.
|
||||
// To be used inside an interrupt routine.
|
||||
FORCE_INLINE unsigned long millis_nc() {
|
||||
#ifdef SYSTEM_TIMER_2
|
||||
return timer2_millis;
|
||||
#else //SYSTEM_TIMER_2
|
||||
return timer0_millis;
|
||||
#endif //SYSTEM_TIMER_2
|
||||
}
|
||||
|
||||
#endif /* FIRMWARE_SYSTEM_TIMER_H_ */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,115 @@
|
|||
// model-based temperature safety checker declarations
|
||||
#ifndef TEMP_MGR_INTV
|
||||
#error "this file is not a public interface, it should be used *only* within temperature.cpp!"
|
||||
#endif
|
||||
|
||||
#include "planner.h"
|
||||
|
||||
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)
|
||||
|
||||
// transport delay buffer size (samples)
|
||||
constexpr uint8_t TEMP_MODEL_LAG_SIZE = (TEMP_MODEL_LAG / TEMP_MGR_INTV + 0.5);
|
||||
|
||||
// resistance values for all fan levels
|
||||
constexpr uint8_t TEMP_MODEL_R_SIZE = (1 << FAN_SOFT_PWM_BITS);
|
||||
|
||||
namespace temp_model {
|
||||
|
||||
struct model_data
|
||||
{
|
||||
// temporary buffers
|
||||
float dT_lag_buf[TEMP_MODEL_LAG_SIZE]; // transport delay buffer
|
||||
uint8_t dT_lag_idx = 0; // transport delay buffer index
|
||||
float dT_err_prev = 0; // previous temperature delta error
|
||||
float T_prev = 0; // last temperature extruder
|
||||
|
||||
// configurable parameters
|
||||
float P; // heater power (W)
|
||||
float C; // heatblock capacitance (J/K)
|
||||
float R[TEMP_MODEL_R_SIZE]; // heatblock resistance for all fan levels (K/W)
|
||||
float Ta_corr; // ambient temperature correction (K)
|
||||
|
||||
// thresholds
|
||||
float warn; // warning threshold (K/s)
|
||||
float err; // error threshold (K/s)
|
||||
|
||||
// status flags
|
||||
union
|
||||
{
|
||||
bool flags;
|
||||
struct
|
||||
{
|
||||
bool uninitialized: 1; // model is not initialized
|
||||
bool error: 1; // error threshold set
|
||||
bool warning: 1; // warning threshold set
|
||||
} flag_bits;
|
||||
};
|
||||
|
||||
// pre-computed values (initialized via reset)
|
||||
float C_i; // heatblock capacitance (precomputed dT/C)
|
||||
float warn_s; // warning threshold (per sample)
|
||||
float err_s; // error threshold (per sample)
|
||||
|
||||
// simulation functions
|
||||
void reset(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp);
|
||||
void step(uint8_t heater_pwm, uint8_t fan_pwm, float heater_temp, float ambient_temp);
|
||||
};
|
||||
|
||||
static bool enabled; // model check enabled
|
||||
static bool warn_beep = true; // beep on warning threshold
|
||||
static model_data data; // default heater data
|
||||
|
||||
static bool calibrated(); // return calibration/model validity status
|
||||
static void check(); // check and trigger errors or warnings based on current state
|
||||
|
||||
// warning state (updated from from isr context)
|
||||
volatile static struct
|
||||
{
|
||||
float dT_err; // temperature delta error (per sample)
|
||||
bool warning: 1; // warning condition
|
||||
bool assert: 1; // warning is still asserted
|
||||
} warning_state;
|
||||
|
||||
static void handle_warning(); // handle warnings from user context
|
||||
|
||||
#ifdef TEMP_MODEL_DEBUG
|
||||
static struct
|
||||
{
|
||||
volatile struct
|
||||
{
|
||||
uint32_t stamp;
|
||||
int8_t delta_ms;
|
||||
uint8_t counter;
|
||||
uint8_t cur_pwm;
|
||||
float cur_temp;
|
||||
float cur_amb;
|
||||
} entry;
|
||||
|
||||
uint8_t serial;
|
||||
bool enabled;
|
||||
} log_buf;
|
||||
|
||||
static void log_usr(); // user log handler
|
||||
static void log_isr(); // isr log handler
|
||||
#endif
|
||||
|
||||
} // namespace temp_model
|
||||
|
||||
namespace temp_model_cal {
|
||||
|
||||
// recording scratch buffer
|
||||
struct rec_entry
|
||||
{
|
||||
float temp; // heater temperature
|
||||
uint8_t pwm; // heater PWM
|
||||
};
|
||||
|
||||
constexpr uint16_t REC_BUFFER_SIZE = TEMP_MODEL_CAL_S / TEMP_MGR_INTV;
|
||||
static rec_entry* const rec_buffer = (rec_entry*)block_buffer; // oh-hey, free memory!
|
||||
static_assert(sizeof(rec_entry[REC_BUFFER_SIZE]) <= sizeof(block_buffer),
|
||||
"recording length too long to fit within available buffer");
|
||||
|
||||
} // namespace temp_model_cal
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -22,29 +22,13 @@
|
|||
#define temperature_h
|
||||
|
||||
#include "Marlin.h"
|
||||
#include "planner.h"
|
||||
|
||||
#include "stepper.h"
|
||||
|
||||
#include "config.h"
|
||||
|
||||
|
||||
#ifdef SYSTEM_TIMER_2
|
||||
|
||||
#define ENABLE_TEMPERATURE_INTERRUPT() TIMSK2 |= (1<<OCIE2B)
|
||||
#define DISABLE_TEMPERATURE_INTERRUPT() TIMSK2 &= ~(1<<OCIE2B)
|
||||
|
||||
#else //SYSTEM_TIMER_2
|
||||
|
||||
#define ENABLE_TEMPERATURE_INTERRUPT() TIMSK0 |= (1<<OCIE0B)
|
||||
#define DISABLE_TEMPERATURE_INTERRUPT() TIMSK0 &= ~(1<<OCIE0B)
|
||||
|
||||
#endif //SYSTEM_TIMER_2
|
||||
|
||||
|
||||
// public functions
|
||||
void tp_init(); //initialize the heating
|
||||
void soft_pwm_init(); //initialize the soft pwm isr
|
||||
void temp_mgr_init(); //initialize the temperature handler
|
||||
void manage_heater(); //it is critical that this is called periodically.
|
||||
bool get_temp_error(); //return true if any thermal error is set
|
||||
|
||||
extern bool checkAllHotends(void);
|
||||
|
||||
|
|
@ -82,20 +66,18 @@ extern int current_voltage_raw_bed;
|
|||
extern uint16_t current_voltage_raw_IR;
|
||||
#endif //IR_SENSOR_ANALOG
|
||||
|
||||
#if defined(CONTROLLERFAN_PIN) && CONTROLLERFAN_PIN > -1
|
||||
extern unsigned char soft_pwm_bed;
|
||||
#endif
|
||||
|
||||
extern bool bedPWMDisabled;
|
||||
|
||||
#ifdef PIDTEMP
|
||||
extern int pid_cycle, pid_number_of_cycles;
|
||||
extern float _Kp,_Ki,_Kd;
|
||||
extern bool pid_tuning_finished;
|
||||
float scalePID_i(float i);
|
||||
float scalePID_d(float d);
|
||||
float unscalePID_i(float i);
|
||||
float unscalePID_d(float d);
|
||||
|
||||
bool pidTuningRunning(); // returns true if PID tuning is still running
|
||||
void preparePidTuning(); // non-blocking call to set "pidTuningRunning" to true immediately
|
||||
#endif
|
||||
|
||||
|
||||
|
|
@ -156,8 +138,7 @@ FORCE_INLINE void setTargetHotend(const float &celsius, uint8_t extruder) {
|
|||
static inline void setTargetHotendSafe(const float &celsius, uint8_t extruder)
|
||||
{
|
||||
if (extruder<EXTRUDERS) {
|
||||
target_temperature[extruder] = celsius;
|
||||
resetPID(extruder);
|
||||
setTargetHotend(celsius, extruder);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -218,7 +199,7 @@ FORCE_INLINE bool isCoolingBed() {
|
|||
#define CHECK_ALL_HEATERS (checkAllHotends()||(target_temperature_bed!=0))
|
||||
|
||||
int getHeaterPower(int heater);
|
||||
void disable_heater(); // Disable all heaters
|
||||
void disable_heater(); // Disable all heaters *instantaneously*
|
||||
void updatePID();
|
||||
|
||||
|
||||
|
|
@ -235,39 +216,27 @@ FORCE_INLINE void autotempShutdown(){
|
|||
|
||||
void PID_autotune(float temp, int extruder, int ncycles);
|
||||
|
||||
void setExtruderAutoFanState(uint8_t state);
|
||||
void checkExtruderAutoFans();
|
||||
#ifdef TEMP_MODEL
|
||||
void temp_model_set_enabled(bool enabled);
|
||||
void temp_model_set_warn_beep(bool enabled);
|
||||
void temp_model_set_params(float C = NAN, float P = NAN, float Ta_corr = NAN, float warn = NAN, float err = NAN);
|
||||
void temp_model_set_resistance(uint8_t index, float R);
|
||||
|
||||
void temp_model_report_settings();
|
||||
void temp_model_reset_settings();
|
||||
void temp_model_load_settings();
|
||||
void temp_model_save_settings();
|
||||
|
||||
#if (defined(FANCHECK) && defined(TACH_0) && (TACH_0 > -1))
|
||||
void temp_model_autotune(int16_t temp = 0, bool selftest = false);
|
||||
|
||||
enum {
|
||||
EFCE_OK = 0, //!< normal operation, both fans are ok
|
||||
EFCE_FIXED, //!< previous fan error was fixed
|
||||
EFCE_DETECTED, //!< fan error detected, but not reported yet
|
||||
EFCE_REPORTED //!< fan error detected and reported to LCD and serial
|
||||
};
|
||||
extern volatile uint8_t fan_check_error;
|
||||
#ifdef TEMP_MODEL_DEBUG
|
||||
void temp_model_log_enable(bool enable);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
void countFanSpeed();
|
||||
void checkFanSpeed();
|
||||
void fanSpeedError(unsigned char _fan);
|
||||
|
||||
void check_fans();
|
||||
|
||||
#endif //(defined(TACH_0))
|
||||
|
||||
void check_min_temp();
|
||||
void check_max_temp();
|
||||
|
||||
#ifdef EXTRUDER_ALTFAN_DETECT
|
||||
extern bool extruder_altfan_detect();
|
||||
extern void altfanOverride_toggle();
|
||||
extern bool altfanOverride_get();
|
||||
#endif //EXTRUDER_ALTFAN_DETECT
|
||||
|
||||
extern unsigned long extruder_autofan_last_check;
|
||||
#ifdef FAN_SOFT_PWM
|
||||
extern unsigned char fanSpeedSoftPwm;
|
||||
#endif
|
||||
extern uint8_t fanSpeedBckp;
|
||||
extern bool fan_measuring;
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
#include "Timer.h"
|
||||
|
||||
#define TMC2130_GCONF_NORMAL 0x00000000 // spreadCycle
|
||||
#define TMC2130_GCONF_SGSENS 0x00003180 // spreadCycle with stallguard (stall activates DIAG0 and DIAG1 [pushpull])
|
||||
#define TMC2130_GCONF_SGSENS 0x00000180 // spreadCycle with stallguard (stall activates DIAG0 and DIAG1 [open collector])
|
||||
#define TMC2130_GCONF_SILENT 0x00000004 // stealthChop
|
||||
|
||||
|
||||
|
|
@ -66,11 +66,6 @@ tmc2130_chopper_config_t tmc2130_chopper_config[4] = {
|
|||
bool tmc2130_sg_stop_on_crash = true;
|
||||
uint8_t tmc2130_sg_diag_mask = 0x00;
|
||||
uint8_t tmc2130_sg_crash = 0;
|
||||
uint16_t tmc2130_sg_err[4] = {0, 0, 0, 0};
|
||||
uint16_t tmc2130_sg_cnt[4] = {0, 0, 0, 0};
|
||||
#ifdef DEBUG_CRASHDET_COUNTERS
|
||||
bool tmc2130_sg_change = false;
|
||||
#endif
|
||||
|
||||
//used for triggering a periodic check (1s) of the overtemperature pre-warning flag at ~120C (+-20C)
|
||||
ShortTimer tmc2130_overtemp_timer;
|
||||
|
|
@ -157,15 +152,21 @@ void tmc2130_init(TMCInitParams params)
|
|||
SET_OUTPUT(Y_TMC2130_CS);
|
||||
SET_OUTPUT(Z_TMC2130_CS);
|
||||
SET_OUTPUT(E0_TMC2130_CS);
|
||||
|
||||
SET_INPUT(X_TMC2130_DIAG);
|
||||
SET_INPUT(Y_TMC2130_DIAG);
|
||||
SET_INPUT(Z_TMC2130_DIAG);
|
||||
SET_INPUT(E0_TMC2130_DIAG);
|
||||
WRITE(X_TMC2130_DIAG,HIGH);
|
||||
WRITE(Y_TMC2130_DIAG,HIGH);
|
||||
WRITE(Z_TMC2130_DIAG,HIGH);
|
||||
WRITE(E0_TMC2130_DIAG,HIGH);
|
||||
|
||||
for (uint_least8_t axis = 0; axis < 2; axis++) // X Y axes
|
||||
{
|
||||
tmc2130_setup_chopper(axis, tmc2130_mres[axis], tmc2130_current_h[axis], tmc2130_current_r[axis]);
|
||||
tmc2130_wr(axis, TMC2130_REG_TPOWERDOWN, 0x00000000);
|
||||
tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16));
|
||||
tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
|
||||
tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, (tmc2130_mode == TMC2130_MODE_SILENT)?0:__tcoolthrs(axis));
|
||||
tmc2130_wr(axis, TMC2130_REG_GCONF, (tmc2130_mode == TMC2130_MODE_SILENT)?TMC2130_GCONF_SILENT:TMC2130_GCONF_SGSENS);
|
||||
tmc2130_wr_PWMCONF(axis, tmc2130_pwm_ampl[axis], tmc2130_pwm_grad[axis], tmc2130_pwm_freq[axis], tmc2130_pwm_auto[axis], 0, 0);
|
||||
|
|
@ -179,7 +180,7 @@ void tmc2130_init(TMCInitParams params)
|
|||
#ifndef TMC2130_STEALTH_Z
|
||||
tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_SGSENS);
|
||||
#else //TMC2130_STEALTH_Z
|
||||
tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16));
|
||||
tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
|
||||
tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, (tmc2130_mode == TMC2130_MODE_SILENT)?0:__tcoolthrs(axis));
|
||||
tmc2130_wr(axis, TMC2130_REG_GCONF, (tmc2130_mode == TMC2130_MODE_SILENT)?TMC2130_GCONF_SILENT:TMC2130_GCONF_SGSENS);
|
||||
tmc2130_wr_PWMCONF(axis, tmc2130_pwm_ampl[axis], tmc2130_pwm_grad[axis], tmc2130_pwm_freq[axis], tmc2130_pwm_auto[axis], 0, 0);
|
||||
|
|
@ -210,15 +211,6 @@ void tmc2130_init(TMCInitParams params)
|
|||
#endif //TMC2130_STEALTH_E
|
||||
}
|
||||
|
||||
tmc2130_sg_err[0] = 0;
|
||||
tmc2130_sg_err[1] = 0;
|
||||
tmc2130_sg_err[2] = 0;
|
||||
tmc2130_sg_err[3] = 0;
|
||||
tmc2130_sg_cnt[0] = 0;
|
||||
tmc2130_sg_cnt[1] = 0;
|
||||
tmc2130_sg_cnt[2] = 0;
|
||||
tmc2130_sg_cnt[3] = 0;
|
||||
|
||||
#ifdef TMC2130_LINEARITY_CORRECTION
|
||||
#ifdef TMC2130_LINEARITY_CORRECTION_XYZ
|
||||
tmc2130_set_wave(X_AXIS, 247, tmc2130_wave_fac[X_AXIS]);
|
||||
|
|
@ -238,48 +230,22 @@ void tmc2130_init(TMCInitParams params)
|
|||
uint8_t tmc2130_sample_diag()
|
||||
{
|
||||
uint8_t mask = 0;
|
||||
if (READ(X_TMC2130_DIAG)) mask |= X_AXIS_MASK;
|
||||
if (READ(Y_TMC2130_DIAG)) mask |= Y_AXIS_MASK;
|
||||
// if (READ(Z_TMC2130_DIAG)) mask |= Z_AXIS_MASK;
|
||||
// if (READ(E0_TMC2130_DIAG)) mask |= E_AXIS_MASK;
|
||||
if (!READ(X_TMC2130_DIAG)) mask |= X_AXIS_MASK;
|
||||
if (!READ(Y_TMC2130_DIAG)) mask |= Y_AXIS_MASK;
|
||||
// if (!READ(Z_TMC2130_DIAG)) mask |= Z_AXIS_MASK;
|
||||
// if (!READ(E0_TMC2130_DIAG)) mask |= E_AXIS_MASK;
|
||||
return mask;
|
||||
}
|
||||
|
||||
void tmc2130_st_isr()
|
||||
{
|
||||
if (tmc2130_mode == TMC2130_MODE_SILENT || tmc2130_sg_stop_on_crash == false) return;
|
||||
uint8_t crash = 0;
|
||||
uint8_t diag_mask = tmc2130_sample_diag();
|
||||
// for (uint8_t axis = X_AXIS; axis <= E_AXIS; axis++)
|
||||
for (uint8_t axis = X_AXIS; axis <= Z_AXIS; axis++)
|
||||
{
|
||||
uint8_t mask = (X_AXIS_MASK << axis);
|
||||
if (diag_mask & mask) tmc2130_sg_err[axis]++;
|
||||
else
|
||||
if (tmc2130_sg_err[axis] > 0) tmc2130_sg_err[axis]--;
|
||||
if (tmc2130_sg_cnt[axis] < tmc2130_sg_err[axis])
|
||||
{
|
||||
tmc2130_sg_cnt[axis] = tmc2130_sg_err[axis];
|
||||
#ifdef DEBUG_CRASHDET_COUNTERS
|
||||
tmc2130_sg_change = true;
|
||||
#endif
|
||||
uint8_t sg_thr = 64;
|
||||
// if (axis == Y_AXIS) sg_thr = 64;
|
||||
if (tmc2130_sg_err[axis] >= sg_thr)
|
||||
{
|
||||
tmc2130_sg_err[axis] = 0;
|
||||
crash |= mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tmc2130_sg_homing_axes_mask == 0)
|
||||
{
|
||||
if (tmc2130_sg_stop_on_crash && crash)
|
||||
{
|
||||
tmc2130_sg_crash = crash;
|
||||
tmc2130_sg_stop_on_crash = false;
|
||||
crashdet_stop_and_save_print();
|
||||
}
|
||||
if (tmc2130_mode == TMC2130_MODE_SILENT || tmc2130_sg_stop_on_crash == false || tmc2130_sg_homing_axes_mask != 0)
|
||||
return;
|
||||
uint8_t mask = tmc2130_sample_diag();
|
||||
if (tmc2130_sg_stop_on_crash && mask) {
|
||||
tmc2130_sg_crash = mask;
|
||||
tmc2130_sg_stop_on_crash = false;
|
||||
crashdet_stop_and_save_print();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -312,7 +278,6 @@ void tmc2130_home_enter(uint8_t axes_mask)
|
|||
//Configuration to spreadCycle
|
||||
tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_NORMAL);
|
||||
tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr_home[axis]) << 16));
|
||||
// tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
|
||||
tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, __tcoolthrs(axis));
|
||||
tmc2130_setup_chopper(axis, tmc2130_mres[axis], tmc2130_current_h[axis], tmc2130_current_r_home[axis]);
|
||||
if (mask & (X_AXIS_MASK | Y_AXIS_MASK | Z_AXIS_MASK))
|
||||
|
|
@ -349,8 +314,7 @@ void tmc2130_home_exit()
|
|||
{
|
||||
// tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_NORMAL);
|
||||
tmc2130_setup_chopper(axis, tmc2130_mres[axis], tmc2130_current_h[axis], tmc2130_current_r[axis]);
|
||||
// tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
|
||||
tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16));
|
||||
tmc2130_wr(axis, TMC2130_REG_COOLCONF, (((uint32_t)tmc2130_sg_thr[axis]) << 16) | ((uint32_t)1 << 24));
|
||||
tmc2130_wr(axis, TMC2130_REG_TCOOLTHRS, __tcoolthrs(axis));
|
||||
tmc2130_wr(axis, TMC2130_REG_GCONF, TMC2130_GCONF_SGSENS);
|
||||
}
|
||||
|
|
@ -413,22 +377,7 @@ void tmc2130_check_overtemp()
|
|||
|
||||
}
|
||||
tmc2130_overtemp_timer.start();
|
||||
#ifdef DEBUG_CRASHDET_COUNTERS
|
||||
tmc2130_sg_change = true;
|
||||
#endif
|
||||
}
|
||||
#ifdef DEBUG_CRASHDET_COUNTERS
|
||||
if (tmc2130_sg_change)
|
||||
{
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
tmc2130_sg_change = false;
|
||||
lcd_set_cursor(0 + i*4, 3);
|
||||
lcd_print(itostr3(tmc2130_sg_cnt[i]));
|
||||
lcd_print(' ');
|
||||
}
|
||||
}
|
||||
#endif //DEBUG_CRASHDET_COUNTERS
|
||||
}
|
||||
|
||||
void tmc2130_setup_chopper(uint8_t axis, uint8_t mres, uint8_t current_h, uint8_t current_r)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue