Compare commits
129 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0edde09d7f | |||
| 3124474c95 | |||
| 09acb61496 | |||
| 8ac0f14796 | |||
| 934fc52d06 | |||
| cf46d1880c | |||
| 4f159a94cd | |||
| b53e14c7f8 | |||
| 94e9f26544 | |||
| 9e87af70b7 | |||
| fe56f5d3a6 | |||
| 90e5826256 | |||
| bb73c335a5 | |||
| 4c1f76567c | |||
| d193c814d3 | |||
| 2cb252da4a | |||
| 9a04c32f7f | |||
| 3b8e9fdd25 | |||
| e0b045da49 | |||
| 4220491a23 | |||
| 3abe1fcf20 | |||
| 02ed020f3f | |||
| 934ac24ad8 | |||
| e2d8b2fc26 | |||
| 3e8483d389 | |||
| a9c529f004 | |||
| a664587219 | |||
| 1619838592 | |||
| 98a33d37c7 | |||
| 12a2e5ae1b | |||
| 9b472a02e9 | |||
| c84bea7110 | |||
| 20f71fafa7 | |||
| 056e5a430e | |||
| 21a604407d | |||
| deb076b9cd | |||
| b8e2ad9d8f | |||
| 0cd9643957 | |||
| 8c78315f71 | |||
| 35dad3fdfe | |||
| bebf5dc6ab | |||
| 2fd1c48e1a | |||
| 301727defc | |||
| 1afd53a933 | |||
| 381aeb94c6 | |||
| 4a36520820 | |||
| 430eedf5c0 | |||
| 793a851d10 | |||
| 2d609487ac | |||
| 295f50379f | |||
| ecfff5099a | |||
| 986344640f | |||
| 69f69606e6 | |||
| 2aa2e5487a | |||
| 23d9020a65 | |||
| 43d9d1ce1b | |||
| ed7f3b21c0 | |||
| b062a3b226 | |||
| 89b0143b09 | |||
| 0790a9d5df | |||
| e05ac66561 | |||
| 3b33f7ec03 | |||
| b94c75bcda | |||
| 8dc8906d78 | |||
| ab684dc484 | |||
| 37fb26b557 | |||
| 7397e5a41d | |||
| 4a9d380117 | |||
| 0ec1a54309 | |||
| ab6e68c312 | |||
| c509603530 | |||
| e5ce65c32b | |||
| 851257c44b | |||
| 1662e953fc | |||
| dde89795c9 | |||
| 6e69ff93ef | |||
| 01b7d6c413 | |||
| 58398832a1 | |||
| 29469a0615 | |||
| b4dd20c0eb | |||
| eab13752dd | |||
| ad60cdd030 | |||
| 78e111a4d0 | |||
| 5558729562 | |||
| 2d32f7c377 | |||
| bcbdac6f6c | |||
| acc8bf1c79 | |||
| 4f107e9fee | |||
| 7844f3fa4f | |||
| 2f3af438ee | |||
| 4483aa5b45 | |||
| 20e6b6315e | |||
| ca4248bdd5 | |||
| 4a5d8ba87e | |||
| 7a5d849857 | |||
| 95c23e55a4 | |||
| 94ce0d24d6 | |||
| cdea1b8460 | |||
| cc122bec82 | |||
| 6afaf0034c | |||
| 7f0d7e760b | |||
| 69004322f6 | |||
| 7441094a41 | |||
| 29a5006df9 | |||
| 1aca038daf | |||
| ce92eb46ec | |||
| c852e9c6a1 | |||
| 2b8db0c2e2 | |||
| 74f297c871 | |||
| 76411392b7 | |||
| 74c81117c3 | |||
| 6f04d8dc77 | |||
| ade05c045e | |||
| 5140726c70 | |||
| 5334a8f0ed | |||
| ee99eed3bf | |||
| 9a5f1d2f51 | |||
| 170df9040b | |||
| f0bc4274f8 | |||
| 20a704b154 | |||
| 30a70c480c | |||
| 1d5e9c7dee | |||
| b8ab2d4987 | |||
| ce796cec97 | |||
| ad4de747ad | |||
| 95c1b7fb31 | |||
| 29b742cfc4 | |||
| 57bda2ff3b | |||
| 228179e09b |
@@ -7,7 +7,7 @@ contact_links:
|
||||
url: https://www.facebook.com/groups/1049718498464482
|
||||
about: Please ask and answer questions here.
|
||||
- name: 🕹 Marlin on Discord
|
||||
url: https://discord.gg/n5NJ59y
|
||||
url: https://discord.com/servers/marlin-firmware-461605380783472640
|
||||
about: Join the Discord server for support and discussion.
|
||||
- name: 🔗 Marlin Discussion Forum
|
||||
url: https://reprap.org/forum/list.php?415
|
||||
|
||||
@@ -43,7 +43,7 @@ We have a Message Board and a Facebook group where our knowledgable user communi
|
||||
|
||||
If chat is more your speed, you can join the MarlinFirmware Discord server:
|
||||
|
||||
* Use the link https://discord.gg/n5NJ59y to join up as a General User.
|
||||
* Use the link https://discord.com/servers/marlin-firmware-461605380783472640 to join up as a General User.
|
||||
* Even though our Discord is pretty active, it may take a while for community members to respond — please be patient!
|
||||
* Use the `#general` channel for general questions or discussion about Marlin.
|
||||
* Other channels exist for certain topics or are limited to Patrons. Check the channel list.
|
||||
|
||||
@@ -43,6 +43,7 @@ jobs:
|
||||
|
||||
# Native
|
||||
- linux_native
|
||||
- simulator_linux_release
|
||||
|
||||
# AVR
|
||||
- mega2560
|
||||
@@ -105,9 +106,9 @@ jobs:
|
||||
|
||||
# STM32F4
|
||||
- ARMED
|
||||
- BIGTREE_BTT002
|
||||
- BIGTREE_GTR_V1_0
|
||||
- BIGTREE_SKR_PRO
|
||||
- BTT_BTT002
|
||||
- BTT_GTR_V1_0
|
||||
- BTT_SKR_PRO
|
||||
- FLYF407ZG
|
||||
- FYSETC_S6
|
||||
- LERDGEK
|
||||
@@ -182,6 +183,13 @@ jobs:
|
||||
pio upgrade --dev
|
||||
pio pkg update --global
|
||||
|
||||
- name: Install Simulator dependencies
|
||||
run: |
|
||||
sudo apt-get install build-essential
|
||||
sudo apt-get install libsdl2-dev
|
||||
sudo apt-get install libsdl2-net-dev
|
||||
sudo apt-get install libglm-dev
|
||||
|
||||
- name: Run ${{ matrix.test-platform }} Tests
|
||||
run: |
|
||||
make tests-single-ci TEST_TARGET=${{ matrix.test-platform }}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# update-base-configs.yml
|
||||
# Generate new base config files if needed
|
||||
#
|
||||
|
||||
name: Update Base Configs
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 */6 * * *'
|
||||
|
||||
jobs:
|
||||
bump_date:
|
||||
name: Update Base Configs
|
||||
if: github.repository == 'MarlinFirmware/Marlin'
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
|
||||
- name: Checkout bugfix-2.1.x
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: bugfix-2.1.x
|
||||
|
||||
- name: Update Base Configs
|
||||
run: make base-configs
|
||||
@@ -125,6 +125,7 @@ vc-fileutils.settings
|
||||
# Visual Studio Code
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Simulation files
|
||||
imgui.ini
|
||||
|
||||
@@ -9,6 +9,7 @@ help:
|
||||
@echo "make marlin : Build marlin for the configured board"
|
||||
@echo "make format-pins -j : Reformat all pins files (-j for parallel execution)"
|
||||
@echo "make validate-pins -j : Validate all pins files, fails if any require reformatting"
|
||||
@echo "make base-configs : Regenerate the base configs in Marlin/src/inc"
|
||||
@echo "make tests-single-ci : Run a single test from inside the CI"
|
||||
@echo "make tests-single-local : Run a single test locally"
|
||||
@echo "make tests-single-local-docker : Run a single test locally, using docker"
|
||||
@@ -102,3 +103,9 @@ format-pins: $(PINS)
|
||||
validate-pins: format-pins
|
||||
@echo "Validating pins files"
|
||||
@git diff --exit-code || (git status && echo "\nError: Pins files are not formatted correctly. Run \"make format-pins\" to fix.\n" && exit 1)
|
||||
|
||||
base-configs:
|
||||
@echo "Generating base configs"
|
||||
@python $(SCRIPTS_DIR)/makeBaseConfigs.py 2>/dev/null \
|
||||
&& git add Marlin/src/inc/BaseConfiguration.h Marlin/src/inc/BaseConfiguration_adv.h \
|
||||
&& git commit -m "[cron] Update Base Configurations"
|
||||
|
||||
+49
-35
@@ -50,7 +50,7 @@
|
||||
*
|
||||
* Calibration Guides: https://reprap.org/wiki/Calibration
|
||||
* https://reprap.org/wiki/Triffid_Hunter%27s_Calibration_Guide
|
||||
* https://web.archive.org/web/20220907014303/https://sites.google.com/site/repraplogphase/calibration-of-your-reprap
|
||||
* https://web.archive.org/web/20220907014303/sites.google.com/site/repraplogphase/calibration-of-your-reprap
|
||||
* https://youtu.be/wAL9d7FgInk
|
||||
* https://teachingtechyt.github.io/calibration.html
|
||||
*
|
||||
@@ -77,7 +77,7 @@
|
||||
* Serial port -1 is the USB emulated serial port, if available.
|
||||
* Note: The first serial port (-1 or 0) will always be used by the Arduino bootloader.
|
||||
*
|
||||
* :[-1, 0, 1, 2, 3, 4, 5, 6, 7]
|
||||
* :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
*/
|
||||
#define SERIAL_PORT 0
|
||||
|
||||
@@ -99,22 +99,22 @@
|
||||
/**
|
||||
* Select a secondary serial port on the board to use for communication with the host.
|
||||
* Currently Ethernet (-2) is only supported on Teensy 4.1 boards.
|
||||
* :[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7]
|
||||
* :[-2, -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
*/
|
||||
//#define SERIAL_PORT_2 -1
|
||||
//#define BAUDRATE_2 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE
|
||||
|
||||
/**
|
||||
* Select a third serial port on the board to use for communication with the host.
|
||||
* Currently only supported for AVR, DUE, LPC1768/9 and STM32/STM32F1
|
||||
* :[-1, 0, 1, 2, 3, 4, 5, 6, 7]
|
||||
* Currently supported for AVR, DUE, SAMD51, LPC1768/9, STM32/STM32F1/HC32, and Teensy 4.x
|
||||
* :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
*/
|
||||
//#define SERIAL_PORT_3 1
|
||||
//#define BAUDRATE_3 250000 // :[2400, 9600, 19200, 38400, 57600, 115200, 250000, 500000, 1000000] Enable to override BAUDRATE
|
||||
|
||||
/**
|
||||
* Select a serial port to communicate with RS485 protocol
|
||||
* :[-1, 0, 1, 2, 3, 4, 5, 6, 7]
|
||||
* :[-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||||
*/
|
||||
//#define RS485_SERIAL_PORT 1
|
||||
#ifdef RS485_SERIAL_PORT
|
||||
@@ -385,14 +385,15 @@
|
||||
* PRUSA_MMU1 : Průša MMU1 (The "multiplexer" version)
|
||||
* PRUSA_MMU2 : Průša MMU2
|
||||
* PRUSA_MMU2S : Průša MMU2S (Requires MK3S extruder with motion sensor, EXTRUDERS = 5)
|
||||
* PRUSA_MMU3 : Průša MMU3 (Requires MK3S extruder with motion sensor and MMU firmware version 3.x.x, EXTRUDERS = 5)
|
||||
* EXTENDABLE_EMU_MMU2 : MMU with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware)
|
||||
* EXTENDABLE_EMU_MMU2S : MMUS with configurable number of filaments (ERCF, SMuFF or similar with Průša MMU2 compatible firmware)
|
||||
*
|
||||
* Requires NOZZLE_PARK_FEATURE to park print head in case MMU unit fails.
|
||||
* See additional options in Configuration_adv.h.
|
||||
* :["PRUSA_MMU1", "PRUSA_MMU2", "PRUSA_MMU2S", "EXTENDABLE_EMU_MMU2", "EXTENDABLE_EMU_MMU2S"]
|
||||
* :["PRUSA_MMU1", "PRUSA_MMU2", "PRUSA_MMU2S", "PRUSA_MMU3", "EXTENDABLE_EMU_MMU2", "EXTENDABLE_EMU_MMU2S"]
|
||||
*/
|
||||
//#define MMU_MODEL PRUSA_MMU2
|
||||
//#define MMU_MODEL PRUSA_MMU3
|
||||
|
||||
// @section psu control
|
||||
|
||||
@@ -713,10 +714,13 @@
|
||||
* Use a physical model of the hotend to control temperature. When configured correctly this gives
|
||||
* better responsiveness and stability than PID and removes the need for PID_EXTRUSION_SCALING
|
||||
* and PID_FAN_SCALING. Enable MPC_AUTOTUNE and use M306 T to autotune the model.
|
||||
* @section mpctemp
|
||||
* @section mpc temp
|
||||
*/
|
||||
#if ENABLED(MPCTEMP)
|
||||
#define MPC_AUTOTUNE // Include a method to do MPC auto-tuning (~6.3K bytes of flash)
|
||||
#if ENABLED(MPC_AUTOTUNE)
|
||||
//#define MPC_AUTOTUNE_DEBUG // Enable MPC debug logging (~870 bytes of flash)
|
||||
#endif
|
||||
//#define MPC_EDIT_MENU // Add MPC editing to the "Advanced Settings" menu. (~1.3K bytes of flash)
|
||||
//#define MPC_AUTOTUNE_MENU // Add MPC auto-tuning to the "Advanced Settings" menu. (~350 bytes of flash)
|
||||
|
||||
@@ -838,14 +842,16 @@
|
||||
// Lasko "MyHeat Personal Heater" (200w) modified with a Fotek SSR-10DA to control only the heating element
|
||||
// and placed inside the small Creality printer enclosure tent.
|
||||
//
|
||||
#define DEFAULT_chamberKp 37.04
|
||||
#define DEFAULT_chamberKi 1.40
|
||||
#define DEFAULT_chamberKp 37.04
|
||||
#define DEFAULT_chamberKi 1.40
|
||||
#define DEFAULT_chamberKd 655.17
|
||||
// M309 P37.04 I1.04 D655.17
|
||||
|
||||
// FIND YOUR OWN: "M303 E-2 C8 S50" to run autotune on the chamber at 50 degreesC for 8 cycles.
|
||||
#endif // PIDTEMPCHAMBER
|
||||
|
||||
// @section pid temp
|
||||
|
||||
#if ANY(PIDTEMP, PIDTEMPBED, PIDTEMPCHAMBER)
|
||||
//#define PID_OPENLOOP // Puts PID in open loop. M104/M140 sets the output power from 0 to PID_MAX
|
||||
//#define SLOW_PWM_HEATERS // PWM with very low frequency (roughly 0.125Hz=8s) and minimum state time of approximately 1s useful for heaters driven by a relay
|
||||
@@ -1464,7 +1470,8 @@
|
||||
* A lightweight, solenoid-driven probe.
|
||||
* For information about this sensor https://github.com/bigtreetech/MicroProbe
|
||||
*
|
||||
* Also requires: PROBE_ENABLE_DISABLE
|
||||
* Also requires PROBE_ENABLE_DISABLE
|
||||
* With FT_MOTION requires ENDSTOP_INTERRUPTS_FEATURE
|
||||
*/
|
||||
//#define BIQU_MICROPROBE_V1 // Triggers HIGH
|
||||
//#define BIQU_MICROPROBE_V2 // Triggers LOW
|
||||
@@ -1634,6 +1641,7 @@
|
||||
#define PROBE_TARE_DELAY 200 // (ms) Delay after tare before
|
||||
#define PROBE_TARE_STATE HIGH // State to write pin for tare
|
||||
//#define PROBE_TARE_PIN PA5 // Override default pin
|
||||
//#define PROBE_TARE_MENU // Display a menu item to tare the probe
|
||||
#if ENABLED(PROBE_ACTIVATION_SWITCH)
|
||||
//#define PROBE_TARE_ONLY_WHILE_INACTIVE // Fail to tare/probe if PROBE_ACTIVATION_SWITCH is active
|
||||
#endif
|
||||
@@ -1723,6 +1731,8 @@
|
||||
#define PROBING_BED_TEMP 50
|
||||
#endif
|
||||
|
||||
// @section stepper drivers
|
||||
|
||||
// For Inverting Stepper Enable Pins (Active Low) use 0, Non Inverting (Active High) use 1
|
||||
// :{ 0:'Low', 1:'High' }
|
||||
#define X_ENABLE_ON 0
|
||||
@@ -1896,6 +1906,8 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @section filament runout sensors
|
||||
*
|
||||
* Filament Runout Sensors
|
||||
* Mechanical or opto endstops are used to check for the presence of filament.
|
||||
*
|
||||
@@ -2418,9 +2430,9 @@
|
||||
#define PREHEAT_2_TEMP_CHAMBER 35
|
||||
#define PREHEAT_2_FAN_SPEED 0 // Value from 0 to 255
|
||||
|
||||
// @section motion
|
||||
|
||||
/**
|
||||
* @section nozzle park
|
||||
*
|
||||
* Nozzle Park
|
||||
*
|
||||
* Park the nozzle at the given XYZ position on idle or G27.
|
||||
@@ -2443,6 +2455,8 @@
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @section nozzle clean
|
||||
*
|
||||
* Clean Nozzle Feature
|
||||
*
|
||||
* Adds the G12 command to perform a nozzle cleaning process.
|
||||
@@ -2603,9 +2617,24 @@
|
||||
//#include "Configuration_Secure.h" // External file with PASSWORD_DEFAULT_VALUE
|
||||
#endif
|
||||
|
||||
//=============================================================================
|
||||
//============================= LCD and SD support ============================
|
||||
//=============================================================================
|
||||
// @section media
|
||||
|
||||
/**
|
||||
* SD CARD
|
||||
*
|
||||
* SD Card support is disabled by default. If your controller has an SD slot,
|
||||
* you must uncomment the following option or it won't work.
|
||||
*/
|
||||
//#define SDSUPPORT
|
||||
|
||||
/**
|
||||
* SD CARD: ENABLE CRC
|
||||
*
|
||||
* Use CRC checks and retries on the SD communication.
|
||||
*/
|
||||
#if ENABLED(SDSUPPORT)
|
||||
//#define SD_CHECK_AND_RETRY
|
||||
#endif
|
||||
|
||||
// @section interface
|
||||
|
||||
@@ -2652,21 +2681,6 @@
|
||||
*/
|
||||
#define LCD_INFO_SCREEN_STYLE 0
|
||||
|
||||
/**
|
||||
* SD CARD
|
||||
*
|
||||
* SD Card support is disabled by default. If your controller has an SD slot,
|
||||
* you must uncomment the following option or it won't work.
|
||||
*/
|
||||
//#define SDSUPPORT
|
||||
|
||||
/**
|
||||
* SD CARD: ENABLE CRC
|
||||
*
|
||||
* Use CRC checks and retries on the SD communication.
|
||||
*/
|
||||
//#define SD_CHECK_AND_RETRY
|
||||
|
||||
/**
|
||||
* LCD Menu Items
|
||||
*
|
||||
@@ -2795,7 +2809,7 @@
|
||||
|
||||
//
|
||||
// Original RADDS LCD Display+Encoder+SDCardReader
|
||||
// https://web.archive.org/web/20200719145306/http://doku.radds.org/dokumentation/lcd-display/
|
||||
// https://web.archive.org/web/20200719145306/doku.radds.org/dokumentation/lcd-display/
|
||||
//
|
||||
//#define RADDS_DISPLAY
|
||||
|
||||
@@ -2861,7 +2875,7 @@
|
||||
|
||||
//
|
||||
// Elefu RA Board Control Panel
|
||||
// https://web.archive.org/web/20140823033947/http://www.elefu.com/index.php?route=product/product&product_id=53
|
||||
// https://web.archive.org/web/20140823033947/www.elefu.com/index.php?route=product/product&product_id=53
|
||||
//
|
||||
//#define RA_CONTROL_PANEL
|
||||
|
||||
@@ -2993,7 +3007,7 @@
|
||||
|
||||
//
|
||||
// Cartesio UI
|
||||
// https://web.archive.org/web/20180605050442/http://mauk.cc/webshop/cartesio-shop/electronics/user-interface
|
||||
// https://web.archive.org/web/20180605050442/mauk.cc/webshop/cartesio-shop/electronics/user-interface
|
||||
//
|
||||
//#define CARTESIO_UI
|
||||
|
||||
|
||||
+180
-49
@@ -47,8 +47,9 @@
|
||||
* 2 = config.ini - File format for PlatformIO preprocessing.
|
||||
* 3 = schema.json - The entire configuration schema. (13 = pattern groups)
|
||||
* 4 = schema.yml - The entire configuration schema.
|
||||
* 5 = Config.h - Minimal configuration by popular demand.
|
||||
*/
|
||||
//#define CONFIG_EXPORT 2 // :[1:'JSON', 2:'config.ini', 3:'schema.json', 4:'schema.yml']
|
||||
//#define CONFIG_EXPORT 105 // :[1:'JSON', 2:'config.ini', 3:'schema.json', 4:'schema.yml', 5:'Config.h']
|
||||
|
||||
//===========================================================================
|
||||
//============================= Thermal Settings ============================
|
||||
@@ -303,9 +304,9 @@
|
||||
* If you get false positives for "Thermal Runaway", increase
|
||||
* THERMAL_PROTECTION_HYSTERESIS and/or THERMAL_PROTECTION_PERIOD
|
||||
*/
|
||||
#if ENABLED(THERMAL_PROTECTION_HOTENDS)
|
||||
#define THERMAL_PROTECTION_PERIOD 40 // (seconds)
|
||||
#define THERMAL_PROTECTION_HYSTERESIS 4 // (°C)
|
||||
#if ALL(HAS_HOTEND, THERMAL_PROTECTION_HOTENDS)
|
||||
#define THERMAL_PROTECTION_PERIOD 40 // (seconds)
|
||||
#define THERMAL_PROTECTION_HYSTERESIS 4 // (°C)
|
||||
|
||||
//#define ADAPTIVE_FAN_SLOWING // Slow down the part-cooling fan if the temperature drops
|
||||
#if ENABLED(ADAPTIVE_FAN_SLOWING)
|
||||
@@ -334,7 +335,7 @@
|
||||
/**
|
||||
* Thermal Protection parameters for the bed are just as above for hotends.
|
||||
*/
|
||||
#if ENABLED(THERMAL_PROTECTION_BED)
|
||||
#if TEMP_SENSOR_BED && ENABLED(THERMAL_PROTECTION_BED)
|
||||
#define THERMAL_PROTECTION_BED_PERIOD 20 // (seconds)
|
||||
#define THERMAL_PROTECTION_BED_HYSTERESIS 2 // (°C)
|
||||
|
||||
@@ -348,7 +349,7 @@
|
||||
/**
|
||||
* Thermal Protection parameters for the heated chamber.
|
||||
*/
|
||||
#if ENABLED(THERMAL_PROTECTION_CHAMBER)
|
||||
#if TEMP_SENSOR_CHAMBER && ENABLED(THERMAL_PROTECTION_CHAMBER)
|
||||
#define THERMAL_PROTECTION_CHAMBER_PERIOD 20 // (seconds)
|
||||
#define THERMAL_PROTECTION_CHAMBER_HYSTERESIS 2 // (°C)
|
||||
|
||||
@@ -362,7 +363,7 @@
|
||||
/**
|
||||
* Thermal Protection parameters for the laser cooler.
|
||||
*/
|
||||
#if ENABLED(THERMAL_PROTECTION_COOLER)
|
||||
#if TEMP_SENSOR_COOLER && ENABLED(THERMAL_PROTECTION_COOLER)
|
||||
#define THERMAL_PROTECTION_COOLER_PERIOD 10 // (seconds)
|
||||
#define THERMAL_PROTECTION_COOLER_HYSTERESIS 3 // (°C)
|
||||
|
||||
@@ -1118,15 +1119,18 @@
|
||||
/**
|
||||
* Fixed-time-based Motion Control -- EXPERIMENTAL
|
||||
* Enable/disable and set parameters with G-code M493.
|
||||
* See ft_types.h for named values used by FTM options.
|
||||
*/
|
||||
//#define FT_MOTION
|
||||
#if ENABLED(FT_MOTION)
|
||||
#define FTM_DEFAULT_MODE ftMotionMode_DISABLED // Default mode of fixed time control. (Enums in ft_types.h)
|
||||
#define FTM_DEFAULT_DYNFREQ_MODE dynFreqMode_DISABLED // Default mode of dynamic frequency calculation. (Enums in ft_types.h)
|
||||
#define FTM_SHAPING_DEFAULT_X_FREQ 37.0f // (Hz) Default peak frequency used by input shapers
|
||||
#define FTM_SHAPING_DEFAULT_Y_FREQ 37.0f // (Hz) Default peak frequency used by input shapers
|
||||
//#define FTM_IS_DEFAULT_MOTION // Use FT Motion as the factory default?
|
||||
#define FTM_DEFAULT_DYNFREQ_MODE dynFreqMode_DISABLED // Default mode of dynamic frequency calculation. (DISABLED, Z_BASED, MASS_BASED)
|
||||
#define FTM_DEFAULT_SHAPER_X ftMotionShaper_NONE // Default shaper mode on X axis (NONE, ZV, ZVD, ZVDD, ZVDDD, EI, 2HEI, 3HEI, MZV)
|
||||
#define FTM_DEFAULT_SHAPER_Y ftMotionShaper_NONE // Default shaper mode on Y axis
|
||||
#define FTM_SHAPING_DEFAULT_FREQ_X 37.0f // (Hz) Default peak frequency used by input shapers
|
||||
#define FTM_SHAPING_DEFAULT_FREQ_Y 37.0f // (Hz) Default peak frequency used by input shapers
|
||||
#define FTM_LINEAR_ADV_DEFAULT_ENA false // Default linear advance enable (true) or disable (false)
|
||||
#define FTM_LINEAR_ADV_DEFAULT_K 0.0f // Default linear advance gain
|
||||
#define FTM_LINEAR_ADV_DEFAULT_K 0 // Default linear advance gain, integer value. (Acceleration-based scaling factor.)
|
||||
#define FTM_SHAPING_ZETA_X 0.1f // Zeta used by input shapers for X axis
|
||||
#define FTM_SHAPING_ZETA_Y 0.1f // Zeta used by input shapers for Y axis
|
||||
|
||||
@@ -1149,18 +1153,13 @@
|
||||
#define FTM_FS 1000 // (Hz) Frequency for trajectory generation. (Reciprocal of FTM_TS)
|
||||
#define FTM_TS 0.001f // (s) Time step for trajectory generation. (Reciprocal of FTM_FS)
|
||||
|
||||
// These values may be configured to adjust the duration of loop().
|
||||
#define FTM_STEPS_PER_LOOP 60 // Number of stepper commands to generate each loop()
|
||||
#define FTM_POINTS_PER_LOOP 100 // Number of trajectory points to generate each loop()
|
||||
|
||||
#if DISABLED(COREXY)
|
||||
#define FTM_STEPPER_FS 20000 // (Hz) Frequency for stepper I/O update
|
||||
|
||||
// Use this to adjust the time required to consume the command buffer.
|
||||
// Try increasing this value if stepper motion is choppy.
|
||||
#define FTM_STEPPERCMD_BUFF_SIZE 3000 // Size of the stepper command buffers
|
||||
// (FTM_STEPS_PER_LOOP * FTM_POINTS_PER_LOOP) is a good start
|
||||
// If you run out of memory, fall back to 3000 and increase progressively
|
||||
|
||||
#else
|
||||
// CoreXY motion needs a larger buffer size. These values are based on our testing.
|
||||
#define FTM_STEPPER_FS 30000
|
||||
@@ -1555,6 +1554,9 @@
|
||||
// BACK menu items keep the highlight at the top
|
||||
//#define TURBO_BACK_MENU_ITEM
|
||||
|
||||
// BACK menu items show "Back" instead of the previous menu name
|
||||
//#define GENERIC_BACK_MENU_ITEM
|
||||
|
||||
// Insert a menu for preheating at the top level to allow for quick access
|
||||
//#define PREHEAT_SHORTCUT_MENU_ITEM
|
||||
|
||||
@@ -2971,7 +2973,7 @@
|
||||
|
||||
#if AXIS_IS_TMC_CONFIG(X)
|
||||
#define X_CURRENT 800 // (mA) RMS current. Multiply by 1.414 for peak current.
|
||||
#define X_CURRENT_HOME X_CURRENT // (mA) RMS current for sensorless homing
|
||||
#define X_CURRENT_HOME X_CURRENT // (mA) RMS current for homing. (Typically lower than *_CURRENT.)
|
||||
#define X_MICROSTEPS 16 // 0..256
|
||||
#define X_RSENSE 0.11
|
||||
#define X_CHAIN_POS -1 // -1..0: Not chained. 1: MCU MOSI connected. 2: Next in chain, ...
|
||||
@@ -3181,6 +3183,13 @@
|
||||
//#define E7_HOLD_MULTIPLIER 0.5
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Use the homing current for all probing. (e.g., Current may be reduced to the
|
||||
* point where a collision makes the motor skip instead of damaging the bed,
|
||||
* though this is unlikely to save delicate probes from being damaged.
|
||||
*/
|
||||
//#define PROBING_USE_CURRENT_HOME
|
||||
|
||||
// @section tmc/spi
|
||||
|
||||
/**
|
||||
@@ -3445,7 +3454,7 @@
|
||||
/**
|
||||
* Step on both rising and falling edge signals (as with a square wave).
|
||||
*/
|
||||
//#define EDGE_STEPPING
|
||||
#define EDGE_STEPPING
|
||||
|
||||
/**
|
||||
* Enable M122 debugging command for TMC stepper drivers.
|
||||
@@ -3527,7 +3536,7 @@
|
||||
//#define PHOTOGRAPH_PIN 23
|
||||
|
||||
// Canon Hack Development Kit
|
||||
// https://web.archive.org/web/20200920094805/https://captain-slow.dk/2014/03/09/3d-printing-timelapses/
|
||||
// https://web.archive.org/web/20200920094805/captain-slow.dk/2014/03/09/3d-printing-timelapses/
|
||||
//#define CHDK_PIN 4
|
||||
|
||||
// Optional second move with delay to trigger the camera shutter
|
||||
@@ -4380,44 +4389,89 @@
|
||||
//#define E_MUX0_PIN 40 // Always Required
|
||||
//#define E_MUX1_PIN 42 // Needed for 3 to 8 inputs
|
||||
//#define E_MUX2_PIN 44 // Needed for 5 to 8 inputs
|
||||
#elif HAS_PRUSA_MMU2
|
||||
// Serial port used for communication with MMU2.
|
||||
#elif HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
// Common settings for MMU2/MMU2S/MMU3
|
||||
// Serial port used for communication with MMU2/MMU2S/MMU3.
|
||||
#define MMU2_SERIAL_PORT 2
|
||||
#define MMU_BAUD 115200
|
||||
|
||||
// Use hardware reset for MMU if a pin is defined for it
|
||||
//#define MMU2_RST_PIN 23
|
||||
|
||||
// Enable if the MMU2 has 12V stepper motors (MMU2 Firmware 1.0.2 and up)
|
||||
//#define MMU2_MODE_12V
|
||||
#if HAS_PRUSA_MMU2
|
||||
// Enable if the MMU2 has 12V stepper motors (MMU2 Firmware 1.0.2 and up)
|
||||
//#define MMU2_MODE_12V
|
||||
|
||||
// G-code to execute when MMU2 F.I.N.D.A. probe detects filament runout
|
||||
#define MMU2_FILAMENT_RUNOUT_SCRIPT "M600"
|
||||
// G-code to execute when MMU2 F.I.N.D.A. probe detects filament runout
|
||||
#define MMU2_FILAMENT_RUNOUT_SCRIPT "M600"
|
||||
#endif
|
||||
|
||||
// Add an LCD menu for MMU2
|
||||
//#define MMU2_MENUS
|
||||
// Add an LCD menu for MMU2/MMU2S/MMU3
|
||||
//#define MMU_MENUS
|
||||
|
||||
// Settings for filament load / unload from the LCD menu.
|
||||
// This is for Průša MK3-style extruders. Customize for your hardware.
|
||||
#define MMU2_FILAMENTCHANGE_EJECT_FEED 80.0
|
||||
|
||||
/**
|
||||
* ------------
|
||||
* MMU2 / MMU2S
|
||||
* ------------
|
||||
* MMU2 sequences use mm/min. Not compatible with MMU3 (see below).
|
||||
* #define MMU2_LOAD_TO_NOZZLE_SEQUENCE \
|
||||
* { 4.4, 871 }, \
|
||||
* { 10.0, 1393 }, \
|
||||
* { 4.4, 871 }, \
|
||||
* { 10.0, 198 }
|
||||
*/
|
||||
|
||||
/* #define MMU2_RAMMING_SEQUENCE \
|
||||
* { 1.0, 1000 }, \
|
||||
* { 1.0, 1500 }, \
|
||||
* { 2.0, 2000 }, \
|
||||
* { 1.5, 3000 }, \
|
||||
* { 2.5, 4000 }, \
|
||||
* { -15.0, 5000 }, \
|
||||
* { -14.0, 1200 }, \
|
||||
* { -6.0, 600 }, \
|
||||
* { 10.0, 700 }, \
|
||||
* { -10.0, 400 }, \
|
||||
* { -50.0, 2000 }
|
||||
*/
|
||||
|
||||
/**
|
||||
* ----
|
||||
* MMU3
|
||||
* ----
|
||||
* These values are compatible with MMU3 as they are defined in mm/s
|
||||
*/
|
||||
|
||||
#define MMU2_EXTRUDER_PTFE_LENGTH 42.3 // (mm)
|
||||
#define MMU2_EXTRUDER_HEATBREAK_LENGTH 17.7 // (mm)
|
||||
|
||||
#define MMU2_LOAD_TO_NOZZLE_SEQUENCE \
|
||||
{ 7.2, 1145 }, \
|
||||
{ 14.4, 871 }, \
|
||||
{ 36.0, 1393 }, \
|
||||
{ 14.4, 871 }, \
|
||||
{ 50.0, 198 }
|
||||
{ MMU2_EXTRUDER_PTFE_LENGTH, MMM_TO_MMS(810) }, /* (13.5 mm/s) Fast load ahead of heatbreak */ \
|
||||
{ MMU2_EXTRUDER_HEATBREAK_LENGTH, MMM_TO_MMS(198) } // ( 3.3 mm/s) Slow load after heatbreak
|
||||
|
||||
#define MMU2_RAMMING_SEQUENCE \
|
||||
{ 1.0, 1000 }, \
|
||||
{ 1.0, 1500 }, \
|
||||
{ 2.0, 2000 }, \
|
||||
{ 1.5, 3000 }, \
|
||||
{ 2.5, 4000 }, \
|
||||
{ -15.0, 5000 }, \
|
||||
{ -14.0, 1200 }, \
|
||||
{ -6.0, 600 }, \
|
||||
{ 10.0, 700 }, \
|
||||
{ -10.0, 400 }, \
|
||||
{ -50.0, 2000 }
|
||||
{ 0.2816, MMM_TO_MMS(1339.0) }, \
|
||||
{ 0.3051, MMM_TO_MMS(1451.0) }, \
|
||||
{ 0.3453, MMM_TO_MMS(1642.0) }, \
|
||||
{ 0.3990, MMM_TO_MMS(1897.0) }, \
|
||||
{ 0.4761, MMM_TO_MMS(2264.0) }, \
|
||||
{ 0.5767, MMM_TO_MMS(2742.0) }, \
|
||||
{ 0.5691, MMM_TO_MMS(3220.0) }, \
|
||||
{ 0.1081, MMM_TO_MMS(3220.0) }, \
|
||||
{ 0.7644, MMM_TO_MMS(3635.0) }, \
|
||||
{ 0.8248, MMM_TO_MMS(3921.0) }, \
|
||||
{ 0.8483, MMM_TO_MMS(4033.0) }, \
|
||||
{ -15.0, MMM_TO_MMS(6000.0) }, \
|
||||
{ -24.5, MMM_TO_MMS(1200.0) }, \
|
||||
{ -7.0, MMM_TO_MMS( 600.0) }, \
|
||||
{ -3.5, MMM_TO_MMS( 360.0) }, \
|
||||
{ 20.0, MMM_TO_MMS( 454.0) }, \
|
||||
{ -20.0, MMM_TO_MMS( 303.0) }, \
|
||||
{ -35.0, MMM_TO_MMS(2000.0) }
|
||||
|
||||
/**
|
||||
* Using a sensor like the MMU2S
|
||||
@@ -4427,11 +4481,26 @@
|
||||
#if HAS_PRUSA_MMU2S
|
||||
#define MMU2_C0_RETRY 5 // Number of retries (total time = timeout*retries)
|
||||
|
||||
/**
|
||||
* This is called after the filament runout sensor is triggered to check if
|
||||
* the filament has been loaded properly by moving the filament back and
|
||||
* forth to see if the filament runout sensor is going to get triggered
|
||||
* again, which should not occur if the filament is properly loaded.
|
||||
*
|
||||
* Thus, the MMU2_CAN_LOAD_SEQUENCE should contain some forward and
|
||||
* backward moves. The forward moves should be greater than the backward
|
||||
* moves.
|
||||
*
|
||||
* This is useless if your filament runout sensor is way behind the gears.
|
||||
* In that case use {0, MMU2_CAN_LOAD_FEEDRATE}
|
||||
*
|
||||
* Adjust MMU2_CAN_LOAD_SEQUENCE according to your setup.
|
||||
*/
|
||||
#define MMU2_CAN_LOAD_FEEDRATE 800 // (mm/min)
|
||||
#define MMU2_CAN_LOAD_SEQUENCE \
|
||||
{ 0.1, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ 60.0, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ -52.0, MMU2_CAN_LOAD_FEEDRATE }
|
||||
{ 5.0, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ 15.0, MMU2_CAN_LOAD_FEEDRATE }, \
|
||||
{ -10.0, MMU2_CAN_LOAD_FEEDRATE }
|
||||
|
||||
#define MMU2_CAN_LOAD_RETRACT 6.0 // (mm) Keep under the distance between Load Sequence values
|
||||
#define MMU2_CAN_LOAD_DEVIATION 0.8 // (mm) Acceptable deviation
|
||||
@@ -4442,6 +4511,68 @@
|
||||
|
||||
// Continue unloading if sensor detects filament after the initial unload move
|
||||
//#define MMU_IR_UNLOAD_MOVE
|
||||
|
||||
#elif HAS_PRUSA_MMU3
|
||||
|
||||
// MMU3 settings
|
||||
|
||||
#define MMU2_MAX_RETRIES 3 // Number of retries (total time = timeout*retries)
|
||||
|
||||
// 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
|
||||
#define MMU2_LOAD_TO_NOZZLE_LENGTH 87.0 + 5.0
|
||||
|
||||
// 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
|
||||
// Beware - this value is used to initialize the MMU logic layer - it will be sent to the MMU upon line up (written into its 8bit register 0x0b)
|
||||
// However - in the G-code we can get a request to set the extra load distance at runtime to something else (M708 A0xb Xsomething).
|
||||
// The printer intercepts such a call and sets its extra load distance to match the new value as well.
|
||||
#define MMU2_FILAMENT_SENSOR_POSITION 0 // (mm)
|
||||
#define MMU2_LOAD_DISTANCE_PAST_GEARS 5 // (mm)
|
||||
#define MMU2_TOOL_CHANGE_LOAD_LENGTH MMU2_FILAMENT_SENSOR_POSITION + MMU2_LOAD_DISTANCE_PAST_GEARS // (mm)
|
||||
|
||||
#define MMU2_LOAD_TO_NOZZLE_FEED_RATE 20.0 // (mm/s)
|
||||
#define MMU2_UNLOAD_TO_FINDA_FEED_RATE 120.0 // (mm/s)
|
||||
|
||||
#define MMU2_VERIFY_LOAD_TO_NOZZLE_FEED_RATE 50.0 // (mm/s)
|
||||
#define MMU2_VERIFY_LOAD_TO_NOZZLE_TWEAK -5.0 // (mm) Amount to adjust the length for verifying load-to-nozzle
|
||||
|
||||
// The first thing the MMU does is initialize its axis.
|
||||
// Meanwhile the E-motor will unload 20mm of filament in about 1 second.
|
||||
#define MMU2_RETRY_UNLOAD_TO_FINDA_LENGTH 80.0 // (mm)
|
||||
#define MMU2_RETRY_UNLOAD_TO_FINDA_FEED_RATE 80.0 // (mm/s)
|
||||
|
||||
// After loading a new filament, the printer will extrude this length of filament
|
||||
// then retract to the original position. This is used to check if the filament sensor
|
||||
// reading flickers or filament is jammed.
|
||||
#define MMU2_CHECK_FILAMENT_PRESENCE_EXTRUSION_LENGTH (MMU2_EXTRUDER_PTFE_LENGTH + MMU2_EXTRUDER_HEATBREAK_LENGTH + MMU2_VERIFY_LOAD_TO_NOZZLE_TWEAK + MMU2_FILAMENT_SENSOR_POSITION) // (mm)
|
||||
|
||||
#define MMU_HAS_CUTTER // Enable cutter related functionalities
|
||||
//#define MMU_FORCE_STEALTH_MODE // Force stealth mode and disable menu item
|
||||
|
||||
/**
|
||||
* SpoolJoin Consumes All Filament -- EXPERIMENTAL
|
||||
*
|
||||
* SpoolJoin normally triggers when FINDA sensor untriggers while printing.
|
||||
* This is the default behaviour and it doesn't consume all the filament
|
||||
* before triggering a filament change. This leaves some filament in the
|
||||
* current slot and before switching to the next slot it is unloaded.
|
||||
*
|
||||
* Enabling this option will trigger the filament change when both FINDA
|
||||
* and Filament Runout Sensor triggers during the print and it allows the
|
||||
* filament in the current slot to be completely consumed before doing the
|
||||
* filament change. But this can cause problems as a little bit of filament
|
||||
* will be left between the extruder gears (thinking that the filament
|
||||
* sensor is triggered through the gears) and the end of the PTFE tube and
|
||||
* can cause filament load issues.
|
||||
*/
|
||||
//#define MMU_SPOOL_JOIN_CONSUMES_ALL_FILAMENT
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
@@ -4464,7 +4595,7 @@
|
||||
|
||||
//#define MMU2_DEBUG // Write debug info to serial output
|
||||
|
||||
#endif // HAS_PRUSA_MMU2
|
||||
#endif // HAS_PRUSA_MMU2 || HAS_PRUSA_MMU3
|
||||
|
||||
/**
|
||||
* Advanced Print Counter settings
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@
|
||||
* here we define this default string as the date where the latest release
|
||||
* version was tagged.
|
||||
*/
|
||||
//#define STRING_DISTRIBUTION_DATE "2024-07-12"
|
||||
//#define STRING_DISTRIBUTION_DATE "2024-08-29"
|
||||
|
||||
/**
|
||||
* Defines a generic printer name to be output to the LCD after booting Marlin.
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
*
|
||||
* Logical Pin: 28 29 30 31 32 33 34 35 20 21 22 23 24 25 26 27 10 11 12 13 14 15 16 17 00 01 02 03 04 05 06 07 08 09(46*47)36 37 18 19 38 39 40 41 42 43 44 45
|
||||
* Port: A0 A1 A2 A3 A4 A5 A6 A7 B0 B1 B2 B3 B4 B5 B6 B7 C0 C1 C2 C3 C4 C5 C6 C7 D0 D1 D2 D3 D4 D5 D6 D7 E0 E1 E2 E3 E4 E5 E6 E7 F0 F1 F2 F3 F4 F5 F6 F7
|
||||
* The logical pins 46 and 47 are not supported by Teensyduino, but are supported below as E2 and E3
|
||||
* Logical pins 46-47 aren't supported by Teensyduino, but are supported below as E2 and E3
|
||||
*/
|
||||
|
||||
#include "../fastio.h"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
#define digitalPinToTimer_DEBUG(p) digitalPinToTimer(p)
|
||||
#define digitalPinToBitMask_DEBUG(p) digitalPinToBitMask(p)
|
||||
#define digitalPinToPort_DEBUG(p) digitalPinToPort(p)
|
||||
#define GET_PINMODE(pin) (*portModeRegister(pin) & digitalPinToBitMask_DEBUG(pin))
|
||||
#define getValidPinMode(pin) (*portModeRegister(pin) & digitalPinToBitMask_DEBUG(pin))
|
||||
|
||||
#elif AVR_ATmega2560_FAMILY_PLUS_70 // So we can access/display all the pins on boards using more than 70
|
||||
|
||||
@@ -50,32 +50,32 @@
|
||||
#define digitalPinToTimer_DEBUG(p) digitalPinToTimer_plus_70(p)
|
||||
#define digitalPinToBitMask_DEBUG(p) digitalPinToBitMask_plus_70(p)
|
||||
#define digitalPinToPort_DEBUG(p) digitalPinToPort_plus_70(p)
|
||||
bool GET_PINMODE(int8_t pin) {return *portModeRegister(digitalPinToPort_DEBUG(pin)) & digitalPinToBitMask_DEBUG(pin); }
|
||||
bool getValidPinMode(pin_t pin) {return *portModeRegister(digitalPinToPort_DEBUG(pin)) & digitalPinToBitMask_DEBUG(pin); }
|
||||
|
||||
#else
|
||||
|
||||
#define digitalPinToTimer_DEBUG(p) digitalPinToTimer(p)
|
||||
#define digitalPinToBitMask_DEBUG(p) digitalPinToBitMask(p)
|
||||
#define digitalPinToPort_DEBUG(p) digitalPinToPort(p)
|
||||
bool GET_PINMODE(int8_t pin) {return *portModeRegister(digitalPinToPort_DEBUG(pin)) & digitalPinToBitMask_DEBUG(pin); }
|
||||
#define GET_ARRAY_PIN(p) pgm_read_byte(&pin_array[p].pin)
|
||||
bool getValidPinMode(pin_t pin) {return *portModeRegister(digitalPinToPort_DEBUG(pin)) & digitalPinToBitMask_DEBUG(pin); }
|
||||
#define getPinByIndex(p) pgm_read_byte(&pin_array[p].pin)
|
||||
|
||||
#endif
|
||||
|
||||
#define VALID_PIN(pin) (pin >= 0 && pin < NUM_DIGITAL_PINS ? 1 : 0)
|
||||
#define isValidPin(pin) (pin >= 0 && pin < NUM_DIGITAL_PINS ? 1 : 0)
|
||||
#if AVR_ATmega1284_FAMILY
|
||||
#define IS_ANALOG(P) WITHIN(P, analogInputToDigitalPin(7), analogInputToDigitalPin(0))
|
||||
#define DIGITAL_PIN_TO_ANALOG_PIN(P) int(IS_ANALOG(P) ? (P) - analogInputToDigitalPin(7) : -1)
|
||||
#define isAnalogPin(P) WITHIN(P, analogInputToDigitalPin(7), analogInputToDigitalPin(0))
|
||||
#define digitalPinToAnalogIndex(P) int(isAnalogPin(P) ? (P) - analogInputToDigitalPin(7) : -1)
|
||||
#else
|
||||
#define _ANALOG1(P) WITHIN(P, analogInputToDigitalPin(0), analogInputToDigitalPin(7))
|
||||
#define _ANALOG2(P) WITHIN(P, analogInputToDigitalPin(8), analogInputToDigitalPin(15))
|
||||
#define IS_ANALOG(P) (_ANALOG1(P) || _ANALOG2(P))
|
||||
#define DIGITAL_PIN_TO_ANALOG_PIN(P) int(_ANALOG1(P) ? (P) - analogInputToDigitalPin(0) : _ANALOG2(P) ? (P) - analogInputToDigitalPin(8) + 8 : -1)
|
||||
#define isAnalogPin(P) (_ANALOG1(P) || _ANALOG2(P))
|
||||
#define digitalPinToAnalogIndex(P) int(_ANALOG1(P) ? (P) - analogInputToDigitalPin(0) : _ANALOG2(P) ? (P) - analogInputToDigitalPin(8) + 8 : -1)
|
||||
#endif
|
||||
#define GET_ARRAY_PIN(p) pgm_read_byte(&pin_array[p].pin)
|
||||
#define getPinByIndex(p) pgm_read_byte(&pin_array[p].pin)
|
||||
#define MULTI_NAME_PAD 26 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
void PRINT_ARRAY_NAME(uint8_t x) {
|
||||
void printPinNameByIndex(uint8_t x) {
|
||||
PGM_P const name_mem_pointer = (PGM_P)pgm_read_ptr(&pin_array[x].name);
|
||||
for (uint8_t y = 0; y < MAX_NAME_LENGTH; ++y) {
|
||||
char temp_char = pgm_read_byte(name_mem_pointer + y);
|
||||
@@ -88,7 +88,7 @@ void PRINT_ARRAY_NAME(uint8_t x) {
|
||||
}
|
||||
}
|
||||
|
||||
#define GET_ARRAY_IS_DIGITAL(x) pgm_read_byte(&pin_array[x].is_digital)
|
||||
#define getPinIsDigitalByIndex(x) pgm_read_byte(&pin_array[x].is_digital)
|
||||
|
||||
#if defined(__AVR_ATmega1284P__) // 1284 IDE extensions set this to the number of
|
||||
#undef NUM_DIGITAL_PINS // digital only pins while all other CPUs have it
|
||||
@@ -276,7 +276,7 @@ void timer_prefix(uint8_t T, char L, uint8_t N) { // T - timer L - pwm N -
|
||||
if (TEST(*TMSK, TOIE)) err_prob_interrupt();
|
||||
}
|
||||
|
||||
void pwm_details(uint8_t pin) {
|
||||
void printPinPWM(uint8_t pin) {
|
||||
switch (digitalPinToTimer_DEBUG(pin)) {
|
||||
|
||||
#if ABTEST(0)
|
||||
@@ -347,7 +347,7 @@ void pwm_details(uint8_t pin) {
|
||||
#else
|
||||
UNUSED(print_is_also_tied);
|
||||
#endif
|
||||
} // pwm_details
|
||||
} // printPinPWM
|
||||
|
||||
#ifndef digitalRead_mod // Use Teensyduino's version of digitalRead - it doesn't disable the PWMs
|
||||
int digitalRead_mod(const pin_t pin) { // same as digitalRead except the PWM stop section has been removed
|
||||
@@ -356,7 +356,7 @@ void pwm_details(uint8_t pin) {
|
||||
}
|
||||
#endif
|
||||
|
||||
void print_port(const pin_t pin) { // print port number
|
||||
void printPinPort(const pin_t pin) { // print port number
|
||||
#ifdef digitalPinToPort_DEBUG
|
||||
uint8_t x;
|
||||
SERIAL_ECHOPGM(" Port: ");
|
||||
@@ -386,7 +386,7 @@ void print_port(const pin_t pin) { // print port number
|
||||
#endif
|
||||
}
|
||||
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
|
||||
#undef ABTEST
|
||||
|
||||
@@ -127,7 +127,7 @@ typedef Servo hal_servo_t;
|
||||
#define HAL_ADC_RESOLUTION 10
|
||||
|
||||
#ifndef analogInputToDigitalPin
|
||||
#define analogInputToDigitalPin(p) ((p < 12U) ? (p) + 54U : -1)
|
||||
#define analogInputToDigitalPin(p) pin_t((p < 12U) ? (p) + 54U : -1)
|
||||
#endif
|
||||
|
||||
//
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
static Flags<_Nbr_16timers> DisablePending; // ISR should disable the timer at the next timer reset
|
||||
|
||||
// ------------------------
|
||||
/// Interrupt handler for the TC0 channel 1.
|
||||
// Interrupt handler for the TC0 channel 1.
|
||||
// ------------------------
|
||||
void Servo_Handler(const timer16_Sequence_t, Tc*, const uint8_t);
|
||||
|
||||
|
||||
@@ -64,19 +64,19 @@
|
||||
#define NUMBER_PINS_TOTAL PINS_COUNT
|
||||
|
||||
#define digitalRead_mod(p) extDigitalRead(p) // AVR digitalRead disabled PWM before it read the pin
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%02d"), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define GET_ARRAY_PIN(p) pin_array[p].pin
|
||||
#define GET_ARRAY_IS_DIGITAL(p) pin_array[p].is_digital
|
||||
#define VALID_PIN(pin) (pin >= 0 && pin < int8_t(NUMBER_PINS_TOTAL))
|
||||
#define DIGITAL_PIN_TO_ANALOG_PIN(p) int(p - analogInputToDigitalPin(0))
|
||||
#define IS_ANALOG(P) WITHIN(P, char(analogInputToDigitalPin(0)), char(analogInputToDigitalPin(NUM_ANALOG_INPUTS - 1)))
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%02d"), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinByIndex(p) pin_array[p].pin
|
||||
#define getPinIsDigitalByIndex(p) pin_array[p].is_digital
|
||||
#define isValidPin(pin) (pin >= 0 && pin < int8_t(NUMBER_PINS_TOTAL))
|
||||
#define digitalPinToAnalogIndex(p) int(p - analogInputToDigitalPin(0))
|
||||
#define isAnalogPin(P) WITHIN(P, pin_t(analogInputToDigitalPin(0)), pin_t(analogInputToDigitalPin(NUM_ANALOG_INPUTS - 1)))
|
||||
#define pwm_status(pin) (((g_pinStatus[pin] & 0xF) == PIN_STATUS_PWM) && \
|
||||
((g_APinDescription[pin].ulPinAttribute & PIN_ATTR_PWM) == PIN_ATTR_PWM))
|
||||
#define MULTI_NAME_PAD 14 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
bool GET_PINMODE(int8_t pin) { // 1: output, 0: input
|
||||
bool getValidPinMode(int8_t pin) { // 1: output, 0: input
|
||||
volatile Pio* port = g_APinDescription[pin].pPort;
|
||||
uint32_t mask = g_APinDescription[pin].ulPin;
|
||||
uint8_t pin_status = g_pinStatus[pin] & 0xF;
|
||||
@@ -85,14 +85,14 @@ bool GET_PINMODE(int8_t pin) { // 1: output, 0: input
|
||||
|| pwm_status(pin));
|
||||
}
|
||||
|
||||
void pwm_details(int32_t pin) {
|
||||
void printPinPWM(int32_t pin) {
|
||||
if (pwm_status(pin)) {
|
||||
uint32_t chan = g_APinDescription[pin].ulPWMChannel;
|
||||
SERIAL_ECHOPGM("PWM = ", PWM_INTERFACE->PWM_CH_NUM[chan].PWM_CDTY);
|
||||
}
|
||||
}
|
||||
|
||||
void print_port(const pin_t) {}
|
||||
void printPinPort(const pin_t) {}
|
||||
|
||||
/**
|
||||
* DUE Board pin | PORT | Label
|
||||
|
||||
@@ -152,7 +152,7 @@ void stepperTask(void *parameter) {
|
||||
xQueueReceive(dma.queue, &dma.current, portMAX_DELAY);
|
||||
dma.rw_pos = 0;
|
||||
|
||||
const bool using_ftMotion = TERN0(FT_MOTION, ftMotion.cfg.mode);
|
||||
const bool using_ftMotion = TERN0(FT_MOTION, ftMotion.cfg.active);
|
||||
|
||||
while (dma.rw_pos < DMA_SAMPLE_COUNT) {
|
||||
|
||||
|
||||
@@ -53,12 +53,11 @@ typedef uint64_t hal_timer_t;
|
||||
#if ENABLED(I2S_STEPPER_STREAM)
|
||||
#define STEPPER_TIMER_PRESCALE 1
|
||||
#define STEPPER_TIMER_RATE 250000 // 250khz, 4µs pulses of i2s word clock
|
||||
#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs // wrong would be 0.25
|
||||
#else
|
||||
#define STEPPER_TIMER_PRESCALE 40
|
||||
#define STEPPER_TIMER_RATE ((HAL_TIMER_RATE) / (STEPPER_TIMER_PRESCALE)) // frequency of stepper timer, 2MHz
|
||||
#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs
|
||||
#endif
|
||||
#define STEPPER_TIMER_TICKS_PER_US ((STEPPER_TIMER_RATE) / 1000000) // stepper timer ticks per µs
|
||||
|
||||
#define STEP_TIMER_MIN_INTERVAL 8 // minimum time in µs between stepper interrupts
|
||||
|
||||
|
||||
+10
-13
@@ -114,22 +114,19 @@
|
||||
// Misc. Functions
|
||||
//
|
||||
#ifndef analogInputToDigitalPin
|
||||
#define analogInputToDigitalPin(p) (p)
|
||||
#define analogInputToDigitalPin(p) pin_t(p)
|
||||
#endif
|
||||
|
||||
#define CRITICAL_SECTION_START \
|
||||
uint32_t primask = __get_PRIMASK(); \
|
||||
(void)__iCliRetVal()
|
||||
#define CRITICAL_SECTION_START() \
|
||||
const bool irqon = !__get_PRIMASK(); \
|
||||
__disable_irq(); \
|
||||
__DSB();
|
||||
#define CRITICAL_SECTION_END() \
|
||||
__DSB(); \
|
||||
if (irqon) __enable_irq();
|
||||
|
||||
#define CRITICAL_SECTION_END \
|
||||
if (!primask) \
|
||||
(void)__iSeiRetVal()
|
||||
|
||||
// Disable interrupts
|
||||
#define cli() noInterrupts()
|
||||
|
||||
// Enable interrupts
|
||||
#define sei() interrupts()
|
||||
#define cli() __disable_irq()
|
||||
#define sei() __enable_irq()
|
||||
|
||||
// bss_end alias
|
||||
#define __bss_end __bss_end__
|
||||
|
||||
@@ -31,24 +31,24 @@
|
||||
|
||||
#define NUM_DIGITAL_PINS BOARD_NR_GPIO_PINS
|
||||
#define NUMBER_PINS_TOTAL BOARD_NR_GPIO_PINS
|
||||
#define VALID_PIN(pin) IS_GPIO_PIN(pin)
|
||||
#define isValidPin(pin) IS_GPIO_PIN(pin)
|
||||
|
||||
// Note: pin_array is defined in `Marlin/src/pins/pinsDebug.h`, and since this file is included
|
||||
// after it, it is available in this file as well.
|
||||
#define GET_ARRAY_PIN(p) pin_t(pin_array[p].pin)
|
||||
#define getPinByIndex(p) pin_t(pin_array[p].pin)
|
||||
#define digitalRead_mod(p) extDigitalRead(p)
|
||||
#define PRINT_PIN(p) \
|
||||
#define printPinNumber(p) \
|
||||
do { \
|
||||
sprintf_P(buffer, PSTR("%3hd "), int16_t(p)); \
|
||||
SERIAL_ECHO(buffer); \
|
||||
} while (0)
|
||||
#define PRINT_PIN_ANALOG(p) \
|
||||
#define printPinAnalog(p) \
|
||||
do { \
|
||||
sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); \
|
||||
sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); \
|
||||
SERIAL_ECHO(buffer); \
|
||||
} while (0)
|
||||
#define PRINT_PORT(p) print_port(p)
|
||||
#define PRINT_ARRAY_NAME(x) \
|
||||
#define PRINT_PORT(p) printPinPort(p)
|
||||
#define printPinNameByIndex(x) \
|
||||
do { \
|
||||
sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); \
|
||||
SERIAL_ECHO(buffer); \
|
||||
@@ -71,14 +71,14 @@
|
||||
#define M43_NEVER_TOUCH(Q) (IS_HOST_USART_PIN(Q) || IS_OSC_PIN(Q))
|
||||
#endif
|
||||
|
||||
static pin_t DIGITAL_PIN_TO_ANALOG_PIN(pin_t pin) {
|
||||
if (!VALID_PIN(pin)) return -1;
|
||||
static int8_t digitalPinToAnalogIndex(pin_t pin) {
|
||||
if (!isValidPin(pin)) return -1;
|
||||
const int8_t adc_channel = int8_t(PIN_MAP[pin].adc_info.channel);
|
||||
return pin_t(adc_channel);
|
||||
}
|
||||
|
||||
static bool IS_ANALOG(pin_t pin) {
|
||||
if (!VALID_PIN(pin)) return false;
|
||||
static bool isAnalogPin(pin_t pin) {
|
||||
if (!isValidPin(pin)) return false;
|
||||
|
||||
if (PIN_MAP[pin].adc_info.channel != ADC_PIN_INVALID)
|
||||
return _GET_MODE(pin) == INPUT_ANALOG && !M43_NEVER_TOUCH(pin);
|
||||
@@ -86,13 +86,13 @@ static bool IS_ANALOG(pin_t pin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool GET_PINMODE(const pin_t pin) {
|
||||
return VALID_PIN(pin) && !IS_INPUT(pin);
|
||||
static bool getValidPinMode(const pin_t pin) {
|
||||
return isValidPin(pin) && !IS_INPUT(pin);
|
||||
}
|
||||
|
||||
static bool GET_ARRAY_IS_DIGITAL(const int16_t array_pin) {
|
||||
const pin_t pin = GET_ARRAY_PIN(array_pin);
|
||||
return (!IS_ANALOG(pin));
|
||||
static bool getPinIsDigitalByIndex(const int16_t array_pin) {
|
||||
const pin_t pin = getPinByIndex(array_pin);
|
||||
return (!isAnalogPin(pin));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -117,7 +117,7 @@ bool pwm_status(const pin_t pin) {
|
||||
return timera_is_unit_initialized(unit) && timera_is_channel_active(unit, channel) && getPinMode(pin) == OUTPUT_PWM;
|
||||
}
|
||||
|
||||
void pwm_details(const pin_t pin) {
|
||||
void printPinPWM(const pin_t pin) {
|
||||
// Get timer assignment for pin
|
||||
timera_config_t *unit;
|
||||
en_timera_channel_t channel;
|
||||
@@ -161,7 +161,7 @@ void pwm_details(const pin_t pin) {
|
||||
}
|
||||
}
|
||||
|
||||
void print_port(pin_t pin) {
|
||||
void printPinPort(pin_t pin) {
|
||||
const char port = 'A' + char(pin >> 4); // Pin div 16
|
||||
const int16_t gbit = PIN_MAP[pin].bit_pos;
|
||||
char buffer[8];
|
||||
|
||||
@@ -97,7 +97,7 @@ void core_hook_sysclock_init() {
|
||||
#endif
|
||||
|
||||
// sysclk is now configured according to F_CPU (i.e., 200MHz PLL output)
|
||||
constexpr uint32_t sysclock = F_CPU;
|
||||
const uint32_t sysclock = F_CPU;
|
||||
|
||||
// Setup clock divisors for sysclk = 200 MHz
|
||||
// Note: PCLK1 is used for step+temp timers, and need to be kept at 50 MHz (until there is a better solution)
|
||||
|
||||
@@ -52,7 +52,7 @@ uint8_t MarlinHAL::active_ch = 0;
|
||||
|
||||
uint16_t MarlinHAL::adc_value() {
|
||||
const pin_t pin = analogInputToDigitalPin(active_ch);
|
||||
if (!VALID_PIN(pin)) return 0;
|
||||
if (!isValidPin(pin)) return 0;
|
||||
return uint16_t((Gpio::get(pin) >> 2) & 0x3FF); // return 10bit value as Marlin expects
|
||||
}
|
||||
|
||||
|
||||
@@ -49,28 +49,28 @@ extern "C" void delay(const int msec) {
|
||||
// IO functions
|
||||
// As defined by Arduino INPUT(0x0), OUTPUT(0x1), INPUT_PULLUP(0x2)
|
||||
void pinMode(const pin_t pin, const uint8_t mode) {
|
||||
if (!VALID_PIN(pin)) return;
|
||||
if (!isValidPin(pin)) return;
|
||||
Gpio::setMode(pin, mode);
|
||||
}
|
||||
|
||||
void digitalWrite(pin_t pin, uint8_t pin_status) {
|
||||
if (!VALID_PIN(pin)) return;
|
||||
if (!isValidPin(pin)) return;
|
||||
Gpio::set(pin, pin_status);
|
||||
}
|
||||
|
||||
bool digitalRead(pin_t pin) {
|
||||
if (!VALID_PIN(pin)) return false;
|
||||
if (!isValidPin(pin)) return false;
|
||||
return Gpio::get(pin);
|
||||
}
|
||||
|
||||
void analogWrite(pin_t pin, int pwm_value) { // 1 - 254: pwm_value, 0: LOW, 255: HIGH
|
||||
if (!VALID_PIN(pin)) return;
|
||||
if (!isValidPin(pin)) return;
|
||||
Gpio::set(pin, pwm_value);
|
||||
}
|
||||
|
||||
uint16_t analogRead(pin_t adc_pin) {
|
||||
if (!VALID_PIN(DIGITAL_PIN_TO_ANALOG_PIN(adc_pin))) return 0;
|
||||
return Gpio::get(DIGITAL_PIN_TO_ANALOG_PIN(adc_pin));
|
||||
if (!isValidPin(digitalPinToAnalogIndex(adc_pin))) return 0;
|
||||
return Gpio::get(digitalPinToAnalogIndex(adc_pin));
|
||||
}
|
||||
|
||||
char *dtostrf(double __val, signed char __width, unsigned char __prec, char *__s) {
|
||||
|
||||
@@ -42,7 +42,7 @@ constexpr pin_t analogInputToDigitalPin(const int8_t p) {
|
||||
}
|
||||
|
||||
// Get the analog index for a digital pin
|
||||
constexpr int8_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t p) {
|
||||
constexpr int8_t digitalPinToAnalogIndex(const pin_t p) {
|
||||
return (WITHIN(p, analog_offset, NUM_DIGITAL_PINS) ? p - analog_offset : P_NC);
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ constexpr int8_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t p) {
|
||||
constexpr int16_t GET_PIN_MAP_INDEX(const pin_t pin) { return pin; }
|
||||
|
||||
// Test whether the pin is valid
|
||||
constexpr bool VALID_PIN(const pin_t p) { return WITHIN(p, 0, NUM_DIGITAL_PINS); }
|
||||
constexpr bool isValidPin(const pin_t p) { return WITHIN(p, 0, NUM_DIGITAL_PINS); }
|
||||
|
||||
// Test whether the pin is PWM
|
||||
constexpr bool PWM_PIN(const pin_t p) { return false; }
|
||||
|
||||
@@ -29,20 +29,20 @@
|
||||
*/
|
||||
|
||||
#define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS
|
||||
#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(P) >= 0 ? 1 : 0)
|
||||
#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0 ? 1 : 0)
|
||||
#define digitalRead_mod(p) digitalRead(p)
|
||||
#define GET_ARRAY_PIN(p) pin_array[p].pin
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinByIndex(p) pin_array[p].pin
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
// active ADC function/mode/code values for PINSEL registers
|
||||
constexpr int8_t ADC_pin_mode(pin_t pin) { return -1; }
|
||||
|
||||
int8_t get_pin_mode(const pin_t pin) { return VALID_PIN(pin) ? 0 : -1; }
|
||||
int8_t get_pin_mode(const pin_t pin) { return isValidPin(pin) ? 0 : -1; }
|
||||
|
||||
bool GET_PINMODE(const pin_t pin) {
|
||||
bool getValidPinMode(const pin_t pin) {
|
||||
const int8_t pin_mode = get_pin_mode(pin);
|
||||
if (pin_mode == -1 || pin_mode == ADC_pin_mode(pin)) // Invalid pin or active analog pin
|
||||
return false;
|
||||
@@ -50,11 +50,11 @@ bool GET_PINMODE(const pin_t pin) {
|
||||
return (Gpio::getMode(pin) != 0); // Input/output state
|
||||
}
|
||||
|
||||
bool GET_ARRAY_IS_DIGITAL(const pin_t pin) {
|
||||
return (!IS_ANALOG(pin) || get_pin_mode(pin) != ADC_pin_mode(pin));
|
||||
bool getPinIsDigitalByIndex(const pin_t pin) {
|
||||
return (!isAnalogPin(pin) || get_pin_mode(pin) != ADC_pin_mode(pin));
|
||||
}
|
||||
|
||||
void pwm_details(const pin_t pin) {}
|
||||
void printPinPWM(const pin_t pin) {}
|
||||
bool pwm_status(const pin_t) { return false; }
|
||||
|
||||
void print_port(const pin_t) {}
|
||||
void printPinPort(const pin_t) {}
|
||||
|
||||
@@ -137,12 +137,12 @@ extern DefaultSerial1 USBSerial;
|
||||
//
|
||||
|
||||
// Test whether the pin is valid
|
||||
constexpr bool VALID_PIN(const pin_t pin) {
|
||||
constexpr bool isValidPin(const pin_t pin) {
|
||||
return LPC176x::pin_is_valid(pin);
|
||||
}
|
||||
|
||||
// Get the analog index for a digital pin
|
||||
constexpr int8_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t pin) {
|
||||
constexpr int8_t digitalPinToAnalogIndex(const pin_t pin) {
|
||||
return (LPC176x::pin_is_valid(pin) && LPC176x::pin_has_adc(pin)) ? pin : -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,12 +29,12 @@
|
||||
*/
|
||||
|
||||
#define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS
|
||||
#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(P) >= 0 ? 1 : 0)
|
||||
#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0 ? 1 : 0)
|
||||
#define digitalRead_mod(p) extDigitalRead(p)
|
||||
#define GET_ARRAY_PIN(p) pin_array[p].pin
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("P%d_%02d"), LPC176x::pin_port(p), LPC176x::pin_bit(p)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR("_A%d "), LPC176x::pin_get_adc_channel(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinByIndex(p) pin_array[p].pin
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("P%d_%02d"), LPC176x::pin_port(p), LPC176x::pin_bit(p)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR("_A%d "), LPC176x::pin_get_adc_channel(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define MULTI_NAME_PAD 17 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
// pins that will cause hang/reset/disconnect in M43 Toggle and Watch utilities
|
||||
@@ -42,15 +42,15 @@
|
||||
#define M43_NEVER_TOUCH(Q) ((Q) == P0_29 || (Q) == P0_30 || (Q) == P2_09) // USB pins
|
||||
#endif
|
||||
|
||||
bool GET_PINMODE(const pin_t pin) {
|
||||
bool getValidPinMode(const pin_t pin) {
|
||||
if (!LPC176x::pin_is_valid(pin) || LPC176x::pin_adc_enabled(pin)) // Invalid pin or active analog pin
|
||||
return false;
|
||||
|
||||
return LPC176x::gpio_direction(pin);
|
||||
}
|
||||
|
||||
#define GET_ARRAY_IS_DIGITAL(x) ((bool) pin_array[x].is_digital)
|
||||
#define getPinIsDigitalByIndex(x) ((bool) pin_array[x].is_digital)
|
||||
|
||||
void print_port(const pin_t) {}
|
||||
void pwm_details(const pin_t) {}
|
||||
void printPinPort(const pin_t) {}
|
||||
void printPinPWM(const pin_t) {}
|
||||
bool pwm_status(const pin_t) { return false; }
|
||||
|
||||
@@ -27,9 +27,9 @@
|
||||
|
||||
int8_t ADC_pin_mode(pin_t pin) { return -1; }
|
||||
|
||||
int8_t get_pin_mode(const pin_t pin) { return VALID_PIN(pin) ? 0 : -1; }
|
||||
int8_t get_pin_mode(const pin_t pin) { return isValidPin(pin) ? 0 : -1; }
|
||||
|
||||
bool GET_PINMODE(const pin_t pin) {
|
||||
bool getValidPinMode(const pin_t pin) {
|
||||
const int8_t pin_mode = get_pin_mode(pin);
|
||||
if (pin_mode == -1 || pin_mode == ADC_pin_mode(pin)) // Invalid pin or active analog pin
|
||||
return false;
|
||||
@@ -37,12 +37,12 @@ bool GET_PINMODE(const pin_t pin) {
|
||||
return (Gpio::getMode(pin) != 0); // Input/output state
|
||||
}
|
||||
|
||||
bool GET_ARRAY_IS_DIGITAL(const pin_t pin) {
|
||||
return !IS_ANALOG(pin) || get_pin_mode(pin) != ADC_pin_mode(pin);
|
||||
bool getPinIsDigitalByIndex(const pin_t pin) {
|
||||
return !isAnalogPin(pin) || get_pin_mode(pin) != ADC_pin_mode(pin);
|
||||
}
|
||||
|
||||
void print_port(const pin_t) {}
|
||||
void pwm_details(const pin_t) {}
|
||||
void printPinPort(const pin_t) {}
|
||||
void printPinPWM(const pin_t) {}
|
||||
bool pwm_status(const pin_t) { return false; }
|
||||
|
||||
#endif
|
||||
|
||||
@@ -30,19 +30,19 @@
|
||||
*/
|
||||
|
||||
#define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS
|
||||
#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(P) >= 0 ? 1 : 0)
|
||||
#define isAnalogPin(P) (digitalPinToAnalogIndex(P) >= 0 ? 1 : 0)
|
||||
#define digitalRead_mod(p) digitalRead(p)
|
||||
#define GET_ARRAY_PIN(p) pin_array[p].pin
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinByIndex(p) pin_array[p].pin
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
// Active ADC function/mode/code values for PINSEL registers
|
||||
int8_t ADC_pin_mode(pin_t pin);
|
||||
int8_t get_pin_mode(const pin_t pin);
|
||||
bool GET_PINMODE(const pin_t pin);
|
||||
bool GET_ARRAY_IS_DIGITAL(const pin_t pin);
|
||||
void print_port(const pin_t);
|
||||
void pwm_details(const pin_t);
|
||||
bool getValidPinMode(const pin_t pin);
|
||||
bool getPinIsDigitalByIndex(const pin_t pin);
|
||||
void printPinPort(const pin_t);
|
||||
void printPinPWM(const pin_t);
|
||||
bool pwm_status(const pin_t);
|
||||
|
||||
@@ -152,7 +152,7 @@
|
||||
: ((P) == 14) ? ADC_INPUTCTRL_MUXPOS_PIN14 \
|
||||
: ADC_INPUTCTRL_MUXPOS_PIN15)
|
||||
|
||||
#define digitalPinToAnalogInput(P) (WITHIN(P, 67, 74) ? (P) - 67 : WITHIN(P, 54, 61) ? 8 + (P) - 54 : WITHIN(P, 12, 13) ? 16 + (P) - 12 : P == 9 ? 18 : -1)
|
||||
#define digitalPinToAnalogIndex(P) (WITHIN(P, 67, 74) ? (P) - 67 : WITHIN(P, 54, 61) ? 8 + (P) - 54 : WITHIN(P, 12, 13) ? 16 + (P) - 12 : P == 9 ? 18 : -1)
|
||||
|
||||
/**
|
||||
* pins
|
||||
|
||||
@@ -30,14 +30,13 @@
|
||||
|
||||
#define digitalRead_mod(p) extDigitalRead(p)
|
||||
#define PRINT_PORT(p) do{ SERIAL_ECHOPGM(" Port: "); sprintf_P(buffer, PSTR("%c%02ld"), 'A' + g_APinDescription[p].ulPort, g_APinDescription[p].ulPin); SERIAL_ECHO(buffer); }while (0)
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define GET_ARRAY_PIN(p) pin_array[p].pin
|
||||
#define GET_ARRAY_IS_DIGITAL(p) pin_array[p].is_digital
|
||||
#define VALID_PIN(pin) (pin >= 0 && pin < (int8_t)NUMBER_PINS_TOTAL)
|
||||
#define DIGITAL_PIN_TO_ANALOG_PIN(p) digitalPinToAnalogInput(p)
|
||||
#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(P)!=-1)
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinByIndex(p) pin_array[p].pin
|
||||
#define getPinIsDigitalByIndex(p) pin_array[p].is_digital
|
||||
#define isValidPin(pin) (pin >= 0 && pin < (int8_t)NUMBER_PINS_TOTAL)
|
||||
#define isAnalogPin(P) (digitalPinToAnalogIndex(P)!=-1)
|
||||
#define pwm_status(pin) digitalPinHasPWM(pin)
|
||||
#define MULTI_NAME_PAD 27 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
@@ -45,13 +44,13 @@
|
||||
// uses pin index
|
||||
#define M43_NEVER_TOUCH(Q) ((Q) >= 75)
|
||||
|
||||
bool GET_PINMODE(int8_t pin) { // 1: output, 0: input
|
||||
bool getValidPinMode(int8_t pin) { // 1: output, 0: input
|
||||
const EPortType samdport = g_APinDescription[pin].ulPort;
|
||||
const uint32_t samdpin = g_APinDescription[pin].ulPin;
|
||||
return PORT->Group[samdport].DIR.reg & MASK(samdpin) || (PORT->Group[samdport].PINCFG[samdpin].reg & (PORT_PINCFG_INEN | PORT_PINCFG_PULLEN)) == PORT_PINCFG_PULLEN;
|
||||
}
|
||||
|
||||
void pwm_details(int32_t pin) {
|
||||
void printPinPWM(int32_t pin) {
|
||||
if (pwm_status(pin)) {
|
||||
//uint32_t chan = g_APinDescription[pin].ulPWMChannel TODO when fast pwm is operative;
|
||||
//SERIAL_ECHOPGM("PWM = ", duty);
|
||||
|
||||
@@ -69,6 +69,16 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef SERIAL_PORT_3
|
||||
#if SERIAL_PORT_3 == -1
|
||||
#define MYSERIAL3 MSerial0
|
||||
#elif WITHIN(SERIAL_PORT_3, 0, 3)
|
||||
#define MYSERIAL3 MSERIAL(SERIAL_PORT_3)
|
||||
#else
|
||||
#error "SERIAL_PORT_3 must be from 0 to 3. You can also use -1 if the board supports Native USB."
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef MMU2_SERIAL_PORT
|
||||
#if MMU2_SERIAL_PORT == -1
|
||||
#define MMU2_SERIAL MSerial0
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
: (P == 17) ? PIN_TO_SAMD_PIN(13) \
|
||||
: PIN_TO_SAMD_PIN(9))
|
||||
|
||||
#define digitalPinToAnalogInput(P) (WITHIN(P, 67, 74) ? (P) - 67 : WITHIN(P, 54, 61) ? 8 + (P) - 54 : WITHIN(P, 12, 13) ? 16 + (P) - 12 : P == 9 ? 18 : -1)
|
||||
#define digitalPinToAnalogIndex(P) (WITHIN(P, 67, 74) ? (P) - 67 : WITHIN(P, 54, 61) ? 8 + (P) - 54 : WITHIN(P, 12, 13) ? 16 + (P) - 12 : P == 9 ? 18 : -1)
|
||||
|
||||
/**
|
||||
* pins
|
||||
|
||||
@@ -29,14 +29,13 @@
|
||||
|
||||
#define digitalRead_mod(p) extDigitalRead(p)
|
||||
#define PRINT_PORT(p) do{ SERIAL_ECHOPGM(" Port: "); sprintf_P(buffer, PSTR("%c%02ld"), 'A' + g_APinDescription[p].ulPort, g_APinDescription[p].ulPin); SERIAL_ECHO(buffer); }while (0)
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define GET_ARRAY_PIN(p) pin_array[p].pin
|
||||
#define GET_ARRAY_IS_DIGITAL(p) pin_array[p].is_digital
|
||||
#define VALID_PIN(pin) (pin >= 0 && pin < int8_t(NUMBER_PINS_TOTAL))
|
||||
#define DIGITAL_PIN_TO_ANALOG_PIN(p) digitalPinToAnalogInput(p)
|
||||
#define IS_ANALOG(P) (DIGITAL_PIN_TO_ANALOG_PIN(P)!=-1)
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%3d "), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinByIndex(p) pin_array[p].pin
|
||||
#define getPinIsDigitalByIndex(p) pin_array[p].is_digital
|
||||
#define isValidPin(pin) (pin >= 0 && pin < int8_t(NUMBER_PINS_TOTAL))
|
||||
#define isAnalogPin(P) (digitalPinToAnalogIndex(P)!=-1)
|
||||
#define pwm_status(pin) digitalPinHasPWM(pin)
|
||||
#define MULTI_NAME_PAD 27 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
@@ -44,13 +43,13 @@
|
||||
// uses pin index
|
||||
#define M43_NEVER_TOUCH(Q) ((Q) >= 75)
|
||||
|
||||
bool GET_PINMODE(int8_t pin) { // 1: output, 0: input
|
||||
bool getValidPinMode(int8_t pin) { // 1: output, 0: input
|
||||
const EPortType samdport = g_APinDescription[pin].ulPort;
|
||||
const uint32_t samdpin = g_APinDescription[pin].ulPin;
|
||||
return PORT->Group[samdport].DIR.reg & MASK(samdpin) || (PORT->Group[samdport].PINCFG[samdpin].reg & (PORT_PINCFG_INEN | PORT_PINCFG_PULLEN)) == PORT_PINCFG_PULLEN;
|
||||
}
|
||||
|
||||
void pwm_details(int32_t pin) {
|
||||
void printPinPWM(int32_t pin) {
|
||||
if (pwm_status(pin)) {
|
||||
//uint32_t chan = g_APinDescription[pin].ulPWMChannel TODO when fast pwm is operative;
|
||||
//SERIAL_ECHOPGM("PWM = ", duty);
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
* TODO: review this to return 1 for pins that are not analog input
|
||||
*/
|
||||
#ifndef analogInputToDigitalPin
|
||||
#define analogInputToDigitalPin(p) (p)
|
||||
#define analogInputToDigitalPin(p) pin_t(p)
|
||||
#endif
|
||||
|
||||
//
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
#ifndef USART5
|
||||
#define USART5 UART5
|
||||
#endif
|
||||
#ifndef USART6
|
||||
#define USART6 UART6
|
||||
#endif
|
||||
#ifndef USART7
|
||||
#define USART7 UART7
|
||||
#endif
|
||||
|
||||
@@ -115,16 +115,16 @@ const XrefInfo pin_xref[] PROGMEM = {
|
||||
#define NUM_ANALOG_LAST ((NUM_ANALOG_FIRST) + (NUM_ANALOG_INPUTS) - 1)
|
||||
#endif
|
||||
#define NUMBER_PINS_TOTAL ((NUM_DIGITAL_PINS) + TERN0(HAS_HIGH_ANALOG_PINS, NUM_ANALOG_INPUTS))
|
||||
#define VALID_PIN(P) (WITHIN(P, 0, (NUM_DIGITAL_PINS) - 1) || TERN0(HAS_HIGH_ANALOG_PINS, WITHIN(P, NUM_ANALOG_FIRST, NUM_ANALOG_LAST)))
|
||||
#define isValidPin(P) (WITHIN(P, 0, (NUM_DIGITAL_PINS) - 1) || TERN0(HAS_HIGH_ANALOG_PINS, WITHIN(P, NUM_ANALOG_FIRST, NUM_ANALOG_LAST)))
|
||||
#define digitalRead_mod(Ard_num) extDigitalRead(Ard_num) // must use Arduino pin numbers when doing reads
|
||||
#define PRINT_PIN(Q)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define DIGITAL_PIN_TO_ANALOG_PIN(ANUM) -1 // will report analog pin number in the print port routine
|
||||
#define printPinNumber(Q)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define digitalPinToAnalogIndex(ANUM) -1 // will report analog pin number in the print port routine
|
||||
|
||||
// x is a variable used to search pin_array
|
||||
#define GET_ARRAY_IS_DIGITAL(x) ((bool) pin_array[x].is_digital)
|
||||
#define GET_ARRAY_PIN(x) ((pin_t) pin_array[x].pin)
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinIsDigitalByIndex(x) ((bool) pin_array[x].is_digital)
|
||||
#define getPinByIndex(x) ((pin_t) pin_array[x].pin)
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define MULTI_NAME_PAD 33 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
//
|
||||
@@ -164,7 +164,7 @@ uint8_t get_pin_mode(const pin_t Ard_num) {
|
||||
}
|
||||
}
|
||||
|
||||
bool GET_PINMODE(const pin_t Ard_num) {
|
||||
bool getValidPinMode(const pin_t Ard_num) {
|
||||
const uint8_t pin_mode = get_pin_mode(Ard_num);
|
||||
return pin_mode == MODE_PIN_OUTPUT || pin_mode == MODE_PIN_ALT; // assume all alt definitions are PWM
|
||||
}
|
||||
@@ -173,11 +173,11 @@ int8_t digital_pin_to_analog_pin(const pin_t Ard_num) {
|
||||
if (WITHIN(Ard_num, NUM_ANALOG_FIRST, NUM_ANALOG_LAST))
|
||||
return Ard_num - NUM_ANALOG_FIRST;
|
||||
|
||||
const uint32_t ind = digitalPinToAnalogInput(Ard_num);
|
||||
const int8_t ind = digitalPinToAnalogIndex(Ard_num);
|
||||
return (ind < NUM_ANALOG_INPUTS) ? ind : -1;
|
||||
}
|
||||
|
||||
bool IS_ANALOG(const pin_t Ard_num) {
|
||||
bool isAnalogPin(const pin_t Ard_num) {
|
||||
return get_pin_mode(Ard_num) == MODE_PIN_ANALOG;
|
||||
}
|
||||
|
||||
@@ -186,7 +186,7 @@ bool is_digital(const pin_t Ard_num) {
|
||||
return pin_mode == MODE_PIN_INPUT || pin_mode == MODE_PIN_OUTPUT;
|
||||
}
|
||||
|
||||
void print_port(const pin_t Ard_num) {
|
||||
void printPinPort(const pin_t Ard_num) {
|
||||
char buffer[16];
|
||||
pin_t Index;
|
||||
for (Index = 0; Index < NUMBER_PINS_TOTAL; Index++)
|
||||
@@ -226,7 +226,7 @@ bool pwm_status(const pin_t Ard_num) {
|
||||
return get_pin_mode(Ard_num) == MODE_PIN_ALT;
|
||||
}
|
||||
|
||||
void pwm_details(const pin_t Ard_num) {
|
||||
void printPinPWM(const pin_t Ard_num) {
|
||||
#ifndef STM32F1xx
|
||||
if (pwm_status(Ard_num)) {
|
||||
uint32_t alt_all = 0;
|
||||
@@ -285,4 +285,4 @@ void pwm_details(const pin_t Ard_num) {
|
||||
#else
|
||||
// TODO: F1 doesn't support changing pins function, so we need to check the function of the PIN and if it's enabled
|
||||
#endif
|
||||
} // pwm_details
|
||||
} // printPinPWM
|
||||
|
||||
@@ -158,7 +158,7 @@
|
||||
* TODO: review this to return 1 for pins that are not analog input
|
||||
*/
|
||||
#ifndef analogInputToDigitalPin
|
||||
#define analogInputToDigitalPin(p) (p)
|
||||
#define analogInputToDigitalPin(p) pin_t(p)
|
||||
#endif
|
||||
|
||||
#ifndef digitalPinHasPWM
|
||||
|
||||
@@ -39,12 +39,12 @@ extern const stm32_pin_info PIN_MAP[BOARD_NR_GPIO_PINS];
|
||||
|
||||
#define NUM_DIGITAL_PINS BOARD_NR_GPIO_PINS
|
||||
#define NUMBER_PINS_TOTAL BOARD_NR_GPIO_PINS
|
||||
#define VALID_PIN(pin) (pin >= 0 && pin < BOARD_NR_GPIO_PINS)
|
||||
#define GET_ARRAY_PIN(p) pin_t(pin_array[p].pin)
|
||||
#define isValidPin(pin) (pin >= 0 && pin < BOARD_NR_GPIO_PINS)
|
||||
#define getPinByIndex(p) pin_t(pin_array[p].pin)
|
||||
#define digitalRead_mod(p) extDigitalRead(p)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%3hd "), int16_t(p)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%3hd "), int16_t(p)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define MULTI_NAME_PAD 21 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
// pins that will cause hang/reset/disconnect in M43 Toggle and Watch utilities
|
||||
@@ -52,10 +52,10 @@ extern const stm32_pin_info PIN_MAP[BOARD_NR_GPIO_PINS];
|
||||
#define M43_NEVER_TOUCH(Q) (Q >= 9 && Q <= 12) // SERIAL/USB pins PA9(TX) PA10(RX)
|
||||
#endif
|
||||
|
||||
int8_t get_pin_mode(const pin_t pin) { return VALID_PIN(pin) ? _GET_MODE(pin) : -1; }
|
||||
int8_t get_pin_mode(const pin_t pin) { return isValidPin(pin) ? _GET_MODE(pin) : -1; }
|
||||
|
||||
pin_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t pin) {
|
||||
if (!VALID_PIN(pin)) return -1;
|
||||
int8_t digitalPinToAnalogIndex(const pin_t pin) {
|
||||
if (!isValidPin(pin)) return -1;
|
||||
pin_t adc_channel = pin_t(PIN_MAP[pin].adc_channel);
|
||||
#ifdef NUM_ANALOG_INPUTS
|
||||
if (adc_channel >= NUM_ANALOG_INPUTS) adc_channel = (pin_t)ADCx;
|
||||
@@ -63,8 +63,8 @@ pin_t DIGITAL_PIN_TO_ANALOG_PIN(const pin_t pin) {
|
||||
return adc_channel;
|
||||
}
|
||||
|
||||
bool IS_ANALOG(const pin_t pin) {
|
||||
if (!VALID_PIN(pin)) return false;
|
||||
bool isAnalogPin(const pin_t pin) {
|
||||
if (!isValidPin(pin)) return false;
|
||||
if (PIN_MAP[pin].adc_channel != ADCx) {
|
||||
#ifdef NUM_ANALOG_INPUTS
|
||||
if (PIN_MAP[pin].adc_channel >= NUM_ANALOG_INPUTS) return false;
|
||||
@@ -74,13 +74,13 @@ bool IS_ANALOG(const pin_t pin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool GET_PINMODE(const pin_t pin) {
|
||||
return VALID_PIN(pin) && !IS_INPUT(pin);
|
||||
bool getValidPinMode(const pin_t pin) {
|
||||
return isValidPin(pin) && !IS_INPUT(pin);
|
||||
}
|
||||
|
||||
bool GET_ARRAY_IS_DIGITAL(const int16_t array_pin) {
|
||||
const pin_t pin = GET_ARRAY_PIN(array_pin);
|
||||
return (!IS_ANALOG(pin)
|
||||
bool getPinIsDigitalByIndex(const int16_t array_pin) {
|
||||
const pin_t pin = getPinByIndex(array_pin);
|
||||
return (!isAnalogPin(pin)
|
||||
#ifdef NUM_ANALOG_INPUTS
|
||||
|| PIN_MAP[pin].adc_channel >= NUM_ANALOG_INPUTS
|
||||
#endif
|
||||
@@ -89,7 +89,7 @@ bool GET_ARRAY_IS_DIGITAL(const int16_t array_pin) {
|
||||
|
||||
#include "../../inc/MarlinConfig.h" // Allow pins/pins.h to set density
|
||||
|
||||
void pwm_details(const pin_t pin) {
|
||||
void printPinPWM(const pin_t pin) {
|
||||
if (PWM_PIN(pin)) {
|
||||
timer_dev * const tdev = PIN_MAP[pin].timer_device;
|
||||
const uint8_t channel = PIN_MAP[pin].timer_channel;
|
||||
@@ -111,7 +111,7 @@ void pwm_details(const pin_t pin) {
|
||||
|
||||
bool pwm_status(const pin_t pin) { return PWM_PIN(pin); }
|
||||
|
||||
void print_port(const pin_t pin) {
|
||||
void printPinPort(const pin_t pin) {
|
||||
const char port = 'A' + char(pin >> 4); // pin div 16
|
||||
const int16_t gbit = PIN_MAP[pin].gpio_bit;
|
||||
char buffer[8];
|
||||
|
||||
@@ -98,7 +98,7 @@ uint32_t __get_PRIMASK(void); // CMSIS
|
||||
// ------------------------
|
||||
|
||||
#ifndef analogInputToDigitalPin
|
||||
#define analogInputToDigitalPin(p) ((p < 12U) ? (p) + 54U : -1)
|
||||
#define analogInputToDigitalPin(p) pin_t((p < 12U) ? (p) + 54U : -1)
|
||||
#endif
|
||||
|
||||
#define HAL_ADC_VREF_MV 3300
|
||||
|
||||
@@ -41,7 +41,7 @@ typedef uint32_t hal_timer_t;
|
||||
#define FTM0_TIMER_PRESCALE_BITS 0b011
|
||||
#define FTM1_TIMER_PRESCALE_BITS 0b010
|
||||
|
||||
#define FTM0_TIMER_RATE (F_BUS / (FTM0_TIMER_PRESCALE)) // 60MHz / 8 = 7500kHz
|
||||
#define FTM0_TIMER_RATE (F_BUS / (FTM0_TIMER_PRESCALE)) // 60MHz / 8 = 7.5MHz
|
||||
#define FTM1_TIMER_RATE (F_BUS / (FTM1_TIMER_PRESCALE)) // 60MHz / 4 = 15MHz
|
||||
|
||||
#define HAL_TIMER_RATE (FTM0_TIMER_RATE)
|
||||
|
||||
@@ -103,7 +103,7 @@ typedef int8_t pin_t;
|
||||
// ------------------------
|
||||
|
||||
#ifndef analogInputToDigitalPin
|
||||
#define analogInputToDigitalPin(p) ((p < 12U) ? (p) + 54U : -1)
|
||||
#define analogInputToDigitalPin(p) pin_t((p < 12U) ? (p) + 54U : -1)
|
||||
#endif
|
||||
|
||||
#define HAL_ADC_VREF_MV 3300
|
||||
|
||||
@@ -53,9 +53,9 @@
|
||||
#define TPM1_CH1_PIN 17
|
||||
#endif
|
||||
|
||||
#define IS_ANALOG(P) ((P) >= analogInputToDigitalPin(0) && (P) <= analogInputToDigitalPin(9)) || ((P) >= analogInputToDigitalPin(12) && (P) <= analogInputToDigitalPin(20))
|
||||
#define isAnalogPin(P) ((P) >= analogInputToDigitalPin(0) && (P) <= analogInputToDigitalPin(9)) || ((P) >= analogInputToDigitalPin(12) && (P) <= analogInputToDigitalPin(20))
|
||||
|
||||
void print_analog_pin(char buffer[], int8_t pin) {
|
||||
void printAnalogPin(char buffer[], int8_t pin) {
|
||||
if (pin <= 23) sprintf_P(buffer, PSTR("(A%2d) "), int(pin - 14));
|
||||
else if (pin <= 39) sprintf_P(buffer, PSTR("(A%2d) "), int(pin - 19));
|
||||
}
|
||||
@@ -108,4 +108,4 @@ bool pwm_status(int8_t pin) {
|
||||
SERIAL_ECHOPGM(" ");
|
||||
}
|
||||
|
||||
void pwm_details(uint8_t pin) { /* TODO */ }
|
||||
void printPinPWM(uint8_t pin) { /* TODO */ }
|
||||
|
||||
@@ -133,7 +133,7 @@ typedef int8_t pin_t;
|
||||
// ------------------------
|
||||
|
||||
#ifndef analogInputToDigitalPin
|
||||
#define analogInputToDigitalPin(p) ((p < 12U) ? (p) + 54U : -1)
|
||||
#define analogInputToDigitalPin(p) pin_t((p < 12U) ? (p) + 54U : -1)
|
||||
#endif
|
||||
|
||||
#define HAL_ADC_VREF_MV 3300
|
||||
|
||||
@@ -30,17 +30,18 @@
|
||||
#define NUMBER_PINS_TOTAL NUM_DIGITAL_PINS
|
||||
|
||||
#define digitalRead_mod(p) extDigitalRead(p) // AVR digitalRead disabled PWM before it read the pin
|
||||
#define PRINT_ARRAY_NAME(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN(p) do{ sprintf_P(buffer, PSTR("%02d"), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define PRINT_PIN_ANALOG(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), DIGITAL_PIN_TO_ANALOG_PIN(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define GET_ARRAY_PIN(p) pin_array[p].pin
|
||||
#define GET_ARRAY_IS_DIGITAL(p) pin_array[p].is_digital
|
||||
#define VALID_PIN(pin) (pin >= 0 && pin < int8_t(NUMBER_PINS_TOTAL))
|
||||
#define DIGITAL_PIN_TO_ANALOG_PIN(p) int(p - analogInputToDigitalPin(0))
|
||||
#define IS_ANALOG(P) ((P) >= analogInputToDigitalPin(0) && (P) <= analogInputToDigitalPin(13)) || ((P) >= analogInputToDigitalPin(14) && (P) <= analogInputToDigitalPin(17))
|
||||
#define GET_PINMODE(PIN) (VALID_PIN(pin) && IS_OUTPUT(pin))
|
||||
#define printPinNameByIndex(x) do{ sprintf_P(buffer, PSTR("%-" STRINGIFY(MAX_NAME_LENGTH) "s"), pin_array[x].name); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinNumber(p) do{ sprintf_P(buffer, PSTR("%02d"), p); SERIAL_ECHO(buffer); }while(0)
|
||||
#define printPinAnalog(p) do{ sprintf_P(buffer, PSTR(" (A%2d) "), digitalPinToAnalogIndex(pin)); SERIAL_ECHO(buffer); }while(0)
|
||||
#define getPinByIndex(p) pin_array[p].pin
|
||||
#define getPinIsDigitalByIndex(p) pin_array[p].is_digital
|
||||
#define isValidPin(pin) (pin >= 0 && pin < int8_t(NUMBER_PINS_TOTAL))
|
||||
#define digitalPinToAnalogIndex(p) int(p - analogInputToDigitalPin(0))
|
||||
#define getValidPinMode(PIN) (isValidPin(pin) && IS_OUTPUT(pin))
|
||||
#define MULTI_NAME_PAD 16 // space needed to be pretty if not first name assigned to a pin
|
||||
|
||||
#define isAnalogPin(P) (pin_t(P) >= analogInputToDigitalPin(0) && pin_t(P) <= analogInputToDigitalPin(13)) || (pin_t(P) >= analogInputToDigitalPin(14) && pin_t(P) <= analogInputToDigitalPin(17))
|
||||
|
||||
struct pwm_pin_info_struct {
|
||||
uint8_t type; // 0=no pwm, 1=flexpwm, 2=quad
|
||||
uint8_t module; // 0-3, 0-3
|
||||
@@ -118,7 +119,7 @@ const struct pwm_pin_info_struct pwm_pin_info[] = {
|
||||
#endif
|
||||
};
|
||||
|
||||
void print_analog_pin(char buffer[], const pin_t pin) {
|
||||
void printAnalogPin(char buffer[], const pin_t pin) {
|
||||
if (pin <= 23) sprintf_P(buffer, PSTR("(A%2d) "), int(pin - 14));
|
||||
else if (pin <= 41) sprintf_P(buffer, PSTR("(A%2d) "), int(pin - 24));
|
||||
}
|
||||
@@ -149,6 +150,6 @@ bool pwm_status(const pin_t pin) {
|
||||
return (*(portConfigRegister(pin)) == info->muxval);
|
||||
}
|
||||
|
||||
void pwm_details(const pin_t) { /* TODO */ }
|
||||
void printPinPWM(const pin_t) { /* TODO */ }
|
||||
|
||||
void print_port(const pin_t) {}
|
||||
void printPinPort(const pin_t) {}
|
||||
|
||||
@@ -81,4 +81,4 @@ void eeprom_write_byte(uint8_t *pos, uint8_t value) {
|
||||
}
|
||||
|
||||
#endif // USE_SHARED_EEPROM
|
||||
#endif // I2C_EEPROM
|
||||
#endif // SPI_EEPROM
|
||||
|
||||
+22
-10
@@ -229,12 +229,14 @@
|
||||
#include "feature/controllerfan.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU1
|
||||
#include "feature/mmu/mmu.h"
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
#include "feature/mmu3/mmu2.h"
|
||||
#include "feature/mmu3/mmu2_reporting.h"
|
||||
#include "feature/mmu3/SpoolJoin.h"
|
||||
#elif HAS_PRUSA_MMU2
|
||||
#include "feature/mmu/mmu2.h"
|
||||
#elif HAS_PRUSA_MMU1
|
||||
#include "feature/mmu/mmu.h"
|
||||
#endif
|
||||
|
||||
#if ENABLED(PASSWORD_FEATURE)
|
||||
@@ -311,20 +313,21 @@ bool wait_for_heatup = false;
|
||||
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wnarrowing"
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
|
||||
bool pin_is_protected(const pin_t pin) {
|
||||
#define pgm_read_pin(P) (sizeof(pin_t) == 2 ? (pin_t)pgm_read_word(P) : (pin_t)pgm_read_byte(P))
|
||||
for (uint8_t i = 0; i < COUNT(sensitive_dio); ++i)
|
||||
if (pin == pgm_read_pin(&sensitive_dio[i])) return true;
|
||||
for (uint8_t i = 0; i < COUNT(sensitive_aio); ++i)
|
||||
if (pin == analogInputToDigitalPin(pgm_read_pin(&sensitive_dio[i]))) return true;
|
||||
if (pin == analogInputToDigitalPin(pgm_read_pin(&sensitive_aio[i]))) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
bool printer_busy() {
|
||||
return planner.movesplanned() || printingIsActive();
|
||||
return planner.has_blocks_queued() || printingIsActive();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,6 +353,7 @@ void startOrResumeJob() {
|
||||
TERN_(CANCEL_OBJECTS, cancelable.reset());
|
||||
TERN_(LCD_SHOW_E_TOTAL, e_move_accumulator = 0);
|
||||
TERN_(SET_REMAINING_TIME, ui.reset_remaining_time());
|
||||
TERN_(HAS_PRUSA_MMU3, MMU3::operation_statistics.reset_per_print_stats());
|
||||
}
|
||||
print_job_timer.start();
|
||||
}
|
||||
@@ -784,7 +788,7 @@ void idle(const bool no_stepper_sleep/*=false*/) {
|
||||
|
||||
// Handle filament runout sensors
|
||||
#if HAS_FILAMENT_SENSOR
|
||||
if (TERN1(HAS_PRUSA_MMU2, !mmu2.enabled()))
|
||||
if (TERN1(HAS_PRUSA_MMU2, !mmu2.enabled()) && TERN1(HAS_PRUSA_MMU3, !mmu3.enabled()))
|
||||
runout.run();
|
||||
#endif
|
||||
|
||||
@@ -849,7 +853,11 @@ void idle(const bool no_stepper_sleep/*=false*/) {
|
||||
#endif
|
||||
|
||||
// Update the Průša MMU2
|
||||
TERN_(HAS_PRUSA_MMU2, mmu2.mmu_loop());
|
||||
#if HAS_PRUSA_MMU3
|
||||
mmu3.mmu_loop();
|
||||
#elif HAS_PRUSA_MMU2
|
||||
mmu2.mmu_loop();
|
||||
#endif
|
||||
|
||||
// Handle Joystick jogging
|
||||
TERN_(POLL_JOG, joystick.inject_jog_moves());
|
||||
@@ -1585,7 +1593,11 @@ void setup() {
|
||||
SETUP_RUN(stepper_driver_backward_report());
|
||||
#endif
|
||||
|
||||
#if HAS_PRUSA_MMU2
|
||||
#if HAS_PRUSA_MMU3
|
||||
if (mmu3.mmu_hw_enabled) SETUP_RUN(mmu3.start());
|
||||
SETUP_RUN(mmu3.status());
|
||||
SETUP_RUN(spooljoin.initStatus());
|
||||
#elif HAS_PRUSA_MMU2
|
||||
SETUP_RUN(mmu2.init());
|
||||
#endif
|
||||
|
||||
|
||||
@@ -132,6 +132,8 @@
|
||||
#define BOARD_PANOWIN_CUTLASS 1165 // Panowin Cutlass (as found in the Panowin F1)
|
||||
#define BOARD_KODAMA_BARDO 1166 // Kodama Bardo V1.x (as found in the Kodama Trinus)
|
||||
#define BOARD_DAGOMA_D6 1167 // Dagoma D6 (as found in the Dagoma DiscoUltimate V2 TMC)
|
||||
#define BOARD_XTLW_MFF_V1 1168 // XTLW MFF V1.0
|
||||
#define BOARD_XTLW_MFF_V2 1169 // XTLW MFF V2.0
|
||||
|
||||
//
|
||||
// RAMBo and derivatives
|
||||
@@ -273,6 +275,7 @@
|
||||
#define BOARD_MKS_SGEN_L_V2 2509 // MKS SGEN_L V2
|
||||
#define BOARD_BTT_SKR_E3_TURBO 2510 // BigTreeTech SKR E3 Turbo
|
||||
#define BOARD_FLY_CDY 2511 // FLYmaker FLY CDY
|
||||
#define BOARD_XTLW_CLIMBER_8TH_LPC 2512 // XTLW_CLIMBER_8TH_LPC
|
||||
|
||||
//
|
||||
// SAM3X8E ARM Cortex-M3
|
||||
@@ -467,6 +470,7 @@
|
||||
#define BOARD_MELLOW_FLY_E3_V2 5249 // Mellow Fly E3 V2 (STM32F407VG)
|
||||
#define BOARD_FYSETC_CHEETAH_V30 5250 // FYSETC Cheetah V3.0 (STM32F446RC)
|
||||
#define BOARD_BLACKBEEZMINI_V1 5251 // BlackBeezMini V1 (STM32F401CCU6)
|
||||
#define BOARD_XTLW_CLIMBER_8TH 5252 // XTLW Climber-8th (STM32F407VGT6)
|
||||
|
||||
//
|
||||
// Other ARM Cortex-M4
|
||||
|
||||
@@ -162,8 +162,8 @@
|
||||
#define STR_SOFT_MIN " Min: "
|
||||
#define STR_SOFT_MAX " Max: "
|
||||
|
||||
#define STR_SAVED_POS "Position saved"
|
||||
#define STR_RESTORING_POS "Restoring position"
|
||||
#define STR_SAVED_POSITION "Saved position #"
|
||||
#define STR_RESTORING_POSITION "Restoring position #"
|
||||
#define STR_INVALID_POS_SLOT "Invalid slot. Total: "
|
||||
#define STR_DONE "Done."
|
||||
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
|
||||
// Macros for bit masks
|
||||
#undef _BV
|
||||
#define _BV(n) (1<<(n))
|
||||
#define _BV(b) (1 << (b))
|
||||
#define TEST(n,b) (!!((n)&_BV(b)))
|
||||
#define SET_BIT_TO(N,B,TF) do{ if (TF) SBI(N,B); else CBI(N,B); }while(0)
|
||||
#ifndef SBI
|
||||
|
||||
@@ -75,8 +75,8 @@ template <> void SERIAL_ECHO(const p_float_t pf) { SERIAL_IMPL.print(pf.value, p
|
||||
template <> void SERIAL_ECHO(const w_float_t wf) { char f1[20]; SERIAL_IMPL.print(dtostrf(wf.value, wf.width, wf.prec, f1)); }
|
||||
|
||||
// Specializations for F-string
|
||||
template <> void SERIAL_ECHO(const FSTR_P fstr) { SERIAL_ECHO_P(FTOP(fstr)); }
|
||||
template <> void SERIAL_ECHOLN(const FSTR_P fstr) { SERIAL_ECHOLN_P(FTOP(fstr)); }
|
||||
template <> void SERIAL_ECHO(FSTR_P const fstr) { SERIAL_ECHO_P(FTOP(fstr)); }
|
||||
template <> void SERIAL_ECHOLN(FSTR_P const fstr) { SERIAL_ECHOLN_P(FTOP(fstr)); }
|
||||
|
||||
void SERIAL_CHAR(char a) { SERIAL_IMPL.write(a); }
|
||||
void SERIAL_EOL() { SERIAL_CHAR('\n'); }
|
||||
|
||||
@@ -171,8 +171,8 @@ template<> void SERIAL_ECHO(const p_float_t pf);
|
||||
template<> void SERIAL_ECHO(const w_float_t wf);
|
||||
|
||||
// Specializations for F-string
|
||||
template<> void SERIAL_ECHO(const FSTR_P fstr);
|
||||
template<> void SERIAL_ECHOLN(const FSTR_P fstr);
|
||||
template<> void SERIAL_ECHO(FSTR_P const fstr);
|
||||
template<> void SERIAL_ECHOLN(FSTR_P const fstr);
|
||||
|
||||
// Print any number of items with arbitrary types (except loose PROGMEM strings)
|
||||
template <typename T, typename ... Args>
|
||||
@@ -247,7 +247,7 @@ inline void print_xyz(const xyz_pos_t &xyz, FSTR_P const prefix=nullptr, FSTR_P
|
||||
|
||||
void print_xyze(LOGICAL_AXIS_ARGS_(const_float_t) FSTR_P const prefix=nullptr, FSTR_P const suffix=nullptr);
|
||||
inline void print_xyze(const xyze_pos_t &xyze, FSTR_P const prefix=nullptr, FSTR_P const suffix=nullptr) {
|
||||
print_xyze(LOGICAL_AXIS_ELEM_(xyze) prefix, suffix);
|
||||
print_xyze(LOGICAL_AXIS_ELEM_LC_(xyze) prefix, suffix);
|
||||
}
|
||||
|
||||
#define SERIAL_POS(SUFFIX,VAR) do { print_xyz(VAR, F(" " STRINGIFY(VAR) "="), F(" : " SUFFIX "\n")); }while(0)
|
||||
|
||||
+147
-45
@@ -42,22 +42,28 @@ template <class L, class R> struct IF<true, L, R> { typedef L type; };
|
||||
#define NUM_AXIS_LIST_1(V) LIST_N_1(NUM_AXES, V)
|
||||
#define NUM_AXIS_ARRAY(V...) { NUM_AXIS_LIST(V) }
|
||||
#define NUM_AXIS_ARRAY_1(V) { NUM_AXIS_LIST_1(V) }
|
||||
#define NUM_AXIS_ARGS(T) NUM_AXIS_LIST(T x, T y, T z, T i, T j, T k, T u, T v, T w)
|
||||
#define NUM_AXIS_ELEM(O) NUM_AXIS_LIST(O.x, O.y, O.z, O.i, O.j, O.k, O.u, O.v, O.w)
|
||||
#define NUM_AXIS_DECL(T,V) NUM_AXIS_LIST(T x=V, T y=V, T z=V, T i=V, T j=V, T k=V, T u=V, T v=V, T w=V)
|
||||
#define NUM_AXIS_ARGS(T) NUM_AXIS_LIST(T X, T Y, T Z, T I, T J, T K, T U, T V, T W)
|
||||
#define NUM_AXIS_ARGS_LC(T) NUM_AXIS_LIST(T x, T y, T z, T i, T j, T k, T u, T v, T w)
|
||||
#define NUM_AXIS_ELEM(O) NUM_AXIS_LIST(O.X, O.Y, O.Z, O.I, O.J, O.K, O.U, O.V, O.W)
|
||||
#define NUM_AXIS_ELEM_LC(O) NUM_AXIS_LIST(O.x, O.y, O.z, O.i, O.j, O.k, O.u, O.v, O.w)
|
||||
#define NUM_AXIS_DECL(T,V) NUM_AXIS_LIST(T X=V, T Y=V, T Z=V, T I=V, T J=V, T K=V, T U=V, T V=V, T W=V)
|
||||
#define NUM_AXIS_DECL_LC(T,V) NUM_AXIS_LIST(T x=V, T y=V, T z=V, T i=V, T j=V, T k=V, T u=V, T v=V, T w=V)
|
||||
#define MAIN_AXIS_NAMES NUM_AXIS_LIST(X, Y, Z, I, J, K, U, V, W)
|
||||
#define MAIN_AXIS_NAMES_LC NUM_AXIS_LIST(x, y, z, i, j, k, u, v, w)
|
||||
#define STR_AXES_MAIN NUM_AXIS_GANG("X", "Y", "Z", STR_I, STR_J, STR_K, STR_U, STR_V, STR_W)
|
||||
|
||||
#define LOGICAL_AXIS_GANG(E,V...) NUM_AXIS_GANG(V) GANG_ITEM_E(E)
|
||||
#define LOGICAL_AXIS_CODE(E,V...) NUM_AXIS_CODE(V) CODE_ITEM_E(E)
|
||||
#define LOGICAL_AXIS_LIST(E,V...) NUM_AXIS_LIST(V) LIST_ITEM_E(E)
|
||||
#define LOGICAL_AXIS_GANG(N,V...) NUM_AXIS_GANG(V) GANG_ITEM_E(N)
|
||||
#define LOGICAL_AXIS_CODE(N,V...) NUM_AXIS_CODE(V) CODE_ITEM_E(N)
|
||||
#define LOGICAL_AXIS_LIST(N,V...) NUM_AXIS_LIST(V) LIST_ITEM_E(N)
|
||||
#define LOGICAL_AXIS_LIST_1(V) NUM_AXIS_LIST_1(V) LIST_ITEM_E(V)
|
||||
#define LOGICAL_AXIS_ARRAY(E,V...) { LOGICAL_AXIS_LIST(E,V) }
|
||||
#define LOGICAL_AXIS_ARRAY(N,V...) { LOGICAL_AXIS_LIST(N,V) }
|
||||
#define LOGICAL_AXIS_ARRAY_1(V) { LOGICAL_AXIS_LIST_1(V) }
|
||||
#define LOGICAL_AXIS_ARGS(T) LOGICAL_AXIS_LIST(T e, T x, T y, T z, T i, T j, T k, T u, T v, T w)
|
||||
#define LOGICAL_AXIS_ELEM(O) LOGICAL_AXIS_LIST(O.e, O.x, O.y, O.z, O.i, O.j, O.k, O.u, O.v, O.w)
|
||||
#define LOGICAL_AXIS_DECL(T,V) LOGICAL_AXIS_LIST(T e=V, T x=V, T y=V, T z=V, T i=V, T j=V, T k=V, T u=V, T v=V, T w=V)
|
||||
#define LOGICAL_AXIS_ARGS(T) LOGICAL_AXIS_LIST(T E, T X, T Y, T Z, T I, T J, T K, T U, T V, T W)
|
||||
#define LOGICAL_AXIS_ARGS_LC(T) LOGICAL_AXIS_LIST(T e, T x, T y, T z, T i, T j, T k, T u, T v, T w)
|
||||
#define LOGICAL_AXIS_ELEM(O) LOGICAL_AXIS_LIST(O.E, O.X, O.Y, O.Z, O.I, O.J, O.K, O.U, O.V, O.W)
|
||||
#define LOGICAL_AXIS_ELEM_LC(O) LOGICAL_AXIS_LIST(O.e, O.x, O.y, O.z, O.i, O.j, O.k, O.u, O.v, O.w)
|
||||
#define LOGICAL_AXIS_DECL(T,V) LOGICAL_AXIS_LIST(T E=V, T X=V, T Y=V, T Z=V, T I=V, T J=V, T K=V, T U=V, T V=V, T W=V)
|
||||
#define LOGICAL_AXIS_DECL_LC(T,V) LOGICAL_AXIS_LIST(T e=V, T x=V, T y=V, T z=V, T i=V, T j=V, T k=V, T u=V, T v=V, T w=V)
|
||||
#define LOGICAL_AXIS_NAMES LOGICAL_AXIS_LIST(E, X, Y, Z, I, J, K, U, V, W)
|
||||
#define LOGICAL_AXIS_NAMES_LC LOGICAL_AXIS_LIST(e, x, y, z, i, j, k, u, v, w)
|
||||
#define LOGICAL_AXIS_MAP(F) MAP(F, LOGICAL_AXIS_NAMES)
|
||||
@@ -68,8 +74,8 @@ template <class L, class R> struct IF<true, L, R> { typedef L type; };
|
||||
#define NUM_AXES_SEP ,
|
||||
#define MAIN_AXIS_MAP(F) MAP(F, MAIN_AXIS_NAMES)
|
||||
#define MAIN_AXIS_MAP_LC(F) MAP(F, MAIN_AXIS_NAMES_LC)
|
||||
#define OPTARGS_NUM(T) , NUM_AXIS_ARGS(T)
|
||||
#define OPTARGS_LOGICAL(T) , LOGICAL_AXIS_ARGS(T)
|
||||
#define OPTARGS_NUM(T) , NUM_AXIS_ARGS_LC(T)
|
||||
#define OPTARGS_LOGICAL(T) , LOGICAL_AXIS_ARGS_LC(T)
|
||||
#else
|
||||
#define NUM_AXES_SEP
|
||||
#define MAIN_AXIS_MAP(F)
|
||||
@@ -81,8 +87,8 @@ template <class L, class R> struct IF<true, L, R> { typedef L type; };
|
||||
#define NUM_AXIS_GANG_(V...) NUM_AXIS_GANG(V) NUM_AXES_SEP
|
||||
#define NUM_AXIS_LIST_(V...) NUM_AXIS_LIST(V) NUM_AXES_SEP
|
||||
#define NUM_AXIS_LIST_1_(V...) NUM_AXIS_LIST_1(V) NUM_AXES_SEP
|
||||
#define NUM_AXIS_ARGS_(T) NUM_AXIS_ARGS(T) NUM_AXES_SEP
|
||||
#define NUM_AXIS_ELEM_(T) NUM_AXIS_ELEM(T) NUM_AXES_SEP
|
||||
#define NUM_AXIS_ARGS_(T) NUM_AXIS_ARGS_LC(T) NUM_AXES_SEP
|
||||
#define NUM_AXIS_ELEM_(T) NUM_AXIS_ELEM_LC(T) NUM_AXES_SEP
|
||||
#define MAIN_AXIS_NAMES_ MAIN_AXIS_NAMES NUM_AXES_SEP
|
||||
#define MAIN_AXIS_NAMES_LC_ MAIN_AXIS_NAMES_LC NUM_AXES_SEP
|
||||
|
||||
@@ -95,15 +101,26 @@ template <class L, class R> struct IF<true, L, R> { typedef L type; };
|
||||
#define LOGICAL_AXIS_GANG_(V...) LOGICAL_AXIS_GANG(V) LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_LIST_(V...) LOGICAL_AXIS_LIST(V) LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_LIST_1_(V...) LOGICAL_AXIS_LIST_1(V) LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_ARGS_(T) LOGICAL_AXIS_ARGS(T) LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_ARGS_(T) LOGICAL_AXIS_ARGS_LC(T) LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_ELEM_(T) LOGICAL_AXIS_ELEM(T) LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_ELEM_LC_(T) LOGICAL_AXIS_ELEM_LC(T) LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_NAMES_ LOGICAL_AXIS_NAMES LOGICAL_AXES_SEP
|
||||
#define LOGICAL_AXIS_NAMES_LC_ LOGICAL_AXIS_NAMES_LC LOGICAL_AXES_SEP
|
||||
|
||||
#define SECONDARY_AXIS_GANG(V...) GANG_N(SECONDARY_AXES, V)
|
||||
#define SECONDARY_AXIS_CODE(V...) CODE_N(SECONDARY_AXES, V)
|
||||
#define SECONDARY_AXIS_LIST(V...) LIST_N(SECONDARY_AXES, V)
|
||||
#define SECONDARY_AXIS_ARGS(T) SECONDARY_AXIS_LIST(T i, T j, T k, T u, T v, T w)
|
||||
#if SECONDARY_AXES
|
||||
#define SECONDARY_AXIS_NAMES SECONDARY_AXIS_LIST(I, J, K, U, V, W)
|
||||
#define SECONDARY_AXIS_NAMES_LC SECONDARY_AXIS_LIST(i, j, k, u, v, w)
|
||||
#define SECONDARY_AXIS_ARGS(T) SECONDARY_AXIS_LIST(T I, T J, T K, T U, T V, T W)
|
||||
#define SECONDARY_AXIS_ARGS_LC(T) SECONDARY_AXIS_LIST(T i, T j, T k, T u, T v, T w)
|
||||
#define SECONDARY_AXIS_MAP(F) MAP(F, SECONDARY_AXIS_NAMES)
|
||||
#define SECONDARY_AXIS_MAP_LC(F) MAP(F, SECONDARY_AXIS_NAMES_LC)
|
||||
#else
|
||||
#define SECONDARY_AXIS_MAP(F)
|
||||
#define SECONDARY_AXIS_MAP_LC(F)
|
||||
#endif
|
||||
|
||||
// Just the XY or XYZ elements
|
||||
#if HAS_Z_AXIS
|
||||
@@ -159,36 +176,90 @@ template <class L, class R> struct IF<true, L, R> { typedef L type; };
|
||||
#define FI FORCE_INLINE
|
||||
|
||||
// Define types based on largest bit width stored value required
|
||||
#define bits_t(W) typename IF<((W)> 16), uint32_t, typename IF<((W)> 8), uint16_t, uint8_t>::type>::type
|
||||
#define bits_t(W) typename IF<((W)> 32), uint64_t, typename IF<((W)> 16), uint32_t, typename IF<((W)>8), uint16_t, uint8_t>::type>::type>::type
|
||||
#define uvalue_t(V) typename IF<((V)>65535), uint32_t, typename IF<((V)>255), uint16_t, uint8_t>::type>::type
|
||||
#define value_t(V) typename IF<((V)>32767), int32_t, typename IF<((V)>127), int16_t, int8_t>::type>::type
|
||||
|
||||
// General Flags for some number of states
|
||||
// Define a template for a bit field of N bits, using the smallest type that can hold N bits
|
||||
template<size_t N, bool UseArray = (N > 64)>
|
||||
struct Flags;
|
||||
|
||||
// Flag bits for <= 64 states
|
||||
template<size_t N>
|
||||
struct Flags {
|
||||
struct Flags<N, false> {
|
||||
typedef bits_t(N) flagbits_t;
|
||||
typedef struct { bool b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1; } N8;
|
||||
typedef struct { bool b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1, b8:1, b9:1, b10:1, b11:1, b12:1, b13:1, b14:1, b15:1; } N16;
|
||||
typedef struct { bool b0:1, b1:1, b2:1, b3:1, b4:1, b5:1, b6:1, b7:1, b8:1, b9:1, b10:1, b11:1, b12:1, b13:1, b14:1, b15:1,
|
||||
b16:1, b17:1, b18:1, b19:1, b20:1, b21:1, b22:1, b23:1, b24:1, b25:1, b26:1, b27:1, b28:1, b29:1, b30:1, b31:1; } N32;
|
||||
union {
|
||||
flagbits_t b;
|
||||
typename IF<(N>16), N32, typename IF<(N>8), N16, N8>::type>::type flag;
|
||||
flagbits_t b;
|
||||
|
||||
class BitProxy {
|
||||
public:
|
||||
BitProxy(flagbits_t& data, int bit) : data_(data), bit_(bit) {}
|
||||
|
||||
BitProxy& operator=(const bool value) {
|
||||
if (value)
|
||||
data_ |= (flagbits_t(1) << bit_);
|
||||
else
|
||||
data_ &= ~(flagbits_t(1) << bit_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const { return bool(data_ & (flagbits_t(1) << bit_)); }
|
||||
|
||||
private:
|
||||
flagbits_t& data_;
|
||||
uint8_t bit_;
|
||||
};
|
||||
|
||||
FI void reset() { b = 0; }
|
||||
FI void set(const int n, const bool onoff) { onoff ? set(n) : clear(n); }
|
||||
FI void set(const int n) { b |= (flagbits_t)_BV(n); }
|
||||
FI void clear(const int n) { b &= ~(flagbits_t)_BV(n); }
|
||||
FI bool test(const int n) const { return TEST(b, n); }
|
||||
FI bool operator[](const int n) { return test(n); }
|
||||
FI void set(const int n) { b |= (flagbits_t(1) << n); }
|
||||
FI void clear(const int n) { b &= ~(flagbits_t(1) << n); }
|
||||
FI bool test(const int n) const { return bool(b & (flagbits_t(1) << n)); }
|
||||
FI BitProxy operator[](const int n) { return BitProxy(b, n); }
|
||||
FI bool operator[](const int n) const { return test(n); }
|
||||
FI int size() const { return sizeof(b); }
|
||||
FI operator bool() const { return b; }
|
||||
FI operator bool() const { return b != 0; }
|
||||
};
|
||||
|
||||
// Flag bits for more than 64 states
|
||||
template<size_t N>
|
||||
struct Flags<N, true> {
|
||||
uint8_t bitmask[(N+7)>>3];
|
||||
// Proxy class for handling bit assignment
|
||||
class BitProxy {
|
||||
public:
|
||||
BitProxy(uint8_t data[], int n) : data_(data[n >> 3]), bit_(n & 7) {}
|
||||
|
||||
// Assignment operator
|
||||
BitProxy& operator=(const bool value) {
|
||||
if (value)
|
||||
data_ |= _BV(bit_);
|
||||
else
|
||||
data_ &= ~_BV(bit_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Conversion operator to bool
|
||||
operator bool() const { return TEST(data_, bit_); }
|
||||
|
||||
private:
|
||||
uint8_t& data_;
|
||||
uint8_t bit_;
|
||||
};
|
||||
|
||||
FI void reset() { for (uint8_t b = 0; b < sizeof(bitmask); ++b) bitmask[b] = 0; }
|
||||
FI void set(const int n, const bool onoff) { onoff ? set(n) : clear(n); }
|
||||
FI void set(const int n) { bitmask[n >> 3] |= _BV(n & 7); }
|
||||
FI void clear(const int n) { bitmask[n >> 3] &= ~_BV(n & 7); }
|
||||
FI bool test(const int n) const { return TEST(bitmask[n >> 3], n & 7); }
|
||||
FI BitProxy operator[](const int n) { return BitProxy(bitmask, n); }
|
||||
FI bool operator[](const int n) const { return test(n); }
|
||||
FI int size() const { return sizeof(bitmask); }
|
||||
FI operator bool() const { for (uint8_t b : bitmask) if (b) return true; return false; }
|
||||
};
|
||||
|
||||
// Specialization for a single bool flag
|
||||
template<>
|
||||
struct Flags<1> {
|
||||
struct Flags<1, false> {
|
||||
bool b;
|
||||
FI void reset() { b = false; }
|
||||
FI void set(const int n, const bool onoff) { onoff ? set(n) : clear(n); }
|
||||
@@ -218,7 +289,7 @@ typedef struct {
|
||||
FI bool operator[](const int n) { return flags[n]; }
|
||||
FI bool operator[](const int n) const { return flags[n]; }
|
||||
FI int size() const { return sizeof(flags); }
|
||||
FI operator bool() const { return flags; }
|
||||
FI operator bool() const { return (bool)flags; }
|
||||
} AxisFlags;
|
||||
|
||||
//
|
||||
@@ -243,7 +314,7 @@ enum AxisEnum : uint8_t {
|
||||
#endif
|
||||
|
||||
// Distinct axes, including all E and Core
|
||||
NUM_AXIS_ENUMS,
|
||||
NUM_AXIS_HEADS,
|
||||
|
||||
// Most of the time we refer only to the single E_AXIS
|
||||
#if HAS_EXTRUDERS
|
||||
@@ -428,7 +499,9 @@ template<typename T>
|
||||
struct XYval {
|
||||
union {
|
||||
struct { T x, y; };
|
||||
struct { T X, Y; };
|
||||
struct { T a, b; };
|
||||
struct { T A, B; };
|
||||
T pos[2];
|
||||
};
|
||||
|
||||
@@ -554,7 +627,9 @@ struct XYZval {
|
||||
union {
|
||||
#if NUM_AXES
|
||||
struct { NUM_AXIS_CODE(T x, T y, T z, T i, T j, T k, T u, T v, T w); };
|
||||
struct { NUM_AXIS_CODE(T X, T Y, T Z, T I, T J, T K, T U, T V, T W); };
|
||||
struct { NUM_AXIS_CODE(T a, T b, T c, T _i, T _j, T _k, T _u, T _v, T _w); };
|
||||
struct { NUM_AXIS_CODE(T A, T B, T C, T II, T JJ, T KK, T UU, T VV, T WW); };
|
||||
#endif
|
||||
T pos[NUM_AXES];
|
||||
};
|
||||
@@ -568,14 +643,14 @@ struct XYZval {
|
||||
FI void set(const T (&arr)[NUM_AXES]) { NUM_AXIS_CODE(x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
|
||||
#if LOGICAL_AXES > NUM_AXES
|
||||
FI void set(const T (&arr)[LOGICAL_AXES]) { NUM_AXIS_CODE(x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
|
||||
FI void set(LOGICAL_AXIS_ARGS(const T)) { NUM_AXIS_CODE(a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
FI void set(LOGICAL_AXIS_ARGS_LC(const T)) { NUM_AXIS_CODE(a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
#if DISTINCT_AXES > LOGICAL_AXES
|
||||
FI void set(const T (&arr)[DISTINCT_AXES]) { NUM_AXIS_CODE(x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Setter for all individual args
|
||||
FI void set(NUM_AXIS_ARGS(const T)) { NUM_AXIS_CODE(a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
FI void set(NUM_AXIS_ARGS_LC(const T)) { NUM_AXIS_CODE(a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
|
||||
// Setters with fewer elements leave the rest untouched
|
||||
#if HAS_Y_AXIS
|
||||
@@ -641,7 +716,7 @@ struct XYZval {
|
||||
// Assignment operator overrides do the expected thing
|
||||
FI XYZval<T>& operator= (const T v) { set(ARRAY_N_1(NUM_AXES, v)); return *this; }
|
||||
FI XYZval<T>& operator= (const XYval<T> &rs) { set(rs.x, rs.y); return *this; }
|
||||
FI XYZval<T>& operator= (const XYZEval<T> &rs) { set(NUM_AXIS_ELEM(rs)); return *this; }
|
||||
FI XYZval<T>& operator= (const XYZEval<T> &rs) { set(NUM_AXIS_ELEM_LC(rs)); return *this; }
|
||||
|
||||
// Override other operators to get intuitive behaviors
|
||||
FI constexpr XYZval<T> operator+ (const XYval<T> &rs) const { return NUM_AXIS_ARRAY(x + rs.x, y + rs.y, z, i, j, k, u, v, w ); }
|
||||
@@ -700,8 +775,10 @@ struct XYZval {
|
||||
template<typename T>
|
||||
struct XYZEval {
|
||||
union {
|
||||
struct { T LOGICAL_AXIS_ARGS_LC(); };
|
||||
struct { T LOGICAL_AXIS_ARGS(); };
|
||||
struct { T LOGICAL_AXIS_LIST(_e, a, b, c, _i, _j, _k, _u, _v, _w); };
|
||||
struct { T LOGICAL_AXIS_LIST(EE, A, B, C, II, JJ, KK, UU, VV, WW); };
|
||||
T pos[LOGICAL_AXES];
|
||||
};
|
||||
// Reset all to 0
|
||||
@@ -710,20 +787,20 @@ struct XYZEval {
|
||||
// Setters taking struct types and arrays
|
||||
FI void set(const XYval<T> pxy) { XY_CODE(x = pxy.x, y = pxy.y); }
|
||||
FI void set(const XYval<T> pxy, const T pz) { XYZ_CODE(x = pxy.x, y = pxy.y, z = pz); }
|
||||
FI void set(const XYZval<T> pxyz) { set(NUM_AXIS_ELEM(pxyz)); }
|
||||
FI void set(const XYZval<T> pxyz) { set(NUM_AXIS_ELEM_LC(pxyz)); }
|
||||
FI void set(const T (&arr)[NUM_AXES]) { NUM_AXIS_CODE(x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
|
||||
#if LOGICAL_AXES > NUM_AXES
|
||||
FI void set(const T (&arr)[LOGICAL_AXES]) { LOGICAL_AXIS_CODE(e = arr[LOGICAL_AXES-1], x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
|
||||
FI void set(const XYval<T> pxy, const T pz, const T pe) { set(pxy, pz); e = pe; }
|
||||
FI void set(const XYZval<T> pxyz, const T pe) { set(pxyz); e = pe; }
|
||||
FI void set(LOGICAL_AXIS_ARGS(const T)) { LOGICAL_AXIS_CODE(_e = e, a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
FI void set(LOGICAL_AXIS_ARGS_LC(const T)) { LOGICAL_AXIS_CODE(_e = e, a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
#if DISTINCT_AXES > LOGICAL_AXES
|
||||
FI void set(const T (&arr)[DISTINCT_AXES]) { LOGICAL_AXIS_CODE(e = arr[LOGICAL_AXES-1], x = arr[0], y = arr[1], z = arr[2], i = arr[3], j = arr[4], k = arr[5], u = arr[6], v = arr[7], w = arr[8]); }
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Setter for all individual args
|
||||
FI void set(NUM_AXIS_ARGS(const T)) { NUM_AXIS_CODE(a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
FI void set(NUM_AXIS_ARGS_LC(const T)) { NUM_AXIS_CODE(a = x, b = y, c = z, _i = i, _j = j, _k = k, _u = u, _v = v, _w = w); }
|
||||
|
||||
// Setters with fewer elements leave the rest untouched
|
||||
#if HAS_Y_AXIS
|
||||
@@ -788,7 +865,7 @@ struct XYZEval {
|
||||
// Assignment operator overrides do the expected thing
|
||||
FI XYZEval<T>& operator= (const T v) { set(LOGICAL_AXIS_LIST_1(v)); return *this; }
|
||||
FI XYZEval<T>& operator= (const XYval<T> &rs) { set(rs.x, rs.y); return *this; }
|
||||
FI XYZEval<T>& operator= (const XYZval<T> &rs) { set(NUM_AXIS_ELEM(rs)); return *this; }
|
||||
FI XYZEval<T>& operator= (const XYZval<T> &rs) { set(NUM_AXIS_ELEM_LC(rs)); return *this; }
|
||||
|
||||
// Override other operators to get intuitive behaviors
|
||||
FI constexpr XYZEval<T> operator+ (const XYval<T> &rs) const { return LOGICAL_AXIS_ARRAY(e, x + rs.x, y + rs.y, z, i, j, k, u, v, w); }
|
||||
@@ -848,7 +925,9 @@ struct XYZarray {
|
||||
union {
|
||||
el data[LOGICAL_AXES];
|
||||
struct { NUM_AXIS_CODE(T x, T y, T z, T i, T j, T k, T u, T v, T w); };
|
||||
struct { NUM_AXIS_CODE(T X, T Y, T Z, T I, T J, T K, T U, T V, T W); };
|
||||
struct { NUM_AXIS_CODE(T a, T b, T c, T _i, T _j, T _k, T _u, T _v, T _w); };
|
||||
struct { NUM_AXIS_CODE(T A, T B, T C, T II, T JJ, T KK, T UU, T VV, T WW); };
|
||||
};
|
||||
FI void reset() { ZERO(data); }
|
||||
|
||||
@@ -894,6 +973,8 @@ struct XYZEarray {
|
||||
union {
|
||||
el data[LOGICAL_AXES];
|
||||
struct { el LOGICAL_AXIS_ARGS(); };
|
||||
struct { el LOGICAL_AXIS_ARGS_LC(); };
|
||||
struct { el LOGICAL_AXIS_LIST(EE, A, B, C, II, JJ, KK, UU, VV, WW); };
|
||||
struct { el LOGICAL_AXIS_LIST(_e, a, b, c, _i, _j, _k, _u, _v, _w); };
|
||||
};
|
||||
FI void reset() { ZERO(data); }
|
||||
@@ -905,7 +986,7 @@ struct XYZEarray {
|
||||
// Setter for all individual args
|
||||
FI void set(const int n OPTARGS_NUM(const T)) { NUM_AXIS_CODE(a[n] = x, b[n] = y, c[n] = z, _i[n] = i, _j[n] = j, _k[n] = k, _u[n] = u, _v[n] = v, _w[n] = w); }
|
||||
#if LOGICAL_AXES > NUM_AXES
|
||||
FI void set(const int n, LOGICAL_AXIS_ARGS(const T)) { LOGICAL_AXIS_CODE(_e[n] = e, a[n] = x, b[n] = y, c[n] = z, _i[n] = i, _j[n] = j, _k[n] = k, _u[n] = u, _v[n] = v, _w[n] = w); }
|
||||
FI void set(const int n, LOGICAL_AXIS_ARGS_LC(const T)) { LOGICAL_AXIS_CODE(_e[n] = e, a[n] = x, b[n] = y, c[n] = z, _i[n] = i, _j[n] = j, _k[n] = k, _u[n] = u, _v[n] = v, _w[n] = w); }
|
||||
#endif
|
||||
|
||||
// Setters with fewer elements leave the rest untouched
|
||||
@@ -941,7 +1022,7 @@ class AxisBits;
|
||||
|
||||
class AxisBits {
|
||||
public:
|
||||
typedef bits_t(NUM_AXIS_ENUMS) el;
|
||||
typedef bits_t(NUM_AXIS_HEADS) el;
|
||||
union {
|
||||
el bits;
|
||||
// Axes x, y, z ... e0, e1, e2 ... hx, hy, hz
|
||||
@@ -994,6 +1075,25 @@ public:
|
||||
};
|
||||
};
|
||||
|
||||
class BitProxy {
|
||||
public:
|
||||
BitProxy(el& data, int bit) : data_(data), bit_(bit) {}
|
||||
|
||||
BitProxy& operator=(const bool value) {
|
||||
if (value)
|
||||
data_ |= (el(1) << bit_);
|
||||
else
|
||||
data_ &= ~(el(1) << bit_);
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const { return bool(data_ & (el(1) << bit_)); }
|
||||
|
||||
private:
|
||||
el& data_;
|
||||
uint8_t bit_;
|
||||
};
|
||||
|
||||
AxisBits() { reset(); }
|
||||
|
||||
// Constructor, setter, and operator= for bit mask
|
||||
@@ -1002,7 +1102,7 @@ public:
|
||||
FI AxisBits& operator=(const el p) { set(p); return *this; }
|
||||
|
||||
FI void reset() { set(0); }
|
||||
FI void fill() { set(_BV(NUM_AXIS_ENUMS) - 1); }
|
||||
FI void fill() { set(_BV(NUM_AXIS_HEADS) - 1); }
|
||||
|
||||
#define MSET(pE,pX,pY,pZ,pI,pJ,pK,pU,pV,pW) LOGICAL_AXIS_CODE(e=pE, x=pX, y=pY, z=pZ, i=pI, j=pJ, k=pK, u=pU, v=pV, w=pW)
|
||||
|
||||
@@ -1094,7 +1194,9 @@ public:
|
||||
FI void bset(const AxisEnum n, const bool b) { if (b) bset(n); else bclr(n); }
|
||||
|
||||
// Accessor via an AxisEnum (or any integer) [index]
|
||||
FI bool operator[](const int n) const { return TEST(bits, n); }
|
||||
FI BitProxy operator[](const int n) { return BitProxy(bits, n); }
|
||||
FI BitProxy operator[](const AxisEnum n) { return BitProxy(bits, n); }
|
||||
FI bool operator[](const int n) const { return TEST(bits, n); }
|
||||
FI bool operator[](const AxisEnum n) const { return TEST(bits, n); }
|
||||
|
||||
FI AxisBits& operator|=(const el &p) { bits |= el(p); return *this; }
|
||||
|
||||
@@ -146,9 +146,9 @@ public:
|
||||
transfer_timeout = millis() + TIMEOUT;
|
||||
switch (static_cast<FileTransfer>(packet_type)) {
|
||||
case FileTransfer::QUERY:
|
||||
SERIAL_ECHOPGM("PFT:version:", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH);
|
||||
SERIAL_ECHO(F("PFT:version:"), VERSION_MAJOR, C('.'), VERSION_MINOR, C('.'), VERSION_PATCH);
|
||||
#if ENABLED(BINARY_STREAM_COMPRESSION)
|
||||
SERIAL_ECHOLNPGM(":compression:heatshrink,", HEATSHRINK_STATIC_WINDOW_BITS, ",", HEATSHRINK_STATIC_LOOKAHEAD_BITS);
|
||||
SERIAL_ECHOLN(F(":compression:heatshrink,"), HEATSHRINK_STATIC_WINDOW_BITS, C(','), HEATSHRINK_STATIC_LOOKAHEAD_BITS);
|
||||
#else
|
||||
SERIAL_ECHOLNPGM(":compression:none");
|
||||
#endif
|
||||
@@ -322,7 +322,7 @@ public:
|
||||
if (packet.header.checksum == packet.header_checksum) {
|
||||
// The SYNC control packet is a special case in that it doesn't require the stream sync to be correct
|
||||
if (static_cast<Protocol>(packet.header.protocol()) == Protocol::CONTROL && static_cast<ProtocolControl>(packet.header.type()) == ProtocolControl::SYNC) {
|
||||
SERIAL_ECHOLNPGM("ss", sync, ",", buffer_size, ",", VERSION_MAJOR, ".", VERSION_MINOR, ".", VERSION_PATCH);
|
||||
SERIAL_ECHOLN(F("ss"), sync, C(','), buffer_size, C(','), VERSION_MAJOR, C('.'), VERSION_MINOR, C('.'), VERSION_PATCH);
|
||||
stream_state = StreamState::PACKET_RESET;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -83,7 +83,7 @@ void LEDLights::setup() {
|
||||
if (i == 1 && PWM_PIN(RGB_LED_G_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_G_PIN), led_pwm); else WRITE(RGB_LED_G_PIN, b < 100 ? HIGH : LOW);
|
||||
if (i == 2 && PWM_PIN(RGB_LED_B_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_B_PIN), led_pwm); else WRITE(RGB_LED_B_PIN, b < 100 ? HIGH : LOW);
|
||||
#if ENABLED(RGBW_LED)
|
||||
if (i == 3){
|
||||
if (i == 3) {
|
||||
if (PWM_PIN(RGB_LED_W_PIN)) hal.set_pwm_duty(pin_t(RGB_LED_W_PIN), led_pwm);
|
||||
else WRITE(RGB_LED_W_PIN, b < 100 ? HIGH : LOW);
|
||||
delay(RGB_STARTUP_TEST_INNER_MS);//More slowing for ending
|
||||
|
||||
@@ -148,7 +148,7 @@ void PCA9632_set_led_color(const LEDColor &color) {
|
||||
|
||||
#if ENABLED(PCA9632_BUZZER)
|
||||
|
||||
void PCA9632_buzz(const long, const uint16_t=0) {
|
||||
void PCA9632_buzz(const long, const uint16_t/*=0*/) {
|
||||
uint8_t data[] = PCA9632_BUZZER_DATA;
|
||||
Wire.beginTransmission(I2C_ADDRESS(PCA9632_ADDRESS));
|
||||
Wire.write(data, sizeof(data));
|
||||
|
||||
@@ -526,7 +526,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
||||
|
||||
switch (*special) {
|
||||
case '?': {
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
const uint8_t index = mmu2_choose_filament();
|
||||
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
|
||||
load_to_nozzle(index);
|
||||
@@ -536,7 +536,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
||||
} break;
|
||||
|
||||
case 'x': {
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
planner.synchronize();
|
||||
const uint8_t index = mmu2_choose_filament();
|
||||
stepper.disable_extruder();
|
||||
@@ -614,7 +614,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
||||
switch (*special) {
|
||||
case '?': {
|
||||
DEBUG_ECHOLNPGM("case ?\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
|
||||
load_to_nozzle(index);
|
||||
@@ -625,7 +625,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
||||
|
||||
case 'x': {
|
||||
DEBUG_ECHOLNPGM("case x\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
planner.synchronize();
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
stepper.disable_extruder();
|
||||
@@ -729,7 +729,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
||||
switch (*special) {
|
||||
case '?': {
|
||||
DEBUG_ECHOLNPGM("case ?\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
while (!thermalManager.wait_for_hotend(active_extruder, false)) safe_delay(100);
|
||||
load_to_nozzle(index);
|
||||
@@ -740,7 +740,7 @@ inline void beep_bad_cmd() { BUZZ(400, 40); }
|
||||
|
||||
case 'x': {
|
||||
DEBUG_ECHOLNPGM("case x\n");
|
||||
#if ENABLED(MMU2_MENUS)
|
||||
#if ENABLED(MMU_MENUS)
|
||||
planner.synchronize();
|
||||
uint8_t index = mmu2_choose_filament();
|
||||
stepper.disable_extruder();
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* SpoolJoin.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "SpoolJoin.h"
|
||||
#include "../../module/settings.h"
|
||||
#include "../../core/language.h"
|
||||
|
||||
SpoolJoin spooljoin;
|
||||
|
||||
bool SpoolJoin::enabled; // Initialized by settings.load
|
||||
int SpoolJoin::epprom_addr; // Initialized by settings.load
|
||||
uint8_t SpoolJoin::currentMMUSlot;
|
||||
|
||||
SpoolJoin::SpoolJoin() { setSlot(0); }
|
||||
|
||||
void SpoolJoin::initStatus() {
|
||||
// Useful information to see during bootup
|
||||
SERIAL_ECHOLN(F("SpoolJoin is "), enabled ? F("On") : F("Off"));
|
||||
}
|
||||
|
||||
void SpoolJoin::toggle() {
|
||||
// Toggle enabled value.
|
||||
enabled = !enabled;
|
||||
|
||||
// Following Prusa's implementation let's save the value to the EEPROM
|
||||
// TODO: Move to settings.cpp
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(epprom_addr, enabled);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SpoolJoin::isEnabled() { return enabled; }
|
||||
|
||||
void SpoolJoin::setSlot(const uint8_t slot) { currentMMUSlot = slot; }
|
||||
|
||||
uint8_t SpoolJoin::nextSlot() {
|
||||
SERIAL_ECHOPGM("SpoolJoin: ", currentMMUSlot);
|
||||
if (++currentMMUSlot >= 4) currentMMUSlot = 0;
|
||||
SERIAL_ECHOLNPGM(" -> ", currentMMUSlot);
|
||||
return currentMMUSlot;
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* SpoolJoin.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
// See documentation here: https://help.prusa3d.com/article/spooljoin-mmu2s_134252
|
||||
|
||||
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 Contrary to Prusa's implementation we store the enabled status in a variable
|
||||
static int epprom_addr;
|
||||
static bool enabled;
|
||||
|
||||
// @brief Called when EEPROM is ready to be read
|
||||
static void initStatus();
|
||||
|
||||
// @brief Toggle SpoolJoin
|
||||
static void toggle();
|
||||
|
||||
// @brief Check if SpoolJoin is enabled
|
||||
// @return true if enabled, false if disabled
|
||||
static bool isEnabled();
|
||||
|
||||
// @brief Update the saved MMU slot number so SpoolJoin can determine the next slot to use
|
||||
// @param slot number of the slot to set
|
||||
static void setSlot(const uint8_t slot);
|
||||
|
||||
// @brief Fetch the next slot number (0 to 4).
|
||||
// When filament slot 4 is depleted, the next slot should be 0.
|
||||
// @return the next slot (0 to 4)
|
||||
static uint8_t nextSlot();
|
||||
|
||||
private:
|
||||
static uint8_t currentMMUSlot; //!< Currently used slot (0 to 4)
|
||||
};
|
||||
|
||||
extern SpoolJoin spooljoin;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,419 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2.h
|
||||
*/
|
||||
|
||||
#include "mmu2_state.h"
|
||||
#include "mmu2_marlin.h"
|
||||
|
||||
#include "mmu2_protocol_logic.h"
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
typedef float feedRate_t;
|
||||
#else
|
||||
//#include <atomic>
|
||||
#endif
|
||||
|
||||
struct E_Step {
|
||||
float extrude; //!< extrude distance in mm
|
||||
float feedRate; //!< feed rate in mm/s
|
||||
};
|
||||
|
||||
static constexpr E_Step ramming_sequence[] PROGMEM = { MMU2_RAMMING_SEQUENCE };
|
||||
static constexpr E_Step load_to_nozzle_sequence[] PROGMEM = { MMU2_LOAD_TO_NOZZLE_SEQUENCE };
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// 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 MMU3 to be (almost) a drop-in replacement for the previous implementation.
|
||||
// Most of the public methods share the original naming convention as well.
|
||||
class MMU3 {
|
||||
public:
|
||||
MMU3();
|
||||
|
||||
// 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();
|
||||
|
||||
// Serial output of MMU state
|
||||
void status();
|
||||
|
||||
xState state() const { return _state; }
|
||||
|
||||
bool enabled() const { mmu_hw_enabled = state() == xState::Active; return mmu_hw_enabled; }
|
||||
|
||||
// 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)
|
||||
EraseEEPROM = 42, //!< erase MMU EEPROM and then perform a software reset
|
||||
};
|
||||
|
||||
// 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 ErrorSource : uint8_t {
|
||||
ErrorSourcePrinter = 0,
|
||||
ErrorSourceMMU = 1,
|
||||
ErrorSourceNone = 0xFF,
|
||||
};
|
||||
|
||||
// Tune value in MMU registers as a way to recover from errors
|
||||
// e.g. Idler Stallguard threshold
|
||||
void tune();
|
||||
|
||||
// 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
|
||||
// @return 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
|
||||
// @return 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 slot of the slot to be selected
|
||||
// @return false if the operation cannot be performed (Stopped)
|
||||
bool tool_change(uint8_t slot);
|
||||
|
||||
// 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.
|
||||
// @return 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)
|
||||
// @return false if the operation cannot be performed (Stopped)
|
||||
bool load_to_feeder(uint8_t slot);
|
||||
|
||||
// Load (push) filament from the MMU into the printer's nozzle
|
||||
// @return false if the operation cannot be performed (Stopped or cold extruder)
|
||||
bool load_to_nozzle(uint8_t slot);
|
||||
|
||||
// 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 slot, bool enableFullScreenMsg=true);
|
||||
|
||||
// Issue a Cut command into the MMU
|
||||
// Requires unloaded filament from the printer (obviously)
|
||||
// @return false if the operation cannot be performed (Stopped)
|
||||
bool cut_filament(uint8_t slot, bool enableFullScreenMsg=true);
|
||||
|
||||
// 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.
|
||||
// @param slot index of slot to be tested
|
||||
// @return true
|
||||
bool loading_test(uint8_t slot);
|
||||
|
||||
// @return the active filament slot index (0-4) or 0xff in case of no active tool
|
||||
uint8_t get_current_tool() const;
|
||||
|
||||
// @return 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 slot, 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);
|
||||
|
||||
// @return current state of FINDA (true=filament present, false=filament not present)
|
||||
bool findaDetectsFilament() const { return logic.findaPressed(); }
|
||||
|
||||
uint16_t totalFailStatistics() const { return logic.FailStatistics(); }
|
||||
|
||||
// @return Current error code
|
||||
ErrorCode mmuCurrentErrorCode() const { return logic.Error(); }
|
||||
|
||||
// @return Command in progress
|
||||
uint8_t getCommandInProgress() const { return logic.CommandInProgress(); }
|
||||
|
||||
// @return Last error source
|
||||
ErrorSource mmuLastErrorSource() const { return lastErrorSource; }
|
||||
|
||||
// @return Last error code
|
||||
ErrorCode getLastErrorCode() const { return lastErrorCode; }
|
||||
|
||||
// @return 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 };
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// @param ec ErrorCode enum value
|
||||
// @return true if auto-retry is ongoing, false when retry is unavailable or retry attempts are all used up
|
||||
bool retryIfPossible(const ErrorCode ec);
|
||||
|
||||
// @return count for toolchange in current print
|
||||
uint16_t toolChangeCounter() const { return toolchange_counter; }
|
||||
|
||||
// Set toolchange counter to zero
|
||||
void resetToolChangeCounter() { toolchange_counter = 0; }
|
||||
|
||||
uint16_t tmcFailures() const { return _tmcFailures; }
|
||||
void incrementTMCFailures() { ++_tmcFailures; }
|
||||
void resetTMCFailures() { _tmcFailures = 0; }
|
||||
|
||||
// Retrieve cached value parsed from readRegister()
|
||||
// or using M707
|
||||
uint16_t getLastReadRegisterValue() const {
|
||||
return lastReadRegisterValue;
|
||||
}
|
||||
void invokeErrorScreen(const ErrorCode ec) {
|
||||
if (logic.CommandInProgress()) return; // MMU must not be busy
|
||||
if (lastErrorCode == ec) return; // The error code is not a duplicate
|
||||
if (mmuCurrentErrorCode() == ErrorCode::OK) { // The protocol must not be in error state
|
||||
reportError(ec, ErrorSource::ErrorSourcePrinter);
|
||||
}
|
||||
}
|
||||
|
||||
void clearPrinterError() {
|
||||
logic.clearPrinterError();
|
||||
lastErrorCode = ErrorCode::OK;
|
||||
lastErrorSource = ErrorSource::ErrorSourceNone;
|
||||
}
|
||||
|
||||
// @brief Queue a button operation which the printer can act upon
|
||||
// @param btn Button operation
|
||||
void setPrinterButtonOperation(Buttons btn) {
|
||||
printerButtonOperation = btn;
|
||||
}
|
||||
|
||||
// @brief Get the printer button operation
|
||||
// @return currently set printer button operation, it can be NoButton if nothing is queued
|
||||
Buttons getPrinterButtonOperation() {
|
||||
return printerButtonOperation;
|
||||
}
|
||||
|
||||
void clearPrinterButtonOperation() {
|
||||
printerButtonOperation = Buttons::NoButton;
|
||||
}
|
||||
|
||||
static uint8_t cutter_mode; // mode 0:disabled | 1:enabled | 2:always (EXPERIMENTAL)
|
||||
static int cutter_mode_addr; // EEPROM addr for cutter enabled setting
|
||||
static uint8_t stealth_mode; // stealth mode
|
||||
static int stealth_mode_addr; // EEPROM addr for stealth_mode setting
|
||||
static bool mmu_hw_enabled; // MMU hardware can be Enabled/Disabled
|
||||
// with the M709 S0 or M709 S1 commands
|
||||
// and the last state is stored in the
|
||||
// EEPROM
|
||||
|
||||
static int mmu_hw_enabled_addr; // EEPROM addr for mmu_hw_enabled
|
||||
|
||||
bool e_active();
|
||||
|
||||
#ifndef UNITTEST
|
||||
private:
|
||||
#endif
|
||||
|
||||
// Perform software self-reset of the MMU (sends an X0 command)
|
||||
void resetX0();
|
||||
|
||||
// Perform software self-reset of the MMU + erase its EEPROM (sends X2a command)
|
||||
void resetX42();
|
||||
|
||||
// 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
|
||||
// @return false if the command could not have been completed (MMU interrupted)
|
||||
[[nodiscard]] bool manage_response(const bool move_axes, const bool turn_off_nozzle);
|
||||
|
||||
// The inner private implementation of mmu_loop()
|
||||
// which is NOT (!!!) recursion-guarded. Use caution - but we do need it during waiting for hotend resume to keep comms alive!
|
||||
// @param reportErrors true if Errors should raise MMU Error screen, false otherwise
|
||||
void mmu_loop_inner(bool reportErrors);
|
||||
|
||||
// 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
|
||||
// @param reportErrors true if Errors should raise MMU Error screen, false otherwise
|
||||
StepStatus logicStep(bool reportErrors);
|
||||
|
||||
void filament_ramming();
|
||||
void execute_extruder_sequence(const E_Step *sequence, uint8_t steps);
|
||||
void execute_load_to_nozzle_sequence();
|
||||
|
||||
// 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, ErrorSource 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
|
||||
// The function is quite complex, because it needs to handle asynchronnous
|
||||
// progress and error reports coming from the MMU without an explicit command
|
||||
// - typically after MMU's start or after some HW issue on the MMU.
|
||||
// It must ensure, that calls to @ref reportProgress and/or @ref reportError are
|
||||
// only executed after @ref BeginReport has been called first.
|
||||
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);
|
||||
|
||||
// @brief Save hotend temperature and set flag to cooldown hotend after 60 minutes
|
||||
// @param turn_off_nozzle if true, the hotend temperature will be set to 0degC after 60 minutes
|
||||
void saveHotendTemp(bool turn_off_nozzle);
|
||||
|
||||
// Save print and park the print head
|
||||
void saveAndPark(bool move_axes);
|
||||
|
||||
// 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.
|
||||
// @return false if the MMU is not ready to perform the command (for whatever reason)
|
||||
bool waitForMMUReady();
|
||||
|
||||
// After MMU completes a tool-change command
|
||||
// the printer will push the filament by a constant distance. If the Fsensor untriggers
|
||||
// at any moment the test fails. Else the test passes, and the E-motor retracts the
|
||||
// filament back to its original position.
|
||||
// @return false if test fails, true otherwise
|
||||
bool verifyFilamentEnteredPTFE();
|
||||
|
||||
// Common processing of pushing filament into the extruder - shared by tool_change, load_to_nozzle and probably others
|
||||
void toolChangeCommon(uint8_t slot);
|
||||
bool toolChangeCommonOnce(uint8_t slot);
|
||||
|
||||
void helpUnloadToFinda();
|
||||
void unloadInner();
|
||||
void cutFilamentInner(uint8_t slot);
|
||||
|
||||
void setCurrentTool(uint8_t ex);
|
||||
|
||||
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;
|
||||
ErrorSource lastErrorSource = ErrorSource::ErrorSourceNone;
|
||||
Buttons lastButton = Buttons::NoButton;
|
||||
uint16_t lastReadRegisterValue = 0;
|
||||
Buttons printerButtonOperation = Buttons::NoButton;
|
||||
|
||||
StepStatus logicStepLastStatus;
|
||||
|
||||
enum xState _state;
|
||||
|
||||
uint8_t mmu_print_saved;
|
||||
bool loadFilamentStarted;
|
||||
bool unloadFilamentStarted;
|
||||
|
||||
uint16_t toolchange_counter;
|
||||
uint16_t _tmcFailures;
|
||||
};
|
||||
|
||||
} // MMU3
|
||||
|
||||
// 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 MMU3::MMU3 mmu3;
|
||||
@@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_crc.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2_crc.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <util/crc16.h>
|
||||
#endif
|
||||
|
||||
namespace modules {
|
||||
|
||||
namespace crc {
|
||||
|
||||
uint8_t CRC8::CCITT_update(uint8_t crc, uint8_t b) {
|
||||
#ifdef __AVR__
|
||||
return _crc8_ccitt_update(crc, b);
|
||||
#else
|
||||
return CCITT_updateCX(crc, b);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace crc
|
||||
|
||||
} // namespace modules
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_crc.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace modules {
|
||||
|
||||
// prevent silly indenting of the whole file
|
||||
|
||||
// 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,376 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_error_converter.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../core/language.h"
|
||||
#include "mmu2_error_converter.h"
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
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
|
||||
// PrusaErrorCodeIndex 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) {
|
||||
static 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_FILAMENT_STUCK) == 1);
|
||||
// static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER) == 2);
|
||||
// static_assert( FindErrorIndex(ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK) == 3);
|
||||
|
||||
constexpr ErrorCode operator&(ErrorCode a, ErrorCode b) {
|
||||
return (ErrorCode)((uint16_t)a & (uint16_t)b);
|
||||
}
|
||||
|
||||
constexpr bool ContainsBit(ErrorCode ec, ErrorCode mask) {
|
||||
return (uint16_t)ec & (uint16_t)mask;
|
||||
}
|
||||
|
||||
uint8_t PrusaErrorCodeIndex(const ErrorCode ec) {
|
||||
switch (ec) {
|
||||
case ErrorCode::FINDA_DIDNT_SWITCH_ON:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FINDA_DIDNT_TRIGGER);
|
||||
case ErrorCode::FINDA_DIDNT_SWITCH_OFF:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FINDA_FILAMENT_STUCK);
|
||||
case ErrorCode::FSENSOR_DIDNT_SWITCH_ON:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER);
|
||||
case ErrorCode::FSENSOR_DIDNT_SWITCH_OFF:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK);
|
||||
case ErrorCode::FSENSOR_TOO_EARLY:
|
||||
return FindErrorIndex(ERR_MECHANICAL_FSENSOR_TOO_EARLY);
|
||||
case ErrorCode::FINDA_FLICKERS:
|
||||
return FindErrorIndex(ERR_MECHANICAL_INSPECT_FINDA);
|
||||
case ErrorCode::LOAD_TO_EXTRUDER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED);
|
||||
case ErrorCode::FILAMENT_EJECTED:
|
||||
return FindErrorIndex(ERR_SYSTEM_FILAMENT_EJECTED);
|
||||
case ErrorCode::FILAMENT_CHANGE:
|
||||
return FindErrorIndex(ERR_SYSTEM_FILAMENT_CHANGE);
|
||||
|
||||
case ErrorCode::STALLED_PULLEY:
|
||||
case ErrorCode::MOVE_PULLEY_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_PULLEY_CANNOT_MOVE);
|
||||
|
||||
case ErrorCode::HOMING_SELECTOR_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_HOME);
|
||||
case ErrorCode::MOVE_SELECTOR_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_SELECTOR_CANNOT_MOVE);
|
||||
|
||||
case ErrorCode::HOMING_IDLER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_HOME);
|
||||
case ErrorCode::MOVE_IDLER_FAILED:
|
||||
return FindErrorIndex(ERR_MECHANICAL_IDLER_CANNOT_MOVE);
|
||||
|
||||
case ErrorCode::MMU_NOT_RESPONDING:
|
||||
return FindErrorIndex(ERR_CONNECT_MMU_NOT_RESPONDING);
|
||||
case ErrorCode::PROTOCOL_ERROR:
|
||||
return FindErrorIndex(ERR_CONNECT_COMMUNICATION_ERROR);
|
||||
case ErrorCode::FILAMENT_ALREADY_LOADED:
|
||||
return FindErrorIndex(ERR_SYSTEM_FILAMENT_ALREADY_LOADED);
|
||||
case ErrorCode::INVALID_TOOL:
|
||||
return FindErrorIndex(ERR_SYSTEM_INVALID_TOOL);
|
||||
case ErrorCode::QUEUE_FULL:
|
||||
return FindErrorIndex(ERR_SYSTEM_QUEUE_FULL);
|
||||
case ErrorCode::VERSION_MISMATCH:
|
||||
return FindErrorIndex(ERR_SYSTEM_FW_UPDATE_NEEDED);
|
||||
case ErrorCode::INTERNAL:
|
||||
return FindErrorIndex(ERR_SYSTEM_FW_RUNTIME_ERROR);
|
||||
case ErrorCode::FINDA_VS_EEPROM_DISREPANCY:
|
||||
return FindErrorIndex(ERR_SYSTEM_UNLOAD_MANUALLY);
|
||||
case ErrorCode::MCU_UNDERVOLTAGE_VCC:
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_MCU_ERROR);
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Electrical issues which can be detected somehow.
|
||||
// Need to be placed before TMC-related errors in order to process couples of error bits between single ones
|
||||
// and to keep the code size down.
|
||||
if (ContainsBit(ec, ErrorCode::TMC_PULLEY_BIT)) {
|
||||
if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_SELECTOR_BIT)) {
|
||||
if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_IDLER_BIT)) {
|
||||
if ((ec & ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION) == ErrorCode::MMU_SOLDERING_NEEDS_ATTENTION)
|
||||
return FindErrorIndex(ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED);
|
||||
}
|
||||
|
||||
// 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 (ContainsBit(ec, ErrorCode::TMC_PULLEY_BIT)) {
|
||||
if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_RESET))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_SELECTOR_BIT)) {
|
||||
if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_RESET))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR);
|
||||
}
|
||||
else if (ContainsBit(ec, ErrorCode::TMC_IDLER_BIT)) {
|
||||
if (ContainsBit(ec, ErrorCode::TMC_IOIN_MISMATCH))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_RESET))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_UNDERVOLTAGE_ON_CHARGE_PUMP))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_SHORT_TO_GROUND))
|
||||
return FindErrorIndex(ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_WARN))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT);
|
||||
if (ContainsBit(ec, ErrorCode::TMC_OVER_TEMPERATURE_ERROR))
|
||||
return FindErrorIndex(ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR);
|
||||
}
|
||||
|
||||
// if nothing got caught, return a generic runtime error
|
||||
return FindErrorIndex(ERR_OTHER_UNKNOWN_ERROR);
|
||||
}
|
||||
|
||||
uint16_t PrusaErrorCode(const uint8_t i) { return (uint16_t)pgm_read_word(&errorCodes[i]); }
|
||||
|
||||
FSTR_P const PrusaErrorTitle(const uint8_t i) { return (FSTR_P const)pgm_read_ptr(&errorTitles[i]); }
|
||||
FSTR_P const PrusaErrorDesc(const uint8_t i) { return (FSTR_P const)pgm_read_ptr(&errorDescs[i]); }
|
||||
|
||||
uint8_t PrusaErrorButtons(const uint8_t i) { return pgm_read_byte(errorButtons + i); }
|
||||
|
||||
FSTR_P const PrusaErrorButtonTitle(const uint8_t bi) {
|
||||
// -1 represents the hidden NoOperation button which is not drawn in any way
|
||||
return (FSTR_P const)pgm_read_ptr(&btnOperation[bi - 1]);
|
||||
}
|
||||
|
||||
Buttons ButtonPressed(const ErrorCode ec) {
|
||||
if (buttonSelectedOperation == ButtonOperations::NoOperation || buttonSelectedOperation == ButtonOperations::MoreInfo)
|
||||
return Buttons::NoButton; // no button
|
||||
|
||||
const auto result = ButtonAvailable(ec);
|
||||
buttonSelectedOperation = ButtonOperations::NoOperation; // Reset operation
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Buttons ButtonAvailable(const ErrorCode 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 Reset MMU)
|
||||
// Based on Prusa-Error-Codes errors_list.h
|
||||
// So far hardcoded, but should be generated in the future
|
||||
switch (PrusaErrorCode(ei)) {
|
||||
case ERR_MECHANICAL_FINDA_DIDNT_TRIGGER:
|
||||
case ERR_MECHANICAL_FINDA_FILAMENT_STUCK:
|
||||
case ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER:
|
||||
case ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK:
|
||||
case ERR_MECHANICAL_FSENSOR_TOO_EARLY:
|
||||
case ERR_MECHANICAL_INSPECT_FINDA:
|
||||
case ERR_MECHANICAL_SELECTOR_CANNOT_MOVE:
|
||||
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 Buttons::Middle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_MECHANICAL_SELECTOR_CANNOT_HOME:
|
||||
case ERR_MECHANICAL_IDLER_CANNOT_HOME:
|
||||
switch (buttonSelectedOperation) {
|
||||
// may be allow move selector right and left in the future
|
||||
case ButtonOperations::Tune: // Tune Stallguard threshold
|
||||
return Buttons::TuneMMU;
|
||||
case ButtonOperations::Retry: // "Repeat action"
|
||||
return Buttons::Middle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED:
|
||||
case ERR_SYSTEM_FILAMENT_EJECTED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Continue: // User solved the serious mechanical problem by hand - there is no other way around
|
||||
return Buttons::Middle;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_SYSTEM_FILAMENT_CHANGE:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Load:
|
||||
return Buttons::Load;
|
||||
case ButtonOperations::Eject:
|
||||
return Buttons::Eject;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT:
|
||||
case ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT:
|
||||
case ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Continue: // "Continue"
|
||||
return Buttons::Left;
|
||||
case ButtonOperations::ResetMMU: // "Reset MMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR:
|
||||
case ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR:
|
||||
case ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR:
|
||||
|
||||
case ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED:
|
||||
case ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED:
|
||||
case ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED:
|
||||
|
||||
case ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED:
|
||||
case ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED:
|
||||
case ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED:
|
||||
|
||||
case ERR_SYSTEM_QUEUE_FULL:
|
||||
case ERR_SYSTEM_FW_RUNTIME_ERROR:
|
||||
case ERR_ELECTRICAL_MMU_MCU_ERROR:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::ResetMMU: // "Reset MMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_CONNECT_MMU_NOT_RESPONDING:
|
||||
case ERR_CONNECT_COMMUNICATION_ERROR:
|
||||
case ERR_SYSTEM_FW_UPDATE_NEEDED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::DisableMMU: // "Disable"
|
||||
return Buttons::DisableMMU;
|
||||
case ButtonOperations::ResetMMU: // "ResetMMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case ERR_SYSTEM_FILAMENT_ALREADY_LOADED:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::Unload: // "Unload"
|
||||
return Buttons::Left;
|
||||
case ButtonOperations::Continue: // "Proceed/Continue"
|
||||
return Buttons::Right;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case ERR_SYSTEM_INVALID_TOOL:
|
||||
switch (buttonSelectedOperation) {
|
||||
case ButtonOperations::StopPrint: // "Stop print"
|
||||
return Buttons::StopPrint;
|
||||
case ButtonOperations::ResetMMU: // "Reset MMU"
|
||||
return Buttons::ResetMMU;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return Buttons::NoButton;
|
||||
}
|
||||
|
||||
void SetButtonResponse(ButtonOperations rsp) {
|
||||
buttonSelectedOperation = rsp;
|
||||
}
|
||||
|
||||
ButtonOperations GetButtonResponse() {
|
||||
return buttonSelectedOperation;
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_error_converter.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "mmu_hw/error_codes.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Translates MMU3::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(const ErrorCode ec);
|
||||
|
||||
// @return pointer to a PROGMEM string representing the Title of the Prusa-Error-Codes error
|
||||
// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
FSTR_P const PrusaErrorTitle(const uint8_t i);
|
||||
|
||||
// @return 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
|
||||
FSTR_P const PrusaErrorDesc(const uint8_t i);
|
||||
|
||||
// @return the actual numerical value of the Prusa-Error-Codes error
|
||||
// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
uint16_t PrusaErrorCode(const uint8_t i);
|
||||
|
||||
// @return Btns pair of buttons for a particular Prusa-Error-Codes error
|
||||
// @param i index of the error - obtained by calling ErrorCodeIndex
|
||||
uint8_t PrusaErrorButtons(const uint8_t i);
|
||||
|
||||
// @return 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
|
||||
FSTR_P const PrusaErrorButtonTitle(const uint8_t bi);
|
||||
|
||||
// Sets the selected button for later pick-up by the MMU state machine.
|
||||
// Used to save the GUI selection/decoupling
|
||||
void SetButtonResponse(const ButtonOperations rsp);
|
||||
ButtonOperations GetButtonResponse();
|
||||
|
||||
// @return button index/code based on currently processed error/screen
|
||||
// Clears the "pressed" button upon exit
|
||||
Buttons ButtonPressed(const ErrorCode ec);
|
||||
|
||||
// @return 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(const ErrorCode ec);
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_fsensor.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../feature/runout.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
#if HAS_FILAMENT_SENSOR
|
||||
|
||||
FSensorBlockRunout::FSensorBlockRunout() {
|
||||
runout.enabled = false; // Suppress filament runouts while loading filament.
|
||||
//fsensor.setAutoLoadEnabled(false); //suppress filament autoloads while loading filament.
|
||||
}
|
||||
|
||||
FSensorBlockRunout::~FSensorBlockRunout() {
|
||||
//fsensor.settings_init(); // restore filament runout state.
|
||||
runout.reset();
|
||||
runout.enabled = true;
|
||||
//SERIAL_ECHOLNPGM("FSUnBlockRunout");
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
FSensorBlockRunout::FSensorBlockRunout() { }
|
||||
FSensorBlockRunout::~FSensorBlockRunout() { }
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
FilamentState WhereIsFilament() {
|
||||
//return fsensor.getFilamentPresent() ? FilamentState::AT_FSENSOR : FilamentState::NOT_PRESENT;
|
||||
return FILAMENT_PRESENT() ? FilamentState::AT_FSENSOR : FilamentState::NOT_PRESENT;
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_fsensor.h
|
||||
*/
|
||||
|
||||
#include "../../core/macros.h"
|
||||
#include <stdint.h>
|
||||
|
||||
#define FILAMENT_PRESENT() (READ(FIL_RUNOUT1_PIN) != FIL_RUNOUT1_STATE)
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// 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();
|
||||
};
|
||||
|
||||
// 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
|
||||
UNAVAILABLE = 3 //!< Sensor not available (likely not connected due broken cable)
|
||||
};
|
||||
|
||||
FilamentState WhereIsFilament();
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_log.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2_log.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
void LogEchoEvent_P(PGM_P const pstr) {
|
||||
SERIAL_ECHO_START(); // @@TODO Decide MMU errors on serial line
|
||||
SERIAL_MMU2();
|
||||
SERIAL_ECHOLN_P(pstr);
|
||||
}
|
||||
|
||||
void LogErrorEvent_P(PGM_P const pstr) {
|
||||
LogEchoEvent_P(pstr);
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_log.h
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
// @param msg pointer to a string in PROGMEM
|
||||
// On the AVR platform this variant reads the input string from PROGMEM.
|
||||
// On the ARM platform it calls LogErrorEvent directly (silently expecting the compiler to optimize it away)
|
||||
void LogErrorEvent_P(PGM_P const pstr);
|
||||
inline void LogErrorEvent(FSTR_P const fstr) { LogErrorEvent_P(FTOP(fstr)); }
|
||||
|
||||
// Report the msg into the general logging subsystem (through Marlin's SERIAL_ECHO stuff)
|
||||
// @param msg pointer to a string in PROGMEM
|
||||
// On the AVR platform this variant reads the input string from PROGMEM.
|
||||
// On the ARM platform it calls LogErrorEvent directly (silently expecting the compiler to optimize it away)
|
||||
void LogEchoEvent_P(PGM_P const pstr);
|
||||
inline void LogEchoEvent(FSTR_P const fstr) { LogEchoEvent_P(FTOP(fstr)); }
|
||||
|
||||
} // MMU3
|
||||
|
||||
#ifndef UNITTEST
|
||||
|
||||
#define SERIAL_MMU2() { SERIAL_ECHO(F("MMU3:")); }
|
||||
|
||||
#define MMU2_ECHO_MSGLN(S) do { \
|
||||
SERIAL_ECHO_START(); \
|
||||
SERIAL_MMU2(); \
|
||||
SERIAL_ECHOLN(S); \
|
||||
}while(0)
|
||||
#define MMU2_ERROR_MSGLN(S) MMU2_ECHO_MSGLN(S) //! @todo Decide MMU errors on serial line
|
||||
#define MMU2_ECHO_MSGRPGM(S) do { \
|
||||
SERIAL_ECHO_START(); \
|
||||
SERIAL_MMU2(); \
|
||||
SERIAL_ECHO_P(S); \
|
||||
}while(0)
|
||||
#define MMU2_ERROR_MSGRPGM(S) MMU2_ECHO_MSGRPGM(S) //! @todo Decide MMU errors on serial line
|
||||
#define MMU2_ECHO_MSG(S) do { \
|
||||
SERIAL_ECHO_START(); \
|
||||
SERIAL_MMU2(); \
|
||||
SERIAL_ECHO(S); \
|
||||
}while(0)
|
||||
#define MMU2_ERROR_MSG(S) MMU2_ECHO_MSG(S) //! @todo Decide MMU errors on serial line
|
||||
|
||||
#else // UNITTEST
|
||||
|
||||
#include "stubs/stub_interfaces.h"
|
||||
#define MMU2_ECHO_MSGLN(S) marlinLogSim.AppendLine(S)
|
||||
#define MMU2_ERROR_MSGLN(S) marlinLogSim.AppendLine(S)
|
||||
#define MMU2_ECHO_MSGRPGM(S) /* marlinLogSim.AppendLine(S) */
|
||||
#define MMU2_ERROR_MSGRPGM(S) /* marlinLogSim.AppendLine(S) */
|
||||
#define SERIAL_ECHOLNPGM(S) /* marlinLogSim.AppendLine(S) */
|
||||
#define SERIAL_ECHOPGM(S) /* */
|
||||
#define SERIAL_ECHOLN(S) /* marlinLogSim.AppendLine(S) */
|
||||
|
||||
#endif // UNITTEST
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_marlin.h
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// This interface separates Marlin1/Marlin2 from the MMU top logic layer.
|
||||
// - Unify implementation among MK3 and Buddy FW
|
||||
// - Enable unit testing of MMU top layer
|
||||
|
||||
void extruder_move(const_float_t distance, const_float_t feedRate_mm_s, const bool sync=true);
|
||||
void extruder_schedule_turning(const_float_t feedRate_mm_s);
|
||||
|
||||
float move_raise_z(const_float_t delta);
|
||||
|
||||
void planner_abort_queued_moves();
|
||||
void planner_synchronize();
|
||||
bool planner_any_moves();
|
||||
float stepper_get_machine_position_E_mm();
|
||||
float planner_get_current_position_E();
|
||||
void planner_set_current_position_E(float e);
|
||||
xyz_pos_t planner_current_position();
|
||||
|
||||
void motion_do_blocking_move_to_xy(float rx, float ry, float feedRate_mm_s);
|
||||
void motion_do_blocking_move_to_z(float z, float feedRate_mm_s);
|
||||
|
||||
void nozzle_park();
|
||||
|
||||
bool marlin_printingIsActive();
|
||||
void marlin_manage_heater();
|
||||
void marlin_manage_inactivity(bool b);
|
||||
void marlin_idle(bool b);
|
||||
void marlin_refresh_print_state_in_ram();
|
||||
void marlin_clear_print_state_in_ram();
|
||||
void marlin_stop_and_save_print_to_ram();
|
||||
|
||||
int16_t thermal_degTargetHotend();
|
||||
int16_t thermal_degHotend();
|
||||
void thermal_setExtrudeMintemp(int16_t t);
|
||||
void thermal_setTargetHotend(int16_t t);
|
||||
|
||||
void safe_delay_keep_alive(uint16_t t);
|
||||
|
||||
void Enable_E0();
|
||||
void Disable_E0();
|
||||
|
||||
bool xy_are_trusted();
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,190 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_marlin1.cpp
|
||||
* MK3 / Marlin1 implementation of support routines for the MMU3
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
#include "../../module/stepper.h"
|
||||
#include "../../module/planner.h"
|
||||
#include "../../module/temperature.h"
|
||||
|
||||
#include "../../feature/pause.h"
|
||||
#include "../../libs/nozzle.h"
|
||||
#include "mmu2_marlin.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
static void planner_line_to_current_position(float feedRate_mm_s) {
|
||||
line_to_current_position(feedRate_mm_s);
|
||||
}
|
||||
|
||||
static void planner_line_to_current_position_sync(float feedRate_mm_s) {
|
||||
planner_line_to_current_position(feedRate_mm_s);
|
||||
planner_synchronize();
|
||||
}
|
||||
|
||||
void extruder_move(const_float_t delta, const_float_t feedRate_mm_s, const bool sync/*=true*/) {
|
||||
current_position.e += delta / planner.e_factor[active_extruder];
|
||||
planner_line_to_current_position(feedRate_mm_s);
|
||||
if (sync) planner.synchronize();
|
||||
}
|
||||
|
||||
float move_raise_z(const_float_t delta) {
|
||||
//return raise_z(delta);
|
||||
xyze_pos_t current_position_before = current_position;
|
||||
do_z_clearance_by(delta);
|
||||
return (current_position - current_position_before).z;
|
||||
}
|
||||
|
||||
void planner_abort_queued_moves() {
|
||||
//planner_abort_hard();
|
||||
quickstop_stepper();
|
||||
|
||||
// Unblock the planner. This should be safe in the
|
||||
// toolchange context. Currently we are mainly aborting
|
||||
// excess E-moves after detecting filament during toolchange.
|
||||
// If a MMU error is reported, the planner must be unblocked
|
||||
// as well so the extruder can be parked safely.
|
||||
//planner_aborted = false;
|
||||
// eoyilmaz: we don't need this part, the print is not aborted
|
||||
}
|
||||
|
||||
void planner_synchronize() {
|
||||
planner.synchronize();
|
||||
}
|
||||
|
||||
bool planner_any_moves() {
|
||||
return planner.has_blocks_queued();
|
||||
}
|
||||
|
||||
float planner_get_machine_position_E_mm() {
|
||||
return current_position.e;
|
||||
}
|
||||
|
||||
float stepper_get_machine_position_E_mm() {
|
||||
return planner.get_axis_position_mm(E_AXIS);
|
||||
}
|
||||
|
||||
float planner_get_current_position_E() {
|
||||
return current_position.e;
|
||||
}
|
||||
|
||||
void planner_set_current_position_E(float e) {
|
||||
current_position.e = e;
|
||||
}
|
||||
|
||||
xyz_pos_t planner_current_position() {
|
||||
return xyz_pos_t(current_position);
|
||||
}
|
||||
|
||||
void motion_do_blocking_move_to_xy(float rx, float ry, float feedRate_mm_s) {
|
||||
current_position[X_AXIS] = rx;
|
||||
current_position[Y_AXIS] = ry;
|
||||
planner_line_to_current_position_sync(feedRate_mm_s);
|
||||
}
|
||||
|
||||
void motion_do_blocking_move_to_z(float z, float feedRate_mm_s) {
|
||||
current_position[Z_AXIS] = z;
|
||||
planner_line_to_current_position_sync(feedRate_mm_s);
|
||||
}
|
||||
|
||||
void nozzle_park() {
|
||||
#if ANY(NOZZLE_CLEAN_FEATURE, NOZZLE_PARK_FEATURE)
|
||||
#if ALL(ADVANCED_PAUSE_FEATURE)
|
||||
xyz_pos_t park_point = NOZZLE_PARK_POINT;
|
||||
nozzle.park(0, park_point);
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
bool marlin_printingIsActive() { return printingIsActive(); }
|
||||
|
||||
void marlin_manage_heater() { thermalManager.task(); }
|
||||
|
||||
void marlin_manage_inactivity(const bool b) { idle(b); }
|
||||
|
||||
void marlin_idle(bool b) {
|
||||
thermalManager.task();
|
||||
idle(b);
|
||||
}
|
||||
|
||||
void marlin_refresh_print_state_in_ram() {
|
||||
// refresh_print_state_in_ram();
|
||||
// TODO: I don't see a comparable implementation in Marlin.
|
||||
}
|
||||
|
||||
void marlin_clear_print_state_in_ram() {
|
||||
// clear_print_state_in_ram();
|
||||
// TODO: I don't see a comparable implementation in Marlin.
|
||||
}
|
||||
|
||||
void marlin_stop_and_save_print_to_ram() {
|
||||
// stop_and_save_print_to_ram(0,0);
|
||||
#if ENABLED(ADVANCED_PAUSE_FEATURE)
|
||||
constexpr xyz_pos_t park_point = NOZZLE_PARK_POINT;
|
||||
pause_print(0, park_point);
|
||||
#endif
|
||||
}
|
||||
|
||||
int16_t thermal_degTargetHotend() {
|
||||
return thermalManager.degTargetHotend(0);
|
||||
}
|
||||
|
||||
int16_t thermal_degHotend() {
|
||||
return thermalManager.degHotend(0);
|
||||
}
|
||||
|
||||
void thermal_setExtrudeMintemp(int16_t t) {
|
||||
thermalManager.extrude_min_temp = t;
|
||||
}
|
||||
|
||||
void thermal_setTargetHotend(int16_t t) {
|
||||
thermalManager.setTargetHotend(t, 0);
|
||||
}
|
||||
|
||||
void safe_delay_keep_alive(uint16_t t) {
|
||||
idle(true);
|
||||
safe_delay(t);
|
||||
}
|
||||
|
||||
void Enable_E0() {
|
||||
stepper.enable_extruder(TERN_(HAS_EXTRUDERS, 0));
|
||||
}
|
||||
|
||||
void Disable_E0() {
|
||||
stepper.disable_extruder(TERN_(HAS_EXTRUDERS, 0));
|
||||
}
|
||||
|
||||
bool xy_are_trusted() {
|
||||
return axis_is_trusted(X_AXIS) && axis_is_trusted(Y_AXIS);
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_marlin_macros.h
|
||||
*/
|
||||
|
||||
// This file will not be the same on Marlin1 and Marlin2.
|
||||
// Its purpose is to unify different macros in either of Marlin incarnations.
|
||||
|
||||
#ifdef __AVR__
|
||||
#include "../../MarlinCore.h"
|
||||
// brings _O and _T macros into MMU
|
||||
#include "../../core/language.h"
|
||||
#include "../../gcode/gcode.h"
|
||||
// we don't have these in Marlin 2.x so just define them here again
|
||||
#define _O(x) x
|
||||
#define _T(x) x
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS)
|
||||
#elif defined(UNITTEST)
|
||||
#define _O(x) x
|
||||
#define _T(x) x
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS /*KEEPALIVE_STATE(IN_PROCESS) TODO*/
|
||||
#else
|
||||
#include "../../gcode/gcode.h"
|
||||
#define _O(x) x
|
||||
#define _T(x) x
|
||||
#define MARLIN_KEEPALIVE_STATE_IN_PROCESS KEEPALIVE_STATE(IN_PROCESS)
|
||||
#endif
|
||||
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_power.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2.h"
|
||||
#include "mmu2_power.h"
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include "../../core/macros.h"
|
||||
#include "../../core/boards.h"
|
||||
#include "../../pins/pins.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// On MK3 we cannot do actual power cycle on HW. Instead trigger a hardware reset.
|
||||
void power_on() {
|
||||
#if PIN_EXISTS(MMU2_RST)
|
||||
OUT_WRITE(MMU2_RST_PIN, HIGH);
|
||||
#endif
|
||||
power_reset();
|
||||
}
|
||||
|
||||
void power_off() {}
|
||||
|
||||
void power_reset() {
|
||||
#if PIN_EXISTS(MMU2_RST) // HW - pulse reset pin
|
||||
WRITE(MMU2_RST_PIN, LOW);
|
||||
safe_delay(100);
|
||||
WRITE(MMU2_RST_PIN, HIGH);
|
||||
#else
|
||||
mmu3.reset(MMU3::Software); // TODO: Needs redesign. This power implementation shouldn't know anything about the MMU itself
|
||||
#endif
|
||||
// otherwise HW reset is not available
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_power.h
|
||||
*/
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
void power_on();
|
||||
|
||||
void power_off();
|
||||
|
||||
void power_reset();
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,82 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_progress_converter.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "../../core/language.h"
|
||||
#include "mmu2_progress_converter.h"
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
FSTR_P const progressTexts[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_PROGRESS_OK),
|
||||
GET_TEXT_F(MSG_PROGRESS_ENGAGE_IDLER),
|
||||
GET_TEXT_F(MSG_PROGRESS_DISENGAGE_IDLER),
|
||||
GET_TEXT_F(MSG_PROGRESS_UNLOAD_FINDA),
|
||||
GET_TEXT_F(MSG_PROGRESS_UNLOAD_PULLEY),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_FINDA),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_EXTRUDER),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_NOZZLE),
|
||||
GET_TEXT_F(MSG_PROGRESS_AVOID_GRIND),
|
||||
GET_TEXT_F(MSG_FINISHING_MOVEMENTS), // reuse from messages.cpp
|
||||
GET_TEXT_F(MSG_PROGRESS_DISENGAGE_IDLER), // err disengaging idler is the same text
|
||||
GET_TEXT_F(MSG_PROGRESS_ENGAGE_IDLER), // engage dtto.
|
||||
GET_TEXT_F(MSG_PROGRESS_WAIT_USER),
|
||||
GET_TEXT_F(MSG_PROGRESS_ERR_INTERNAL),
|
||||
GET_TEXT_F(MSG_PROGRESS_ERR_HELP_FIL),
|
||||
GET_TEXT_F(MSG_PROGRESS_ERR_TMC),
|
||||
GET_TEXT_F(MSG_UNLOADING_FILAMENT), // reuse from messages.cpp
|
||||
GET_TEXT_F(MSG_LOADING_FILAMENT), // reuse from messages.cpp
|
||||
GET_TEXT_F(MSG_PROGRESS_SELECT_SLOT),
|
||||
GET_TEXT_F(MSG_PROGRESS_PREPARE_BLADE),
|
||||
GET_TEXT_F(MSG_PROGRESS_PUSH_FILAMENT),
|
||||
GET_TEXT_F(MSG_PROGRESS_PERFORM_CUT),
|
||||
GET_TEXT_F(MSG_PROGRESSPSTRETURN_SELECTOR),
|
||||
GET_TEXT_F(MSG_PROGRESS_PARK_SELECTOR),
|
||||
GET_TEXT_F(MSG_PROGRESS_EJECT_FILAMENT),
|
||||
GET_TEXT_F(MSG_PROGRESSPSTRETRACT_FINDA),
|
||||
GET_TEXT_F(MSG_PROGRESS_HOMING),
|
||||
GET_TEXT_F(MSG_PROGRESS_MOVING_SELECTOR),
|
||||
GET_TEXT_F(MSG_PROGRESS_FEED_FSENSOR)
|
||||
};
|
||||
|
||||
FSTR_P const ProgressCodeToText(const ProgressCode pc) {
|
||||
// @@TODO ?? a better fallback option?
|
||||
return (int(pc) < COUNT(progressTexts))
|
||||
? static_cast<FSTR_P const>(pgm_read_ptr(&progressTexts[(uint16_t)pc]))
|
||||
: static_cast<FSTR_P const>(pgm_read_ptr(&progressTexts[0]));
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_progress_converter.h
|
||||
*/
|
||||
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
|
||||
#include "../../HAL/shared/Marduino.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
FSTR_P const ProgressCodeToText(const ProgressCode pc);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,418 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_protocol.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#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;
|
||||
}
|
||||
}
|
||||
else {
|
||||
requestMsg.code = RequestMsgCodes::unknown;
|
||||
rqState = RequestStates::Error;
|
||||
return DecodeStatus::Error;
|
||||
}
|
||||
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.getCRC(), 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.getCRC(), 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.getCRC(), 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.getCRC(), 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.getCRC(), 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
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,318 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_protocol.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mmu2_crc.h"
|
||||
|
||||
// prevent ARM HAL macros from breaking our code
|
||||
#undef CRC
|
||||
|
||||
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 getCRC() 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 getCRC() 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:
|
||||
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
|
||||
// @return state of the message being decoded
|
||||
DecodeStatus DecodeRequest(uint8_t c);
|
||||
|
||||
// Decodes response message in rxbuff
|
||||
// @return 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
|
||||
// @return 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
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeWriteRequest(uint8_t address, uint16_t value, uint8_t *txbuff);
|
||||
|
||||
// @return 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; }
|
||||
|
||||
// @return 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
|
||||
// @return 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
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseReadFINDA(const RequestMsg &msg, uint8_t findaValue, uint8_t *txbuff);
|
||||
|
||||
// Encode response to Version query
|
||||
// @param msg source request message for this response
|
||||
// @param value version number (0-255)
|
||||
// @param txbuff where to format the message
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseVersion(const RequestMsg &msg, uint16_t value, 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
|
||||
// @return 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
|
||||
// @return number of bytes written into txbuff
|
||||
static uint8_t EncodeResponseRead(const RequestMsg &msg, bool accepted, uint16_t value2, uint8_t *txbuff);
|
||||
|
||||
// @return the most recently lexed request message
|
||||
inline const RequestMsg GetRequestMsg() const { return requestMsg; }
|
||||
|
||||
// @return 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' ... '9': return c - '0';
|
||||
case 'a' ... 'f': return c - 'a' + 10;
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr uint8_t Nibble2Char(uint8_t n) {
|
||||
switch (n) {
|
||||
case 0x0 ... 0x9: return n + '0';
|
||||
case 0xA ... 0xF: return n - 10 + 'a';
|
||||
default: return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// @return number of characters written
|
||||
static uint8_t UInt8ToHex(uint8_t value, uint8_t *dst);
|
||||
|
||||
// @return 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
|
||||
|
||||
@@ -0,0 +1,899 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_protocol_logic.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2_protocol_logic.h"
|
||||
#include "mmu2_log.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
|
||||
#ifdef __AVR__
|
||||
// on MK3/S/+ we shuffle the timers a bit, thus "_millis" may not equal "millis"
|
||||
// #include "system_timer.h"
|
||||
#define _millis millis
|
||||
#else
|
||||
// irrelevant on Buddy FW, just keep "_millis" as "millis"
|
||||
// #include <wiring_time.h>
|
||||
#define _millis millis
|
||||
#ifdef UNITTEST
|
||||
#define strncmp_P strncmp
|
||||
#else
|
||||
#include "../../core/serial.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#include <string.h>
|
||||
#include "mmu2_supported_version.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
static constexpr uint8_t supportedMmuFWVersion[] PROGMEM = { mmuVersionMajor, mmuVersionMinor, mmuVersionPatch };
|
||||
|
||||
const Register ProtocolLogic::regs8Addrs[ProtocolLogic::regs8Count] PROGMEM = {
|
||||
Register::FINDA_State, // FINDA state
|
||||
Register::Set_Get_Selector_Slot, // Selector slot
|
||||
Register::Set_Get_Idler_Slot, // Idler slot
|
||||
};
|
||||
|
||||
const Register ProtocolLogic::regs16Addrs[ProtocolLogic::regs16Count] PROGMEM = {
|
||||
Register::MMU_Errors, // MMU errors - aka statistics
|
||||
Register::Get_Pulley_Position, // Pulley position [mm]
|
||||
};
|
||||
|
||||
const Register ProtocolLogic::initRegs8Addrs[ProtocolLogic::initRegs8Count] PROGMEM = {
|
||||
Register::Extra_Load_Distance, // Extra load distance [mm]
|
||||
Register::Pulley_Slow_Feedrate, // Pulley slow feedrate [mm/s]
|
||||
};
|
||||
|
||||
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::StartReading8bitRegisters() {
|
||||
regIndex = 0;
|
||||
SendReadRegister(pgm_read_byte(regs8Addrs + regIndex), ScopeState::Reading8bitRegisters);
|
||||
}
|
||||
|
||||
void ProtocolLogic::ProcessRead8bitRegister() {
|
||||
regs8[regIndex] = rsp.paramValue;
|
||||
++regIndex;
|
||||
if (regIndex >= regs8Count)
|
||||
// proceed with reading 16bit registers
|
||||
StartReading16bitRegisters();
|
||||
else
|
||||
SendReadRegister(pgm_read_byte(regs8Addrs + regIndex), ScopeState::Reading8bitRegisters);
|
||||
}
|
||||
|
||||
void ProtocolLogic::StartReading16bitRegisters() {
|
||||
regIndex = 0;
|
||||
SendReadRegister(pgm_read_byte(regs16Addrs + regIndex), ScopeState::Reading16bitRegisters);
|
||||
}
|
||||
|
||||
ProtocolLogic::ScopeState __attribute__((noinline)) ProtocolLogic::ProcessRead16bitRegister(ProtocolLogic::ScopeState stateAtEnd) {
|
||||
regs16[regIndex] = rsp.paramValue;
|
||||
++regIndex;
|
||||
if (regIndex >= regs16Count)
|
||||
return stateAtEnd;
|
||||
else
|
||||
SendReadRegister(pgm_read_byte(regs16Addrs + regIndex), ScopeState::Reading16bitRegisters);
|
||||
return ScopeState::Reading16bitRegisters;
|
||||
}
|
||||
|
||||
void ProtocolLogic::StartWritingInitRegisters() {
|
||||
regIndex = 0;
|
||||
SendWriteRegister(pgm_read_byte(initRegs8Addrs + regIndex), initRegs8[regIndex], ScopeState::WritingInitRegisters);
|
||||
}
|
||||
|
||||
bool __attribute__((noinline)) ProtocolLogic::ProcessWritingInitRegister() {
|
||||
++regIndex;
|
||||
if (regIndex >= initRegs8Count)
|
||||
return true;
|
||||
else
|
||||
SendWriteRegister(pgm_read_byte(initRegs8Addrs + regIndex), initRegs8[regIndex], ScopeState::WritingInitRegisters);
|
||||
return false;
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
// @return 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::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 = MMU2_SERIAL.read()) >= 0) {
|
||||
++bytesConsumed;
|
||||
RecordReceivedByte(c);
|
||||
switch (protocol.DecodeResponse(c)) {
|
||||
case DecodeStatus::MessageCompleted:
|
||||
rsp = protocol.GetResponseMsg();
|
||||
LogResponse();
|
||||
// @@TODO reset direction of communication
|
||||
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)
|
||||
// Old MMU FW 1.0.6 detected. Firmwares are incompatible.
|
||||
return VersionMismatch;
|
||||
else if (old == OldMMUFWDetector::State::MatchingPart)
|
||||
break;
|
||||
}
|
||||
// [[fallthrough]]; // otherwise
|
||||
// fall through
|
||||
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) && currentScope != Scope::Stopped) {
|
||||
return CommunicationTimeout;
|
||||
}
|
||||
return Processing;
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendMsg(RequestMsg rq) {
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// Buddy FW cannot use stack-allocated txbuff - DMA doesn't work with CCMRAM
|
||||
// No restrictions on MK3/S/+ though
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()];
|
||||
#endif
|
||||
uint8_t len = Protocol::EncodeRequest(rq, txbuff);
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// TODO: I'm not sure if this is the correct approach with AVR
|
||||
for ( uint8_t i = 0; i < len; i++) {
|
||||
MMU2_SERIAL.write(txbuff[i]);
|
||||
}
|
||||
#else
|
||||
MMU2_SERIAL.write(txbuff, len);
|
||||
#endif
|
||||
LogRequestMsg(txbuff, len);
|
||||
RecordUARTActivity();
|
||||
}
|
||||
|
||||
void ProtocolLogic::SendWriteMsg(RequestMsg rq) {
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// Buddy FW cannot use stack-allocated txbuff - DMA doesn't work with CCMRAM
|
||||
// No restrictions on MK3/S/+ though
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()];
|
||||
#endif
|
||||
uint8_t len = Protocol::EncodeWriteRequest(rq.value, rq.value2, txbuff);
|
||||
|
||||
#if defined(__AVR__) || defined(TARGET_LPC1768)
|
||||
// TODO: I'm not sure if this is the correct approach with AVR
|
||||
for ( uint8_t i = 0; i < len; i++) {
|
||||
MMU2_SERIAL.write(txbuff[i]);
|
||||
}
|
||||
#else
|
||||
MMU2_SERIAL.write(txbuff, len);
|
||||
#endif
|
||||
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;
|
||||
SendVersion(stage);
|
||||
}
|
||||
else {
|
||||
ResetCommunicationTimeoutAttempts(); // 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
|
||||
auto expmsg = ExpectingMessage();
|
||||
if (expmsg != MessageReady)
|
||||
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 - patch
|
||||
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 - initial parametrization is started
|
||||
StartWritingInitRegisters();
|
||||
}
|
||||
return Processing;
|
||||
case ScopeState::WritingInitRegisters:
|
||||
if (ProcessWritingInitRegister())
|
||||
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.
|
||||
default:
|
||||
return VersionMismatch;
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::DelayedRestartWait() {
|
||||
if (Elapsed(heartBeatPeriod)) { // this basically means, that we are waiting until there is some traffic on
|
||||
while (MMU2_SERIAL.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
|
||||
progressCode = ProgressCode::ERRWaitingForUser;
|
||||
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:
|
||||
// We must check whether the "finished" is actually related to the command issued into the MMU
|
||||
// It can also be an X0 F which means MMU just successfully restarted.
|
||||
if (ReqMsg().code == rsp.request.code && ReqMsg().value == rsp.request.value) {
|
||||
progressCode = ProgressCode::OK;
|
||||
errorCode = ErrorCode::OK;
|
||||
scopeState = ScopeState::Ready;
|
||||
rq = RequestMsg(RequestMsgCodes::unknown, 0); // clear the successfully finished request
|
||||
return Finished;
|
||||
}
|
||||
else {
|
||||
// got response to some other command - the originally issued command was interrupted!
|
||||
return Interrupted;
|
||||
}
|
||||
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:
|
||||
StartReading8bitRegisters();
|
||||
return Processing;
|
||||
case ScopeState::Reading8bitRegisters:
|
||||
ProcessRead8bitRegister();
|
||||
return Processing;
|
||||
case ScopeState::Reading16bitRegisters:
|
||||
scopeState = ProcessRead16bitRegister(ScopeState::Wait);
|
||||
return Processing;
|
||||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted)
|
||||
// Button was accepted, decrement the retry.
|
||||
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);
|
||||
StartReading8bitRegisters();
|
||||
return ButtonPushed;
|
||||
case ResponseMsgParamCodes::Finished:
|
||||
if (ReqMsg().code != RequestMsgCodes::unknown) {
|
||||
// got reset while doing some other command - the originally issued command was interrupted!
|
||||
// this must be solved by the upper layer, protocol logic doesn't have all the context (like unload before trying again)
|
||||
IdleRestart();
|
||||
return Interrupted;
|
||||
}
|
||||
// [[fallthrough]];
|
||||
// fall through
|
||||
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
|
||||
progressCode = static_cast<ProgressCode>(rsp.paramValue);
|
||||
errorCode = ErrorCode::OK;
|
||||
break;
|
||||
default:
|
||||
progressCode = ProgressCode::ERRWaitingForUser;
|
||||
errorCode = static_cast<ErrorCode>(rsp.paramValue);
|
||||
StartReading8bitRegisters(); // continue Idle state without restarting the communication
|
||||
return CommandError;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return ProtocolError;
|
||||
}
|
||||
StartReading8bitRegisters();
|
||||
return Processing;
|
||||
case ScopeState::Reading8bitRegisters:
|
||||
ProcessRead8bitRegister();
|
||||
return Processing;
|
||||
case ScopeState::Reading16bitRegisters:
|
||||
scopeState = ProcessRead16bitRegister(ScopeState::Ready);
|
||||
return scopeState == ScopeState::Ready ? Finished : Processing;
|
||||
case ScopeState::ButtonSent:
|
||||
if (rsp.paramCode == ResponseMsgParamCodes::Accepted)
|
||||
// Button was accepted, decrement the retry.
|
||||
DecrementRetryAttempts();
|
||||
StartReading8bitRegisters();
|
||||
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(uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate)
|
||||
: explicitPrinterError(ErrorCode::OK)
|
||||
, 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)
|
||||
, errorCode(ErrorCode::OK)
|
||||
, progressCode(ProgressCode::OK)
|
||||
, buttonCode(Buttons::NoButton)
|
||||
, lastFSensor((uint8_t)WhereIsFilament())
|
||||
, regIndex(0)
|
||||
, retryAttempts(MMU2_MAX_RETRIES)
|
||||
, inAutoRetry(false) {
|
||||
// @@TODO currently, I don't see a way of writing the initialization better :(
|
||||
// I'd like to write something like: initRegs8 { extraLoadDistance, pulleySlowFeedrate }
|
||||
// avr-gcc seems to like such a syntax, ARM gcc doesn't
|
||||
initRegs8[0] = extraLoadDistance;
|
||||
initRegs8[1] = pulleySlowFeedrate;
|
||||
}
|
||||
|
||||
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(uint8_t mode /* = 0 */) {
|
||||
PlanGenericRequest(RequestMsg(RequestMsgCodes::Reset, mode));
|
||||
}
|
||||
|
||||
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 0x0 ... 0x9: return c + '0';
|
||||
case 0xA ... 0xF: 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];
|
||||
// Check for printable character, including space
|
||||
if (b < 32 || 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() + 1;
|
||||
char tmp[rqs] = ">";
|
||||
static char lastMsg[rqs] = "";
|
||||
for (uint8_t i = 0; i < size; ++i) {
|
||||
uint8_t b = txbuff[i];
|
||||
// Check for printable character, including space
|
||||
if (b < 32 || b > 127)
|
||||
b = '.';
|
||||
tmp[i + 1] = b;
|
||||
}
|
||||
tmp[size + 1] = 0;
|
||||
if (!strncmp_P(tmp, PSTR(">S0*c6."), 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_MSGLN(tmp);
|
||||
}
|
||||
strncpy(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_MSGLN(lrb);
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::SuppressShortDropOuts(const char *msg_P, StepStatus ss) {
|
||||
if (dataTO.Record(ss)) {
|
||||
LogError(msg_P);
|
||||
ResetCommunicationTimeoutAttempts(); // prepare for another run of consecutive retries before firing an error
|
||||
return dataTO.InitialCause();
|
||||
}
|
||||
else {
|
||||
return Processing; // suppress short drop outs of communication
|
||||
}
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::HandleCommunicationTimeout() {
|
||||
MMU2_SERIAL.flush(); // clear the output buffer
|
||||
protocol.ResetResponseDecoder();
|
||||
start();
|
||||
return SuppressShortDropOuts(PSTR("Communication timeout"), CommunicationTimeout);
|
||||
}
|
||||
|
||||
StepStatus ProtocolLogic::HandleProtocolError() {
|
||||
MMU2_SERIAL.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
|
||||
if (!ActivatePlannedRequest()) { // if nothing is planned, switch to Idle
|
||||
SwitchToIdle();
|
||||
}
|
||||
else if (ExpectsResponse()) {
|
||||
// if the previous cycle was Idle and now we have planned a new command -> avoid returning Finished
|
||||
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 should 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"));
|
||||
break;
|
||||
case ProtocolError:
|
||||
currentStatus = HandleProtocolError();
|
||||
break;
|
||||
case CommunicationTimeout:
|
||||
currentStatus = HandleCommunicationTimeout();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// special handling of explicit printer errors
|
||||
return IsPrinterError() ? StepStatus::PrinterError : currentStatus;
|
||||
}
|
||||
|
||||
uint8_t ProtocolLogic::CommandInProgress() const {
|
||||
if (currentScope != Scope::Command) return 0;
|
||||
return (uint8_t)ReqMsg().code;
|
||||
}
|
||||
|
||||
void ProtocolLogic::DecrementRetryAttempts() {
|
||||
if (inAutoRetry && retryAttempts) {
|
||||
SERIAL_ECHOLNPGM("DecrementRetryAttempts");
|
||||
retryAttempts--;
|
||||
}
|
||||
}
|
||||
|
||||
void ProtocolLogic::ResetRetryAttempts() {
|
||||
SERIAL_ECHOLNPGM("ResetRetryAttempts");
|
||||
retryAttempts = MMU2_MAX_RETRIES;
|
||||
}
|
||||
|
||||
void __attribute__((noinline)) ProtocolLogic::ResetCommunicationTimeoutAttempts() {
|
||||
SERIAL_ECHOLNPGM("RSTCommTimeout");
|
||||
dataTO.reset();
|
||||
}
|
||||
|
||||
bool DropOutFilter::Record(StepStatus ss) {
|
||||
if (occurrences == maxOccurrences) cause = ss;
|
||||
--occurrences;
|
||||
return occurrences == 0;
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,397 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_protocol_logic.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "mmu_hw/registers.h"
|
||||
#include "mmu2_protocol.h"
|
||||
|
||||
// #include <array> std array is not available on AVR ... we need to "fake" it
|
||||
namespace std {
|
||||
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; }
|
||||
static constexpr uint8_t size() { return N; }
|
||||
inline T &operator[](uint8_t i) { return data[i]; }
|
||||
};
|
||||
} // std
|
||||
|
||||
#else // !__AVR__
|
||||
|
||||
#include <array>
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
|
||||
// Prevent ARM HAL macros from breaking our code
|
||||
#undef CRC
|
||||
#include "mmu2_protocol.h"
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "registers.h"
|
||||
|
||||
#endif // !__AVR__
|
||||
|
||||
// New MMU3 protocol logic
|
||||
namespace MMU3 {
|
||||
|
||||
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, //!< Scope finished successfully
|
||||
Interrupted, //!< Received "Finished" message related to a different command than originally issued (most likely the MMU restarted while doing something)
|
||||
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
|
||||
PrinterError, //!< Printer's explicit error - MMU is fine, but the printer was unable to complete the requested operation
|
||||
CommunicationRecovered,
|
||||
ButtonPushed //!< The MMU reported the user pushed one of its three buttons.
|
||||
};
|
||||
|
||||
/*inline*/ constexpr uint32_t linkLayerTimeout = 2000; //!< Default link layer communication timeout
|
||||
/*inline*/ constexpr uint32_t dataLayerTimeout = linkLayerTimeout * 3; //!< Data layer communication timeout
|
||||
/*inline*/ 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 {
|
||||
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;
|
||||
|
||||
// @return true if the error should be reported to higher levels (max. number of consecutive occurrences reached)
|
||||
bool Record(StepStatus ss);
|
||||
|
||||
// @return 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; }
|
||||
|
||||
private:
|
||||
StepStatus cause;
|
||||
uint8_t occurrences = maxOccurrences;
|
||||
};
|
||||
|
||||
// Logic layer of the MMU vs. printer communication protocol
|
||||
class ProtocolLogic {
|
||||
public:
|
||||
ProtocolLogic(uint8_t extraLoadDistance, uint8_t pulleySlowFeedrate);
|
||||
|
||||
// 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(uint8_t mode=0);
|
||||
void button(uint8_t index);
|
||||
void home(uint8_t mode);
|
||||
void readRegister(uint8_t address);
|
||||
void writeRegister(uint8_t address, uint16_t data);
|
||||
|
||||
// Set the extra load distance to be reported to the MMU.
|
||||
// Beware - this call doesn't send anything to the MMU.
|
||||
// The MMU gets the newly set value either by a communication restart or via an explicit writeRegister call
|
||||
inline void PlanExtraLoadDistance(uint8_t eld_mm) { initRegs8[0] = eld_mm; }
|
||||
// @return the currently preset extra load distance
|
||||
inline uint8_t ExtraLoadDistance() const { return initRegs8[0]; }
|
||||
|
||||
// Sets the Pulley slow feed rate to be reported to the MMU.
|
||||
// Beware - this call doesn't send anything to the MMU.
|
||||
// The MMU gets the newly set value either by a communication restart or via an explicit writeRegister call
|
||||
inline void PlanPulleySlowFeedRate(uint8_t psfr) {
|
||||
initRegs8[1] = psfr;
|
||||
}
|
||||
// @return the currently preset Pulley slow feed rate
|
||||
inline uint8_t PulleySlowFeedRate() const {
|
||||
return initRegs8[1]; // even though MMU register 0x14 is 16bit, reasonable speeds are way below 255mm/s - saving space ;)
|
||||
}
|
||||
|
||||
// Step the state machine
|
||||
StepStatus Step();
|
||||
|
||||
// @return the current/latest error code as reported by the MMU
|
||||
ErrorCode Error() const { return errorCode; }
|
||||
|
||||
// @return the current/latest process code as reported by the MMU
|
||||
ProgressCode Progress() const { return progressCode; }
|
||||
|
||||
// @return 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 regs8[0]; }
|
||||
|
||||
inline uint16_t FailStatistics() const { return regs16[0]; }
|
||||
|
||||
inline uint8_t mmuFwVersionMajor() const { return mmuFwVersion[0]; }
|
||||
inline uint8_t mmuFwVersionMinor() const { return mmuFwVersion[1]; }
|
||||
inline uint8_t mmuFwVersionRevision() const { return mmuFwVersion[2]; }
|
||||
|
||||
// Current number of retry attempts left
|
||||
constexpr uint8_t RetryAttempts() const { return retryAttempts; }
|
||||
|
||||
// Decrement the retry attempts, if in a retry.
|
||||
// Called by the MMU protocol when a sent button is acknowledged.
|
||||
void DecrementRetryAttempts();
|
||||
|
||||
// Reset the retryAttempts back to the default value
|
||||
void ResetRetryAttempts();
|
||||
|
||||
void ResetCommunicationTimeoutAttempts();
|
||||
|
||||
constexpr bool InAutoRetry() const { return inAutoRetry; }
|
||||
inline void SetInAutoRetry(const bool iar) { inAutoRetry = iar; }
|
||||
|
||||
inline void SetPrinterError(const ErrorCode ec) { explicitPrinterError = ec; }
|
||||
inline void clearPrinterError() { explicitPrinterError = ErrorCode::OK; }
|
||||
inline bool IsPrinterError() const { return explicitPrinterError != ErrorCode::OK; }
|
||||
inline ErrorCode PrinterError() const { return explicitPrinterError; }
|
||||
|
||||
#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();
|
||||
|
||||
ErrorCode explicitPrinterError;
|
||||
|
||||
enum class State : uint_fast8_t {
|
||||
Stopped, //!< stopped for whatever reason
|
||||
InitSequence, //!< initial sequence running
|
||||
Running //!< normal operation - Idle + Command processing
|
||||
};
|
||||
|
||||
enum class Scope : uint_fast8_t {
|
||||
Stopped,
|
||||
StartSeq,
|
||||
DelayedRestart,
|
||||
Idle,
|
||||
Command
|
||||
};
|
||||
Scope currentScope;
|
||||
|
||||
// basic scope members
|
||||
// @return 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,
|
||||
Reading8bitRegisters,
|
||||
Reading16bitRegisters,
|
||||
WritingInitRegisters,
|
||||
ButtonSent,
|
||||
ReadRegisterSent, // standalone requests for reading registers - from higher layers
|
||||
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
|
||||
|
||||
// @return 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);
|
||||
|
||||
// @return 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 should 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 StartReading8bitRegisters();
|
||||
void ProcessRead8bitRegister();
|
||||
void StartReading16bitRegisters();
|
||||
ScopeState ProcessRead16bitRegister(ProtocolLogic::ScopeState stateAtEnd);
|
||||
void StartWritingInitRegisters();
|
||||
// @return true when all registers have been written into the MMU
|
||||
bool ProcessWritingInitRegister();
|
||||
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(const 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
|
||||
|
||||
std::array<uint8_t, 16> lastReceivedBytes; //!< remembers the last few bytes of incoming communication for diagnostic purposes
|
||||
uint8_t lrb;
|
||||
|
||||
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
|
||||
|
||||
#ifndef __AVR__
|
||||
uint8_t txbuff[Protocol::MaxRequestSize()]; //!< In Buddy FW - a static transmit buffer needs to exist as DMA cannot be used from CCMRAM.
|
||||
//!< On MK3/S/+ the transmit buffer is allocated on the stack without restrictions
|
||||
#endif
|
||||
|
||||
// 8bit registers
|
||||
static constexpr uint8_t regs8Count = 3;
|
||||
static_assert(regs8Count > 0); // code is not ready for empty lists of registers
|
||||
static const Register regs8Addrs[regs8Count] PROGMEM;
|
||||
uint8_t regs8[regs8Count] = { 0, 0, 0 };
|
||||
|
||||
// 16bit registers
|
||||
static constexpr uint8_t regs16Count = 2;
|
||||
static_assert(regs16Count > 0); // code is not ready for empty lists of registers
|
||||
static const Register regs16Addrs[regs16Count] PROGMEM;
|
||||
uint16_t regs16[regs16Count] = { 0, 0 };
|
||||
|
||||
// 8bit init values to be sent to the MMU after line up
|
||||
static constexpr uint8_t initRegs8Count = 2;
|
||||
static_assert(initRegs8Count > 0); // code is not ready for empty lists of registers
|
||||
static const Register initRegs8Addrs[initRegs8Count] PROGMEM;
|
||||
uint8_t initRegs8[initRegs8Count];
|
||||
|
||||
uint8_t regIndex;
|
||||
|
||||
uint8_t mmuFwVersion[3] = { 0, 0, 0 };
|
||||
uint16_t mmuFwVersionBuild;
|
||||
|
||||
uint8_t retryAttempts;
|
||||
bool inAutoRetry;
|
||||
|
||||
friend class MMU3;
|
||||
};
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,704 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* mmu2_reporting.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfig.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2.h"
|
||||
#include "mmu2_log.h"
|
||||
#include "mmu2_fsensor.h"
|
||||
#include "mmu2_reporting.h"
|
||||
#include "mmu2_error_converter.h"
|
||||
#include "mmu2_marlin_macros.h"
|
||||
#include "mmu2_progress_converter.h"
|
||||
#include "mmu_hw/buttons.h"
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
#include "ultralcd.h"
|
||||
#include "sound.h"
|
||||
|
||||
#include "../../core/language.h"
|
||||
#include "../../gcode/gcode.h"
|
||||
#include "../../feature/host_actions.h"
|
||||
#include "../../lcd/marlinui.h"
|
||||
#include "../../lcd/menu/menu.h"
|
||||
#include "../../lcd/menu/menu_item.h"
|
||||
#include "../../module/temperature.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
OperationStatistics operation_statistics;
|
||||
|
||||
uint16_t OperationStatistics::fail_total_num; // total failures
|
||||
uint8_t OperationStatistics::fail_num; // fails during print
|
||||
uint16_t OperationStatistics::load_fail_total_num; // total load failures
|
||||
uint8_t OperationStatistics::load_fail_num; // load failures during print
|
||||
uint16_t OperationStatistics::tool_change_counter; // number of tool changes per print
|
||||
uint32_t OperationStatistics::tool_change_total_counter; // number of total tool changes
|
||||
int OperationStatistics::fail_total_num_addr; // total failures EEPROM addr
|
||||
int OperationStatistics::fail_num_addr; // fails during print EEPROM addr
|
||||
int OperationStatistics::load_fail_total_num_addr; // total load failures EEPROM addr
|
||||
int OperationStatistics::load_fail_num_addr; // load failures during print EEPROM addr
|
||||
int OperationStatistics::tool_change_counter_addr; // number of total tool changes EEPROM addr
|
||||
int OperationStatistics::tool_change_total_counter_addr; // number of total tool changes EEPROM addr
|
||||
|
||||
/**
|
||||
* Increment both the total load fails and Per print job load fails.
|
||||
*/
|
||||
void OperationStatistics::increment_load_fails() {
|
||||
load_fail_num += 1;
|
||||
load_fail_total_num += 1;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// save load_fail_num to eeprom
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
|
||||
// save load_fail_total_num to eeprom
|
||||
persistentStore.write_data(load_fail_total_num_addr, load_fail_total_num);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment both the total fails and the per print job fails.
|
||||
*/
|
||||
void OperationStatistics::increment_mmu_fails() {
|
||||
fail_num += 1;
|
||||
fail_total_num += 1;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// save fail_num to eeprom
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
// save fail_total_num to eeprom
|
||||
persistentStore.write_data(fail_total_num_addr, fail_total_num);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment tool change counter
|
||||
*/
|
||||
void OperationStatistics::increment_tool_change_counter() {
|
||||
tool_change_counter += 1;
|
||||
tool_change_total_counter += 1;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// save tool_change_total_counter to eeprom
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(tool_change_total_counter_addr, tool_change_total_counter);
|
||||
persistentStore.access_finish();
|
||||
settings.save();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset only per print operation statistics and update EEPROM.
|
||||
*
|
||||
* @return true if everything went okay, false otherwise.
|
||||
*/
|
||||
bool OperationStatistics::reset_per_print_stats() {
|
||||
// Update data
|
||||
load_fail_num = 0;
|
||||
fail_num = 0;
|
||||
tool_change_counter = 0;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// Update EEPROM
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
persistentStore.write_data(tool_change_counter_addr, tool_change_counter);
|
||||
persistentStore.access_finish();
|
||||
return settings.save();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset fail statistics and update EEPROM.
|
||||
*
|
||||
* This will keep the tool change counter change counters and delete anything
|
||||
* else.
|
||||
*
|
||||
* @return true if everything went okay, false otherwise.
|
||||
*/
|
||||
bool OperationStatistics::reset_fail_stats() {
|
||||
// Update data
|
||||
load_fail_num = 0;
|
||||
load_fail_total_num = 0;
|
||||
fail_num = 0;
|
||||
fail_total_num = 0;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// Update EEPROM
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
persistentStore.write_data(load_fail_total_num_addr, load_fail_total_num);
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
persistentStore.write_data(fail_total_num_addr, fail_total_num);
|
||||
persistentStore.access_finish();
|
||||
return settings.save();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Reset all operation statistics and update EEPROM.
|
||||
*
|
||||
* @return true if everything went okay, false otherwise.
|
||||
*/
|
||||
bool OperationStatistics::reset_stats() {
|
||||
// Update data
|
||||
load_fail_num = 0;
|
||||
load_fail_total_num = 0;
|
||||
fail_num = 0;
|
||||
fail_total_num = 0;
|
||||
tool_change_counter = 0;
|
||||
tool_change_total_counter = 0;
|
||||
|
||||
#if ENABLED(EEPROM_SETTINGS)
|
||||
// Update EEPROM
|
||||
persistentStore.access_start();
|
||||
persistentStore.write_data(load_fail_num_addr, load_fail_num);
|
||||
persistentStore.write_data(load_fail_total_num_addr, load_fail_total_num);
|
||||
persistentStore.write_data(fail_num_addr, fail_num);
|
||||
persistentStore.write_data(fail_total_num_addr, fail_total_num);
|
||||
persistentStore.write_data(tool_change_counter_addr, tool_change_counter);
|
||||
persistentStore.write_data(tool_change_total_counter_addr, tool_change_total_counter);
|
||||
persistentStore.access_finish();
|
||||
return settings.save();
|
||||
#else
|
||||
return true;
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
void BeginReport(CommandInProgress /*cip*/, ProgressCode ec) {
|
||||
// custom_message_type = CustomMsg::MMUProgress;
|
||||
ui.set_status(ProgressCodeToText(ec));
|
||||
}
|
||||
|
||||
void EndReport(CommandInProgress /*cip*/, ProgressCode /*ec*/) {
|
||||
// clear the status msg line - let the printed filename get visible again
|
||||
if (!printJobOngoing()) ui.reset_status();
|
||||
//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) {
|
||||
#if HAS_WIRED_LCD
|
||||
// beware - this optimization abuses the fact, that findaDetectsFilament returns 0 or 1 and '0' is followed by '1' in the ASCII table
|
||||
lcd_put_int(3, LCD_HEIGHT - 1, mmu3.findaDetectsFilament() + '0');
|
||||
lcd_put_int(8, LCD_HEIGHT - 1, FILAMENT_PRESENT() + '0');
|
||||
|
||||
// print active/changing filament slot
|
||||
lcd_moveto(10, LCD_HEIGHT - 1);
|
||||
lcdui_print_extruder();
|
||||
|
||||
// Print active extruder temperature
|
||||
lcd_put_int(16, LCD_HEIGHT - 1, (int)(thermalManager.degHotend(0) + 0.5));
|
||||
#endif
|
||||
}
|
||||
|
||||
static bool drawing_more_info_screen = false;
|
||||
static bool msg_next_is_consumed = false;
|
||||
static FSTR_P msg_next = nullptr;
|
||||
|
||||
/**
|
||||
* Display more info about the error. If the error message doesn't fit into
|
||||
* the screen, clicking the LCD button will go to the next screen to display
|
||||
* the rest of the message, until no messages left to display and a final
|
||||
* click will return to the previous screen.
|
||||
*
|
||||
* This gets the message data from the "editable.uint8" which is set in the
|
||||
* action item.
|
||||
*/
|
||||
void show_more_info_screen() {
|
||||
#if HAS_WIRED_LCD
|
||||
if (drawing_more_info_screen) return;
|
||||
drawing_more_info_screen = true;
|
||||
FSTR_P fmsg = PrusaErrorDesc(editable.uint8);
|
||||
if (ui.use_click()) {
|
||||
if (msg_next_is_consumed) {
|
||||
msg_next_is_consumed = false;
|
||||
drawing_more_info_screen = false;
|
||||
msg_next = nullptr;
|
||||
// Prevent this function being triggered again...
|
||||
SetButtonResponse(ButtonOperations::NoOperation);
|
||||
return ui.go_back();
|
||||
}
|
||||
fmsg = msg_next;
|
||||
}
|
||||
else if (msg_next_is_consumed) {
|
||||
fmsg = msg_next;
|
||||
}
|
||||
|
||||
FSTR_P const msg_next_int = lcd_display_message_fullscreen(fmsg);
|
||||
msg_next_is_consumed = strlen_P(FTOP(msg_next_int)) == 0;
|
||||
if (!msg_next_is_consumed) msg_next = msg_next_int;
|
||||
// Set the button response to MoreInfo so we keep coming back to this screen until all messages are consumed
|
||||
SetButtonResponse(ButtonOperations::MoreInfo);
|
||||
#else
|
||||
// no lcd, no error display... just break the loop...
|
||||
msg_next_is_consumed = false;
|
||||
msg_next = nullptr;
|
||||
SetButtonResponse(ButtonOperations::NoOperation);
|
||||
#endif // HAS_WIRED_LCD
|
||||
drawing_more_info_screen = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
#if HAS_WIRED_LCD
|
||||
//! 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
|
||||
//! |prusa.io/04504 | <- URL max 20 characters
|
||||
//! |FI:1 FS:1 5>3 t201°| <- status line, t is thermometer symbol
|
||||
//! |>Retry >Done >W| <- buttons
|
||||
bool two_choices = false;
|
||||
|
||||
// Read and determine what operations should be shown on the menu
|
||||
const uint8_t button_operation = PrusaErrorButtons(ei),
|
||||
button_op_right = BUTTON_OP_RIGHT(button_operation),
|
||||
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;
|
||||
}
|
||||
|
||||
START_MENU();
|
||||
#ifndef __AVR__
|
||||
// TODO: I couldn't make this work on AVR
|
||||
STATIC_ITEM_F(PrusaErrorTitle(ei), SS_DEFAULT | SS_INVERT);
|
||||
|
||||
// Write the help page and error code
|
||||
MString<LCD_WIDTH> url("");
|
||||
url.appendf("prusa.io/04%hu", PrusaErrorCode(ei));
|
||||
STATIC_ITEM_F(nullptr, SS_DEFAULT, url.buffer());
|
||||
|
||||
//ReportErrorHookSensorLineRender();
|
||||
|
||||
editable.uint8 = button_op_middle;
|
||||
ACTION_ITEM_F(
|
||||
PrusaErrorButtonTitle(button_op_middle),
|
||||
[]{ SetButtonResponse((ButtonOperations)editable.uint8); }
|
||||
);
|
||||
|
||||
if (!two_choices) {
|
||||
editable.uint8 = button_op_right;
|
||||
ACTION_ITEM_F(
|
||||
PrusaErrorButtonTitle(button_op_right),
|
||||
[]{ SetButtonResponse((ButtonOperations)editable.uint8); }
|
||||
);
|
||||
}
|
||||
|
||||
// Add a More Info option
|
||||
editable.uint8 = ei;
|
||||
ACTION_ITEM_F(
|
||||
GET_TEXT_F(MSG_BTN_MORE),
|
||||
[]{
|
||||
// only when the menu item is used push the current screen back
|
||||
ui.push_current_screen();
|
||||
msg_next_is_consumed = false;
|
||||
msg_next = nullptr;
|
||||
SetButtonResponse(ButtonOperations::MoreInfo);
|
||||
}
|
||||
);
|
||||
|
||||
#endif // !__AVR__
|
||||
|
||||
// Render the choices
|
||||
//if (two_choices) {
|
||||
// lcd_show_choices_prompt_P(
|
||||
// LCD_LEFT_BUTTON_CHOICE,
|
||||
// PrusaErrorButtonTitle(button_op_middle),
|
||||
// GET_TEXT(MSG_BTN_MORE),
|
||||
// 18, nullptr
|
||||
// );
|
||||
//}
|
||||
//else {
|
||||
// lcd_show_choices_prompt_P(LCD_MIDDLE_BUTTON_CHOICE,
|
||||
// PrusaErrorButtonTitle(button_op_middle),
|
||||
// PrusaErrorButtonTitle(button_op_right),
|
||||
// 9, GET_TEXT(MSG_BTN_MORE)
|
||||
// );
|
||||
//}
|
||||
|
||||
END_MENU();
|
||||
//ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
|
||||
#endif // HAS_WIRED_LCD
|
||||
}
|
||||
|
||||
void ReportErrorHookSensorLineRender() {
|
||||
#if HAS_WIRED_LCD
|
||||
// Render static characters in third line
|
||||
lcd_put_u8str(
|
||||
0,
|
||||
LCD_HEIGHT - 1,
|
||||
F("FI: FS: > " LCD_STR_THERMOMETER " " LCD_STR_DEGREE)
|
||||
);
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
if (GetButtonResponse() == ButtonOperations::MoreInfo) {
|
||||
SetButtonResponse(ButtonOperations::NoOperation);
|
||||
ret = 1;
|
||||
}
|
||||
else if (GetButtonResponse() != ButtonOperations::NoOperation) {
|
||||
ret = 2;
|
||||
}
|
||||
// Next MMU error screen should reset the choice selection
|
||||
// reset_button_selection = 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;
|
||||
|
||||
// Helper variable to monitor knob in MMU error screen in blocking functions e.g. manage_response
|
||||
static bool is_mmu_error_monitor_active;
|
||||
|
||||
// Helper variable to stop rendering the error screen when the firmware is rendering complementary
|
||||
// UI to resolve the error screen, for example tuning Idler Stallguard Threshold
|
||||
// Set to false to allow the error screen to render again.
|
||||
static bool putErrorScreenToSleep;
|
||||
|
||||
void CheckErrorScreenUserInput() {
|
||||
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((CommandInProgress)mmu3.getCommandInProgress(), mmu3.getLastErrorCode(), mmu3.mmuLastErrorSource());
|
||||
}
|
||||
}
|
||||
|
||||
bool TuneMenuEntered() {
|
||||
return putErrorScreenToSleep;
|
||||
}
|
||||
|
||||
void ReportErrorHook(CommandInProgress /*cip*/, ErrorCode ec, uint8_t /*es*/) {
|
||||
if (putErrorScreenToSleep) return;
|
||||
|
||||
if (mmu3.mmuCurrentErrorCode() == ErrorCode::OK && mmu3.mmuLastErrorSource() == MMU3::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;
|
||||
drawing_more_info_screen = false;
|
||||
msg_next_is_consumed = true;
|
||||
}
|
||||
|
||||
const uint8_t ei = PrusaErrorCodeIndex((ErrorCode)ec);
|
||||
|
||||
// This should be the equivelent of the switch..case above...
|
||||
if ((uint8_t)ReportErrorHookState == (uint8_t)ReportErrorHookStates::RENDER_ERROR_SCREEN) {
|
||||
KEEPALIVE_STATE(PAUSED_FOR_USER);
|
||||
#if HAS_WIRED_LCD
|
||||
drawing_more_info_screen = false;
|
||||
msg_next_is_consumed = false;
|
||||
msg_next = nullptr;
|
||||
editable.uint8 = ei;
|
||||
ui.defer_status_screen();
|
||||
ui.goto_screen([]{ ReportErrorHookStaticRender(editable.uint8); });
|
||||
#endif
|
||||
ReportErrorHookState = ReportErrorHookStates::MONITOR_SELECTION;
|
||||
}
|
||||
|
||||
if ((uint8_t)ReportErrorHookState == (uint8_t)ReportErrorHookStates::MONITOR_SELECTION) {
|
||||
is_mmu_error_monitor_active = true;
|
||||
// ReportErrorHookDynamicRender(); // Render dynamic characters
|
||||
sound_wait_for_user();
|
||||
uint8_t result = ReportErrorHookMonitor(ei);
|
||||
if (result == 0) {
|
||||
// No choice selected, return to loop()
|
||||
}
|
||||
else if (result == 1) {
|
||||
// More Info button selected, change state
|
||||
editable.uint8 = ei;
|
||||
//ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
|
||||
ui.goto_screen(show_more_info_screen);
|
||||
ReportErrorHookState = ReportErrorHookStates::MONITOR_SELECTION;
|
||||
}
|
||||
else if (result == 2) {
|
||||
// Exit error screen and enable lcd updates
|
||||
TERN_(HAS_WIRED_LCD, ui.return_to_status());
|
||||
sound_wait_for_user_reset();
|
||||
// Reset the state in case a new error is reported
|
||||
is_mmu_error_monitor_active = false;
|
||||
KEEPALIVE_STATE(IN_HANDLER);
|
||||
ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
}
|
||||
return; // Always return to loop() to let MMU trigger a call to ReportErrorHook again
|
||||
}
|
||||
else if ((uint8_t)ReportErrorHookState == (uint8_t)ReportErrorHookStates::DISMISS_ERROR_SCREEN) {
|
||||
TERN_(HAS_WIRED_LCD, ui.return_to_status());
|
||||
sound_wait_for_user_reset();
|
||||
// Reset the state in case a new error is reported
|
||||
is_mmu_error_monitor_active = false;
|
||||
KEEPALIVE_STATE(IN_HANDLER);
|
||||
ReportErrorHookState = ReportErrorHookStates::RENDER_ERROR_SCREEN;
|
||||
}
|
||||
}
|
||||
|
||||
void ReportProgressHook(CommandInProgress cip, ProgressCode ec) {
|
||||
if (cip != CommandInProgress::NoCommand) {
|
||||
// custom_message_type = CustomMsg::MMUProgress;
|
||||
ui.set_status(ProgressCodeToText(ec));
|
||||
}
|
||||
}
|
||||
|
||||
TryLoadUnloadReporter::TryLoadUnloadReporter(float delta_mm)
|
||||
: dpixel0(0), dpixel1(0), lcd_cursor_col(0)
|
||||
, pixel_per_mm(0.5F * float(LCD_WIDTH) / (delta_mm)
|
||||
) {
|
||||
//lcd_clearstatus();
|
||||
ui.reset_status();
|
||||
}
|
||||
|
||||
TryLoadUnloadReporter::~TryLoadUnloadReporter() {
|
||||
#if HAS_WIRED_LCD
|
||||
// Delay the next status message just so
|
||||
// the user can see the results clearly
|
||||
ui.set_status_no_expire(ui.status_message);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TryLoadUnloadReporter::Render(uint8_t col, bool sensorState) {
|
||||
#if HAS_WIRED_LCD
|
||||
// Set the cursor position each time in case some other
|
||||
// part of the firmware changes the cursor position
|
||||
lcd_insert_char_into_status(col, sensorState ? LCD_STR_SOLID_BLOCK[0] : '-');
|
||||
if (ui.lcdDrawUpdate == LCDViewAction::LCDVIEW_NONE)
|
||||
ui.draw_status_message(false);
|
||||
#endif
|
||||
}
|
||||
|
||||
void TryLoadUnloadReporter::Progress(bool sensorState) {
|
||||
// Always round up, you can only have 'whole' pixels. (floor is also an option)
|
||||
dpixel1 = ceil((stepper_get_machine_position_E_mm() - planner_get_current_position_E()) * pixel_per_mm);
|
||||
if (dpixel1 - dpixel0) {
|
||||
dpixel0 = dpixel1;
|
||||
if (lcd_cursor_col > (LCD_WIDTH - 1)) lcd_cursor_col = LCD_WIDTH - 1;
|
||||
Render(lcd_cursor_col++, sensorState);
|
||||
}
|
||||
}
|
||||
|
||||
void TryLoadUnloadReporter::DumpToSerial() {
|
||||
char buf[LCD_WIDTH + 1];
|
||||
TERN_(HAS_WIRED_LCD, ui.status_message.copyto(buf));
|
||||
for (uint8_t i = 0; i < sizeof(buf); i++) {
|
||||
// 0xFF is -1 when converting from unsigned to signed char
|
||||
// If the number is negative, that means filament is present
|
||||
buf[i] = (buf[i] < 0) ? '1' : '0';
|
||||
}
|
||||
buf[LCD_WIDTH] = 0;
|
||||
MMU2_ECHO_MSGLN(buf);
|
||||
}
|
||||
|
||||
void IncrementLoadFails() {
|
||||
operation_statistics.increment_load_fails();
|
||||
}
|
||||
|
||||
void IncrementMMUFails() {
|
||||
operation_statistics.increment_mmu_fails();
|
||||
}
|
||||
|
||||
bool cutter_enabled() {
|
||||
return mmu3.cutter_mode > 0;
|
||||
}
|
||||
|
||||
void MakeSound(SoundType s) {
|
||||
Sound_MakeSound((eSOUND_TYPE)s);
|
||||
}
|
||||
|
||||
static void fullScreenMsg(FSTR_P const fstr, uint8_t slot) {
|
||||
#if HAS_WIRED_LCD
|
||||
ui.clear_lcd();
|
||||
#ifndef __AVR__
|
||||
SETCURSOR(0, 1);
|
||||
lcd_put_u8str(fstr);
|
||||
lcd_put_lchar(' ');
|
||||
lcd_put_int(slot + 1);
|
||||
#else
|
||||
UNUSED(fstr);
|
||||
#endif
|
||||
ui.refresh(LCDVIEW_CALL_REDRAW_NEXT);
|
||||
ui.screen_changed = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void fullScreenMsgCut(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_CUT_FILAMENT), slot); }
|
||||
void fullScreenMsgEject(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_EJECT_FROM_MMU), slot); }
|
||||
void fullScreenMsgTest(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_TESTING_FILAMENT), slot); }
|
||||
void fullScreenMsgLoad(uint8_t slot) { fullScreenMsg(GET_TEXT_F(MSG_LOADING_FILAMENT), slot); }
|
||||
|
||||
void fullScreenMsgRestoringTemperature() {
|
||||
#if HAS_WIRED_LCD
|
||||
lcd_display_message_fullscreen(F("MMU Retry: Restoring temperature..."));
|
||||
#endif
|
||||
}
|
||||
|
||||
void ScreenUpdateEnable() {
|
||||
TERN_(HAS_WIRED_LCD, ui.refresh(LCDVIEW_CALL_REDRAW_NEXT));
|
||||
}
|
||||
|
||||
void ScreenClear() { ui.clear_lcd(); }
|
||||
|
||||
struct TuneItem {
|
||||
uint8_t address;
|
||||
uint8_t minValue;
|
||||
uint8_t maxValue;
|
||||
}
|
||||
__attribute__((packed));
|
||||
|
||||
static const TuneItem TuneItems[] PROGMEM = {
|
||||
{ (uint8_t)Register::Selector_sg_thrs_R, 1, 4 },
|
||||
{ (uint8_t)Register::Idler_sg_thrs_R, 2, 10 },
|
||||
};
|
||||
|
||||
static_assert(COUNT(TuneItems) == 2);
|
||||
|
||||
typedef struct {
|
||||
// Variables used when editing values.
|
||||
const char* editLabel;
|
||||
uint8_t editValueBits; // 8 or 16
|
||||
void* editValuePtr;
|
||||
int16_t currentValue;
|
||||
int16_t minEditValue;
|
||||
int16_t maxEditValue;
|
||||
int16_t minJumpValue;
|
||||
} menu_data_edit_t;
|
||||
|
||||
struct _menu_tune_data_t {
|
||||
menu_data_edit_t reserved; // 13 bytes reserved for number editing functions
|
||||
int8_t status; // 1 byte
|
||||
uint8_t currentValue; // 1 byte
|
||||
TuneItem item; // 3 bytes
|
||||
};
|
||||
|
||||
//static_assert(sizeof(_menu_tune_data_t) == 18);
|
||||
//static_assert(sizeof(menu_data)>= sizeof(_menu_tune_data_t), "_menu_tune_data_t doesn't fit into menu_data");
|
||||
|
||||
void tuneIdlerStallguardThresholdMenu() {
|
||||
// const uint8_t menu_data[32] = "Set Stallguard Threshold";
|
||||
// //static constexpr _menu_tune_data_t * const _md = (_menu_tune_data_t*)&(menu_data[0]);
|
||||
// static constexpr _menu_tune_data_t * const _md = (_menu_tune_data_t*)&(menu_data[0]);
|
||||
|
||||
// // Do not timeout the screen, otherwise there will be FW crash (menu recursion)
|
||||
// //lcd_timeoutToStatus.stop();
|
||||
//if (_md->status == 0) {
|
||||
// _md->status = 1; // Menu entered for the first time
|
||||
|
||||
// // Fetch the TuneItem from PROGMEM
|
||||
// const uint8_t offset = (mmu3.mmuCurrentErrorCode() == ErrorCode::HOMING_IDLER_FAILED) ? 1 : 0;
|
||||
// memcpy_P(&(_md->item), &TuneItems[offset], sizeof(TuneItem));
|
||||
|
||||
// // Fetch the value which is currently in MMU EEPROM
|
||||
// mmu3.readRegister(_md->item.address);
|
||||
// _md->currentValue = mmu3.getLastReadRegisterValue();
|
||||
//}
|
||||
|
||||
// //MENU_BEGIN();
|
||||
// //ON_MENU_LEAVE(
|
||||
// // mmu3.writeRegister(_md->item.address, (uint16_t)_md->currentValue);
|
||||
// // putErrorScreenToSleep = false;
|
||||
// // lcd_return_to_status();
|
||||
// // return;
|
||||
// //);
|
||||
// //MENU_ITEM_BACK(MSG_DONE);
|
||||
// //MENU_ITEM_EDIT_int3_P(
|
||||
// // _i("Sensitivity"), ////MSG_MMU_SENSITIVITY c=18
|
||||
// // &_md->currentValue,
|
||||
// // _md->item.minValue,
|
||||
// // _md->item.maxValue
|
||||
// //);
|
||||
// //MENU_END();
|
||||
|
||||
//START_MENU();
|
||||
//BACK_ITEM(MSG_BACK);
|
||||
//EDIT_ITEM(
|
||||
// int8,
|
||||
// MSG_MMU_SENSITIVITY,
|
||||
// &_md->currentValue,
|
||||
// _md->item.minValue,
|
||||
// _md->item.maxValue,
|
||||
// []{
|
||||
// write_register_and_return_to_status_menu(_md->item.address, _md->currentValue);
|
||||
// }
|
||||
// );
|
||||
//END_MENU();
|
||||
}
|
||||
|
||||
void write_register_and_return_to_status_menu(uint8_t address, uint8_t value) {
|
||||
mmu3.writeRegister(address, (uint16_t)value);
|
||||
putErrorScreenToSleep = false;
|
||||
ui.return_to_status();
|
||||
}
|
||||
|
||||
void tuneIdlerStallguardThreshold() {
|
||||
putErrorScreenToSleep = true;
|
||||
//menu_submenu(tuneIdlerStallguardThresholdMenu);
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_reporting.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include "mmu_hw/error_codes.h"
|
||||
#include "mmu_hw/progress_codes.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
enum CommandInProgress : uint8_t {
|
||||
NoCommand = 0,
|
||||
CutFilament = 'K',
|
||||
EjectFilament = 'E',
|
||||
Homing = 'H',
|
||||
LoadFilament = 'L',
|
||||
Reset = 'X',
|
||||
ToolChange = 'T',
|
||||
UnloadFilament = 'U',
|
||||
};
|
||||
|
||||
/**
|
||||
* Data class for MMU operation statistics.
|
||||
*
|
||||
* This is used to load/save data from/to EEPROM.
|
||||
* The data is initialized by the settings.load() method.
|
||||
*/
|
||||
class OperationStatistics {
|
||||
public:
|
||||
void increment_load_fails();
|
||||
void increment_mmu_fails();
|
||||
void increment_tool_change_counter();
|
||||
bool reset_per_print_stats(); // Reset only the per print stats.
|
||||
bool reset_fail_stats(); // Reset only fail stats and keep tool change counters
|
||||
bool reset_stats(); // Reset MMU stats and update EEPROM
|
||||
|
||||
static uint16_t fail_total_num; // total failures
|
||||
static uint8_t fail_num; // fails during print
|
||||
static uint16_t load_fail_total_num; // total load failures
|
||||
static uint8_t load_fail_num; // load failures during print
|
||||
static uint16_t tool_change_counter; // number of tool changes during print
|
||||
static uint32_t tool_change_total_counter; // number of total tool changes
|
||||
static int fail_total_num_addr; // total failures EEPROM addr
|
||||
static int fail_num_addr; // fails during print EEPROM addr
|
||||
static int load_fail_total_num_addr; // total load failures EEPROM addr
|
||||
static int load_fail_num_addr; // load failures during print EEPROM addr
|
||||
static int tool_change_counter_addr; // number of tool changes EEPROM addr
|
||||
static int tool_change_total_counter_addr; // number of total tool changes EEPROM addr
|
||||
};
|
||||
|
||||
extern OperationStatistics operation_statistics;
|
||||
|
||||
// Called at the begin of every MMU operation
|
||||
void BeginReport(CommandInProgress cip, ProgressCode ec);
|
||||
|
||||
// Called at the end of every MMU operation
|
||||
void EndReport(CommandInProgress cip, ProgressCode ec);
|
||||
|
||||
// Checks for error screen user input, if the error screen is open
|
||||
void CheckErrorScreenUserInput();
|
||||
|
||||
// Return true if the error screen is sleeping in the background
|
||||
// Error screen sleeps when the firmware is rendering complementary
|
||||
// UI to resolve the error screen, for example tuning Idler Stallguard Threshold
|
||||
bool TuneMenuEntered();
|
||||
|
||||
// @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] es error source
|
||||
void ReportErrorHook(CommandInProgress cip, ErrorCode ec, uint8_t es);
|
||||
|
||||
// Called when the MMU sends operation progress update
|
||||
void ReportProgressHook(CommandInProgress cip, ProgressCode ec);
|
||||
|
||||
struct TryLoadUnloadReporter {
|
||||
TryLoadUnloadReporter(float delta_mm);
|
||||
~TryLoadUnloadReporter();
|
||||
void Progress(bool sensorState);
|
||||
void DumpToSerial();
|
||||
|
||||
private:
|
||||
// @brief Add one block to the progress bar
|
||||
// @param col pixel position on the LCD status line, should range from 0 to (LCD_WIDTH - 1)
|
||||
// @param sensorState if true, filament is not present, else filament is present. This controls which character to render
|
||||
void Render(uint8_t col, bool sensorState);
|
||||
|
||||
uint8_t dpixel0, dpixel1;
|
||||
uint8_t lcd_cursor_col;
|
||||
// The total length is twice delta_mm. Divide that length by number of pixels
|
||||
// available to get length per pixel.
|
||||
// Note: Below is the reciprocal of (2 * delta_mm) / LCD_WIDTH [mm/pixel]
|
||||
float pixel_per_mm;
|
||||
};
|
||||
|
||||
// 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();
|
||||
|
||||
// @return 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();
|
||||
|
||||
// Disable MMU in EEPROM
|
||||
//void DisableMMUInSettings();
|
||||
|
||||
// Increments EEPROM cell - number of failed loads into the nozzle
|
||||
// Note: technically, this is not an MMU error but an error of the printer.
|
||||
void IncrementLoadFails();
|
||||
|
||||
// Increments EEPROM cell - number of MMU errors
|
||||
void IncrementMMUFails();
|
||||
|
||||
// @return true when Cutter is enabled in the menus
|
||||
bool cutter_enabled();
|
||||
|
||||
// Beware: enum values intentionally chosen to match the 8bit FW to save code size
|
||||
enum SoundType {
|
||||
Prompt = 2,
|
||||
Confirm = 3
|
||||
};
|
||||
|
||||
void MakeSound(SoundType s);
|
||||
|
||||
void fullScreenMsgCut(uint8_t slot);
|
||||
void fullScreenMsgEject(uint8_t slot);
|
||||
void fullScreenMsgTest(uint8_t slot);
|
||||
void fullScreenMsgLoad(uint8_t slot);
|
||||
void fullScreenMsgRestoringTemperature();
|
||||
|
||||
void ScreenUpdateEnable();
|
||||
void ScreenClear();
|
||||
|
||||
void tuneIdlerStallguardThreshold();
|
||||
|
||||
void write_register_and_return_to_status_menu(uint8_t address, uint8_t value);
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_state.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU3 {
|
||||
/**
|
||||
* @brief status of mmu
|
||||
*
|
||||
* States of a printer with the MMU:
|
||||
* - Active
|
||||
* - Connecting
|
||||
* - Stopped
|
||||
*
|
||||
* When the printer's FW starts, the MMU 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.
|
||||
};
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* mmu2_supported_version.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#define mmuVersionMajor 3
|
||||
#define mmuVersionMinor 0
|
||||
#define mmuVersionPatch 2
|
||||
@@ -0,0 +1,159 @@
|
||||
# MMU3 Messages
|
||||
|
||||
Starting with the version 2.0.19 of the MMU firmware, requests and responses have a trailing section that contains the CRC8 of the original message. The general structure is as follows:
|
||||
|
||||
```
|
||||
Requests (what Marlin requests):
|
||||
MMU3:>{RequestMsgCode}{Value}*{CRC8}\n
|
||||
|
||||
Responses (what MMU responds with):
|
||||
MMU3:<{RequestMsgCode}{Value} {ResponseMsgParamCode}{paramValue}*{CRC8}\n
|
||||
```
|
||||
|
||||
An example of that would be:
|
||||
|
||||
```
|
||||
MMU3:>S0*c6\n
|
||||
MMU3:<S0 A3*22\n
|
||||
|
||||
MMU3:>S1*ad\n
|
||||
MMU3:<S1 A0*34\n
|
||||
|
||||
MMU3:>S2*10\n
|
||||
MMU3:<S2 A2*65\n
|
||||
```
|
||||
|
||||
This set of responses combines to indicate firmware version 3.0.2.
|
||||
|
||||
## Startup sequence
|
||||
|
||||
When initialized the MMU waits for requests. Marlin repeatedly sends `S0` commands until it gets an answer:
|
||||
```
|
||||
MMU3:>S0*c6\n
|
||||
MMU3:>S0*c6\n
|
||||
MMU3:>S0*c6\n
|
||||
...
|
||||
```
|
||||
|
||||
Once communication is established the MMU responds with:
|
||||
```
|
||||
MMU3:<S0 A3*22\n
|
||||
```
|
||||
|
||||
Then Marlin continues to get the rest of the MMU firmware version.
|
||||
```
|
||||
MMU3:>S1*ad\n
|
||||
MMU3:<S1 A0*34\n
|
||||
MMU3:>S2*10\n
|
||||
MMU3:<S2 A2*65\n
|
||||
```
|
||||
|
||||
Setting the stepper mode to SpreadCycle (M0) or StealthChop (M1):
|
||||
```
|
||||
MMU3:>M1*{CRC8};
|
||||
MMU3:<---nothing---
|
||||
```
|
||||
|
||||
```
|
||||
MMU3:>P0
|
||||
MMU3:<P0 A{FINDA status}*{CRC8}\n
|
||||
```
|
||||
|
||||
At this point we can be sure the MMU is available and ready. If there was a timeout or other communication problem somewhere, the printer will not be killed, but for safety the MMU feature will be disabled.
|
||||
|
||||
- *Firmware version* is an integer value, and we care about it. As there is no other way of knowing which protocol to use.
|
||||
- *FINDA status* is 1 if the filament is loaded to the extruder, 0 otherwise.
|
||||
|
||||
The *Firmware version* is checked against the required value. If it doesn't match the printer will not be halted, but for safety the MMU feature will be disabled.
|
||||
|
||||
## Toolchange
|
||||
|
||||
```
|
||||
MMU3:>T{Filament index}*{CRC8}\n
|
||||
MMU3:<Q0*ea\n
|
||||
```
|
||||
|
||||
The MMU sends:
|
||||
```
|
||||
MMU3:<T{filament index}*P{ProgressCode}{CRC8}\n
|
||||
```
|
||||
|
||||
Which in normal operation would be as follows, let's say that we requested MMU to load `T0``:
|
||||
```
|
||||
MMU3:>T0*{CRC8}\n
|
||||
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<T0*P5{CRC8}\n # P5 => FeedingToFinda
|
||||
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<T0*P7{CRC8}\n # P7 => FeedingToNozzle
|
||||
```
|
||||
|
||||
As soon as the filament is fed down to the extruder we follow with:
|
||||
|
||||
```
|
||||
MMU3:>C0*{CRC8}\n
|
||||
```
|
||||
|
||||
The MMU will feed a few more millimeters of filament for the extruder gears to grab. When done, the MMU sends:
|
||||
```
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<T0*P9{CRC8}\n # P9 => FinishingMoves
|
||||
```
|
||||
|
||||
After the `T0*P9` response we immediately continue with the next G-code which should be one or more extruder moves to feed the filament into the hotend.
|
||||
|
||||
|
||||
## FINDA status
|
||||
```
|
||||
MMU3:>P0*{CRC8}\n
|
||||
```
|
||||
|
||||
If the filament is loaded to the extruder, FINDA status is 1 and the MMU responds with:
|
||||
```
|
||||
MMU3:<P0 A1*{CRC8}\n
|
||||
```
|
||||
|
||||
…otherwise it replies:
|
||||
```
|
||||
MMU3:<P0 A0*7b\n
|
||||
```
|
||||
|
||||
This could be used as a filament runout sensor if polled regularly.
|
||||
|
||||
## Load filament
|
||||
|
||||
To load a filament to the MMU itself, we run:
|
||||
```
|
||||
MMU3:>L{Filament index}*{CRC8}\n
|
||||
MMU3:<L{Filament index} A1*{CRC8}\n
|
||||
```
|
||||
|
||||
…and immediately after that we query the status:
|
||||
```
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
```
|
||||
|
||||
The MMU will respond with status messages:
|
||||
```
|
||||
MMU3:<L0*P5{CRC8}\n
|
||||
```
|
||||
|
||||
The MMU will load the filament and when done:
|
||||
```
|
||||
MMU3:>Q0*{CRC8}\n
|
||||
MMU3:<L0*P9{CRC8}\n
|
||||
```
|
||||
|
||||
## Unload filament
|
||||
|
||||
- `MMU <= 'U0\n'`
|
||||
|
||||
The MMU will retract current filament from the extruder, and when done:
|
||||
|
||||
- `MMU => 'ok\n'`
|
||||
|
||||
## Eject filament
|
||||
|
||||
- `MMU <= 'E*Filament index*\n'`
|
||||
- `MMU => 'ok\n'`
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* buttons.h
|
||||
*/
|
||||
|
||||
#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 MMU3 {
|
||||
|
||||
// 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,
|
||||
ResetMMU = 3,
|
||||
Unload = 4,
|
||||
Load = 5,
|
||||
Eject = 6,
|
||||
Tune = 7,
|
||||
StopPrint = 8,
|
||||
DisableMMU = 9,
|
||||
MoreInfo = 10,
|
||||
};
|
||||
|
||||
// Button codes + extended actions performed on the printer's side
|
||||
enum class Buttons : uint_least8_t {
|
||||
Right = 0,
|
||||
Middle,
|
||||
Left,
|
||||
|
||||
// Performed on the printer's side
|
||||
ResetMMU,
|
||||
Load,
|
||||
Eject,
|
||||
StopPrint,
|
||||
DisableMMU,
|
||||
TuneMMU, // Printer changes MMU register value
|
||||
|
||||
NoButton = 0xFF // should be kept last
|
||||
};
|
||||
|
||||
constexpr uint_least8_t buttons_to_uint8t(Buttons b) {
|
||||
return static_cast<uint8_t>(b);
|
||||
}
|
||||
|
||||
} // MMU3
|
||||
Executable
+55
@@ -0,0 +1,55 @@
|
||||
#!/bin/bash
|
||||
|
||||
# download Prusa Error Codes for MMU
|
||||
#wget https://raw.githubusercontent.com/3d-gussner/Prusa-Error-Codes/master/04_MMU/error-codes.yaml --output-document=error-codes.yaml
|
||||
wget https://raw.githubusercontent.com/prusa3d/Prusa-Error-Codes/master/04_MMU/error-codes.yaml --output-document=error-codes.yaml
|
||||
|
||||
oifs="$IFS" ## save original IFS
|
||||
IFS=$'\n' ## set IFS to break on newline
|
||||
codes=($(cat error-codes.yaml |grep "code:" |cut -d '"' -f2))
|
||||
titles=($(cat error-codes.yaml |grep 'title:' |cut -d '"' -f2))
|
||||
texts=($(cat error-codes.yaml |grep "text:" |cut -d '"' -f2))
|
||||
actions=($(cat error-codes.yaml |grep "action:" |cut -d ':' -f2))
|
||||
ids=($(cat error-codes.yaml |grep "id:" |cut -d '"' -f2))
|
||||
IFS="$oifs" ## restore original IFS
|
||||
|
||||
filename=errors_list.h
|
||||
|
||||
clear
|
||||
for ((i = 0; i < ${#codes[@]}; i++)) do
|
||||
code=${codes[i]}
|
||||
id=$(cat $filename |grep "${code#04*}" | cut -d "=" -f1 | cut -d "_" -f3- |cut -d " " -f1)
|
||||
title=$(cat $filename |grep "${id}" |grep --max-count=1 "MSG_TITLE" |cut -d '"' -f2)
|
||||
text=$(cat $filename |grep "${id}" |grep --max-count=1 "MSG_DESC" |cut -d '"' -f2)
|
||||
action1=$(cat $filename |grep "),//$id"| cut -d "," -f1)
|
||||
action2=$(cat $filename |grep "),//$id"| cut -d "," -f2)
|
||||
action1=$(echo $action1 | cut -d ":" -f2- |cut -d ":" -f2)
|
||||
action2=$(echo $action2 | cut -d ":" -f2- |cut -d ":" -f2 |cut -d ")" -f1)
|
||||
if [ "$action2" == "NoOperation" ]; then
|
||||
action=" [$action1]"
|
||||
else
|
||||
action=" [$action1,$action2]"
|
||||
fi
|
||||
echo -n "code: $code |"
|
||||
if [ "$id" != "${ids[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $id $(tput sgr0) # $(tput setaf 2)${ids[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $id |"
|
||||
fi
|
||||
if [ "$title" != "${titles[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $title $(tput sgr0) # $(tput setaf 2)${titles[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $title |"
|
||||
fi
|
||||
if [ "$text" != "${texts[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $text $(tput sgr0) # $(tput setaf 2)${texts[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $text |"
|
||||
fi
|
||||
if [ "$action" != "${actions[i]}" ]; then
|
||||
echo -n "$(tput setaf 1) $action $(tput sgr0) # $(tput setaf 2)${actions[i]}$(tput sgr0)|"
|
||||
else
|
||||
echo -n " $action |"
|
||||
fi
|
||||
echo
|
||||
done
|
||||
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* error_codes.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A complete set of error codes which may be a result of a high-level command/operation.
|
||||
* This header file should 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 FastFeedToBondtech - 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
|
||||
|
||||
FINDA_FLICKERS = 0x800A, //!< FINDA flickers - seems to be badly calibrated and happens to be pressed at spots where it used to be not pressed before.
|
||||
//!< The user is obliged to inspect FINDA and tune its switching
|
||||
|
||||
MOVE_FAILED = 0x800B, //!< 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
|
||||
|
||||
FILAMENT_EJECTED = 0x800C, //!< Filament was ejected, waiting for user input - technically, this is not an error
|
||||
|
||||
MCU_UNDERVOLTAGE_VCC = 0x800D, //!< MCU VCC rail undervoltage.
|
||||
|
||||
FILAMENT_CHANGE = 0x8029, //!< E32809 internal error of the printer - try-load-unload sequence detected missing filament -> failed load into the nozzle
|
||||
LOAD_TO_EXTRUDER_FAILED = 0x802A, //!< E32810 internal error of the printer - try-load-unload sequence detected missing filament -> failed load into the nozzle
|
||||
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
|
||||
// - E33408 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,
|
||||
|
||||
// TMC driver - IO pins are unreliable. While in theory it's recoverable, in practice it most likely
|
||||
// means your hardware is borked (we can't command the drivers reliably via STEP/EN/DIR due to electrical
|
||||
// issues or hardware fault. Possible "fixable" cause is undervoltage on the 5v logic line.
|
||||
// Unfixable possible cause: bad or cracked solder joints on the PCB, failed shift register, failed driver.
|
||||
MMU_SOLDERING_NEEDS_ATTENTION = 0xC200,
|
||||
|
||||
};
|
||||
@@ -0,0 +1,352 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* errors_list.h
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extracted from Prusa-Error-Codes repo
|
||||
* Subject to automation and optimization
|
||||
* BEWARE - This file should only be included by mmu2_error_converter.cpp!
|
||||
*/
|
||||
#include "inttypes.h"
|
||||
#include "../../../core/language.h"
|
||||
#include "../../../lcd/marlinui.h"
|
||||
#ifdef __AVR__
|
||||
#include <avr/pgmspace.h>
|
||||
#endif
|
||||
#include "buttons.h"
|
||||
#include "../strlen_cx.h"
|
||||
#include "../ultralcd.h"
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
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_FILAMENT_STUCK = 102,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER = 103,
|
||||
ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK = 104,
|
||||
|
||||
ERR_MECHANICAL_PULLEY_CANNOT_MOVE = 105,
|
||||
ERR_MECHANICAL_FSENSOR_TOO_EARLY = 106,
|
||||
ERR_MECHANICAL_INSPECT_FINDA = 107,
|
||||
ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED = 108,
|
||||
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_WARNING_TMC_PULLEY_TOO_HOT = 201,
|
||||
ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT = 211,
|
||||
ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT = 221,
|
||||
|
||||
ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR = 202,
|
||||
ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR = 212,
|
||||
ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR = 222,
|
||||
|
||||
|
||||
ERR_ELECTRICAL = 300,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR = 301,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR = 311,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR = 321,
|
||||
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET = 302,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET = 312,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET = 322,
|
||||
|
||||
ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR = 303,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR = 313,
|
||||
ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR = 323,
|
||||
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED = 304,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED = 314,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED = 324,
|
||||
|
||||
ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED = 305,
|
||||
ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED = 315,
|
||||
ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED = 325,
|
||||
|
||||
ERR_ELECTRICAL_MMU_MCU_ERROR = 306,
|
||||
|
||||
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_SYSTEM_FILAMENT_EJECTED = 507,
|
||||
ERR_SYSTEM_FILAMENT_CHANGE = 508,
|
||||
|
||||
ERR_OTHER_UNKNOWN_ERROR = 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 err_num_t errorCodes[] PROGMEM = {
|
||||
ERR_MECHANICAL_FINDA_DIDNT_TRIGGER,
|
||||
ERR_MECHANICAL_FINDA_FILAMENT_STUCK,
|
||||
ERR_MECHANICAL_FSENSOR_DIDNT_TRIGGER,
|
||||
ERR_MECHANICAL_FSENSOR_FILAMENT_STUCK,
|
||||
ERR_MECHANICAL_PULLEY_CANNOT_MOVE,
|
||||
ERR_MECHANICAL_FSENSOR_TOO_EARLY,
|
||||
ERR_MECHANICAL_INSPECT_FINDA,
|
||||
ERR_MECHANICAL_LOAD_TO_EXTRUDER_FAILED,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_HOME,
|
||||
ERR_MECHANICAL_SELECTOR_CANNOT_MOVE,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_HOME,
|
||||
ERR_MECHANICAL_IDLER_CANNOT_MOVE,
|
||||
ERR_TEMPERATURE_WARNING_TMC_PULLEY_TOO_HOT,
|
||||
ERR_TEMPERATURE_WARNING_TMC_SELECTOR_TOO_HOT,
|
||||
ERR_TEMPERATURE_WARNING_TMC_IDLER_TOO_HOT,
|
||||
ERR_TEMPERATURE_TMC_PULLEY_OVERHEAT_ERROR,
|
||||
ERR_TEMPERATURE_TMC_SELECTOR_OVERHEAT_ERROR,
|
||||
ERR_TEMPERATURE_TMC_IDLER_OVERHEAT_ERROR,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_ERROR,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_RESET,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_TMC_IDLER_UNDERVOLTAGE_ERROR,
|
||||
ERR_ELECTRICAL_TMC_PULLEY_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_TMC_SELECTOR_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_TMC_IDLER_DRIVER_SHORTED,
|
||||
ERR_ELECTRICAL_MMU_PULLEY_SELFTEST_FAILED,
|
||||
ERR_ELECTRICAL_MMU_SELECTOR_SELFTEST_FAILED,
|
||||
ERR_ELECTRICAL_MMU_IDLER_SELFTEST_FAILED,
|
||||
ERR_ELECTRICAL_MMU_MCU_ERROR,
|
||||
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,
|
||||
ERR_SYSTEM_FILAMENT_EJECTED,
|
||||
ERR_SYSTEM_FILAMENT_CHANGE,
|
||||
ERR_OTHER_UNKNOWN_ERROR
|
||||
};
|
||||
|
||||
FSTR_P const errorTitles[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_TITLE_FINDA_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_TITLE_FINDA_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_TITLE_FSENSOR_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_TITLE_FSENSOR_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_TITLE_PULLEY_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_TITLE_FSENSOR_TOO_EARLY),
|
||||
GET_TEXT_F(MSG_TITLE_INSPECT_FINDA),
|
||||
GET_TEXT_F(MSG_TITLE_LOAD_TO_EXTRUDER_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_SELECTOR_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_TITLE_SELECTOR_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_TITLE_IDLER_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_TITLE_IDLER_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_WARNING_TMC_TOO_HOT),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_OVERHEAT_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_RESET),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_UNDERVOLTAGE_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
GET_TEXT_F(MSG_TITLE_TMC_DRIVER_SHORTED),
|
||||
GET_TEXT_F(MSG_TITLE_SELFTEST_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_SELFTEST_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_SELFTEST_FAILED),
|
||||
GET_TEXT_F(MSG_TITLE_MMU_MCU_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_MMU_NOT_RESPONDING),
|
||||
GET_TEXT_F(MSG_TITLE_COMMUNICATION_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_FILAMENT_ALREADY_LOADED),
|
||||
GET_TEXT_F(MSG_TITLE_INVALID_TOOL),
|
||||
GET_TEXT_F(MSG_TITLE_QUEUE_FULL),
|
||||
GET_TEXT_F(MSG_TITLE_FW_UPDATE_NEEDED),
|
||||
GET_TEXT_F(MSG_TITLE_FW_RUNTIME_ERROR),
|
||||
GET_TEXT_F(MSG_TITLE_UNLOAD_MANUALLY),
|
||||
GET_TEXT_F(MSG_TITLE_FILAMENT_EJECTED),
|
||||
GET_TEXT_F(MSG_TITLE_FILAMENT_CHANGE),
|
||||
GET_TEXT_F(MSG_TITLE_UNKNOWN_ERROR)
|
||||
};
|
||||
|
||||
// @@TODO Looking at the texts, they can be composed of several parts and/or parameterized (could save a lot of space) )
|
||||
// Moreover, some of them have been disabled in favour of saving some more code size.
|
||||
|
||||
FSTR_P const errorDescs[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_DESC_FINDA_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_DESC_FINDA_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_DESC_FSENSOR_DIDNT_TRIGGER),
|
||||
GET_TEXT_F(MSG_DESC_FSENSOR_FILAMENT_STUCK),
|
||||
GET_TEXT_F(MSG_DESC_PULLEY_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_DESC_FSENSOR_TOO_EARLY),
|
||||
GET_TEXT_F(MSG_DESC_INSPECT_FINDA),
|
||||
GET_TEXT_F(MSG_DESC_LOAD_TO_EXTRUDER_FAILED),
|
||||
GET_TEXT_F(MSG_DESC_SELECTOR_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_DESC_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_DESC_IDLER_CANNOT_HOME),
|
||||
GET_TEXT_F(MSG_DESC_CANNOT_MOVE),
|
||||
GET_TEXT_F(MSG_DESC_TMC), // WARNING_TMC_PULLEY_TOO_HOT
|
||||
GET_TEXT_F(MSG_DESC_TMC), // WARNING_TMC_SELECTOR_TOO_HOT
|
||||
GET_TEXT_F(MSG_DESC_TMC), // WARNING_TMC_IDLER_TOO_HOT
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_OVERHEAT_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_OVERHEAT_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_OVERHEAT_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_DRIVER_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_DRIVER_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_DRIVER_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_DRIVER_RESET
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_DRIVER_RESET
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_DRIVER_RESET
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_UNDERVOLTAGE_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_UNDERVOLTAGE_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_UNDERVOLTAGE_ERROR
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_PULLEY_DRIVER_SHORTED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_SELECTOR_DRIVER_SHORTED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // TMC_IDLER_DRIVER_SHORTED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MMU_PULLEY_SELFTEST_FAILED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MMU_SELECTOR_SELFTEST_FAILED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MMU_IDLER_SELFTEST_FAILED
|
||||
GET_TEXT_F(MSG_DESC_TMC), // MSG_DESC_MMU_MCU_ERROR
|
||||
GET_TEXT_F(MSG_DESC_MMU_NOT_RESPONDING),
|
||||
GET_TEXT_F(MSG_DESC_COMMUNICATION_ERROR),
|
||||
GET_TEXT_F(MSG_DESC_FILAMENT_ALREADY_LOADED),
|
||||
GET_TEXT_F(MSG_DESC_INVALID_TOOL),
|
||||
GET_TEXT_F(MSG_DESC_QUEUE_FULL),
|
||||
GET_TEXT_F(MSG_DESC_FW_UPDATE_NEEDED),
|
||||
GET_TEXT_F(MSG_DESC_FW_RUNTIME_ERROR),
|
||||
GET_TEXT_F(MSG_DESC_UNLOAD_MANUALLY),
|
||||
GET_TEXT_F(MSG_DESC_FILAMENT_EJECTED),
|
||||
GET_TEXT_F(MSG_DESC_FILAMENT_CHANGE),
|
||||
GET_TEXT_F(MSG_DESC_UNKNOWN_ERROR)
|
||||
};
|
||||
|
||||
// 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.
|
||||
// It is displayed with W (Double down arrow, special character from CGRAM)
|
||||
// 01234567890123456789
|
||||
// >bttxt >bttxt >W
|
||||
// Therefore at least some of the buttons, which can occur on the screen together, can only be 8-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)
|
||||
|
||||
// Used to parse the buttons from Btns().
|
||||
FSTR_P const btnOperation[] PROGMEM = {
|
||||
GET_TEXT_F(MSG_BTN_RETRY),
|
||||
GET_TEXT_F(MSG_DONE),
|
||||
GET_TEXT_F(MSG_BTN_RESET_MMU),
|
||||
GET_TEXT_F(MSG_BTN_UNLOAD),
|
||||
GET_TEXT_F(MSG_BTN_LOAD),
|
||||
GET_TEXT_F(MSG_BTN_EJECT),
|
||||
GET_TEXT_F(MSG_TUNE),
|
||||
GET_TEXT_F(MSG_BTN_STOP),
|
||||
GET_TEXT_F(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::NoOperation), // FINDA_DIDNT_TRIGGER
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FINDA_FILAMENT_STUCK
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FSENSOR_DIDNT_TRIGGER
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FSENSOR_FILAMENT_STUCK
|
||||
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // PULLEY_CANNOT_MOVE
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // FSENSOR_TOO_EARLY
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // INSPECT_FINDA
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::NoOperation), // LOAD_TO_EXTRUDER_FAILED
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::Tune), // SELECTOR_CANNOT_HOME
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // SELECTOR_CANNOT_MOVE
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::Tune), // IDLER_CANNOT_HOME
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // IDLER_CANNOT_MOVE
|
||||
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::ResetMMU), // WARNING_TMC_PULLEY_TOO_HOT
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::ResetMMU), // WARNING_TMC_SELECTOR_TOO_HOT
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::ResetMMU), // WARNING_TMC_IDLER_TOO_HOT
|
||||
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_OVERHEAT_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_DRIVER_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_DRIVER_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_DRIVER_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_DRIVER_RESET
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_DRIVER_RESET
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_DRIVER_RESET
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_UNDERVOLTAGE_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_PULLEY_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_SELECTOR_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // TMC_IDLER_DRIVER_SHORTED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_PULLEY_SELFTEST_FAILED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_SELECTOR_SELFTEST_FAILED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_IDLER_SELFTEST_FAILED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // MMU_MCU_ERROR
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::DisableMMU), // MMU_NOT_RESPONDING
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::DisableMMU), // COMMUNICATION_ERROR
|
||||
|
||||
Btns(ButtonOperations::Unload, ButtonOperations::Continue), // FILAMENT_ALREADY_LOADED
|
||||
Btns(ButtonOperations::StopPrint, ButtonOperations::ResetMMU), // INVALID_TOOL
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // QUEUE_FULL
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::DisableMMU), // FW_UPDATE_NEEDED
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // FW_RUNTIME_ERROR
|
||||
Btns(ButtonOperations::Retry, ButtonOperations::NoOperation), // UNLOAD_MANUALLY
|
||||
Btns(ButtonOperations::Continue, ButtonOperations::NoOperation), // FILAMENT_EJECTED
|
||||
Btns(ButtonOperations::Eject, ButtonOperations::Load), // FILAMENT_CHANGE
|
||||
Btns(ButtonOperations::ResetMMU, ButtonOperations::NoOperation), // UNKOWN_ERROR
|
||||
};
|
||||
|
||||
static_assert(COUNT(errorCodes) == COUNT(errorDescs));
|
||||
static_assert(COUNT(errorCodes) == COUNT(errorTitles));
|
||||
static_assert(COUNT(errorCodes) == COUNT(errorButtons));
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* progress_codes.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* A complete set of progress codes which may be reported while running a high-level command/operation.
|
||||
* This header file should be included in the printer's firmware as well as a reference, so the progress
|
||||
* codes are 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,70 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* registers.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Register map for MMU
|
||||
enum class Register : uint8_t {
|
||||
Project_Major = 0x00,
|
||||
Project_Minor = 0x01,
|
||||
Project_Revision = 0x02,
|
||||
Project_Build_Number = 0x03,
|
||||
MMU_Errors = 0x04,
|
||||
Current_Progress_Code = 0x05,
|
||||
Current_Error_Code = 0x06,
|
||||
Filament_State = 0x07,
|
||||
FINDA_State = 0x08,
|
||||
FSensor_State = 0x09,
|
||||
Motor_Mode = 0x0A,
|
||||
Extra_Load_Distance = 0x0B,
|
||||
FSensor_Unload_Check_Distance = 0xC,
|
||||
Pulley_Unload_Feedrate = 0x0D,
|
||||
Pulley_Acceleration = 0x0E,
|
||||
Selector_Acceleration = 0x0F,
|
||||
Idler_Acceleration = 0x10,
|
||||
Pulley_Load_Feedrate = 0x11,
|
||||
Selector_Nominal_Feedrate = 0x12,
|
||||
Idler_Nominal_Feedrate = 0x13,
|
||||
Pulley_Slow_Feedrate = 0x14,
|
||||
Selector_Homing_Feedrate = 0x15,
|
||||
Idler_Homing_Feedrate = 0x16,
|
||||
Pulley_sg_thrs_R = 0x17,
|
||||
Selector_sg_thrs_R = 0x18,
|
||||
Idler_sg_thrs_R = 0x19,
|
||||
Get_Pulley_Position = 0x1A,
|
||||
Set_Get_Selector_Slot = 0x1B,
|
||||
Set_Get_Idler_Slot = 0x1C,
|
||||
Set_Get_Selector_Cut_iRun = 0x1D,
|
||||
Set_Get_Pulley_iRun = 0x1E,
|
||||
Set_Get_Selector_iRun = 0x1F,
|
||||
Set_Get_Idler_iRun = 0x20,
|
||||
Reserved = 0x21,
|
||||
};
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* registers.h
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
namespace MMU3 {
|
||||
|
||||
// Register map for MMU
|
||||
enum class Register : uint8_t {
|
||||
Project_Major = 0x00,
|
||||
Project_Minor = 0x01,
|
||||
Project_Revision = 0x02,
|
||||
Project_Build_Number = 0x03,
|
||||
MMU_Errors = 0x04,
|
||||
Current_Progress_Code = 0x05,
|
||||
Current_Error_Code = 0x06,
|
||||
Filament_State = 0x07,
|
||||
FINDA_State = 0x08,
|
||||
FSensor_State = 0x09,
|
||||
Motor_Mode = 0x0A,
|
||||
Extra_Load_Distance = 0x0B,
|
||||
FSensor_Unload_Check_Distance = 0xC,
|
||||
Pulley_Unload_Feedrate = 0x0D,
|
||||
Pulley_Acceleration = 0x0E,
|
||||
Selector_Acceleration = 0x0F,
|
||||
Idler_Acceleration = 0x10,
|
||||
Pulley_Load_Feedrate = 0x11,
|
||||
Selector_Nominal_Feedrate = 0x12,
|
||||
Idler_Nominal_Feedrate = 0x13,
|
||||
Pulley_Slow_Feedrate = 0x14,
|
||||
Selector_Homing_Feedrate = 0x15,
|
||||
Idler_Homing_Feedrate = 0x16,
|
||||
Pulley_sg_thrs_R = 0x17,
|
||||
Selector_sg_thrs_R = 0x18,
|
||||
Idler_sg_thrs_R = 0x19,
|
||||
Get_Pulley_Position = 0x1A,
|
||||
Set_Get_Selector_Slot = 0x1B,
|
||||
Set_Get_Idler_Slot = 0x1C,
|
||||
Set_Get_Selector_Cut_iRun = 0x1D,
|
||||
Set_Get_Pulley_iRun = 0x1E,
|
||||
Set_Get_Selector_iRun = 0x1F,
|
||||
Set_Get_Idler_iRun = 0x20,
|
||||
Reserved = 0x21,
|
||||
};
|
||||
|
||||
} // MMU3
|
||||
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* sound.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
//#include "backlight.h"
|
||||
#include "../../libs/buzzer.h"
|
||||
#include "sound.h"
|
||||
|
||||
// eSOUND_MODE eSoundMode=e_SOUND_MODE_LOUD;
|
||||
// doesn't matter if Sound_Init is called (i.e. the value is in the EEPROM)
|
||||
// !?! eSOUND_MODE eSoundMode; in ultraldc.cpp :: cd_settings_menu() it appears as a local variable like this
|
||||
eSOUND_MODE eSoundMode; // =e_SOUND_MODE_DEFAULT;
|
||||
|
||||
|
||||
static void Sound_SaveMode(void);
|
||||
static void Sound_DoSound_Echo(void);
|
||||
static void Sound_DoSound_Prompt(void);
|
||||
static void Sound_DoSound_Alert(bool bOnce);
|
||||
static void Sound_DoSound_Encoder_Move(void);
|
||||
static void Sound_DoSound_Blind_Alert(void);
|
||||
|
||||
void Sound_Init(void) {
|
||||
// SET_OUTPUT(BEEPER);
|
||||
// eSoundMode = static_cast<eSOUND_MODE>(eeprom_init_default_byte((uint8_t*)EEPROM_SOUND_MODE, e_SOUND_MODE_DEFAULT));
|
||||
}
|
||||
|
||||
void Sound_SaveMode(void) {
|
||||
// eeprom_update_byte((uint8_t*)EEPROM_SOUND_MODE,(uint8_t)eSoundMode);
|
||||
}
|
||||
|
||||
void Sound_CycleState(void) {
|
||||
switch (eSoundMode) {
|
||||
case e_SOUND_MODE_LOUD: eSoundMode = e_SOUND_MODE_ONCE; break;
|
||||
case e_SOUND_MODE_ONCE: eSoundMode = e_SOUND_MODE_SILENT; break;
|
||||
case e_SOUND_MODE_SILENT: eSoundMode = e_SOUND_MODE_BLIND; break;
|
||||
case e_SOUND_MODE_BLIND: eSoundMode = e_SOUND_MODE_LOUD; break;
|
||||
default: eSoundMode = e_SOUND_MODE_LOUD;
|
||||
}
|
||||
Sound_SaveMode();
|
||||
}
|
||||
|
||||
// if critical is true then silent and once mode is ignored
|
||||
void __attribute__((noinline)) Sound_MakeCustom(uint16_t ms, uint16_t tone_, bool critical) {
|
||||
if (critical || eSoundMode != e_SOUND_MODE_SILENT)
|
||||
//if (!tone_) {
|
||||
// WRITE(BEEPER, HIGH);
|
||||
// _delay(ms);
|
||||
// WRITE(BEEPER, LOW);
|
||||
//}
|
||||
//else {
|
||||
// _tone(BEEPER, tone_);
|
||||
// _delay(ms);
|
||||
// _noTone(BEEPER);
|
||||
//}
|
||||
BUZZ(ms, tone_);
|
||||
}
|
||||
|
||||
void Sound_MakeSound(eSOUND_TYPE eSoundType) {
|
||||
switch (eSoundMode) {
|
||||
case e_SOUND_MODE_LOUD:
|
||||
if (eSoundType == e_SOUND_TYPE_ButtonEcho)
|
||||
Sound_DoSound_Echo();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardPrompt)
|
||||
Sound_DoSound_Prompt();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(false);
|
||||
break;
|
||||
case e_SOUND_MODE_ONCE:
|
||||
if (eSoundType == e_SOUND_TYPE_ButtonEcho)
|
||||
Sound_DoSound_Echo();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardPrompt)
|
||||
Sound_DoSound_Prompt();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(true);
|
||||
break;
|
||||
case e_SOUND_MODE_SILENT:
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(true);
|
||||
break;
|
||||
case e_SOUND_MODE_BLIND:
|
||||
if (eSoundType == e_SOUND_TYPE_ButtonEcho)
|
||||
Sound_DoSound_Echo();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardPrompt)
|
||||
Sound_DoSound_Prompt();
|
||||
if (eSoundType == e_SOUND_TYPE_StandardAlert)
|
||||
Sound_DoSound_Alert(false);
|
||||
if (eSoundType == e_SOUND_TYPE_EncoderMove)
|
||||
Sound_DoSound_Encoder_Move();
|
||||
if (eSoundType == e_SOUND_TYPE_BlindAlert)
|
||||
Sound_DoSound_Blind_Alert();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Blind_Alert(void) {
|
||||
// backlight_wake(1);
|
||||
uint8_t nI;
|
||||
for (nI = 0; nI < 20; nI++) {
|
||||
BUZZ(94, 404);
|
||||
BUZZ(94, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Encoder_Move(void) {
|
||||
uint8_t nI;
|
||||
for (nI = 0; nI < 5; nI++) {
|
||||
BUZZ(75, 404);
|
||||
BUZZ(75, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Echo(void) {
|
||||
uint8_t nI;
|
||||
for (nI = 0; nI < 10; nI++) {
|
||||
BUZZ(100, 404);
|
||||
BUZZ(100, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Prompt(void) {
|
||||
// backlight_wake(2);
|
||||
BUZZ(500, 404);
|
||||
}
|
||||
|
||||
static void Sound_DoSound_Alert(bool bOnce) {
|
||||
uint8_t nI, nMax;
|
||||
nMax = bOnce ? 1 : 3;
|
||||
for (nI = 0; nI < nMax; nI++) {
|
||||
BUZZ(200, 404);
|
||||
BUZZ(500, 0);
|
||||
}
|
||||
}
|
||||
|
||||
static int16_t constexpr CONTINOUS_BEEP_PERIOD = 2000; // in ms
|
||||
// static ShortTimer beep_timer; // Timer to keep track of continous beeping
|
||||
static bool bFirst; // true if the first beep has occurred, e_SOUND_MODE_ONCE
|
||||
|
||||
// @brief Handles sound when waiting for user input
|
||||
// the function must be non-blocking. It is up to the caller
|
||||
// to call this function repeatedly.
|
||||
// Make sure to call sound_wait_for_user_reset() when the user has clicked the knob
|
||||
// Loud - should continuously beep
|
||||
// Silent - should be silent
|
||||
// Once - should beep once
|
||||
// Assist/Blind - as loud with beep and click on knob rotation and press
|
||||
void sound_wait_for_user() {
|
||||
#if BEEPER > 0
|
||||
if (eSoundMode == e_SOUND_MODE_SILENT) return;
|
||||
|
||||
// Handle case where only one beep is needed
|
||||
if (eSoundMode == e_SOUND_MODE_ONCE) {
|
||||
if (bFirst) return;
|
||||
Sound_MakeCustom(80, 0, false);
|
||||
bFirst = true;
|
||||
}
|
||||
|
||||
// Handle case where there should be continous beeps
|
||||
if (beep_timer.expired_cont(CONTINOUS_BEEP_PERIOD)) {
|
||||
beep_timer.start();
|
||||
if (eSoundMode == e_SOUND_MODE_LOUD)
|
||||
Sound_MakeCustom(80, 0, false);
|
||||
else
|
||||
// Assist (lower volume sound)
|
||||
Sound_MakeSound(e_SOUND_TYPE_ButtonEcho);
|
||||
}
|
||||
#endif // BEEPER > 0
|
||||
}
|
||||
|
||||
// @brief Resets the global state of sound_wait_for_user()
|
||||
void sound_wait_for_user_reset() {
|
||||
// beep_timer.stop();
|
||||
bFirst = false;
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* sound.h
|
||||
*/
|
||||
|
||||
#include <stdint.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;
|
||||
|
||||
#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;
|
||||
|
||||
extern eSOUND_MODE eSoundMode;
|
||||
|
||||
extern void Sound_Init(void);
|
||||
extern void Sound_CycleState(void);
|
||||
extern void Sound_MakeSound(eSOUND_TYPE eSoundType);
|
||||
extern void Sound_MakeCustom(uint16_t ms, uint16_t tone_, bool critical);
|
||||
void sound_wait_for_user();
|
||||
void sound_wait_for_user_reset();
|
||||
|
||||
//static void Sound_DoSound_Echo(void);
|
||||
//static void Sound_DoSound_Prompt(void);
|
||||
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* strlen_cx.h
|
||||
*/
|
||||
|
||||
constexpr inline int strlen_constexpr(const char *str) {
|
||||
return *str ? 1 + strlen_constexpr(str + 1) : 0;
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* ultralcd.cpp
|
||||
*/
|
||||
|
||||
#include "../../inc/MarlinConfigPre.h"
|
||||
|
||||
#if HAS_PRUSA_MMU3
|
||||
|
||||
#include "mmu2.h"
|
||||
#include "mmu2_marlin_macros.h"
|
||||
#include "mmu_hw/errors_list.h"
|
||||
#include "ultralcd.h"
|
||||
|
||||
#include "../../lcd/menu/menu_item.h"
|
||||
#include "../../gcode/gcode.h"
|
||||
#include "../../lcd/marlinui.h"
|
||||
|
||||
//! @brief Show a two-choice prompt on the last line of the LCD
|
||||
//! @param selected Show first choice as selected if true, the second otherwise
|
||||
//! @param first_choice text caption of first possible choice
|
||||
//! @param second_choice text caption of second possible choice
|
||||
//! @param second_col column on LCD where second choice is rendered.
|
||||
//! @param third_choice text caption of third, optional, choice.
|
||||
void lcd_show_choices_prompt_P(uint8_t selected, const char *first_choice, const char *second_choice, uint8_t second_col, const char *third_choice) {
|
||||
lcd_put_lchar(0, 3, selected == LCD_LEFT_BUTTON_CHOICE ? '>' : ' ');
|
||||
lcd_put_u8str(first_choice);
|
||||
lcd_put_lchar(second_col, 3, selected == LCD_MIDDLE_BUTTON_CHOICE ? '>' : ' ');
|
||||
lcd_put_u8str(second_choice);
|
||||
if (third_choice) {
|
||||
lcd_put_lchar(18, 3, selected == LCD_RIGHT_BUTTON_CHOICE ? '>' : ' ');
|
||||
lcd_put_u8str(third_choice);
|
||||
}
|
||||
}
|
||||
|
||||
void lcd_space(uint8_t n) {
|
||||
while (n--) lcd_put_lchar(' ');
|
||||
}
|
||||
|
||||
// Print extruder status (5 chars total)
|
||||
// Scenario 1: "F?"
|
||||
// There is no filament loaded and no tool change is in progress
|
||||
// Scenario 2: "F[nr.]"
|
||||
// [nr.] ranges from 1 to 5.
|
||||
// Shows which filament is loaded. No tool change is in progress
|
||||
// Scenario 3: "?>[nr.]"
|
||||
// [nr.] ranges from 1 to 5.
|
||||
// There is no filament currently loaded, but [nr.] is currently being loaded via tool change
|
||||
// Scenario 4: "[nr.]>?"
|
||||
// [nr.] ranges from 1 to 5.
|
||||
// This scenario indicates a bug in the firmware if ? is on the right side
|
||||
// Scenario 5: "[nr1.]>[nr2.]"
|
||||
// [nr1.] ranges from 1 to 5.
|
||||
// [nr2.] ranges from 1 to 5.
|
||||
// Filament [nr1.] was loaded, but [nr2.] is currently being loaded via tool change
|
||||
// Scenario 6: "?>?"
|
||||
// This scenario should not be possible and indicates a bug in the firmware
|
||||
uint8_t lcdui_print_extruder(void) {
|
||||
uint8_t chars = 1;
|
||||
lcd_space(1);
|
||||
if (mmu3.get_current_tool() == mmu3.get_tool_change_tool()) {
|
||||
lcd_put_lchar('F');
|
||||
lcd_put_lchar(mmu3.get_current_tool() == (uint8_t)MMU3::FILAMENT_UNKNOWN ? '?' : mmu3.get_current_tool() + '1');
|
||||
chars += 2;
|
||||
}
|
||||
else {
|
||||
lcd_put_lchar(mmu3.get_current_tool() == (uint8_t)MMU3::FILAMENT_UNKNOWN ? '?' : mmu3.get_current_tool() + '1');
|
||||
lcd_put_lchar('>');
|
||||
lcd_put_lchar(mmu3.get_tool_change_tool() == (uint8_t)MMU3::FILAMENT_UNKNOWN ? '?' : mmu3.get_tool_change_tool() + '1');
|
||||
chars += 3;
|
||||
}
|
||||
return chars;
|
||||
}
|
||||
|
||||
bool pgm_is_whitespace(const char *c_addr) {
|
||||
const char c = pgm_read_byte(c_addr);
|
||||
return c == ' ' || c == '\t' || c == '\r' || c == '\n';
|
||||
}
|
||||
|
||||
bool pgm_is_interpunction(const char *c_addr) {
|
||||
const char c = pgm_read_byte(c_addr);
|
||||
return c == '.' || c == ',' || c == ':' || c == ';' || c == '?' || c == '!' || c == '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief show full screen message
|
||||
*
|
||||
* This function is non-blocking
|
||||
* @param msg message to be displayed from PROGMEM
|
||||
* @return rest of the text (to be displayed on next page)
|
||||
*/
|
||||
static FSTR_P const lcd_display_message_fullscreen_nonBlocking(FSTR_P const fmsg) {
|
||||
PGM_P msg = FTOP(fmsg);
|
||||
PGM_P msgend = msg;
|
||||
//bool multi_screen = false;
|
||||
for (uint8_t row = 0; row < LCD_HEIGHT; ++row) {
|
||||
if (pgm_read_byte(msgend) == 0) break;
|
||||
SETCURSOR(0, row);
|
||||
|
||||
// Previous row ended with a complete word, so the first character in the
|
||||
// next row is a whitespace. We can skip the whitespace on a new line.
|
||||
if (pgm_is_whitespace(msg) && ++msg == nullptr) break; // End of the message.
|
||||
|
||||
uint8_t linelen = (strlen_P(msg) > LCD_WIDTH) ? LCD_WIDTH : strlen_P(msg);
|
||||
PGM_P const msgend2 = msg + linelen;
|
||||
msgend = msgend2;
|
||||
if (row == 3 && linelen == LCD_WIDTH) {
|
||||
// Last line of the display, full line should be displayed.
|
||||
// Find out, whether this message will be split into multiple screens.
|
||||
//multi_screen = pgm_read_byte(msgend) != 0;
|
||||
// We do not need this...
|
||||
//if (multi_screen) msgend = (msgend2 -= 2);
|
||||
}
|
||||
if (pgm_read_byte(msgend) != 0 && !pgm_is_whitespace(msgend) && !pgm_is_interpunction(msgend)) {
|
||||
// Splitting a word. Find the start of the current word.
|
||||
while (msgend > msg && !pgm_is_whitespace(msgend - 1)) --msgend;
|
||||
if (msgend == msg) msgend = msgend2; // Found a single long word, which cannot be split. Just cut it.
|
||||
}
|
||||
for (; msg < msgend; ++msg) {
|
||||
const char c = char(pgm_read_byte(msg));
|
||||
if (c == '\n') {
|
||||
// Abort early if '\n' is encountered.
|
||||
// This character is used to force the following words to be printed on the next line.
|
||||
break;
|
||||
}
|
||||
lcd_put_lchar(c);
|
||||
}
|
||||
}
|
||||
// We do not need this part...
|
||||
//if (multi_screen) {
|
||||
// // Display the double down arrow.
|
||||
// lcd_put_lchar(LCD_WIDTH - 2, LCD_HEIGHT - 2, LCD_STR_ARROW_2_DOWN[0]);
|
||||
//}
|
||||
//return multi_screen ? msgend : nullptr;
|
||||
return FPSTR(msgend);
|
||||
}
|
||||
|
||||
FSTR_P const lcd_display_message_fullscreen(FSTR_P const fmsg) {
|
||||
// Disable update of the screen by the usual lcd_update(0) routine.
|
||||
#if HAS_WIRED_LCD
|
||||
//ui.lcdDrawUpdate = LCDViewAction::LCDVIEW_NONE;
|
||||
ui.clear_lcd();
|
||||
return lcd_display_message_fullscreen_nonBlocking(fmsg);
|
||||
#else
|
||||
return fmsg
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief show full screen message and wait
|
||||
*
|
||||
* This function is blocking.
|
||||
* @param msg message to be displayed from PROGMEM
|
||||
*/
|
||||
void lcd_show_fullscreen_message_and_wait(FSTR_P const fmsg) {
|
||||
LcdUpdateDisabler lcdUpdateDisabler;
|
||||
FSTR_P fmsg_next = lcd_display_message_fullscreen(fmsg);
|
||||
const bool multi_screen = fmsg_next != nullptr;
|
||||
ui.use_click();
|
||||
KEEPALIVE_STATE(PAUSED_FOR_USER);
|
||||
// Until confirmed by a button click.
|
||||
for (;;) {
|
||||
if (fmsg_next == nullptr) {
|
||||
// Display the confirm char.
|
||||
//lcd_put_lchar(LCD_WIDTH - 2, LCD_HEIGHT - 2, LCD_STR_CONFIRM[0]);
|
||||
}
|
||||
// Wait for 5 seconds before displaying the next text.
|
||||
for (uint8_t i = 0; i < 100; ++i) {
|
||||
idle(true);
|
||||
safe_delay(50);
|
||||
if (ui.use_click()) {
|
||||
if (fmsg_next == nullptr) {
|
||||
KEEPALIVE_STATE(IN_HANDLER);
|
||||
return ui.go_back();
|
||||
}
|
||||
if (!multi_screen) break;
|
||||
if (fmsg_next == nullptr) fmsg_next = fmsg;
|
||||
fmsg_next = lcd_display_message_fullscreen(fmsg_next);
|
||||
}
|
||||
}
|
||||
//if (multi_screen) {
|
||||
// if (fmsg_next == nullptr) fmsg_next = fmsg;
|
||||
// fmsg_next = lcd_display_message_fullscreen(fmsg_next);
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
void lcd_insert_char_into_status(uint8_t position, const char message) {
|
||||
if (position >= LCD_WIDTH) return;
|
||||
//int size = ui.status_message.length();
|
||||
char *str = ui.status_message.buffer();
|
||||
str[position] = message;
|
||||
ui.refresh(LCDVIEW_REDRAW_NOW); // force redraw
|
||||
}
|
||||
|
||||
#endif // HAS_PRUSA_MMU3
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Marlin 3D Printer Firmware
|
||||
* Copyright (c) 2024 MarlinFirmware [https://github.com/MarlinFirmware/Marlin]
|
||||
*
|
||||
* Based on Sprinter and grbl.
|
||||
* Copyright (c) 2011 Camiel Gubbels / Erik van der Zalm
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* ultralcd.h
|
||||
*/
|
||||
|
||||
#include "../../MarlinCore.h"
|
||||
#include "../../lcd/marlinui.h"
|
||||
|
||||
#define LCD_LEFT_BUTTON_CHOICE 0
|
||||
#define LCD_MIDDLE_BUTTON_CHOICE 1
|
||||
#define LCD_RIGHT_BUTTON_CHOICE 2
|
||||
|
||||
#define LCD_STR_ARROW_2_DOWN "\x88"
|
||||
#define LCD_STR_CONFIRM "\x89"
|
||||
#define LCD_STR_SOLID_BLOCK "\xFF" // from the default character set
|
||||
|
||||
/**
|
||||
* @brief Helper class to temporarily disable LCD updates
|
||||
*
|
||||
* When constructed (on stack), original state state of lcd_update_enabled is stored
|
||||
* and LCD updates are disabled.
|
||||
* When destroyed (gone out of scope), original state of LCD update is restored.
|
||||
* It has zero overhead compared to storing bool saved = lcd_update_enabled
|
||||
* and calling lcd_update_enable(false) and lcd_update_enable(saved).
|
||||
*/
|
||||
class LcdUpdateDisabler {
|
||||
public:
|
||||
LcdUpdateDisabler() : m_updateEnabled(ui.lcdDrawUpdate) {
|
||||
TERN_(HAS_WIRED_LCD, ui.lcdDrawUpdate = LCDViewAction::LCDVIEW_NONE);
|
||||
}
|
||||
~LcdUpdateDisabler() {
|
||||
#if HAS_WIRED_LCD
|
||||
ui.lcdDrawUpdate = m_updateEnabled;
|
||||
ui.clear_lcd();
|
||||
ui.update();
|
||||
#endif
|
||||
}
|
||||
|
||||
private:
|
||||
LCDViewAction m_updateEnabled;
|
||||
};
|
||||
|
||||
bool pgm_is_whitespace(const char *c_addr);
|
||||
bool pgm_is_interpunction(const char *c_addr);
|
||||
FSTR_P const lcd_display_message_fullscreen(FSTR_P const pmsg);
|
||||
void lcd_show_choices_prompt_P(uint8_t selected, const char *first_choice, const char *second_choice, uint8_t second_col, const char *third_choice=nullptr);
|
||||
void lcd_show_fullscreen_message_and_wait(FSTR_P const fmsg);
|
||||
uint8_t lcdui_print_extruder(void);
|
||||
void lcd_space(uint8_t n);
|
||||
void lcd_insert_char_into_status(uint8_t position, const char message);
|
||||
@@ -611,9 +611,27 @@ void wait_for_confirmation(const bool is_reload/*=false*/, const int8_t max_beep
|
||||
* - Send host action for resume, if configured
|
||||
* - Resume the current SD print job, if any
|
||||
*/
|
||||
void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_length/*=0*/, const_float_t purge_length/*=ADVANCED_PAUSE_PURGE_LENGTH*/, const int8_t max_beep_count/*=0*/, const celsius_t targetTemp/*=0*/ DXC_ARGS) {
|
||||
void resume_print(
|
||||
const_float_t slow_load_length/*=0*/,
|
||||
const_float_t fast_load_length/*=0*/,
|
||||
const_float_t purge_length/*=ADVANCED_PAUSE_PURGE_LENGTH*/,
|
||||
const int8_t max_beep_count/*=0*/,
|
||||
const celsius_t targetTemp/*=0*/,
|
||||
const bool show_lcd/*=true*/,
|
||||
const bool pause_for_user/*=false*/
|
||||
DXC_ARGS
|
||||
) {
|
||||
DEBUG_SECTION(rp, "resume_print", true);
|
||||
DEBUG_ECHOLNPGM("... slowlen:", slow_load_length, " fastlen:", fast_load_length, " purgelen:", purge_length, " maxbeep:", max_beep_count, " targetTemp:", targetTemp DXC_SAY);
|
||||
DEBUG_ECHOLNPGM(
|
||||
"... slowlen:", slow_load_length
|
||||
, " fastlen:", fast_load_length
|
||||
, " purgelen:", purge_length
|
||||
, " maxbeep:", max_beep_count
|
||||
, " targetTemp:", targetTemp
|
||||
, " show_lcd:", show_lcd
|
||||
, " pause_for_user:", pause_for_user
|
||||
DXC_SAY
|
||||
);
|
||||
|
||||
/*
|
||||
SERIAL_ECHOLNPGM(
|
||||
@@ -627,7 +645,7 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_
|
||||
if (!did_pause_print) return;
|
||||
|
||||
// Re-enable the heaters if they timed out
|
||||
bool nozzle_timed_out = false;
|
||||
bool nozzle_timed_out = pause_for_user;
|
||||
HOTEND_LOOP() {
|
||||
nozzle_timed_out |= thermalManager.heater_idle[e].timed_out;
|
||||
thermalManager.reset_hotend_idle_timer(e);
|
||||
@@ -637,7 +655,7 @@ void resume_print(const_float_t slow_load_length/*=0*/, const_float_t fast_load_
|
||||
thermalManager.setTargetHotend(targetTemp, active_extruder);
|
||||
|
||||
// Load the new filament
|
||||
load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, true, nozzle_timed_out, PAUSE_MODE_SAME DXC_PASS);
|
||||
load_filament(slow_load_length, fast_load_length, purge_length, max_beep_count, show_lcd, nozzle_timed_out, PAUSE_MODE_SAME DXC_PASS);
|
||||
|
||||
if (targetTemp > 0) {
|
||||
thermalManager.setTargetHotend(targetTemp, active_extruder);
|
||||
|
||||
@@ -109,7 +109,9 @@ void resume_print(
|
||||
const_float_t fast_load_length=0, // (mm) Fast Load Length for initial move
|
||||
const_float_t purge_length=ADVANCED_PAUSE_PURGE_LENGTH, // (mm) Purge length
|
||||
const int8_t max_beep_count=0, // Beep alert for attention
|
||||
const celsius_t targetTemp=0 // (°C) A target temperature for the hotend
|
||||
const celsius_t targetTemp=0, // (°C) A target temperature for the hotend
|
||||
const bool show_lcd=true, // Set LCD status messages?
|
||||
const bool pause_for_user=false // Pause for user before returning?
|
||||
DXC_PARAMS // Dual-X-Carriage extruder index
|
||||
);
|
||||
|
||||
|
||||
@@ -35,8 +35,12 @@
|
||||
// Inline laser power
|
||||
#include "../module/planner.h"
|
||||
|
||||
#define RPM_TO_PWM(X) ((X) * 255 / (SPEED_POWER_MAX))
|
||||
#define PWM_TO_RPM(X) ((X) * (SPEED_POWER_MAX) / 255)
|
||||
#define PCT_TO_PWM(X) ((X) * 255 / 100)
|
||||
#define PWM_TO_PCT(X) ((X) * 100 / 255)
|
||||
#define PCT_TO_SERVO(X) ((X) * 180 / 100)
|
||||
#define CUTTER_PWM_TO_SPWR(X) (CUTTER_UNIT_IS(PERCENT) ? PWM_TO_PCT(X) : (CUTTER_UNIT_IS(RPM) ? PWM_TO_RPM(X) : X))
|
||||
|
||||
// Laser/Cutter operation mode
|
||||
enum CutterMode : int8_t {
|
||||
|
||||
@@ -942,7 +942,7 @@
|
||||
* M122 report functions
|
||||
*/
|
||||
|
||||
void tmc_report_all(LOGICAL_AXIS_ARGS(const bool)) {
|
||||
void tmc_report_all(LOGICAL_AXIS_ARGS_LC(const bool)) {
|
||||
#define TMC_REPORT(LABEL, ITEM) do{ SERIAL_ECHOPGM(LABEL); tmc_debug_loop(ITEM OPTARGS_LOGICAL()); }while(0)
|
||||
#define DRV_REPORT(LABEL, ITEM) do{ SERIAL_ECHOPGM(LABEL); drv_status_loop(ITEM OPTARGS_LOGICAL()); }while(0)
|
||||
|
||||
@@ -1152,7 +1152,7 @@
|
||||
SERIAL_EOL();
|
||||
}
|
||||
|
||||
void tmc_get_registers(LOGICAL_AXIS_ARGS(bool)) {
|
||||
void tmc_get_registers(LOGICAL_AXIS_ARGS_LC(bool)) {
|
||||
#define _TMC_GET_REG(LABEL, ITEM) do{ SERIAL_ECHOPGM(LABEL); tmc_get_registers(ITEM OPTARGS_LOGICAL()); }while(0)
|
||||
#define TMC_GET_REG(NAME, TABS) _TMC_GET_REG(STRINGIFY(NAME) TABS, TMC_GET_##NAME)
|
||||
_TMC_GET_REG("\t", TMC_AXIS_CODES);
|
||||
@@ -1232,7 +1232,7 @@ static bool test_connection(TMC &st) {
|
||||
return test_result;
|
||||
}
|
||||
|
||||
void test_tmc_connection(LOGICAL_AXIS_ARGS(const bool)) {
|
||||
void test_tmc_connection(LOGICAL_AXIS_ARGS_LC(const bool)) {
|
||||
uint8_t axis_connection = 0;
|
||||
|
||||
if (TERN0(HAS_X_AXIS, x)) {
|
||||
|
||||
@@ -320,14 +320,14 @@ class TMCMarlin<TMC2660Stepper, AXIS_LETTER, DRIVER_ID, AXIS_ID> : public TMC266
|
||||
};
|
||||
|
||||
void monitor_tmc_drivers();
|
||||
void test_tmc_connection(LOGICAL_AXIS_DECL(const bool, true));
|
||||
void test_tmc_connection(LOGICAL_AXIS_DECL_LC(const bool, true));
|
||||
|
||||
#if ENABLED(TMC_DEBUG)
|
||||
#if ENABLED(MONITOR_DRIVER_STATUS)
|
||||
void tmc_set_report_interval(const uint16_t update_interval);
|
||||
#endif
|
||||
void tmc_report_all(LOGICAL_AXIS_DECL(const bool, true));
|
||||
void tmc_get_registers(LOGICAL_AXIS_ARGS(const bool));
|
||||
void tmc_report_all(LOGICAL_AXIS_DECL_LC(const bool, true));
|
||||
void tmc_get_registers(LOGICAL_AXIS_ARGS_LC(const bool));
|
||||
#endif
|
||||
|
||||
/**
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user