Compare commits

...

129 Commits

Author SHA1 Message Date
Scott Lahteine 0edde09d7f etc 2024-09-10 03:13:43 -05:00
Scott Lahteine 3124474c95 comment 2024-09-10 03:11:53 -05:00
InsanityAutomation 09acb61496 Optomize G425 to compute Primary Axis before probing remainders 2024-08-29 16:36:27 -04:00
Scott Lahteine 8ac0f14796 🪠 More granular sub-options undef (#27373) 2024-08-28 20:41:12 -05:00
thinkyhead 934fc52d06 [cron] Bump distribution date (2024-08-29) 2024-08-29 00:25:00 +00:00
Scott Lahteine cf46d1880c 🚸 Purge PLR on power-off (2) 2024-08-28 15:46:53 -05:00
thinkyhead 4f159a94cd [cron] Bump distribution date (2024-08-28) 2024-08-28 06:08:14 +00:00
Scott Lahteine b53e14c7f8 🧑‍💻 Guard queue advance_r 2024-08-28 00:42:25 -05:00
Scott Lahteine 94e9f26544 🚸 Purge PLR on power-off 2024-08-28 00:42:25 -05:00
thinkyhead 9e87af70b7 [cron] Bump distribution date (2024-08-27) 2024-08-27 00:24:38 +00:00
Scott Lahteine fe56f5d3a6 🧑‍💻 Improve build_example 2024-08-26 16:11:47 -05:00
Scott Lahteine 90e5826256 🔧 No "base" needed for minimal config 2024-08-26 16:11:13 -05:00
Scott Lahteine bb73c335a5 🩹 Fix Ender-3 S1 Plus build 2024-08-26 16:11:13 -05:00
Scott Lahteine 4c1f76567c 🧑‍💻 build_all_examples --base --archive 2024-08-26 10:50:13 -05:00
Scott Lahteine d193c814d3 🩹 Fix misc warnings 2024-08-26 10:50:13 -05:00
narno2202 2cb252da4a 🩹 Fix FT_MOTION compile (#27367) 2024-08-25 23:20:11 -05:00
thinkyhead 9a04c32f7f [cron] Bump distribution date (2024-08-26) 2024-08-26 00:24:58 +00:00
Scott Lahteine 3b8e9fdd25 🔧 Reduce / update base configs 2024-08-25 00:16:04 -05:00
Scott Lahteine e0b045da49 🧑‍💻 Enhance build example scripts 2024-08-24 20:03:12 -05:00
thinkyhead 4220491a23 [cron] Bump distribution date (2024-08-25) 2024-08-25 00:26:53 +00:00
narno2202 3abe1fcf20 🔧🚸 FT_MOTION adjustments (#27359)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-08-23 23:58:31 -05:00
marbocub 02ed020f3f 🩹 Fix shaping init (#27365) 2024-08-23 23:45:46 -05:00
Scott Lahteine 934ac24ad8 🐛 FT Motion time-based endstop (#27349)
Co-authored-by: ulendoalex <111143418+ulendoalex@users.noreply.github.com>
2024-08-23 23:42:02 -05:00
Scott Lahteine e2d8b2fc26 🚸 Prefer probe XY feedrate for "blocking move"
Reverting part of 89b0143b
2024-08-23 23:32:14 -05:00
thinkyhead 3e8483d389 [cron] Bump distribution date (2024-08-24) 2024-08-24 00:23:23 +00:00
Erkan Ozgur Yilmaz a9c529f004 Prusa MMU3 (#26635)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-08-23 18:33:13 -05:00
Keith Bennett a664587219 🩹 Fix Sensorless Homing Current Warning (#27366) 2024-08-23 17:25:09 -05:00
thinkyhead 1619838592 [cron] Bump distribution date (2024-08-23) 2024-08-23 00:24:05 +00:00
Lubomir Rintel 98a33d37c7 🩹 Fix MINITRONICS v1 pins (#27150) 2024-08-22 11:01:42 -05:00
thinkyhead 12a2e5ae1b [cron] Bump distribution date (2024-08-22) 2024-08-22 00:24:06 +00:00
Scott Lahteine 9b472a02e9 PROBE_TARE_MENU 2024-08-21 18:36:49 -05:00
Scott Lahteine c84bea7110 🚸 LED Default may toggle On 2024-08-21 18:36:49 -05:00
ellensp 20f71fafa7 🧑‍💻 Use sim env for BOARD_SIMULATED in mftest (#27299) 2024-08-21 18:33:58 -05:00
ellensp 056e5a430e 🔧 Azteeg X3 Pro doesn't need DIGIPOTS_I2C_SDA_* (#27337) 2024-08-21 18:25:25 -05:00
Scott Lahteine 21a604407d 🚸 Move solo Case Light menu up 2024-08-21 17:46:27 -05:00
thinkyhead deb076b9cd [cron] Bump distribution date (2024-08-21) 2024-08-21 00:23:53 +00:00
Scott Lahteine b8e2ad9d8f 👷 resume_print params for timeout, filament load
Co-Authored-By: Erkan Ozgur Yilmaz <1786804+eoyilmaz@users.noreply.github.com>
2024-08-20 16:38:10 -05:00
Scott Lahteine 0cd9643957 🎨 Misc. cleanup (from MMU3 PR)
Co-Authored-By: Erkan Ozgur Yilmaz <1786804+eoyilmaz@users.noreply.github.com>
2024-08-20 16:38:10 -05:00
thinkyhead 8c78315f71 [cron] Bump distribution date (2024-08-20) 2024-08-20 00:24:02 +00:00
Scott Lahteine 35dad3fdfe 📝 BIQU MicroProbe followup 2024-08-19 15:00:15 -05:00
Scott Lahteine bebf5dc6ab GENERIC_BACK_MENU_ITEM 2024-08-19 14:50:26 -05:00
Scott Lahteine 2fd1c48e1a 👷 Prefer has_blocks_queued over movesplanned 2024-08-19 14:50:23 -05:00
thinkyhead 301727defc [cron] Bump distribution date (2024-08-18) 2024-08-18 00:26:18 +00:00
Scott Lahteine 1afd53a933 🔨 Suppress some Maple warnings 2024-08-17 18:02:23 -05:00
narno2202 381aeb94c6 🩹 Fix FT Motion toggle in active job (#27335)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-08-17 13:02:05 -05:00
thinkyhead 4a36520820 [cron] Bump distribution date (2024-08-17) 2024-08-17 00:23:13 +00:00
Scott Lahteine 430eedf5c0 👷 Optional IJKUVW endstops 2024-08-16 14:35:29 -05:00
Scott Lahteine 793a851d10 👷 !ELAPSED => PENDING 2024-08-16 14:34:05 -05:00
Scott Lahteine 2d609487ac 👷 FT Motion refactor, minor fix 2024-08-16 14:33:38 -05:00
Scott Lahteine 295f50379f 👷 Add UC elements to axis types 2024-08-16 14:31:51 -05:00
Scott Lahteine ecfff5099a 🎨 Misc. format, cleanup 2024-08-16 00:18:33 -05:00
Scott Lahteine 986344640f 🔧 Minimal configuration with Config.h (#27338) 2024-08-15 23:08:26 -05:00
thinkyhead 69f69606e6 [cron] Bump distribution date (2024-08-16) 2024-08-16 00:23:58 +00:00
sargonphin 2aa2e5487a ️ Improve Homing / Probing Current (#26714)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-08-15 18:30:55 -05:00
John Robertson 23d9020a65 M3 / M4 O for laser/spindle (#26883) 2024-08-15 16:09:41 -05:00
Scott Lahteine 43d9d1ce1b 🩹 Config-related preparations (2) 2024-08-15 15:37:30 -05:00
thinkyhead ed7f3b21c0 [cron] Bump distribution date (2024-08-13) 2024-08-13 00:33:32 +00:00
Scott Lahteine b062a3b226 🎨 FT Motion, axis cleanup 2024-08-12 17:48:41 -05:00
Scott Lahteine 89b0143b09 🔧 Config-related preparations 2024-08-11 20:35:23 -05:00
Scott Lahteine 0790a9d5df 🔧 Fix some LCD probing margins 2024-08-11 20:35:23 -05:00
Scott Lahteine e05ac66561 🔧 Fix extra endstop auto-assignment 2024-08-11 20:35:23 -05:00
Scott Lahteine 3b33f7ec03 🔨 Build script improvements 2024-08-11 20:35:23 -05:00
Scott Lahteine b94c75bcda 🎨 ProUI / MKS UI code cleanup 2024-08-11 20:35:23 -05:00
Scott Lahteine 8dc8906d78 🎨 Conditional probe.h 2024-08-11 20:35:23 -05:00
Keith Bennett ab684dc484 📝 New Discord link (#27330) 2024-08-11 20:02:00 -05:00
Scott Lahteine 37fb26b557 🩹 Fix W axis blocking move
Fixes #27332

Co-Authored-By: thebjtfellow <110046297+thebjtfellow@users.noreply.github.com>
Co-Authored-By: DerAndere <26200979+DerAndere1@users.noreply.github.com>
2024-08-11 20:00:54 -05:00
thinkyhead 7397e5a41d [cron] Bump distribution date (2024-08-12) 2024-08-12 00:25:11 +00:00
Keith Bennett 4a9d380117 🩹 Fix MKS Gen-L V1 PWM pins (#26974)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-08-11 15:45:37 -05:00
thinkyhead 0ec1a54309 [cron] Bump distribution date (2024-08-07) 2024-08-07 00:24:00 +00:00
Bkahl ab6e68c312 🎨 Improve LulzBot FTDI Eve Touch UI (#27275)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-08-06 14:52:17 -05:00
thinkyhead c509603530 [cron] Bump distribution date (2024-08-06) 2024-08-06 00:23:52 +00:00
narno2202 e5ce65c32b 🔧 BIQU MicroProbe + FT_MOTION sanity-check (#27302) 2024-08-05 17:54:18 -05:00
tombrazier 851257c44b 🐛 Fix MPC differential tuning (#27274) 2024-08-05 17:44:15 -05:00
thinkyhead 1662e953fc [cron] Bump distribution date (2024-07-30) 2024-07-30 00:23:59 +00:00
Scott Lahteine dde89795c9 🔨 Rename "BTT" things, MCU-based SKR 2 (#27319)
Co-authored-by: Keith Bennett <13375512+thisiskeithb@users.noreply.github.com>
2024-07-29 13:25:17 -05:00
thinkyhead 6e69ff93ef [cron] Bump distribution date (2024-07-29) 2024-07-29 18:06:59 +00:00
Keith Bennett 01b7d6c413 ️ Hold BTT renaming for later (#27307)
This reverts #27301
2024-07-29 11:25:31 -05:00
Andrew 58398832a1 📝 Tweak G26 O description (#27310)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-29 10:59:12 -05:00
dmitrygribenchuk 29469a0615 🎨 Python style tweaks (#27312) 2024-07-29 10:41:50 -05:00
Andrew b4dd20c0eb 🔨 Use Adafruit NeoPixel 1.12.3 (#27314) 2024-07-29 10:39:21 -05:00
thinkyhead eab13752dd [cron] Bump distribution date (2024-07-27) 2024-07-27 00:23:08 +00:00
Scott Lahteine ad60cdd030 🚸 G42 P as flag (not bool) 2024-07-26 02:40:35 -05:00
Evan Felix 78e111a4d0 🚸 Implement G60 / G61 to spec (#27281)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-26 00:13:52 -05:00
thinkyhead 5558729562 [cron] Bump distribution date (2024-07-26) 2024-07-26 00:23:28 +00:00
Scott Lahteine 2d32f7c377 👷 Smarter Flags<N> templates (#27309) 2024-07-25 15:36:20 -05:00
thinkyhead bcbdac6f6c [cron] Bump distribution date (2024-07-25) 2024-07-25 00:23:35 +00:00
Scott Lahteine acc8bf1c79 👷 Use char in binary send 2024-07-24 03:04:29 -05:00
Scott Lahteine 4f107e9fee 🔨 Suggest gcc14 for macOS Sim 2024-07-24 02:25:56 -05:00
Scott Lahteine 7844f3fa4f 🔨 Use -g2 for macOS Sim build to fix warnings 2024-07-24 01:02:31 -05:00
Keith Bennett 2f3af438ee 🔨 Rename BTT SKR V2.0 PIO envs (#27301) 2024-07-23 21:19:18 -05:00
thinkyhead 4483aa5b45 [cron] Bump distribution date (2024-07-24) 2024-07-24 00:24:31 +00:00
Scott Lahteine 20e6b6315e 📝 Config section reorganization 2024-07-23 17:59:14 -05:00
ellensp ca4248bdd5 🚸 Optional thermistors in Info Menu (#27303) 2024-07-23 16:47:22 -05:00
Scott Lahteine 4a5d8ba87e 👷 Local URL checking script (#26975) 2024-07-23 15:41:29 -05:00
Scott Lahteine 7a5d849857 🔨 No CONFIG_EXAMPLES_DIR in config signature 2024-07-23 15:18:06 -05:00
thinkyhead 95c23e55a4 [cron] Bump distribution date (2024-07-23) 2024-07-23 00:23:49 +00:00
Andrew 94ce0d24d6 👷 Add optimal stepper.set_e_position() (#27293)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-22 15:33:37 -05:00
Scott Lahteine cdea1b8460 👷 ATOMIC_SECTION macros 2024-07-22 14:54:17 -05:00
thinkyhead cc122bec82 [cron] Bump distribution date (2024-07-22) 2024-07-22 06:07:44 +00:00
Keith Bennett 6afaf0034c 🔧 Assert EDGE_STEPPING with TMC drivers (#27292)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-21 20:53:51 -05:00
thinkyhead 7f0d7e760b [cron] Bump distribution date (2024-07-20) 2024-07-20 12:07:55 +00:00
Keith Bennett 69004322f6 👷 Add Simulator CI test (#27288) 2024-07-20 01:47:23 -05:00
thinkyhead 7441094a41 [cron] Bump distribution date (2024-07-19) 2024-07-19 00:24:14 +00:00
Chris 29a5006df9 🧑‍💻 Update HC32 CRITICAL_SECTION macros (#27283)
Fixes #27282
2024-07-17 21:03:35 -05:00
thinkyhead 1aca038daf [cron] Bump distribution date (2024-07-18) 2024-07-18 00:23:29 +00:00
ellensp ce92eb46ec 🔧 Prevent LCD_PINS_EN false pin conflict (#27284) 2024-07-17 18:07:06 -05:00
ellensp c852e9c6a1 🔨 Fix preflight checks order (#27285)
Followup to #27249
2024-07-17 18:01:10 -05:00
ellensp 2b8db0c2e2 🎨 fix endif comment (#27286) 2024-07-17 17:58:55 -05:00
Scott Lahteine 74f297c871 🚸 FT Motion tune menu (#27279)
Co-authored-by: narno2202 <130909513+narno2202@users.noreply.github.com>
2024-07-17 12:05:53 -05:00
thinkyhead 76411392b7 [cron] Bump distribution date (2024-07-17) 2024-07-17 00:23:52 +00:00
Keith Bennett 74c81117c3 🔨 MarlinSimUI updates (#27276) 2024-07-16 14:31:42 -05:00
thinkyhead 6f04d8dc77 [cron] Bump distribution date (2024-07-16) 2024-07-16 00:23:45 +00:00
Scott Lahteine ade05c045e 🚸 Revert FT Motion tune menu 2024-07-15 15:28:03 -05:00
ellensp 5140726c70 🩹 Fix _MAX type warning (#27272) 2024-07-15 14:01:32 -05:00
Scott Lahteine 5334a8f0ed 🚸 Fix UI behavior for G29 with retry (#27146) 2024-07-15 13:59:19 -05:00
Mihai ee99eed3bf 🚸 Improved menu responsiveness with MarlinUI + U8Glib. (#26555)
Co-authored-by: Jason Smith <jason.inet@gmail.com>
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-15 13:57:25 -05:00
narno2202 9a5f1d2f51 🚸 FT Motion: Linear Advance adjustments (#26785)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-15 13:30:42 -05:00
narno2202 170df9040b 🚸 FT_MOTION Menu available in situ (#26670) 2024-07-15 13:27:10 -05:00
narno2202 f0bc4274f8 🧑‍💻 FT Motion: Individual axis shaping, new buffer management (#26848)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-15 13:13:00 -05:00
thinkyhead 20a704b154 [cron] Bump distribution date (2024-07-15) 2024-07-15 00:24:44 +00:00
ellensp 30a70c480c XTLW boards (#27260) 2024-07-14 12:57:17 -05:00
thinkyhead 1d5e9c7dee [cron] Bump distribution date (2024-07-14) 2024-07-14 06:08:19 +00:00
Scott Lahteine b8ab2d4987 🩹 Fix PINS_EXIST usage 2024-07-13 23:39:27 -05:00
ellensp ce796cec97 🧑‍💻 Fix pins debug / FastIO issues (#27261)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-13 23:33:11 -05:00
thinkyhead ad4de747ad [cron] Bump distribution date (2024-07-13) 2024-07-13 18:05:54 +00:00
Keith Bennett 95c1b7fb31 🔧 USART6 for STM32, expand port range (#27262) 2024-07-13 12:18:47 -05:00
ellensp 29b742cfc4 🔧 Fix MKS Base default 1.4 RGB pins (#27263) 2024-07-13 11:55:40 -05:00
tombrazier 57bda2ff3b 🐛 Fix NUM_ENDSTOP_STATES with Z_MIN_PROBE = Z_MIN (#27190)
Co-authored-by: Scott Lahteine <thinkyhead@users.noreply.github.com>
2024-07-13 11:50:25 -05:00
John Robertson 228179e09b 🩹 Clock-based planner trapezoidal nominal_rate (#26881) 2024-07-13 11:47:46 -05:00
364 changed files with 16743 additions and 4310 deletions
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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 &mdash; 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.
+11 -3
View File
@@ -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 }}
+27
View File
@@ -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
+1
View File
@@ -125,6 +125,7 @@ vc-fileutils.settings
# Visual Studio Code
.vscode/*
!.vscode/extensions.json
*.code-workspace
# Simulation files
imgui.ini
+7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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.
+1 -1
View File
@@ -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"
+17 -17
View File
@@ -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
+1 -1
View File
@@ -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
//
+1 -1
View File
@@ -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);
+11 -11
View File
@@ -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
+1 -1
View File
@@ -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) {
+1 -2
View File
@@ -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
View File
@@ -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__
+18 -18
View File
@@ -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];
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
}
+6 -6
View File
@@ -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) {
+2 -2
View File
@@ -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; }
+11 -11
View File
@@ -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) {}
+2 -2
View File
@@ -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;
}
+9 -9
View File
@@ -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; }
+6 -6
View File
@@ -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
+9 -9
View File
@@ -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);
+1 -1
View File
@@ -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
+9 -10
View File
@@ -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);
+10
View File
@@ -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
+1 -1
View File
@@ -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
+9 -10
View File
@@ -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);
+1 -1
View File
@@ -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
//
+3
View File
@@ -37,6 +37,9 @@
#ifndef USART5
#define USART5 UART5
#endif
#ifndef USART6
#define USART6 UART6
#endif
#ifndef USART7
#define USART7 UART7
#endif
+13 -13
View File
@@ -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
+1 -1
View File
@@ -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
+17 -17
View File
@@ -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];
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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
+3 -3
View File
@@ -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 */ }
+1 -1
View File
@@ -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
+13 -12
View File
@@ -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) {}
+1 -1
View File
@@ -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
View File
@@ -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
+4
View File
@@ -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
+2 -2
View File
@@ -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."
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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'); }
+3 -3
View File
@@ -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
View File
@@ -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; }
+3 -3
View File
@@ -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;
}
+1 -1
View File
@@ -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
+1 -1
View File
@@ -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));
+6 -6
View File
@@ -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();
+73
View File
@@ -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
+72
View File
@@ -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
+419
View File
@@ -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;
+53
View File
@@ -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
+73
View File
@@ -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
+65
View File
@@ -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
+55
View File
@@ -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
+47
View File
@@ -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
+82
View File
@@ -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
+74
View File
@@ -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
+190
View File
@@ -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
+65
View File
@@ -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
+36
View File
@@ -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);
}
+418
View File
@@ -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
+318
View File
@@ -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
+704
View File
@@ -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
+168
View File
@@ -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
+48
View File
@@ -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'`
+73
View File
@@ -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
+55
View File
@@ -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
+70
View File
@@ -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
+203
View File
@@ -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
+69
View File
@@ -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);
+30
View File
@@ -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;
}
+217
View File
@@ -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
+72
View File
@@ -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);
+22 -4
View File
@@ -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);
+3 -1
View File
@@ -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
);
+4
View File
@@ -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 {
+3 -3
View File
@@ -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)) {
+3 -3
View File
@@ -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